Gsonを使用したリストのシリアル化と逆シリアル化

1. 前書き

このチュートリアルでは、いくつかの高度なlink:/gson-serialization-guide[serialization]およびlink:/gson-deserialization-guide[deserialization]のケースを調べます。 https://github.com/google/gson[GoogleのGsonライブラリ]を使用して_List_。

*2. オブジェクトのリスト+

*
一般的な使用例の1つは、POJOのリストをシリアライズおよびデシリアライズすることです。
クラスを考えてみましょう:
public class MyClass {
    private int id;
    private String name;

    public MyClass(int id, String name) {
        this.id = id;
        this.name = name;
    }

    // getters and setters
}
_List <MyClass> _をシリアル化する方法は次のとおりです。
@Test
public void givenListOfMyClass_whenSerializing_thenCorrect() {
    List<MyClass> list = Arrays.asList(new MyClass(1, "name1"), new MyClass(2, "name2"));

    Gson gson = new Gson();
    String jsonString = gson.toJson(list);
    String expectedString = "[{\"id\":1,\"name\":\"name1\"},{\"id\":2,\"name\":\"name2\"}]";

    assertEquals(expectedString, jsonString);
}
ご覧のとおり、シリアル化は非常に簡単です。
ただし、逆シリアル化には注意が必要です。 間違った方法は次のとおりです。
@Test(expected = ClassCastException.class)
public void givenJsonString_whenIncorrectDeserializing_thenThrowClassCastException() {
    String inputString = "[{\"id\":1,\"name\":\"name1\"},{\"id\":2,\"name\":\"name2\"}]";

    Gson gson = new Gson();
    List<MyClass> outputList = gson.fromJson(inputString, ArrayList.class);

    assertEquals(1, outputList.get(0).getId());
}
ここでは、*デシリアライゼーション後にサイズ2の_List_を取得しますが、_MyClass_ *の_List_ではありません。 したがって、6行目で_ ClassCastException _がスローされます。
  • Gsonは任意のオブジェクトのコレクションをシリアル化できますが、追加情報がなければデータを逆シリアル化できません。 それは、ユーザーが結果のオブジェクトの型を示す方法がないためです。

    _List_を逆シリアル化する正しい方法は次のとおりです。
@Test
public void givenJsonString_whenDeserializing_thenReturnListOfMyClass() {
    String inputString = "[{\"id\":1,\"name\":\"name1\"},{\"id\":2,\"name\":\"name2\"}]";
    List<MyClass> inputList = Arrays.asList(new MyClass(1, "name1"), new MyClass(2, "name2"));

    Type listOfMyClassObject = new TypeToken<ArrayList<MyClass>>() {}.getType();

    Gson gson = new Gson();
    List<MyClass> outputList = gson.fromJson(inputString, listOfMyClassObject);

    assertEquals(inputList, outputList);
}
ここでは、* Gsonの_TypeToken_を使用して、逆シリアル化される正しいタイプを決定します– _ArrayList <MyClass> _ *。 _listOfMyClassObject_の取得に使用されるイディオムは、完全にパラメーター化された型を返すメソッド_getType()_を含む匿名ローカル内部クラスを実際に定義します。

3. ポリモーフィックオブジェクトのリスト

* 3.1。 問題*

動物のクラス階層の例を考えてみましょう。
public abstract class Animal {
    // ...
}

public class Dog extends Animal {
    // ...
}

public class Cow extends Animal {
    // ...
}
_List <Animal> _をシリアル化および逆シリアル化するにはどうすればよいですか? 前のセクションで使用したように、_TypeToken <ArrayList <Animal >> _を使用できます。 ただし、Gsonは、リストに格納されているオブジェクトの具体的なデータ型を把握することはできません。

* 3.2。 カスタムデシリアライザーの使用+

*
これを解決する1つの方法は、シリアル化されたJSONに型情報を追加することです。 JSONの逆シリアル化中にその型情報を尊重します。 このために、独自のカスタムシリアライザーとデシリアライザーを作成する必要があります。
まず、基本クラス_Animal_に_type_という新しい_String_フィールドを導入します。 所属するクラスの単純名を保存します。
サンプルクラスを見てみましょう。
public abstract class Animal {
    public String type = "Animal";
}
public class Dog extends Animal {
    private String petName;

    public Dog() {
        petName = "Milo";
        type = "Dog";
    }

    // getters and setters
}
public class Cow extends Animal {
    private String breed;

    public Cow() {
        breed = "Jersey";
        type = "Cow";
    }

    // getters and setters
}
シリアル化は以前と同様に問題なく機能し続けます。
@Test
public void givenPolymorphicList_whenSerializeWithTypeAdapter_thenCorrect() {
    String expectedString
      = "[{\"petName\":\"Milo\",\"type\":\"Dog\"},{\"breed\":\"Jersey\",\"type\":\"Cow\"}]";

    List<Animal> inList = new ArrayList<>();
    inList.add(new Dog());
    inList.add(new Cow());

    String jsonString = new Gson().toJson(inList);

    assertEquals(expectedString, jsonString);
}
リストをデシリアライズするには、カスタムデシリアライザーを提供する必要があります。
public class AnimalDeserializer implements JsonDeserializer<Animal> {
    private String animalTypeElementName;
    private Gson gson;
    private Map<String, Class<? extends Animal>> animalTypeRegistry;

    public AnimalDeserializer(String animalTypeElementName) {
        this.animalTypeElementName = animalTypeElementName;
        this.gson = new Gson();
        this.animalTypeRegistry = new HashMap<>();
    }

    public void registerBarnType(String animalTypeName, Class<? extends Animal> animalType) {
        animalTypeRegistry.put(animalTypeName, animalType);
    }

    public Animal deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
        JsonObject animalObject = json.getAsJsonObject();
        JsonElement animalTypeElement = animalObject.get(animalTypeElementName);

        Class<? extends Animal> animalType = animalTypeRegistry.get(animalTypeElement.getAsString());
        return gson.fromJson(animalObject, animalType);
    }
}
ここで、_animalTypeRegistry_マップは、クラス名とクラスタイプ間のマッピングを維持します。
逆シリアル化中に、最初に新しく追加された_type_フィールドを抽出します。 この値を使用して、_animalTypeRegistry_マップを検索して、具体的なデータ型を取得します。 このデータ型は、_fromJson()_に渡されます。
カスタムデシリアライザーの使用方法を見てみましょう。
@Test
public void givenPolymorphicList_whenDeserializeWithTypeAdapter_thenCorrect() {
    String inputString
      = "[{\"petName\":\"Milo\",\"type\":\"Dog\"},{\"breed\":\"Jersey\",\"type\":\"Cow\"}]";

    AnimalDeserializer deserializer = new AnimalDeserializer("type");
    deserializer.registerBarnType("Dog", Dog.class);
    deserializer.registerBarnType("Cow", Cow.class);
    Gson gson = new GsonBuilder()
      .registerTypeAdapter(Animal.class, deserializer)
      .create();

    List<Animal> outList = gson.fromJson(inputString, new TypeToken<List<Animal>>(){}.getType());

    assertEquals(2, outList.size());
    assertTrue(outList.get(0) instanceof Dog);
    assertTrue(outList.get(1) instanceof Cow);
}

* 3.3。 RuntimeTypeAdapterFactory *の使用

カスタムデシリアライザーを記述する代わりに、https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryにある_RuntimeTypeAdapterFactory_クラスを使用することもできます。 java [Gsonソースコード]。 ただし、*ユーザーが使用するために*ライブラリによって公開されていません*。 したがって、Javaプロジェクトでクラスのコピーを作成する必要があります。
これが完了したら、それを使用してリストを逆シリアル化できます。
@Test
public void givenPolymorphicList_whenDeserializeWithRuntimeTypeAdapter_thenCorrect() {
    String inputString
      = "[{\"petName\":\"Milo\",\"type\":\"Dog\"},{\"breed\":\"Jersey\",\"type\":\"Cow\"}]";

    Type listOfAnimals = new TypeToken<ArrayList<Animal>>(){}.getType();

    RuntimeTypeAdapterFactory<Animal> adapter = RuntimeTypeAdapterFactory.of(Animal.class, "type")
      .registerSubtype(Dog.class)
      .registerSubtype(Cow.class);

    Gson gson = new GsonBuilder().registerTypeAdapterFactory(adapter).create();

    List<Animal> outList = gson.fromJson(inputString, listOfAnimals);

    assertEquals(2, outList.size());
    assertTrue(outList.get(0) instanceof Dog);
    assertTrue(outList.get(1) instanceof Cow);
}
基になるメカニズムは同じままであることに注意してください。
シリアル化時に型情報を導入する必要があります。 タイプ情報は、デシリアライズ中に後で使用できます。 *したがって、フィールド_type_は、このソリューションが機能するためにすべてのクラスで必要です。*独自のデシリアライザーを記述する必要はありません。
_RuntimeTypeAdapterFactory_は、渡されたフィールド名と登録されたサブタイプに基づいて正しいタイプアダプターを提供します。

4. 結論

この記事では、Gsonを使用してオブジェクトのリストをシリアライズおよびデシリアライズする方法を説明しました。
いつものように、コードはhttps://github.com/eugenp/tutorials/tree/master/gson[over on GitHub]で入手できます。