知識庫 / JSON / Jackson RSS 訂閱

Jackson中的多態反序列化:@JsonSubTypes 與反射的比較

Data,Jackson
HongKong
6
09:44 PM · Dec 05 ,2025

1. 概述

多態化反序列化是 Jackson 的一項特性,Jackson 是一個流行的 Java JSON 序列化和反序列化庫。它允許我們將 JSON 反序列化為 Java 對象層次結構,即使在編譯時未知道具體的類型。當您擁有父類和多個子類時,這種特性非常有用,我們可以確定反序列化過程中的對象實際類型,而不會丟失對象多態性的信息。

在本教程中,我們將探索如何通過兩種方式實現這一點:使用類型處理註解來指示父類子類型的基類,或使用 Reflections 方式掃描並註冊所有子類型。

2. 使用 @JsonTypeInfo@JsonSubTypes 進行多態化反序列化

Jackson 的多態化類型處理註解是其中一種最直接的方法。

讓我們看一個實現示例,其中我們使用 @JsonTypeInfo@JsonSubTypes 來指示 Vehicle 實體及其子類型,並基於現有屬性進行反序列化:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Vehicle.ElectricVehicle.class, name = "ELECTRIC_VEHICLE"),
    @JsonSubTypes.Type(value = Vehicle.FuelVehicle.class, name = "FUEL_VEHICLE")
})
public class Vehicle {

    public String type;

    // standard setters and getters

    public static class ElectricVehicle extends Vehicle {

        String autonomy;
        String chargingTime;

        // standard setters and getters
    }

    public static class FuelVehicle extends Vehicle {

        String fuelType;
        String transmissionType;

        // standard setters and getters
    }
}

現在,讓我們看看如何將 JSON 輸入反序列化為 Vehicle 類型的子類的工作方式:

@Test
public void whenDeserializingPolymorphic_thenCorrect() throws JsonProcessingException {
    String json = "{\"type\":\"ELECTRIC_VEHICLE\",\"autonomy\":\"500\",\"chargingTime\":\"200\"}";

    Vehicle vehicle = new ObjectMapper().readerFor(Vehicle.class).readValue(json);

    assertEquals(Vehicle.ElectricVehicle.class, vehicle.getClass());
}

3. 使用 @JsonTypeInfoReflections 進行多態化反序列化以註冊子類型

接下來,讓我們探索一種通過創建自定義註解並使用 Reflections 庫 進行掃描和註冊所有現有子類型的不同方法。

3.1 反射介紹

反射是一種強大的特性,允許 Java 程序在運行時檢查或操縱其結構和行為。這非常實用,因為我們可以創建一個自定義註解來指示每個子類型的類型名稱,並使用 Reflections 來識別和註冊它們。

我們的 Reflections 庫指南會更詳細地描述它,以及它的使用案例。

3.2. Maven 依賴

首先,要使用 Reflections,我們需要添加 reflections 依賴:

<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.10.2</version>
</dependency>

我們可以在 Maven Central 倉庫 中找到其最新版本。

3.3. 創建自定義標註以指示子類型的類型名稱

Java 標註是一種元數據形式,可在編譯時或運行時提供有關類、方法、字段和其他程序元素的附加信息。它們不會直接影響代碼的邏輯,而是向編譯器或運行時環境提供指令或詳細信息。

關於自定義標註的更多信息,請參閲我們在 Java 中創建自定義標註的文章。

為了指示每個 Vehicle 子類型的類型名稱,我們將創建一個以下標註,具有運行時可見性,並適用於類型(類):

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface VehicleSubType {
    String value();
}

現在,讓我們看看如何更新現有代碼以指定每個子類定義的類型:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true)
public class Vehicle {

    public String type;

    // standard setters and getters

    @VehicleSubType("ELECTRIC_VEHICLE")
    public static class ElectricVehicle extends Vehicle {

        String autonomy;
        String chargingTime;

        // standard setters and getters
    }

    @VehicleSubType("FUEL_VEHICLE")
    public static class FuelVehicle extends Vehicle {

        String fuelType;
        String transmissionType;

        // standard setters and getters
    }
}

請注意,我們仍然需要使用 @JsonTypeInfo 註解來指定用於存儲類型信息的屬性,在父類上。

3.4. 使用反射註冊子類型

最後,我們需要自定義 Jackson 的 ObjectMapper 以將帶有自定義註解的類註冊為子類型。

我們將首先識別所有帶有自定義註解 @VehicleSubType 的類。 隨後,對於每個找到的類,我們可以提取註解的值,並使用關聯的類型名稱註冊子類型:

private ObjectMapper getCustomObjectMapper() {

    ObjectMapper objectMapper = new ObjectMapper();

    Reflections reflections = new Reflections("com.baeldung.jackson.polymorphicdeserialization.reflection");
    Set<Class<?>> subtypes = reflections.getTypesAnnotatedWith(VehicleSubType.class);

    for (Class<?> subType : subtypes) {
        VehicleSubType annotation = subType.getAnnotation(VehicleSubType.class);
        if (annotation != null) {
            String typeName = annotation.value();
            objectMapper.registerSubtypes(new NamedType(subType, typeName));
        }
    }

    return objectMapper;
}

現在,讓我們使用之前相同的輸入測試我們的代碼:

@Test
public void whenDeserializingPolymorphic_thenCorrect() throws JsonProcessingException {
    String json = "{\"type\":\"ELECTRIC_VEHICLE\",\"autonomy\":\"500\",\"chargingTime\":\"200\"}";
    ObjectMapper objectMapper = getCustomObjectMapper();

    Vehicle vehicle = objectMapper.readValue(json, Vehicle.class);

    assertEquals(Vehicle.ElectricVehicle.class, vehicle.getClass());
}

4. 兩種方法的差異

使用 @JsonSubTypes 的方法提供顯式配置,通過註解定義子類型及其類型名稱。 這提供了一個集中且清晰的層次結構,確保編譯時安全。

基於 Reflections 的註冊允許在運行時動態發現子類型。 雖然它減少了樣板代碼,但引入了運行時開銷,並且缺乏編譯時安全,並且需要外部依賴進行類路徑掃描。 但是,這種方法在處理大量子類型時可能 適用,因為添加一個新的子類型不會影響已有的代碼。

5. 結論在本文中,我們探討了兩種不同的方法,重點介紹了使用自定義標註和Reflections來識別和註冊子類型。

總而言之,選擇哪一種方法取決於應用程序的特定需求。如果項目的子類型已知且穩定,@JsonSubTypes提供了一種更健壯和安全的選項。相反,基於Reflections的註冊可能更適合對靈活性和運行時適應性要求較高的項目。

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

發佈 評論

Some HTML is okay.