最終更新日:2015/07/12
間違いや和訳ミスがあれば随時ご指摘ください。
また、内容の間違いには一切の責任を持ちませんのでご了承ください。
Overview
Gson は Java Object を JSON へ変換するためのライブラリである
また JSON 文字列から Java Object への変換も可能である。
Gson は http://code.google.com/p/google-gson によって提供されるオープンソースプロジェクトである
Gson は任意の Java Object で利用可能で、ソースコード上にまだ存在しない Java Object であっても取り扱える。
Goals for Gson
-
toString()
や Factory Method のコンストラクタのように簡単に利用し、Java Object と JSON の間でコンバートすることができる - 既存の変更不可なオブジェクトもコンバートすることができる
- オブジェクトの表示方法を変更できる
- 任意の複雑なオブジェクトをサポートしている
- コンパクトかつ読みやすく生成された JSON を出力できる
Gson Preformance and Scalability
ここにデスクトップマシン(2コアCPU、8GB RAM、64bit Ubuntu)における様々なテスト結果がある
- Strings:25MB を超える文字列のデシリアライズで問題が起きなかった(詳しくは PerformanceTest 内の disabled_testStringDeserializationPerformance を参照)
- Large collections:
- 1,400万の要素を持つオブジェクトのシリアライズ(詳しくは PerformanceTest 内の disabled_testLargeCollectionSerialization を参照)
- 8万7千の要素を持つオブジェクトのデシリアライズ(詳しくは PerformanceTest 内の disabled_testLargeCollectionDeserialization を参照)
- Gson 1.4 では配列およびコレクションの最大デシリアライズサイズが 80KB から 11MB 超まで増加された
Note:これらのテストを実行するためには disabled_ を外すこと。我々はこれを JUnitによる各テストを停止するために利用した。
Gson Users
Gson は Google 内部で作られ、現在ではいくつかのプロジェクトで利用されている。Gson は様々な企業のプロジェクトで利用されている。くわしくはこちら
Using Gson
Gson を利用するための基本的なクラスは new Gson() よ生成するだけである。
また、バージョン管理のように利用するための様々な設定値を利用してインスタンスを生成するための GsonBuilder も用意されている。
Gson インスタンスは Json 操作を行う間のどんな状態も保持しない。
そのため、複数の Json をシリアライズ/デシリアライズするために同じオブジェクトを再利用することができる。
例
(Serialization)
Gson gson = new Gson();
gson.toJson(1); ==> prints 1
gson.toJson("abcd"); ==> prints "abcd"
gson.toJson(new Long(10)); ==> prints 10
int[] values = { 1 };
gson.toJson(values); ==> prints [1]
(Deserialization)
int one = gson.fromJson("1", int.class);
Integer one = gson.fromJson("1", Integer.class);
Long one = gson.fromJson("1", Long.class);
Boolean false = gson.fromJson("false", Boolean.class);
String str = gson.fromJson("\"abc\"", String.class);
String anotherStr = gson.fromJson("[\"abc\"]", String.class);
オブジェクト例
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// no-args constructor
}
}
(Serialization)
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
==> json is {"value1":1,"value2":"abc"}
なお、結果が永遠に再起されてしまうため、巡回的にシリアライズすることはできない
(Deserialization)
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
==> obj2 is just like obj
Finer Points with Objects
- プライベートなフィールドとして利用できる(推奨)
- フィールドがシリアライズもしくはデシリアライズを含むなどというアノテーション類は必要ない
- フィールドが transient の場合、(デフォルトでは) JSON のシリアライズ/デシリアライズではそれは無視される
- 実装では null となる
- シリアライズ中、null 値は出力されない
- デシリアライズ中、オブジェクト中でフィールドとして設定されていないものは JSON では null となる
- 合成フィールドの場合、シリアライズ/デシリアライズでは無視され、JSON に含まれない
- 内部クラス、匿名クラス、ローカルクラスのものと同様の外部クラスのフィールドはシリアライズ/デシリアライズでは無視される
Nested Classes (including inner Classes)
Gson はネストされたクラスも簡単にシリアライズできる
また、Gson はネストされたクラスのデシリアライズも可能である。
ただし、デシリアライズ時に利用可能なオブジェクトを引数にとるようなコンストラクタがないため、
純粋なインナークラスの自動的なデシリアライズは不可能である。
public class A {
public String a;
class B {
public String b;
public B() {
// No args constructor for B
}
}
}
NOTE: 上記のクラス B は(デフォルトでは) Gson にシリアライズ不可能である
クラス B がインナークラスのため、Gson が {"b":"abc"}
をクラス B にはデシリアライズできない。
もしクラス B が static class B
として宣言されていれば、Gson はデシリアライズすることができる。
もしくは、クラス B のためのコンストラクタを新たに実装することで解決することができる。
public class InstanceCreatorForB implements InstanceCreator<A.B> {
private final A a;
public InstanceCreatorForB(A a) {
this.a = a;
}
public A.B createInstance(Type type) {
return a.new B();
}
}
上記は動作するが、推奨はしない。
Array Examples
Gson gson = new Gson();
int[] ints = {1, 2, 3, 4, 5};
String[] strings = {"abc", "def", "ghi"};
(Serialication)
gson.toJson(ints); ==> prints [1,2,3,4,5]
gson.toJson(strings); ==> prints ["abc", "def", "ghi"]
(Deserialization)
int[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class);
==> ints2 will be same as ints
複雑な要素で構成された多次元配列にも対応している。
Collections Examples
Gson gson = new Gson();
Collection<Integer> ints = Lists.immutableList(1,2,3,4,5);
(Serialization)
String json = gson.toJson(ints); ==> json is [1,2,3,4,5]
(Deserialization)
Type collectionType = new TypeToken<Collection<Integer>>(){}.getType();
Collection<Integer> ints2 = gson.fromJson(json, collectionType);
ints2 は ints と同じである
Fairly hideous: どのようにコレクションを定義するか記載しておく
不幸にも、これを Java ではうまく表現できない。
Collections Limitations
- 任意のオブジェクトをシリアライズ可能であるが、任意のオブジェクトからのデシリアライズはできない
- なぜなら、ユーザがオブジェクトの型を指定する方法がないためである
- デシリアライズでは、コレクションは特定のジェネリクス型である必要がある
これらが意味するのは、良い Java を書いていればほとんど問題になることはないということである。
Serializing and Deserializing Generic Types
toJson(obj)
を呼び出した時、Gson はシリアライズするための型を知るために obj.getClass()
を呼び出す。
MyClass.class
を 利用して fromJson(json, MyClass.class)
を呼ぶ際にも同様のことが行われる。
これはオブジェクトがジェネリクス型でない場合にはうまくいくが、ジェネリクス型の場合は Java が型を除去してしまうため、型の情報を取得できない。
ここで例を使ってポイントを説明する。
class Foo<T> {
T value;
}
Gson gson = new Gson();
Foo<Bar> foo = new Foo<Bar>();
gson.toJson(foo); // May not serialize foo.value correctly
gson.fromJson(json, foo.getClass()); // Fails to deserialize foo.value as Bar
上記のコードでは Gson が型の情報を取得すために list.getClass
を実行するため、Bar 型の解決に失敗してしまう。
そのためこのメソッドは Foo.class
を返してしまう。
これが意味するのは、Gson はこのオブジェクトの方が Foo ではなく Foo であることを知る方法がないことである。
この問題を解決するためには、ジェネリクス型の正しい型情報を知らせてやる必要がある。
これは TypeToken
クラスを利用することで実現できる
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);
gson.fromJson(json, fooType);
この記法は型情報を返却する getType()
を持つ匿名のインナークラス fooType
の型取得するために使われる。
Serializing and Deserializing Collection with Objects of Arbitrary Types
['hello',5,{name:'GREETINGS',source:'guest'}]
のような異なる型を持つ JSON 配列を扱う場合
これは同様に下記のコレクションのように表せられる
Collection collection = new ArrayList();
collection.add("hello");
collection.add(5);
collection.add(new Event("GREETINGS", "guest"));
ここで、Event クラス は下記に定義される
class Event {
private String name;
private String source;
private Event(String name, String source) {
this.name = name;
this.source = source;
}
}
特に特別なことは必要なく、Gson の toJson(Collection) を利用することでシリアライズが可能である。
しかし、Gson が入力された型をどのようにマッピングするか把握する方法がないため、fromJson(json, Collection.class) を用いたデシリアライズは行えない。
そのため、Gson の fromJson では、コレクションの型を知らせてやる必要がある。
このために、3つのオプションが用意されている。
- Option1: 配列の要素をパースするために、Gson のパース API (低レベルなひどい、DOM を利用した Json パーサ) を用いて、各要素で Gson.fromJson() を利用する。ここにその例がある。
- Option2: 各配列の要素を決め、最適なオブジェクトにマッピングするための Collection クラスの型アダプタを用意する。この方法のデメリットは、Gson を用いて他の型をデシリアライズがめちゃくちゃになってしまうことがある。
- fromJson と共に 適した型を持つ MyCollectionMemberType を用意し、Collection を利用する。この方法は、配列がトップレベルの要素の場合や保持している Collection の型を変更可能にするための唯一の方法となるだろう。
Built-in Serializers and Deserializers
Gson にはよく使われるようなクラスのシリアライザ/デシリアライザが用意されているが、それらの初期表示は不適切なものになるだろう。
ここに、そのようなクラスのリストを挙げる。
- java.net.URL to match it with strings like "http://code.google.com/p/google-gson/".
- java.net.URI to match it with strings like "/p/google-gson/".
JodaTime のようなよくあるクラスのソースコードはここで探すことができる。
Custom Serialization and Deserialization
デフォルト表示は時に必要とする者とは異なる。
これらは DateTime などのライブラリクラスでよく起きる。
そのため、Gson ではカスタムしたシリアライザ/デシリアライザを定義することができる。
これは下記に示す 2つのパートから行われる
- シリアライザ:オブジェクトに対するカスタムシリアライズザを定義する必要がある
- デシリアライザ:型に対するカスタムデシリアライザを定義する必要がある
- インスタンスクリエイタ:引数のないコンストラクタもしくはデシリアライザが用意されている場合、特に必要はない
GsonBuilder gson = new GsonBuilder();
gson.registerTypeAdapter(MyType2.class, new MyTypeAdapter());
gson.registerTypeAdapter(MyType.class, new MySerializer());
gson.registerTypeAdapter(MyType.class, new MyDeserializer());
gson.registerTypeAdapter(MyType.class, new MyInstanceCreator());
型アダプタが1つ以上のインターフェースを実装し、それがすべてを定義じている場合、registerTypeAdapter はチェックされることになる。
Writing a Serializer
ここに JodaTime の DateTime クラスをシリアライズする際の例を示す。
private class DateTimeSerializer implements JsonSerializer<DateTime> {
public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.toString());
}
}
Gson は DateTime オブジェクトをシリアライズする際に toJson() を呼び出す。
Writing a Deserializer
ここに JodaTime の DateTime クラスをデシリアライズする際の例を示す。
private class DateTimeDeserializer implements JsonDeserializer<DateTime> {
public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return new DateTime(json.getAsJsonPrimitive().getAsString());
}
}
Gson は JSON 文字列を DateTime オブジェクトにデシリアライズする際に fromJson() を呼び出す必要がある。
Finer points with Serializers and Deserializers
すべてのジェネリクス型を元の型のように扱いたい場合、ハンドラを用意したい場合がある。
- 例えば、Id の表示/変換のための Id クラスを持っているとする (つまり、内部向けおよび外部向けの表示形式である)
- Id 型 はすべてのジェネリクス型に対するシリアライズザを持つ
- 基本的に id の値を出力する
- デシリアライザはとても似ているが、厳密には同じではない
- Id 型のインスタンスを返すような
new Id(Class<T>, String)
を呼び出す必要がある。
- Id 型のインスタンスを返すような
Gson では、これに対するハンドラを登録することができる。
特殊ジェネリクス型への特殊なハンドラというものを登録することができるのである(Id<RequiresSpecialHandling>
は特殊なハンドラを必要とするということである)
toJson および fromJson のための Type
パラメータはすべての方を元の型に対応させるハンドラを記述するためのジェネリクス型に関する情報を含んでいる。
Writing an Instance Creator
オブジェクトをデシリアライズする際、Gson はクラスのデフォルトインスタンスを生成する必要がある。
シリアライズザやデシリアライザのための仕様に沿ったクラスは引数なしのコンストラクタを持っている必要がある。
- public もしくは private に関係なく、である
通常、引数なしのコンストラクタが実装されていないライブラリクラスなどに対応するために、インスタンスクリエイタが必要となる。
Instance Creator Example
private class MoneyInstanceCreator implements InstanceCreator<Money> {
public Money createInstance(Type type) {
return new Money("1000000", CurrencyCode.USD);
}
}
型はジェネリクス型である必要がある
- 非常に多くの場合、コンストラクタを実行するためには特殊なジェネリクス型の情報が必要となる
- 例えば、Id クラスが Id クラスを生成するクラスを保持しているときなどである
InstanceCreator for a Parameterized Type
インスタンス化しようとしている型がパラメータ化されていることがある。
大抵の場合、実際の方は元の型となるため、これは問題とはならない。
ここに例を挙げる。
class MyList<T> extends ArrayList<T> {
}
class MyListInstanceCreator implements InstanceCreator<MyList<?>> {
@SuppressWarnings("unchecked")
public MyList<?> createInstance(Type type) {
// No need to use a parameterized list since the actual instance will have the raw type anyway.
return new MyList();
}
}
しかし、パラメータ化された型にもとづいてインスタンス化する必要がある場合もある。
この場合、型パラメータを createInstance
メソッドに移動することができる。
ここに例を示す。
public class Id<T> {
private final Class<T> classOfId;
private final long value;
public Id(Class<T> classOfId, long value) {
this.classOfId = classOfId;
this.value = value;
}
}
class IdInstanceCreator implements InstanceCreator<Id<?>> {
public Id<?> createInstance(Type type) {
Type[] typeParameters = ((ParameterizedType)type).getActualTypeArguments();
Type idType = typeParameters[0]; // Id has only one parameterized type T
return Id.get((Class)idType, 0L);
}
}
上記の例では、パラメータ化された型が実際に消失するまで、Id クラスのインスタンスは生成されない。
この問題は、type
の消失したパラメータを用いて解決される。
この時、type
オブジェクトは Id のパラメータ化された型であり、この時実際のインスタンスは Id となる必要がある。
Id クラスは 1つのパラメータ化された型 T を持つため、Foo クラスの持つ getActualTypeArgument()
によって返される配列から、ゼロ番目の要素を利用する。
Compact Vs. Pretty Printing for JSON Output Format
Gson によって提供される デフォルトの JSON オブジェクトはコンパクトなフォーマットとなっている。
これは出力される JSON 構造の中に空白などがないことを示す。
そのため、出力される JSON にはどんな名前、値、オブジェクトフィールド、配列の間にもスペースが入っていない。
その上、null 値は出力から無視される(Note: ここで言う null 値にはコレクションや配列、オブジェクトも含まれる)
Gson を用いて null を出力する方法については、Null Object Supportを参照して欲しい。
Pretty Print 機能を利用したい場合は、Gson インスタンスを GsonBuilder を用いて生成する必要がある。
JsonFomatter は public な API ではないため、クライアントは JSON のデフォルト出力フォーマットを定義することはできない。
今のところ、デフォルトの JsonPrintFormatter は 1行あたり 80文字、2文字のインデント、4文字の右マージンとして定義されている。
下記に JsonCompactFormatter に代わって JsonPrintFormatter を利用する Gson インスタンスの構成方法を示す。
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String jsonOutput = gson.toJson(someObject);
Null Object Support
Gson によって実装されるデフォルトの動作では、null オブジェクトは無視される。
これによって、出力はコンパクトとなるが、クライアントは JSON フォーマットを Java コードに再コンバートするために、これらフィールドの初期値を設定する必要がある
ここに null を出力する Gson インスタンスの生成方法を示す。
Gson gson = new GsonBuilder().serializeNulls().create();
NOTE: Gson で null をシリアライズする時、JsonElement 構造には JsonNull が追加される。
そのため、このオブジェクトはカスタムシリアライズザ/デシリアライザにも利用することが可能である。
ここに例を示す。
public class Foo {
private final String s;
private final int i;
public Foo() {
this(null, 5);
}
public Foo(String s, int i) {
this.s = s;
this.i = i;
}
}
Gson gson = new GsonBuilder().serializeNulls().create();
Foo foo = new Foo();
String json = gson.toJson(foo);
System.out.println(json);
json = gson.toJson(null);
System.out.println(json);
======== OUTPUT ========
{"s":null,"i":5}
null
Versioning Support
同じオブジェクトに対する複数のバージョン管理機能は @Since アノテーションを用いて提供される。
このアノテーションは、クラス、フィールド、メソッドに対して付与できる。
この機能を利用するために、指定したバージョンより大きいバージョンの値を無視するように Gson インスタンスを生成しなければならない。
Gson インスタンスにバージョンが指定されていない場合は、すべての値がシリアライズ/デシリアライズされる。
public class VersionedClass {
@Since(1.1) private final String newerField;
@Since(1.0) private final String newField;
private final String field;
public VersionedClass() {
this.newerField = "newer";
this.newField = "new";
this.field = "old";
}
}
VersionedClass versionedObject = new VersionedClass();
Gson gson = new GsonBuilder().setVersion(1.0).create();
String jsonOutput = gson.toJson(someObject);
System.out.println(jsonOutput);
System.out.println();
gson = new Gson();
jsonOutput = gson.toJson(someObject);
System.out.println(jsonOutput);
======== OUTPUT ========
{"newField":"new","field":"old"}
{"newerField":"newer","newField":"new","field":"old"}
Excluding Fields From Serialization and Deserialization
Gson はトップレベルクラス、フォールド、そしてフィールドタイプを排除するための、非常に多くの機能をサポートしている。
以下に示すものは、フィールドやクラスを排除するためのプラグイン機能である。
以下の機能が求めるものを満たさない場合は、カスタムシリアライズザ/デシリアライザを利用できる
Java Modifier Exclusion
デフォルトの動作では、transient
として指定されているフィールドは除外される。
また、"static" として指定されているものもデフォルトでは除外される。
もし transient なフィールドも含めたい場合は、下記のようにして設定できる。
import java.lang.reflect.Modifier;
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.STATIC)
.create();
NOTE: excludeFieldsWithModifiers
の中で Modifier
定数はいくつでも設定可能である。
例:
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT, Modifier.VOLATILE)
.create();
Gson's @Expose
この機能は JSON のシリアライズ/デシリアライズを行うにあたり、いくつかのフィールドオブジェクトを除外すべきか考慮するための機能を提供する。
このアノテーションを使うためには、 new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()
として Gson を生成する必要がある。
生成された Gson オブジェクトは @Expose
アノテーションによって指定されていない全てのフィールドを除外する。
User Defined Exclusion Strategies
もしこれまでに示した除外機能が正常に働かない場合は、自分自身で除外機構を作り、 Gson にプラグインさせることができる。
詳細は ExclusionStrategy の JavaDoc に記載がある。
下記の例は @Foo アノテーションによってクラス内のフィールドを除外する方法を示している。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Foo {
// Field tag only annotation
}
public class SampleObjectForTest {
@Foo private final int annotatedField;
private final String stringField;
private final long longField;
private final Class<?> clazzField;
public SampleObjectForTest() {
annotatedField = 5;
stringField = "someDefaultValue";
longField = 1234;
}
}
public class MyExclusionStrategy implements ExclusionStrategy {
private final Class<?> typeToSkip;
private MyExclusionStrategy(Class<?> typeToSkip) {
this.typeToSkip = typeToSkip;
}
public boolean shouldSkipClass(Class<?> clazz) {
return (clazz == typeToSkip);
}
public boolean shouldSkipField(FieldAttributes f) {
return f.getAnnotation(Foo.class) != null;
}
}
public static void main(String[] args) {
Gson gson = new GsonBuilder()
.setExclusionStrategies(new MyExclusionStrategy(String.class))
.serializeNulls()
.create();
SampleObjectForTest src = new SampleObjectForTest();
String json = gson.toJson(src);
System.out.println(json);
}
======== OUTPUT ========
{"longField":1234}
JSON Field Naming Support
Gson はスタンダードな Java フィールドの命名 (例:"sampleFieldNameInJava" のようなキャメルケース) を Json の命名 (例:"sample_field_name_in_java") に変更するコンバート機構を幾つか有している。
予め用意されている命名規則については FieldNamingPolicy に情報が記載されている。
各フィールドに対して独自の命名規則を設定する方法として、アノテーションベースの方法がある。
ここで、アノテーションベースの方法では "Runtime" 例外を引き起こす可能せいがあることを知っておく必要がある。
下記に例を示す。
private class SomeObject {
@SerializedName("custom_naming") private final String someField;
private final String someOtherField;
public SomeObject(String a, String b) {
this.someField = a;
this.someOtherField = b;
}
}
SomeObject someObject = new SomeObject("first", "second");
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
String jsonRepresentation = gson.toJson(someObject);
System.out.println(jsonRepresentation);
======== OUTPUT ========
{"custom_naming":"first","SomeOtherField":"second"}
独自の命名規則をもたせる場合は @SirializedName アノテーションを用いて実現できる (こちらのやりとりも見て欲しい)
Sharing State Across Custom Serializers and Deserializers
時に、カスタムシリアライズザ/デシリアライザの間で状態を共有したい場合があるだろう(こちらの議論も参照)
これには下記に示す 3つの方法がある。
- static フィールドを用いて共有する状態を保持する。
- 共有する状態を保持するために、シリアライズザ/デシリアライザを親の型で宣言し、親のインスタンスを利用する。
- Java の ThreadLocal を利用する
1 および 2 はスレッドセーフではないが、3 はスレッドセーフである。
Streaming
さらなる Gson のオプションやデータバインディングを利用するために、stream を用いて Gson を読み書きすることができる。
それぞれの利点を利用するために、ストリーミングとオブジェクトモデルを組み合わせて利用することができる。
Issues in Designing Gson
Gson を作るにあたって直面している問題やそれに関する議論については Gson design document に記載がある。
ここには Json のコンバートに関する Gson とその他ライブラリの比較も記載されている。
Future Enhancements to Gson
新しい機能の提案一覧や新しいものを提案したい場合は、プロジェクトの下にある Issues section を参照して欲しい。