Inheritance with Jackson – 与杰克逊的继承关系

最后修改: 2016年 3月 12日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this article, we’ll have a look at working with class hierarchies in Jackson.

在这篇文章中,我们将看看如何在Jackson中处理类的层次结构。

Two typical use cases are the inclusion of subtype metadata and ignoring properties inherited from superclasses. We’re going to describe those two scenarios and a couple of circumstances where special treatment of subtypes is needed.

两个典型的用例是包含子类型元数据和忽略从超类继承的属性。我们将描述这两种情况以及一些需要对子类型进行特殊处理的情况。

2. Inclusion of Subtype Information

2.列入亚型信息

There are two ways to add type information when serializing and deserializing data objects, namely global default typing and per-class annotations.

在序列化和反序列化数据对象时,有两种方法可以添加类型信息,即全局默认类型和每类注释。

2.1. Global Default Typing

2.1.全局默认类型

The following three Java classes will be used to illustrate global inclusion of type metadata.

下面三个Java类将被用来说明类型元数据的全局包含。

Vehicle superclass:

车辆超类。

public abstract class Vehicle {
    private String make;
    private String model;

    protected Vehicle(String make, String model) {
        this.make = make;
        this.model = model;
    }

    // no-arg constructor, getters and setters
}

Car subclass:

汽车子类。

public class Car extends Vehicle {
    private int seatingCapacity;
    private double topSpeed;

    public Car(String make, String model, int seatingCapacity, double topSpeed) {
        super(make, model);
        this.seatingCapacity = seatingCapacity;
        this.topSpeed = topSpeed;
    }

    // no-arg constructor, getters and setters
}

Truck subclass:

卡车子类。

public class Truck extends Vehicle {
    private double payloadCapacity;

    public Truck(String make, String model, double payloadCapacity) {
        super(make, model);
        this.payloadCapacity = payloadCapacity;
    }

    // no-arg constructor, getters and setters
}

Global default typing allows type information to be declared just once by enabling it on an ObjectMapper object. That type metadata will then be applied to all designated types. As a result, it is very convenient to use this method for adding type metadata, especially when there are a large number of types involved. The downside is that it uses fully-qualified Java type names as type identifiers, and is thus unsuitable for interactions with non-Java systems, and is only applicable to several pre-defined kinds of types.

全局默认类型化允许通过在ObjectMapper对象上启用类型信息而只声明一次。然后,该类型元数据将被应用于所有指定的类型。因此,使用这种方法来添加类型元数据是非常方便的,尤其是在涉及大量类型的时候。缺点是,它使用完全限定的Java类型名作为类型标识符,因此不适合与非Java系统交互,而且只适用于几种预先定义的类型。

The Vehicle structure shown above is used to populate an instance of Fleet class:

上面显示的Vehicle结构被用来填充Fleet类的实例。

public class Fleet {
    private List<Vehicle> vehicles;
    
    // getters and setters
}

To embed type metadata, we need to enable the typing functionality on the ObjectMapper object that will be used for serialization and deserialization of data objects later on:

为了嵌入类型元数据,我们需要在ObjectMapper对象上启用类型功能,该对象将在以后用于数据对象的序列化和反序列化。

ObjectMapper.activateDefaultTyping(PolymorphicTypeValidator ptv, 
  ObjectMapper.DefaultTyping applicability, JsonTypeInfo.As includeAs)

The parameter PolymorphicTypeValidator is used to verify that the actual subtypes to deserialize are valid as per the specified criteria. Furthermore, the applicability parameter determines the types requiring type information, and the includeAs parameter is the mechanism for type metadata inclusion. Additionally, two other variants of the activateDefaultTyping method are provided:

参数PolymorphicTypeValidator用于验证要反序列化的实际子类型是否按照指定的标准有效。此外,applicability参数决定了需要类型信息的类型,而includeAs参数是类型元数据的包含机制。此外,还提供了activateDefaultTyping方法的另外两个变体。

  • ObjectMapper.activateDefaultTyping(PolymorphicTypeValidator ptv, ObjectMapper.DefaultTyping applicability): allows the caller to specify the validator and applicability, while using WRAPPER_ARRAY as the default value for includeAs
  • ObjectMapper.activateDefaultTyping(PolymorphicTypeValidator ptv): allows the caller to specify the validator while using OBJECT_AND_NON_CONCRETE as the default value for applicability and WRAPPER_ARRAY as the default value for includeAs

Let’s see how it works. To begin, we need to create a validator:

让我们看看它是如何工作的。首先,我们需要创建一个验证器。

PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
  .allowIfSubType("com.baeldung.jackson.inheritance")
  .allowIfSubType("java.util.ArrayList")
  .build();

Next, let’s create an ObjectMapper object and activate default typing on it using the above validator:

接下来,让我们创建一个ObjectMapper对象,并使用上述验证器激活它的默认键入。

ObjectMapper mapper = new ObjectMapper();
mapper.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL);

The next step is to instantiate and populate the data structure introduced at the beginning of this sub-section. The code to do it will be re-used later on in the subsequent sub-sections. For the sake of convenience and re-use, we will name it the vehicle instantiation block.

下一步是实例化和填充本小节开始时介绍的数据结构。做到这一点的代码将在后面的小节中重新使用。为了方便和重复使用,我们将其命名为车辆实例化块

Car car = new Car("Mercedes-Benz", "S500", 5, 250.0);
Truck truck = new Truck("Isuzu", "NQR", 7500.0);

List<Vehicle> vehicles = new ArrayList<>();
vehicles.add(car);
vehicles.add(truck);

Fleet serializedFleet = new Fleet();
serializedFleet.setVehicles(vehicles);

Those populated objects will then be serialized:

然后,这些填充的对象将被序列化。

String jsonDataString = mapper.writeValueAsString(serializedFleet);

The resulting JSON string:

产生的JSON字符串。

{
    "vehicles": 
    [
        "java.util.ArrayList",
        [
            [
                "com.baeldung.jackson.inheritance.Car",
                {
                    "make": "Mercedes-Benz",
                    "model": "S500",
                    "seatingCapacity": 5,
                    "topSpeed": 250.0
                }
            ],

            [
                "com.baeldung.jackson.inheritance.Truck",
                {
                    "make": "Isuzu",
                    "model": "NQR",
                    "payloadCapacity": 7500.0
                }
            ]
        ]
    ]
}

During deserialization, objects are recovered from the JSON string with type data preserved:

在反序列化过程中,对象被从JSON字符串中恢复,并保留了类型数据。

Fleet deserializedFleet = mapper.readValue(jsonDataString, Fleet.class);

The recreated objects will be the same concrete subtypes as they were before serialization:

重新创建的对象将与序列化之前的具体子类型相同。

assertThat(deserializedFleet.getVehicles().get(0), instanceOf(Car.class));
assertThat(deserializedFleet.getVehicles().get(1), instanceOf(Truck.class));

2.2. Per-Class Annotations

2.2.每个类别的注释

Per-class annotation is a powerful method to include type information and can be very useful for complex use cases where a significant level of customization is necessary. However, this can only be achieved at the expense of complication. Per-class annotations override global default typing if type information is configured in both ways.

每类注释是一种包含类型信息的强大方法,对于需要大量定制的复杂用例非常有用。然而,这只能以复杂化为代价来实现。如果类型信息是以两种方式配置的,那么每类注释就会覆盖全局默认类型。

To make use of this method, the supertype should be annotated with @JsonTypeInfo and several other relevant annotations. This subsection will use a data model similar to the Vehicle structure in the previous example to illustrate per-class annotations. The only change is the addition of annotations on Vehicle abstract class, as shown below:

为了利用这个方法,超类型应该被注解为@JsonTypeInfo和其它几个相关的注解。本小节将使用与前面例子中的Vehicle结构类似的数据模型来说明每类注释。唯一的变化是在Vehicle抽象类上添加注解,如下所示。

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME, 
  include = JsonTypeInfo.As.PROPERTY, 
  property = "type")
@JsonSubTypes({ 
  @Type(value = Car.class, name = "car"), 
  @Type(value = Truck.class, name = "truck") 
})
public abstract class Vehicle {
    // fields, constructors, getters and setters
}

Data objects are created using the vehicle instantiation block introduced in the previous subsection, and then serialized:

数据对象是使用上一小节介绍的车辆实例化块创建的,然后再进行序列化。

String jsonDataString = mapper.writeValueAsString(serializedFleet);

The serialization produces the following JSON structure:

串行化产生以下JSON结构。

{
    "vehicles": 
    [
        {
            "type": "car",
            "make": "Mercedes-Benz",
            "model": "S500",
            "seatingCapacity": 5,
            "topSpeed": 250.0
        },

        {
            "type": "truck",
            "make": "Isuzu",
            "model": "NQR",
            "payloadCapacity": 7500.0
        }
    ]
}

That string is used to re-create data objects:

该字符串被用来重新创建数据对象。

Fleet deserializedFleet = mapper.readValue(jsonDataString, Fleet.class);

Finally, the whole progress is validated:

最后,整个进展得到了验证。

assertThat(deserializedFleet.getVehicles().get(0), instanceOf(Car.class));
assertThat(deserializedFleet.getVehicles().get(1), instanceOf(Truck.class));

3. Ignoring Properties from a Supertype

3.忽略来自超类型的属性

Sometimes, some properties inherited from superclasses need to be ignored during serialization or deserialization. This can be achieved by one of three methods: annotations, mix-ins and annotation introspection.

有时,在序列化或反序列化过程中,一些从超类继承的属性需要被忽略。这可以通过三种方法之一来实现:注解、混入和注解自省。

3.1. Annotations

3.1.注释

There are two commonly used Jackson annotations to ignore properties, which are @JsonIgnore and @JsonIgnoreProperties. The former is directly applied to type members, telling Jackson to ignore the corresponding property when serializing or deserializing. The latter is used at any level, including type and type member, to list properties that should be ignored.

有两个常用的Jackson注解来忽略属性,它们是@JsonIgnore@JsonIgnoreProperties。前者直接应用于类型成员,告诉Jackson在序列化或反序列化时忽略相应的属性。后者被用于任何级别,包括类型和类型成员,以列出应该被忽略的属性。

@JsonIgnoreProperties is more powerful than the other since it allows us to ignore properties inherited from supertypes that we do not have control of, such as types in an external library. In addition, this annotation allows us to ignore many properties at once, which can lead to more understandable code in some cases.

@JsonIgnoreProperties比其他的更强大,因为它允许我们忽略从我们无法控制的超类型继承的属性,例如外部库中的类型。此外,这个注解允许我们一次忽略许多属性,这在某些情况下可以使代码更容易理解。

The following class structure is used to demonstrate annotation usage:

下面的类结构被用来演示注释的使用。

public abstract class Vehicle {
    private String make;
    private String model;

    protected Vehicle(String make, String model) {
        this.make = make;
        this.model = model;
    }

    // no-arg constructor, getters and setters
}

@JsonIgnoreProperties({ "model", "seatingCapacity" })
public abstract class Car extends Vehicle {
    private int seatingCapacity;
    
    @JsonIgnore
    private double topSpeed;

    protected Car(String make, String model, int seatingCapacity, double topSpeed) {
        super(make, model);
        this.seatingCapacity = seatingCapacity;
        this.topSpeed = topSpeed;
    }

    // no-arg constructor, getters and setters
}

public class Sedan extends Car {
    public Sedan(String make, String model, int seatingCapacity, double topSpeed) {
        super(make, model, seatingCapacity, topSpeed);
    }

    // no-arg constructor
}

public class Crossover extends Car {
    private double towingCapacity;

    public Crossover(String make, String model, int seatingCapacity, 
      double topSpeed, double towingCapacity) {
        super(make, model, seatingCapacity, topSpeed);
        this.towingCapacity = towingCapacity;
    }

    // no-arg constructor, getters and setters
}

As you can see, @JsonIgnore tells Jackson to ignore Car.topSpeed property, while @JsonIgnoreProperties ignores the Vehicle.model and Car.seatingCapacity ones.

正如你所看到的,@JsonIgnore告诉Jackson忽略Car.topSpeed属性,而@JsonIgnoreProperties则忽略Vehicle.modelCar.seatingCapacity的。

The behavior of both annotations is validated by the following test. First, we need to instantiate ObjectMapper and data classes, then use that ObjectMapper instance to serialize data objects:

这两个注解的行为通过以下测试得到验证。首先,我们需要实例化ObjectMapper和数据类,然后使用该ObjectMapper实例来序列化数据对象。

ObjectMapper mapper = new ObjectMapper();

Sedan sedan = new Sedan("Mercedes-Benz", "S500", 5, 250.0);
Crossover crossover = new Crossover("BMW", "X6", 5, 250.0, 6000.0);

List<Vehicle> vehicles = new ArrayList<>();
vehicles.add(sedan);
vehicles.add(crossover);

String jsonDataString = mapper.writeValueAsString(vehicles);

jsonDataString contains the following JSON array:

jsonDataString包含以下JSON数组。

[
    {
        "make": "Mercedes-Benz"
    },
    {
        "make": "BMW",
        "towingCapacity": 6000.0
    }
]

Finally, we will prove the presence or absence of various property names in the resulting JSON string:

最后,我们将证明所产生的JSON字符串中存在或不存在各种属性名称。

assertThat(jsonDataString, containsString("make"));
assertThat(jsonDataString, not(containsString("model")));
assertThat(jsonDataString, not(containsString("seatingCapacity")));
assertThat(jsonDataString, not(containsString("topSpeed")));
assertThat(jsonDataString, containsString("towingCapacity"));

3.2. Mix-ins

3.2.混合配料

Mix-ins allow us to apply behavior (such as ignoring properties when serializing and deserializing) without the need to directly apply annotations to a class. This is especially useful when dealing with third-party classes, in which we cannot modify the code directly.

混合插件允许我们应用行为(比如在序列化和反序列化时忽略属性),而不需要直接对类应用注解。这在处理第三方类时特别有用,因为我们不能直接修改代码。

This sub-section reuses the class inheritance chain introduced in the previous one, except that the @JsonIgnore and @JsonIgnoreProperties annotations on the Car class have been removed:

这一节重用了上一节介绍的类的继承链,只是删除了@JsonIgnore@JsonIgnorePropertiesCar类的注释。

public abstract class Car extends Vehicle {
    private int seatingCapacity;
    private double topSpeed;
        
    // fields, constructors, getters and setters
}

In order to demonstrate operations of mix-ins, we will ignore Vehicle.make and Car.topSpeed properties, then use a test to make sure everything works as expected.

为了演示混入的操作,我们将忽略Vehicle.makeCar.topSpeed属性,然后使用测试来确保一切都按预期进行。

The first step is to declare a mix-in type:

第一步是声明一个混入类型。

private abstract class CarMixIn {
    @JsonIgnore
    public String make;
    @JsonIgnore
    public String topSpeed;
}

Next, the mix-in is bound to a data class through an ObjectMapper object:

接下来,混合器通过一个ObjectMapper对象被绑定到一个数据类。

ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(Car.class, CarMixIn.class);

After that, we instantiate data objects and serialize them into a string:

之后,我们实例化数据对象并将其序列化为字符串。

Sedan sedan = new Sedan("Mercedes-Benz", "S500", 5, 250.0);
Crossover crossover = new Crossover("BMW", "X6", 5, 250.0, 6000.0);

List<Vehicle> vehicles = new ArrayList<>();
vehicles.add(sedan);
vehicles.add(crossover);

String jsonDataString = mapper.writeValueAsString(vehicles);

jsonDataString now contains the following JSON:

jsonDataString现在包含以下JSON。

[
    {
        "model": "S500",
        "seatingCapacity": 5
    },
    {
        "model": "X6",
        "seatingCapacity": 5,
        "towingCapacity": 6000.0
    }
]

Finally, let’s verify the result:

最后,让我们验证一下结果。

assertThat(jsonDataString, not(containsString("make")));
assertThat(jsonDataString, containsString("model"));
assertThat(jsonDataString, containsString("seatingCapacity"));
assertThat(jsonDataString, not(containsString("topSpeed")));
assertThat(jsonDataString, containsString("towingCapacity"));

3.3. Annotation Introspection

3.3.注释性自省</strong

Annotation introspection is the most powerful method to ignore supertype properties since it allows for detailed customization using the AnnotationIntrospector.hasIgnoreMarker API.

注释自省是忽略超类型属性的最强大的方法,因为它允许使用AnnotationIntrospector.hasIgnoreMarker API进行详细定制。

This sub-section makes use of the same class hierarchy as the preceding one. In this use case, we will ask Jackson to ignore Vehicle.model, Crossover.towingCapacity and all properties declared in the Car class. Let’s start with the declaration of a class that extends the JacksonAnnotationIntrospector interface:

这个小节利用了与前一个小节相同的类层次结构。在这个用例中,我们将要求 Jackson 忽略 Vehicle.modelCrossover.towingCapacity 以及在 Car 类中声明的所有属性。让我们从声明一个扩展了JacksonAnnotationIntrospector接口的类开始。

class IgnoranceIntrospector extends JacksonAnnotationIntrospector {
    public boolean hasIgnoreMarker(AnnotatedMember m) {
        return m.getDeclaringClass() == Vehicle.class && m.getName() == "model" 
          || m.getDeclaringClass() == Car.class 
          || m.getName() == "towingCapacity" 
          || super.hasIgnoreMarker(m);
    }
}

The introspector will ignore any properties (that is, it will treat them as if they were marked as ignored via one of the other methods) that match the set of conditions defined in the method.

自省器将忽略任何符合该方法中定义的条件集的属性(也就是说,它将把它们当作通过其他方法之一标记为忽略的属性)。

The next step is to register an instance of the IgnoranceIntrospector class with an ObjectMapper object:

下一步是将IgnoranceIntrospector类的一个实例与ObjectMapper对象注册。

ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new IgnoranceIntrospector());

Now we create and serialize data objects in the same way as in section 3.2. The contents of the newly produced string are:

现在我们以与3.2节相同的方式创建和序列化数据对象。新产生的字符串的内容是。

[
    {
        "make": "Mercedes-Benz"
    },
    {
        "make": "BMW"
    }
]

Finally, we’ll verify that the introspector worked as intended:

最后,我们将验证内省器是否按计划工作。

assertThat(jsonDataString, containsString("make"));
assertThat(jsonDataString, not(containsString("model")));
assertThat(jsonDataString, not(containsString("seatingCapacity")));
assertThat(jsonDataString, not(containsString("topSpeed")));
assertThat(jsonDataString, not(containsString("towingCapacity")));

4. Subtype Handling Scenarios

4.子类型处理方案

This section will deal with two interesting scenarios relevant to subclass handling.

本节将讨论与子类处理有关的两种有趣的情况。

4.1. Conversion Between Subtypes

4.1.子类型之间的转换

Jackson allows an object to be converted to a type other than the original one. In fact, this conversion may happen among any compatible types, but it is most helpful when used between two subtypes of the same interface or class to secure values and functionality.

杰克逊允许一个对象被转换为除原始类型以外的其他类型。事实上,这种转换可能发生在任何兼容的类型之间,但当它在同一接口或类的两个子类型之间使用时,对保证数值和功能是最有帮助的。

In order to demonstrate conversion of a type to another one, we will reuse the Vehicle hierarchy taken from section 2, with the addition of the @JsonIgnore annotation on properties in Car and Truck to avoid incompatibility.

为了演示一个类型向另一个类型的转换,我们将重新使用第 2 节中的 Vehicle 层次结构,并在 CarTruck 中的属性上添加 @JsonIgnore 注解,以避免不兼容。

public class Car extends Vehicle {
    @JsonIgnore
    private int seatingCapacity;

    @JsonIgnore
    private double topSpeed;

    // constructors, getters and setters
}

public class Truck extends Vehicle {
    @JsonIgnore
    private double payloadCapacity;

    // constructors, getters and setters
}

The following code will verify that a conversion is successful and that the new object preserves data values from the old one:

下面的代码将验证转换是否成功,新对象是否保留了旧对象的数据值。

ObjectMapper mapper = new ObjectMapper();

Car car = new Car("Mercedes-Benz", "S500", 5, 250.0);
Truck truck = mapper.convertValue(car, Truck.class);

assertEquals("Mercedes-Benz", truck.getMake());
assertEquals("S500", truck.getModel());

4.2. Deserialization Without No-arg Constructors

4.2.没有无条件构造函数的反序列化

By default, Jackson recreates data objects by using no-arg constructors. This is inconvenient in some cases, such as when a class has non-default constructors and users have to write no-arg ones just to satisfy Jackson’s requirements. It is even more troublesome in a class hierarchy where a no-arg constructor must be added to a class and all those higher in the inheritance chain. In these cases, creator methods come to the rescue.

默认情况下,Jackson通过使用无参数的构造函数来重新创建数据对象。这在某些情况下是不方便的,比如当一个类有非默认的构造函数时,用户必须编写无条件的构造函数来满足杰克逊的要求。在类的层次结构中就更麻烦了,因为一个类和继承链中所有更高的类都必须添加无条件的构造函数。在这种情况下,创建者方法就会来拯救。

This section will use an object structure similar to the one in section 2, with some changes to constructors. Specifically, all no-arg constructors are dropped, and constructors of concrete subtypes are annotated with @JsonCreator and @JsonProperty to make them creator methods.

本节将使用与第2节类似的对象结构,并对构造函数进行一些修改。具体来说,所有的无参数构造函数都被放弃,具体子类型的构造函数被注解为@JsonCreator@JsonProperty,使其成为创建者方法。

public class Car extends Vehicle {

    @JsonCreator
    public Car(
      @JsonProperty("make") String make, 
      @JsonProperty("model") String model, 
      @JsonProperty("seating") int seatingCapacity, 
      @JsonProperty("topSpeed") double topSpeed) {
        super(make, model);
        this.seatingCapacity = seatingCapacity;
            this.topSpeed = topSpeed;
    }

    // fields, getters and setters
}

public class Truck extends Vehicle {

    @JsonCreator
    public Truck(
      @JsonProperty("make") String make, 
      @JsonProperty("model") String model, 
      @JsonProperty("payload") double payloadCapacity) {
        super(make, model);
        this.payloadCapacity = payloadCapacity;
    }

    // fields, getters and setters
}

A test will verify that Jackson can deal with objects that lack no-arg constructors:

一个测试将验证Jackson是否能够处理缺乏无参数构造函数的对象。

ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
        
Car car = new Car("Mercedes-Benz", "S500", 5, 250.0);
Truck truck = new Truck("Isuzu", "NQR", 7500.0);

List<Vehicle> vehicles = new ArrayList<>();
vehicles.add(car);
vehicles.add(truck);

Fleet serializedFleet = new Fleet();
serializedFleet.setVehicles(vehicles);

String jsonDataString = mapper.writeValueAsString(serializedFleet);
mapper.readValue(jsonDataString, Fleet.class);

5. Conclusion

5.结论

This tutorial has covered several interesting use cases to demonstrate Jackson’s support for type inheritance, with a focus on polymorphism and ignorance of supertype properties.

本教程涵盖了几个有趣的用例,展示了Jackson对类型继承的支持,重点是多态性和对超类型属性的忽略。

The implementation of all these examples and code snippets can be found in a GitHub project.

所有这些例子和代码片段的实现都可以在一个GitHub项目中找到