Contents

Jackson中基于演绎的多态性

1. 概述

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

2. 基于名称的多态性

假设我们有一个类结构,如下图所示。

/uploads/jackson_deduction_based_polymorphism/2.png
2.1 字符图

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

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

那么让我们看一下类的定义。请注意,对于基本接口,我们必须使用 Jackson 注解来声明我们要使用的推导。此外,我们必须添加@JsonSubTypes*注解来声明我们要扣除哪些类。*

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

此外,我们还可以在接口Character与 King和 Knight类之间有一个中间类。因此,Jackson,我们也将知道如何在这种情况下推断多态性:

public class NamedCharacter implements Character {
    private String name;
    // standard setters and getters
}

随后,我们将实现 Character接口的子类。我们已经在前面的代码示例中将这些子类声明为子类型。因此,实现对 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
}

我们想要映射的 JSON 示例如下:

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

首先,如果我们尝试读取上面的 JSON 结构,Jackson 会抛出运行时异常,并显示消息Could not resolve subtype of [simple type, class com.blogdemo.jackson.deductionbasedpolymorphism.Character]: missing type id property ‘@type’ :

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

此外,  formatJson实用方法通过将引号字符转换为双引号来帮助我们保持测试中的代码简单,正如JSON要求的那样:

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

因此,为了能够多态地推断出我们角色的类型,我们必须修改 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. 基于演绎的多态性

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

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

4. 简单推理

让我们探索如何通过简单的推理以多态方式读取 JSON。我们要读取的对象如下:

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

首先,我们将读取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());
}

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

@Test
void givenAnEmptyObject_whenMapping_thenExpectAnImperialSpy() throws Exception {
    String imperialSpyJson = "{}";
    Character character = objectMapper.readValue(imperialSpyJson, Character.class);
    assertTrue(character instanceof ImperialSpy);
}

此外,Jackson 也会将 null JSON 对象扣除为 null 对象

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

5. 不区分大小写的推理

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

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

然后,使用实例化的*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. 包含推理

我们还可以推导出包含在其他对象中的对象的多态性。我们将使用 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());
}