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;
// 標準的 setter 和 getter 方法
public static class ElectricVehicle extends Vehicle {
String autonomy;
String chargingTime;
// 標準的 setter 和 getter 方法
}
public static class FuelVehicle extends Vehicle {
String fuelType;
String transmissionType;
// 標準的 setter 和 getter 方法
}
}
現在,讓我們看看如何將 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());
}
with for Registering Subtypes
下一環節,我們來探索如何使用不同的方法,通過創建自定義註解並使用 Reflections Library 來 掃描並註冊所有現有的子類型。
3.1. Reflection 介紹
Reflection 是一個強大的特性,允許 Java 程序在運行時檢查或操作其結構和行為。這很有用,因為我們可以創建一個自定義註解來指示每個子類型的名稱,並使用 來識別和註冊它們。
我們的 Reflections Library 指南會更詳細地描述它,以及它的使用案例。
3.2. Maven 依賴
首先,要使用 ,我們需要添加 依賴:
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
我們可以從 Maven Central Repository 找到它的最新版本。
3.3. 創建自定義註解來指示子類型的名稱
Java 註解是一種元數據形式,為類、方法、字段和其他程序元素提供編譯時或運行時附加的信息。它們不會直接影響代碼的邏輯,但為編譯器或運行時環境提供指令或詳細信息。
關於自定義註解的更多信息可以在我們創建 Java 中自定義註解的文章中找到。
為了指示每個 子類型的名稱,我們將創建一個以下註解,具有運行時可見性和適用於類型(類):
@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
}
}
請注意,我們需要仍然使用 註解在父類上指定用於存儲類型信息的屬性。
3.4. 使用 Reflection 註冊子類型
最後,我們需要自定義 Jackson 以註冊帶有自定義註解 的類作為子類型。
我們將首先識別帶有自定義註解 的所有類。之後,對於找到的每個類,我們可以提取註解的值並使用關聯的類型名稱註冊子類型:
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. 結論
在本文中,我們探討了兩種不同的方法,重點是使用自定義標註和 反射來識別和註冊子類型。
總而言之,選擇哪種方法取決於應用程序的特定要求。如果項目的子類型已知且穩定,@JsonSubTypes提供了一種穩健且更安全的選項。相反,基於反射的註冊可能更適合對靈活性和運行時適應性要求較高的項目。