@JsonSubTypes 与 Reflections 在 Jackson 中用于多态反序列化
最后更新:2024年5月11日
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-based 注册允许在运行时动态发现子类型。虽然它减少了样板代码,但引入了运行时开销,并且缺乏编译时安全性,并且需要外部依赖项来进行类路径扫描。 然而,当处理许多子类型时,这种方法可能**适用于处理许多子类型,因为添加新的子类型不会影响已有的代码。**
5. 结论
在本文中,我们研究了两种不同的方法,重点是使用自定义注解和 Reflections 来识别和注册子类型。
总之,两者之间的选择取决于应用程序的特定需求。 如果项目的子类型已经知道并且稳定,@JsonSubTypes 提供了一个更强大和更安全的选择。 相反,对于需要灵活性和运行时适应性的项目,基于 Reflections 的注册可能是更好的选择。
支持本文的代码可在 GitHub 上获取。 一旦你以 Baeldung Pro 会员 身份登录,就开始学习并在项目上进行编码。















