Jackson 2.12中的基于推断的多态性
Jackson 2.12中的基于推断的多态性
在本教程中,我们将探讨如何使用Jackson库中的基于推断的多态性特性。
假设我们有如下所示的类结构: 
首先,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 类之间有一个中间类。因此,Jackson也将知道如何在这种情况下推断多态性:
public class NamedCharacter implements Character {
private String name;
// 标准的setter和getter
}
接下来,我们将实现Character接口的子类。我们已经在之前的代码示例中声明了这些子类作为子类型。因此,实现不依赖于Jackson库:
public class ImperialSpy implements Character {
}
public class King extends NamedCharacter {
private String land;
// 标准的setter和getter
}
public class Knight extends NamedCharacter {
private String weapon;
// 标准的setter和getter
}
我们想要映射的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. 基于推断的多态性
**要激活基于推断的多态性,我们唯一需要做的改变是使用 @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);
}
同样,一个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库进行基于推断的多态性。
文章的源代码可以在GitHub上找到。