概述
在本教程中,我们将探索如何使用Jackson库中基于推理的多态特性。
基于Name(Name-based)的多态性
让我们假设我们有一个如下图所示的类结构。
首先,NamedCharacter 和 ImperialSpy 类实现 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 接口和 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会抛出一个运行时异常,并显示消息Can 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());
}
基于推理(Deduction-based)的多态性
要激活基于推理的多态,我们需要做的唯一更改是使用 @JsonTypeInfo(Use=Id.DEDUCTION):
@JsonTypeInfo(use = Id.DEDUCTION)
@JsonSubTypes({ @Type(ImperialSpy.class), @Type(King.class), @Type(Knight.class) })
public interface Character {
}
简单推理
让我们探索如何通过简单的推理以多态的方式阅读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);
}
此外,一个空的JSON对象也将被Jackson作为一个空对象:
@Test
void givenANullObject_whenMapping_thenExpectANullObject() throws Exception {
Character character = objectMapper.readValue("null", Character.class);
assertNull(character);
}
不区分大小写的推理
Jackson 还可以在属性的大小写不匹配推理多态。
首先,我们将实例化一个启用了 ACCEPT_CASE_INSENSIVE_PROPERTIES 的 ObjectMapper:
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());
}
包含推理(Contained Inference)
我们还可以推导出包含在其他对象中的对象的多态。我们将使用 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());
}