Deduction-Based Polymorphism in Jackson 2.12 – Jackson 2.12中基于演绎的多态性

最后修改: 2022年 4月 18日

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

1. Overview

1.概述

In this tutorial, we’ll explore how we can use the deduction-based polymorphism feature from the Jackson library.

在本教程中,我们将探讨如何使用Jackson库中的基于演绎的多态性功能

2. Name-based Polymorphism

2.基于名字的多态性

Let’s imagine we have a structure of classes like the one described in the following image.

让我们想象一下,我们有一个类似于下面图片中描述的类的结构。

Character Diagram

First, the NamedCharacter and ImperialSpy classes implement the Character interface. Secondly, the King and Knight classes are implementing the NamedCharacter class. Lastly, we have a ControlledCharacter class, which contains a reference to a character that the player controls.

首先,NamedCharacterImperialSpy实现了角色接口。其次,国王和骑士类正在实现NamedCharacter类。最后,我们有一个ControlledCharacter类,它包含对玩家控制的角色的引用。

We would like to parse JSON objects into Java objects, without having to modify the structure of the received JSON.

我们希望将JSON对象解析为Java对象,而不必修改收到的JSON的结构。

So let’s take a look at the definition of the classes. Notice that for the base interface, we’ll have to use the Jackson annotations to declare which deduction we will want to use. Also, we’ll have to add the @JsonSubTypes annotations to declare which classes we want to deduct.

所以让我们来看看这些类的定义。请注意,对于基础接口,我们必须使用Jackson注解来声明我们想要使用的扣减。此外,我们还必须添加@JsonSubTypes注解来声明我们要扣除哪些类。

@JsonTypeInfo(use = Id.NAME)
@JsonSubTypes({ @Type(ImperialSpy.class), @Type(King.class), @Type(Knight.class) })
public interface Character {
}

Furthermore, we can also have an intermediary class between the interface Character and King and Knight classes. Hence, Jackson, we’ll also know how to deduct the polymorphism in this case as well:

此外,我们也可以在接口CharacterKing以及Knight类之间有一个中介类。因此,杰克逊,我们也会知道在这种情况下如何扣除多态性。

public class NamedCharacter implements Character {
    private String name;

    // standard setters and getters
}

Subsequently, we’ll implement the subclasses of the Character interface. We already declared these subclasses as subtypes in the previous code example. Consequently, the implementation does not have any dependencies on the Jackson library:

随后,我们将实现Characterinterface的子类。我们已经在前面的代码例子中把这些子类声明为子类型。因此,该实现对Jackson库没有任何依赖性。

public class ImperialSpy implements Character {
}
public class King extends NamedCharacter {
    private String land;

    // standard setters and getters
}
public class Knight extends NamedCharacter {
    private String weapon;

    // standard setters and getters
}

An example of a JSON that we would like to map is the following:

我们想要映射的JSON的一个例子如下。

{
    "name": "Old King Allant",
    "land": "Boletaria",
}

Firstly, if we try to read the above JSON structure, Jackson would throw a runtime exception with the message Could not resolve subtype of [simple type, class com.baeldung.jackson.deductionbasedpolymorphism.Character]: missing type id property ‘@type’:

首先,如果我们试图读取上述JSON结构,Jackson会抛出一个运行时异常,信息是无法解决[simple type, class com.baeldung.jackson.deductionbasedpolymorphism.Character]的子类型:缺少类型id属性’@type’:

@Test
void givenAKingWithoutType_whenMapping_thenExpectAnError() {
    String kingJson = formatJson("{'name': 'Old King Allant', 'land':'Boletaria'}");
    assertThrows(InvalidTypeIdException.class, () -> objectMapper.readValue(kingJson, Character.class));
}

In addition, the formatJson utility method helps us keep the code in the test simple, by converting the quote characters to double quotes, as JSON requires:

此外,formatJson实用方法帮助我们保持测试中的代码简单,它将引号字符转换为双引号,因为JSON要求。

public static String formatJson(String input) {
    return input.replaceAll("'", "\"");
}

Consequently, to be able to polymorphically deduct the type of our character, we’ll have to modify the JSON structure and explicitly add the type of the object. Therefore, we would have to couple the polymorphic behavior with our JSON structure:

因此,为了能够多态地扣除我们角色的类型,我们必须修改JSON结构,明确地添加对象的类型。因此,我们将不得不把多态行为与我们的JSON结构结合起来。

{
    "@type": "King"
    "name": "Old King Allant",
    "land": "Boletaria",
}
@Test
void givenAKing_whenMapping_thenExpectAKingType() throws Exception {
    String kingJson = formatJson("{'name': 'Old King Allant', 'land':'Boletaria', '@type':'King'}");

    Character character = objectMapper.readValue(kingJson, Character.class);
    assertTrue(character instanceof King);
    assertSame(character.getClass(), King.class);
    King king = (King) character;
    assertEquals("Boletaria", king.getLand());
}

3. Deduction-based Polymorphism

3.基于演绎的多态性

To activate the deduction-based polymorphism, the only change we have to do is to use @JsonTypeInfo(use = Id.DEDUCTION):

为了激活基于演绎的多态性,我们唯一要做的改变是使用@JsonTypeInfo(use = Id.DEDUCTION):

@JsonTypeInfo(use = Id.DEDUCTION)
@JsonSubTypes({ @Type(ImperialSpy.class), @Type(King.class), @Type(Knight.class) })
public interface Character {
}

4. Simple Inference

4.简单推理

Let’s explore how we can read JSON in a polymorphic way with simple inference. The object that we want to read is the following:

让我们来探讨一下,我们如何用简单的推理方法以多态的方式读取JSON。我们要读取的对象如下。

{
    "name": "Ostrava, of Boletaria",
    "weapon": "Rune Sword",
}

Firstly, we’ll read the value in a Character object. Then, we’ll test that Jackson deducted correctly the type of the JSON:

首先,我们将读取Character对象中的值。然后,我们将测试Jackson是否正确扣除了JSON的类型。

@Test
void givenAKnight_whenMapping_thenExpectAKnightType() throws Exception {
    String knightJson = formatJson("{'name':'Ostrava, of Boletaria', 'weapon':'Rune Sword'}");

    Character character = objectMapper.readValue(knightJson, Character.class);

    assertTrue(character instanceof Knight);
    assertSame(character.getClass(), Knight.class);
    Knight king = (Knight) character;
    assertEquals("Ostrava, of Boletaria", king.getName());
    assertEquals("Rune Sword", king.getWeapon());
}

Moreover, if the JSON is an empty object, Jackson will interpret it as an ImperialSpy, which is a class with no attributes:

此外,如果JSON是一个空对象,Jackson将把它解释为一个ImperialSpy,这是一个没有属性的类。

@Test
void givenAnEmptyObject_whenMapping_thenExpectAnImperialSpy() throws Exception {
    String imperialSpyJson = "{}";

    Character character = objectMapper.readValue(imperialSpyJson, Character.class);

    assertTrue(character instanceof ImperialSpy);
}

Also, a null JSON object will be deducted as a null object by Jackson as well:

另外,一个空的JSON对象也会被Jackson扣为一个空对象

@Test
void givenANullObject_whenMapping_thenExpectANullObject() throws Exception {
    Character character = objectMapper.readValue("null", Character.class);

    assertNull(character);
}

5. Case Insensitive Inference

5.不区分大小写的推理

Jackson can also deduct the polymorphism, even if the case of attributes does not match. First, we’ll instantiate an object mapper with the ACCEPT_CASE_INSENSITIVE_PROPERTIES enabled:

Jackson也可以扣除多态性,即使属性的情况不匹配。首先,我们将实例化一个具有ACCEPT_CASE_INSENSITIVE_PROPERTIES 启用的对象映射器。

ObjectMapper objectMapper = JsonMapper.builder().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true).build();

Then, using the instantiated objectMapper, we can test that the polymorphism is deducted correctly:

然后,使用实例化的objectMapper,我们可以测试多态性是否被正确扣除。

{
    "NaMe": "Ostrava, of Boletaria",
    "WeaPON": "Rune Sword",
}
@Test
void givenACaseInsensitiveKnight_whenMapping_thenExpectKnight() throws Exception {
    String knightJson = formatJson("{'NaMe':'Ostrava, of Boletaria', 'WeaPON':'Rune Sword'}");

    Character character = objectMapper.readValue(knightJson, Character.class);

    assertTrue(character instanceof Knight);
    assertSame(character.getClass(), Knight.class);
    Knight knight = (Knight) character;
    assertEquals("Ostrava, of Boletaria", knight.getName());
    assertEquals("Rune Sword", knight.getWeapon());
}

6. Contained Inference

6.包含的推理

We can also deduct the polymorphism of objects that are contained in other objects. We will use the ControlledCharacter class definition to demonstrate the mapping of the following JSON:

我们也可以扣除包含在其他对象中的对象的多态性。我们将使用ControlledCharacter类定义来演示以下JSON的映射。

{
    "character": {
        "name": "Ostrava, of Boletaria",
        "weapon": "Rune Sword"
    }
}
@Test
void givenAKnightControlledCharacter_whenMapping_thenExpectAControlledCharacterWithKnight() throws Exception {
    String controlledCharacterJson = formatJson("{'character': {'name': 'Ostrava, of Boletaria', 'weapon': 'Rune Sword'}}");

    ControlledCharacter controlledCharacter = objectMapper.readValue(controlledCharacterJson, ControlledCharacter.class);
    Character character = controlledCharacter.getCharacter();

    assertTrue(character instanceof Knight);
    assertSame(character.getClass(), Knight.class);
    Knight knight = (Knight) character;
    assertEquals("Ostrava, of Boletaria", knight.getName());
    assertEquals("Rune Sword", knight.getWeapon());
}

7. Conclusion

7.结语

In this tutorial, we have explored how we can use deduction-based polymorphism using the Jackson library.

在本教程中,我们已经探讨了如何使用Jackson库来使用基于演绎的多态性

The source code that accompanies the article can be found over on GitHub.

伴随文章的源代码可以在GitHub上找到