GoogleのGsonのメモ。
Gsonは、GoogleのJSONライブラリー。
JavaのオブジェクトをJSON形式で保存・復元(シリアライズ・デシリアライズと同様)するのに便利。
必要なライブラリーはGsonのjarファイルのみ(バージョン2.2.2の場合はgson-2.2.2.jar)で、他に依存するjarファイルが無いのも利点。
ExampleBeanクラスを保存・復元する例。
public class ExampleBean { private String text; private int value; public ExampleBean(String s, int n) { this.text = s; this.value = n; } @Override public String toString() { return String.format("Example(%s, %d)", text, value); } }
Gsonでは、privateフィールドを自動的に保存してくれる。(セッター・ゲッターメソッドは不要)
import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import com.google.gson.Gson; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter;
保存 | 復元 |
---|---|
void write(File file) throws IOException { ExampleBean bean = new ExampleBean("text1", 123); try (JsonWriter writer = new JsonWriter(new FileWriter(file))) { writer.setIndent(" "); Gson gson = new Gson(); gson.toJson(bean, ExampleBean.class, writer); } } |
void read(File file) throws IOException { ExampleBean bean; try (JsonReader reader = new JsonReader(new FileReader(file))) { Gson gson = new Gson(); bean = gson.fromJson(reader, ExampleBean.class); } System.out.println(bean); } |
setIndent()を指定しないと、改行無しで一行のJSONとして出力される。
{"text":"text1","value":123}
setIndent()でインデントを指定すると、以下の様に改行され、各行の行頭にインデントが付けられた状態で出力される。
{ "text": "text1", "value": 123 }
保存したいクラスのフィールドにSerializedNameアノテーションを付けると、その名前でJSONが作られる。
import com.google.gson.annotations.SerializedName;
public class ExampleBean { @SerializedName("string") private String text; @SerializedName("integer") private int value; 〜 }
↓
{ "string": "text1", "integer": 123 }
標準のSerializableではtransientを付けたフィールドはシリアライズされない(保存されない)。
Gsonでは、Exposeアノテーションを付けたフィールドだけシリアライズ対象にするという事が出来る。
import com.google.gson.annotations.Expose;
public class ExampleBean { @Expose private String text; //保存する private int value; //保存しない 〜 }
Exposeアノテーションを有効にするには、Gsonオブジェクトを作る際にexcludeFieldsWithoutExposeAnnotationを指定する必要がある。
import com.google.gson.GsonBuilder;
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); gson.toJson(bean, ExampleBean.class, writer);
↓
{ "text": "text1" }
JSONにしたいオブジェクトがフィールドに別のオブジェクトを保持している場合、そのオブジェクトもJSONに変換される。
(java.util.Listは配列として扱われる)
この際、参照が循環していると無限ループになり、スタックオーバーフローになる。
参照をJSONに出力する必要が無いなら、Exposeを使って出力有無を制御する方法が使える。
Gsonオブジェクトを使わずに、直接JsonWriter/JsonReaderで名前や値を指定して保存・復元することが出来る。
import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter;
保存 | 復元 |
---|---|
void write(File file) throws IOException { ExampleBean bean = new ExampleBean("text1", 123); try (JsonWriter writer = new JsonWriter(new FileWriter(file))) { writer.setIndent(" "); writer.beginObject(); writer.name("text").value(bean.getText()); writer.name("value").value(bean.getValue()); writer.endObject(); } } |
void read(File file) throws IOException { ExampleBean bean; try (JsonReader reader = new JsonReader(new FileReader(file))) { String text = null; int value = 0; reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); switch (name) { case "text": text = reader.nextString(); break; case "value": value = reader.nextInt(); break; default: reader.skipValue(); break; } } reader.endObject(); bean = new ExampleBean(text, value); } System.out.println(bean); } |
beginObject()・endObject()でオブジェクトの開始・終了を示す。
読み込む際は、beginObject()の後でhasNext()を呼び出すことで、オブジェクトの終わりが来るまでループさせることが出来る。
データの並び順が違っていても対応できるようにしようと思ったら、ループして名前で判定するのが良いのだろう。
想定外の名前が来た場合はskipValue()で値をスキップする。値をスキップしないと、後続処理で名前が欲しいのに値が来るのでエラーになる。
配列を保存・復元する例。
beginArray()・endArray()で配列の開始・終了を示す。
読み込む際は、beginArray()の後でhasNext()を呼び出すことで、配列の終わりが来るまでループさせることが出来る。
保存 | 復元 |
---|---|
void write(File file) throws IOException { try (JsonWriter writer = new JsonWriter(new FileWriter(file))) { writer.setIndent(" "); writer.beginObject(); writer.name("beans"); writer.beginArray(); for (int i = 1; i <= 3; i++) { writer.beginObject(); writer.name("text").value("text" + i); writer.name("value").value(100 + i); writer.endObject(); } writer.endArray(); writer.endObject(); } } |
void read(File file) throws IOException { try (JsonReader reader = new JsonReader(new FileReader(file))) { reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); switch (name) { case "beans": reader.beginArray(); while (reader.hasNext()) { reader.beginObject(); while (reader.hasNext()) { switch (reader.nextName()) { case "text": System.out.println(reader.nextString()); break; case "value": System.out.println(reader.nextInt()); break; default: reader.skipValue(); break; } } reader.endObject(); } reader.endArray(); break; default: reader.skipValue(); break; } } reader.endObject(); } } |
↓
{ "beans": [ { "text": "text1", "value": 101 }, { "text": "text2", "value": 102 }, { "text": "text3", "value": 103 } ] }
色々な種類の子クラスが入っているリストを保存・復元する例。[2013-03-20]
参考: stackoverflowのPolymorphism with gson
MyClass0という親クラスがあって、その子クラスとしてMyClass1とMyClass2があるものとする。
それらを保持するMyRootというクラスを保存・復元してみる。
public class MyClass0 { public String name; }
public class MyClass1 extends MyClass0 { public String value1; @Override public String toString() { return String.format("MyClass1(%s, %s)", name, value1); } }
public class MyClass2 extends MyClass0 { public String value2; @Override public String toString() { return String.format("MyClass2(%s, %s)", name, value2); } }
public class MyRoot { private List<MyClass0> list = new ArrayList<>(); public void add1(String name, String value) { MyClass1 m = new MyClass1(); m.name = name; m.value1 = value; list.add(m); } public void add2(String name, String value) { MyClass2 m = new MyClass2(); m.name = name; m.value2 = value; list.add(m); } @Override public String toString() { return String.format("MyRoot%s", list); } }
import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter;
public void write(File file) throws IOException { MyRoot root = new MyRoot(); root.add1("name1a", "abc"); root.add1("name1b", "def"); root.add2("name2", "zzz"); try (JsonWriter writer = new JsonWriter(new FileWriter(file))) { writer.setIndent(" "); Gson gson = new GsonBuilder().registerTypeAdapter(MyClass0.class, new MyTypeAdapter()).create(); gson.toJson(root, MyRoot.class, writer); } }
public void read(File file) throws IOException { MyRoot root; try (JsonReader reader = new JsonReader(new FileReader(file))) { Gson gson = new GsonBuilder().registerTypeAdapter(MyClass0.class, new MyTypeAdapter()).create(); root = gson.fromJson(reader, MyRoot.class); } System.out.println(root); }
TypeAdaperを用意し、Gsonに登録するのがポイント。
TypeAdapterでは、クラス名を表すプロパティーとインスタンス本体を別々に登録・取得する。
static class MyTypeAdapter implements JsonSerializer<MyClass0>, JsonDeserializer<MyClass0> { private static final String CLASSNAME = "CLASSNAME"; private static final String INSTANCE = "INSTANCE";
@Override public JsonElement serialize(MyClass0 src, Type typeOfSrc, JsonSerializationContext context) { JsonObject retValue = new JsonObject(); String className = src.getClass().getSimpleName(); retValue.addProperty(CLASSNAME, className); JsonElement elem = context.serialize(src); retValue.add(INSTANCE, elem); return retValue; }
@Override public MyClass0 deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = json.getAsJsonObject(); JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME); String className = prim.getAsString(); Class<?> klass; switch (className) { case "MyClass1": klass = MyClass1.class; break; case "MyClass2": klass = MyClass2.class; break; default: throw new JsonParseException("class not found. className=" + className); } return context.deserialize(jsonObject.get(INSTANCE), klass); } }