1. 序章

このチュートリアルでは、 GoogleのGsonライブラリを使用して、リストのいくつかの高度なシリアル化および逆シリアル化のケースについて説明します。

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
}

シリアル化する方法は次のとおりですリスト

@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のリストを取得しますが、MyClassのリストではありません。 したがって、6行目はClassCastExceptionをスローします。

Gsonは任意のオブジェクトのコレクションをシリアル化できますが、追加情報なしでデータを逆シリアル化することはできません。 これは、ユーザーが結果のオブジェクトのタイプを示す方法がないためです。 代わりに、逆シリアル化中に、 コレクション特定の汎用タイプである必要があります。

リストを逆シリアル化する正しい方法は次のとおりです。

@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 listOfMyClassObject を取得するために使用されるイディオムは、完全にパラメーター化された型を返すメソッド getType()を含む匿名のローカル内部クラスを実際に定義します。

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

3.1. 問題

動物のクラス階層の例を考えてみましょう。

public abstract class Animal {
    // ...
}

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

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

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

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

これを解決する1つの方法は、シリアル化されたJSONに型情報を追加することです。 JSON逆シリアル化中にその型情報を尊重します。 このために、独自のカスタムシリアライザーとデシリアライザーを作成する必要があります。

まず、基本クラスAnimaltypeという新しい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を使用する

カスタムデシリアライザーを作成する代わりに、GsonソースコードにあるRuntimeTypeAdapterFactoryクラスを使用することもできます。 ただし、は、ユーザーがを使用できるようにライブラリによって公開されていません。 したがって、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);
}

基本的なメカニズムは同じであることに注意してください。

シリアル化中にタイプ情報を導入する必要があります。 タイプ情報は、後で逆シリアル化中に使用できます。 したがって、このソリューションが機能するには、すべてのクラスでフィールドタイプが引き続き必要です。独自のデシリアライザーを作成する必要はありません。

RuntimeTypeAdapterFactory は、渡されたフィールド名と登録されたサブタイプに基づいて、正しいタイプアダプターを提供します。

4. 結論

この記事では、Gsonを使用してオブジェクトのリストをシリアル化および逆シリアル化する方法を説明しました。

いつものように、コードはGitHubから入手できます。