1. 概述
在本教程中,我們將探討如何使用 Jackson 庫中的基於推理的多態性功能。
2. 基於名稱的多態性
設想一下我們有一個類似於下面圖片所示的類結構。
首先,<em>NamedCharacter</em> 和 <em>ImperialSpy</em> 類實現了 <em>Character</em> 接口。 其次,King and Knight classes類實現了NamedCharacter類。 最後,我們有一個ControlledCharacter class`,其中包含指向玩家控制的角色引用。
我們希望將 JSON 對象解析為 Java 對象,而無需修改接收到的 JSON 的結構。
因此,讓我們看一下類的定義。 請注意,對於基接口,我們需要使用 Jackson 註解來聲明我們想要使用的推斷。 此外,我們還需要添加 <em>@JsonSubTypes</em> 註解來聲明我們想要推斷的類。
@JsonTypeInfo(use = Id.NAME)
@JsonSubTypes({ @Type(ImperialSpy.class), @Type(King.class), @Type(Knight.class) })
public interface Character {
}此外,我們還可以定義一個介於接口 Character 和 King 以及 Knight 類之間的中間類。因此,我們也能理解在這種情況下的多態性:
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));
}此外,<em formatJson</em>實用方法也有助於簡化測試代碼,通過將引號轉換為雙引號,因為<em JSON</em>需要:
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. 基於推斷的 polymorphism
為了激活基於推斷的 polymorphism,我們唯一需要做的就是使用 @JsonTypeInfo(use = Id.DEDUCTION):
@JsonTypeInfo(use = Id.DEDUCTION)
@JsonSubTypes({ @Type(ImperialSpy.class), @Type(King.class), @Type(Knight.class) })
public interface Character {
}4. 簡單推理
讓我們探索如何以多態的方式讀取 JSON,並利用簡單的推理。我們想要讀取的 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());
}7. 結論
在本教程中,我們探討了如何使用基於演繹的多態性,並利用 Jackson 庫實現。