@JsonSubTypes 對比。 Jackson 中多態反序列化的思考
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. 使用@JsonTypeInfo
和Reflections
進行多態反序列化來註冊子類型
接下來,讓我們探索如何通過創建自定義註釋並使用Reflections Library來掃描和註冊所有現有子類型來使用不同的方法。
3.1.反思介紹
反射是一個強大的功能,允許 Java 程序在運行時檢查或操作其結構和行為。這很方便,因為我們可以創建自定義註釋來指示每個子類型的類型名稱,並使用Reflections
來識別和註冊它們。
我們的 Reflections Library 指南更詳細地描述了它及其用例。
3.2. Maven依賴
首先,要使用Reflections
,我們需要添加reflections
依賴:
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
我們可以在Maven Central Repository上找到它的最新版本。
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
的註冊可能是更好的選擇。
與往常一樣,源代碼可以在 GitHub 上獲取。