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多态的方案:

  1. 包装对象:简单但不够灵活,适合类型固定的场景
  2. 类型字段:仅支持序列化,适合单向数据流
  3. 自定义适配器:功能最完整,但实现稍复杂

下次处理JSON多态时,根据实际需求选择合适方案。完整代码示例可在GitHub获取。


原始标题:Polymorphism with Gson