S-JIS[2013-03-14/2013-03-20] 変更履歴

Google Gson

GoogleのGsonのメモ。


概要

Gsonは、GoogleのJSONライブラリー。
JavaのオブジェクトをJSON形式で保存・復元(シリアライズ・デシリアライズと同様)するのに便利。

必要なライブラリーはGsonのjarファイルのみ(バージョン2.2.2の場合はgson-2.2.2.jar)で、他に依存するjarファイルが無いのも利点。


ExampleBeanクラスを保存・復元する例。

ExampleBean.java

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が作られる。

ExampleBean.java:

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アノテーションを付けたフィールドだけシリアライズ対象にするという事が出来る。

ExampleBean.java:

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

Java目次へ戻る
メールの送信先:ひしだま