知識庫 / JSON RSS 訂閱

Gson 序列化和反序列化列表

JSON
HongKong
9
09:54 PM · Dec 05 ,2025

1. 引言

本教程將探討使用 List 以及 Google Gson 庫 的一些高級序列化和反序列化案例。

2. 對象列表

一個常見用例是將 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());
}

在這裏,儘管經過反序列化後我們會得到一個大小為二的 List,但它不會是 MyClassList。因此,第6行拋出了 ClassCastException

Gson 可以序列化任意對象的集合,但無法在缺少額外信息的情況下進行反序列化。這是因為用户無法指示生成對象的類型。 而是,在反序列化過程中,Collection 必須具有特定的泛型類型。

正確反序列化 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. 使用自定義反序列器

一種解決此問題的辦法是將類型信息添加到序列化的 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

另一種方法是使用 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);
}

請注意,底層機制仍然相同。

在序列化過程中,仍需要引入類型信息。該類型信息可用於後續的反序列化。 因此,為了使該解決方案有效,每個類中仍需包含 type 字段。 我們無需編寫自己的反序列器。

RuntimeTypeAdapterFactory 根據傳遞給它字段名稱和已註冊的子類型,提供正確的類型適配器。

4. 結論

在本文中,我們學習瞭如何使用 Gson 序列化和反序列化對象列表。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.