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 数据时更加游刃有余。