1. 引言

本教程将深入探讨使用 Google Gson 库List 进行序列化和反序列化的几种高级场景。

2. 对象列表的处理

在实际开发中,最常见的场景之一就是对一组 POJO 对象组成的 List 进行序列化与反序列化操作。

假设我们有如下类:

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 类型,而是 LinkedTreeMap。因此,在访问 .getId() 时会抛出 ClassCastException

Gson 能轻松地序列化任意对象集合,但在反序列化时需要额外信息才能确定泛型类型。

正确的做法是使用 TypeToken 指定目标类型:

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

✅ 使用 new TypeToken<ArrayList<MyClass>>(){}.getType() 可以告诉 Gson 具体要反序列化成什么类型。

3. 多态对象列表

3.1. 问题背景

考虑一个动物类的继承结构:

public abstract class Animal {
    // ...
}

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

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

如果我们想序列化和反序列化 List<Animal>,直接用 TypeToken<List<Animal>> 是不够的,因为 Gson 无法知道每个元素的真实子类型。

3.2. 自定义反序列化器

解决这个问题的一种方式是在 JSON 中加入类型信息,并在反序列化时读取这些信息。为此我们需要自定义序列化和反序列化逻辑。

首先,我们在基类 Animal 中添加一个字段 type 来记录具体类型:

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 映射类型名到具体的类,然后根据 JSON 中的 type 字段找到对应的类并完成反序列化。

使用方式如下:

@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(⚠️ 注意这个类不是公开 API,需要复制到项目中)。

使用方法也很简单粗暴:

@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 字段来标识类型。

4. 总结

本文介绍了使用 Gson 序列化与反序列化 List 的几种常见和高级用法:

  • 对于普通对象列表,使用 TypeToken 即可搞定;
  • 对于多态对象列表,需要显式提供类型信息,可以通过自定义反序列化器或使用 RuntimeTypeAdapterFactory 实现;
  • 避免使用 ArrayList.class 直接反序列化,会踩坑!

熟练掌握这些技巧,能让你在处理复杂 JSON 数据时更加游刃有余。


原始标题:Serializing and Deserializing a List with Gson | Baeldung