1. 概述

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库的更详细描述以及其用例,请参阅我们的Reflections库指南

3.2. Maven依赖

首先,为了使用Reflections,我们需要添加reflections依赖:

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

您可以在Maven中央仓库找到其最新版本。

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的注册可能更适合需要灵活性和运行时适应性的项目。