1. Обзор
В этом руководстве мы рассмотрим, как мы можем использовать функцию полиморфизма на основе дедукции из библиотеки Джексона .
2. Полиморфизм на основе имен
Давайте представим, что у нас есть структура классов, подобная той, что описана на следующем рисунке.
Во-первых, классы
NamedCharacter
и ImperialSpy
реализуют интерфейс Character.
Во-вторых, классы King и Knight
реализуют класс NamedCharacter
. Наконец, у нас есть класс ControlledCharacter,
который содержит ссылку на персонажа, которым управляет игрок.
``
Мы хотели бы преобразовать объекты JSON в объекты Java, не изменяя структуру полученного JSON.
Итак, давайте взглянем на определение классов. Обратите внимание, что для базового интерфейса нам придется использовать аннотации Джексона, чтобы объявить, какой вывод мы хотим использовать. Кроме того, нам нужно будет добавить аннотации @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 . Мы уже объявили эти подклассы как подтипы в предыдущем примере кода. Следовательно, реализация не имеет никаких зависимостей от библиотеки Джексона:
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, Джексон выдаст исключение времени выполнения с сообщением Не удалось разрешить подтип [простой тип, класс com.foreach.jackson.deductionbasedpolymorphism.Character]: отсутствует свойство идентификатора типа '@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 .
Затем мы проверим, что Джексон
правильно вычислил тип 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 является пустым объектом, Джексон интерпретирует его как ImperialSpy
, который является классом без атрибутов:
@Test
void givenAnEmptyObject_whenMapping_thenExpectAnImperialSpy() throws Exception {
String imperialSpyJson = "{}";
Character character = objectMapper.readValue(imperialSpyJson, Character.class);
assertTrue(character instanceof ImperialSpy);
}
Кроме того, нулевой объект JSON также будет считаться нулевым объектом Джексоном :
@Test
void givenANullObject_whenMapping_thenExpectANullObject() throws Exception {
Character character = objectMapper.readValue("null", Character.class);
assertNull(character);
}
5. Вывод без учета регистра
Джексон также может вычесть полиморфизм, даже если регистр атрибутов не совпадает . Во-первых, мы создадим объектный преобразователь с включенным 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. Заключение
В этом уроке мы рассмотрели, как мы можем использовать полиморфизм на основе дедукции с помощью библиотеки Джексона .
Исходный код, прилагаемый к статье, можно найти на GitHub.