1. 引言
本文将探讨如何使用Gson处理多态类型,并介绍几种实现多态序列化和反序列化的技术方案。
2. JSON中的多态
Java中的多态机制大家都很熟悉:通过类层次结构,在特定场景下可以统一处理不同但相关的类型。例如,我们定义一组2D图形类:
interface Shape {
double getArea();
}
class Circle implements Shape {
private final double radius;
private final double area;
Circle(double radius) {
this.radius = radius;
this.area = Math.PI * radius * radius;
}
@Override
public double getArea() {
return area;
}
}
class Square implements Shape {
private final double side;
private final double area;
Square(double side) {
this.side = side;
this.area = side * side;
}
@Override
public double getArea() {
return area;
}
}
这些图形各有特点,但都实现了Shape
接口,可以统一计算面积。那么这与JSON有什么关系?虽然JSON文档不能包含功能代码,但可以表示具有重叠数据结构的多态对象。例如:
[
{
"shape": "circle",
"radius": 4,
"area": 50.26548245743669
}, {
"shape": "square",
"side": 5,
"area": 25
}
]
这样既能直接获取面积,又能通过shape
字段识别具体类型。
3. 使用包装对象
最简单粗暴的方案是使用包装对象,为每种类型创建独立字段:
class Wrapper {
private final Circle circle;
private final Square square;
Wrapper(Circle circle) {
this.circle = circle;
this.square = null;
}
Wrapper(Square square) {
this.square = square;
this.circle = null;
}
}
生成的JSON结构会变成:
[
{
"circle": {
"radius": 4,
"area": 50.26548245743669
}
}, {
"square": {
"side": 5,
"area": 25
}
}
]
✅ 优点:实现简单,Gson自动处理
❌ 缺点:JSON结构改变,新增类型需修改包装类
序列化代码:
List<Wrapper> shapes = Arrays.asList(
new Wrapper(new Circle(4d)),
new Wrapper(new Square(5d))
);
Gson gson = new Gson();
String json = gson.toJson(shapes);
反序列化同样简单:
Gson gson = new Gson();
Type collectionType = new TypeToken<List<Wrapper>>(){}.getType();
List<Wrapper> shapes = gson.fromJson(json, collectionType);
⚠️ 注意:这里使用TypeToken
是因为要反序列化为泛型列表,与多态结构无关。
4. 在对象中添加类型字段
如果只需要序列化,可以直接在类中添加类型标识字段:
public class Square implements Shape {
private final String type = "square"; // 新增字段
private final double side;
private final double area;
public Square(double side) {
this.side = side;
this.area = side * side;
}
}
生成的JSON会包含类型信息:
{
"type": "square",
"radius": 5,
"area": 25
}
✅ 优点:JSON结构接近原始设计
❌ 缺点:无法直接反序列化,仅适用于单向序列化场景
5. 自定义类型适配器
最灵活的方案是编写自定义类型适配器,完全掌控序列化和反序列化过程。
5.1 自定义序列化器
首先实现JsonSerializer
,在标准序列化结果中添加类型字段:
public class ShapeTypeAdapter implements JsonSerializer<Shape> {
@Override
public JsonElement serialize(Shape shape, Type type, JsonSerializationContext context) {
JsonElement elem = new Gson().toJsonTree(shape);
elem.getAsJsonObject().addProperty("type", shape.getClass().getName());
return elem;
}
}
⚠️ 踩坑点:必须创建新的Gson实例执行序列化,否则会导致无限递归!
生成的JSON示例:
[
{
"radius": 4,
"area": 50.26548245743669,
"type": "com.baeldung.gson.polymorphic.TypeAdapterUnitTest$Circle"
},
{
"side": 5,
"area": 25,
"type": "com.baeldung.gson.polymorphic.TypeAdapterUnitTest$Square"
}
]
5.2 自定义反序列化器
接着实现JsonDeserializer
,根据类型字段动态创建对象:
public class ShapeTypeAdapter implements JsonDeserializer<Shape> {
@Override
public Shape deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String typeName = jsonObject.get("type").getAsString();
try {
Class<? extends Shape> cls = (Class<? extends Shape>) Class.forName(typeName);
return new Gson().fromJson(json, cls);
} catch (ClassNotFoundException e) {
throw new JsonParseException(e);
}
}
}
✅ 最佳实践:将序列化器和反序列化器放在同一个类中,确保逻辑一致性。
5.3 注册类型适配器
最后将适配器注册到Gson实例:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeHierarchyAdapter(Shape.class, new ShapeTypeAdapter());
Gson gson = builder.create();
关键点:
- 使用
registerTypeHierarchyAdapter
确保适配器作用于Shape
及其所有实现类 - 序列化/反序列化
Shape
接口时会自动触发适配器
完整测试用例:
List<Shape> shapes = List.of(new Circle(4d), new Square(5d));
String json = gson.toJson(shapes);
Type collectionType = new TypeToken<List<Shape>>(){}.getType();
List<Shape> result = gson.fromJson(json, collectionType);
assertEquals(shapes, result);
6. 总结
我们探讨了三种处理Gson多态的方案:
- 包装对象:简单但不够灵活,适合类型固定的场景
- 类型字段:仅支持序列化,适合单向数据流
- 自定义适配器:功能最完整,但实现稍复杂
下次处理JSON多态时,根据实际需求选择合适方案。完整代码示例可在GitHub获取。