Jackson 2.12 基於推理的多態性

Jackson
Remote
1
09:22 PM · Nov 30 ,2025

1. 概述

在本教程中,我們將探討如何使用 Jackson 庫中的基於演繹的多態性功能。

2. 基於名稱的多態

設想一下我們有一個類結構,就像下面圖片描述的那樣。

Character Diagram

首先,NamedCharacterImperialSpy 實現 Character 接口。 其次,King 和 Knight 類 實現 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 接口KingKnight 類之間的一箇中間類。 因此,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.baeldung.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. 基於推斷的多態性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);
}

另外,一個 null JSON 對象將被 Jackson 推斷為 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());
}

7. 結論

在本教程中,我們探討了如何使用基於演繹的多態性,並使用 Jackson 庫來實現

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.