知識庫 / JSON RSS 訂閱

Gson 中的多態性

JSON
HongKong
10
09:43 PM · Dec 05 ,2025

1. 引言

在本教程中,我們將探討如何使用 Gson 管理多態性。我們還將探索一些處理多態方式序列化和反序列化的技術。

2. JSON 中的多態性

在 Java 中,多態性得到了很好的理解。我們有一個類層次結構,使得在適當的情況下,我們可以以某種方式將不同的相關類型視為相同。

例如,我們可能有一些各種二維形狀的定義。不同的形狀以不同的方式定義,但它們都具有一些共同的功能——例如,它們都可以計算面積。

因此,我們可以定義一些用於定義形狀的類,如下所示:

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 文檔中包含任何功能,但我們可以包含重疊的數據,並且我們可能希望在 JSON 中以合理的方式表示多態類

例如,上述形狀可能被表示為:

[
    {
        "shape": "circle",
        "radius": 4,
        "area": 50.26548245743669
    }, {
        "shape": "square",
        "side": 5,
        "area": 25
    }
]

如果我們僅僅將它們視為 Shape 實例,我們已經有 area 可供使用。但是,如果我們想知道它們究竟是什麼形狀,那麼我們可以識別並從它們中提取額外信息。

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
        }
    }
]

這並非嚴格意義上的多態,而且這意味着 JSON 的結構與我們之前見到的有所不同,但實現起來非常簡單。特別是,我們只需要編寫我們的包裝類型,Gson 就會自動完成所有工作:

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,因為我們正在將數據反序列化到泛型列表。這與我們的包裝類型和多態結構無關。

但是,這意味着我們的 Wrapper 類型需要支持所有可能的子類型。 添加新的子類型意味着要付出更多的工作來達到我們想要的結果。

4. 為對象添加類型字段

如果我們僅對對象進行序列化感興趣,可以簡單地向它們添加一個字段來指示類型:

public class Square implements Shape {
    private final String type = "square"; // Added field
    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. 自定義類型適配器

我們將探索的最終方法是編寫自定義類型適配器。這是一種我們可以在 Gson 實例中貢獻的代碼,它將處理我們類型序列化和反序列化,從而為我們完成任務。

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實例。如果使用原始實例(通過JsonSerializationContext)則會陷入無限循環,序列化器會不斷調用自身。

這裏我們使用完整的類名作為類型,但我們也可以使用任何我們想要支持的類型。我們只需要某種方式來唯一地將此字符串與類名進行轉換。

使用此方法,生成的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. 自定義反序列器

現在我們能夠將我們的類型序列化為 JSON,我們也需要能夠對其進行反序列化。這意味着我們需要理解 type 字段,然後使用它將其反序列化為正確的類。

我們通過為我們的類型編寫自定義的 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);
        }
    }
}

我們可以通過簡單地實現這兩個接口,將我們的序列化器和反序列器都放在同一個類中實現。 這樣做有助於保持邏輯的一致性,確保這兩個組件之間是兼容的。

正如之前所述,實際進行反序列化需要使用一個新的 Gson 實例。 否則,我們將陷入無限循環。

5.3. 將類型適配器連接到 Gson

現在我們已經擁有一個可以用於序列化和反序列化我們的多態類型,並且可以被使用,那麼我們需要創建一個 Gson 實例並將其連接上。

GsonBuilder builder = new GsonBuilder();
builder.registerTypeHierarchyAdapter(Shape.class, new ShapeTypeAdapter());
Gson gson = builder.create();

我們使用 registerTypeHierarchyAdapter> 調用來完成這項工作,因為這意味着它將被用於我們的 Shape 類及其所有實現者。 這將導致該 Gson 實例在嘗試將任何實現我們的 Shape 接口的內容序列化為 JSON 或嘗試將 JSON 序列化為任何實現 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 以及將它們從 JSON 中反序列化回來。

下次您處理 JSON 和多態類型時,不妨嘗試一下這些技術。

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

發佈 評論

Some HTML is okay.