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);
}
}