При сериализации и десериализации обычных классов в JSON и обратно проблем, как правило, не возникает. Но что делать, если классы образуют из себя иерархию? Если нам нужно десериализовать экземпляр интерфейса? Абстрактного класса? Куда сохранять информацию о типе Java?
Давайте посмотрим на следующие классы:
1 2 3 4 5 6 7 8 9 10 11 12 |
public abstract class Monster { private int health; public int getHealth() { return health; } public void setHealth(int health) { this.health = health; } } |
1 2 3 4 5 6 7 8 9 10 11 |
public class Skeleton extends Monster { private int bones; public int getBones() { return bones; } public void setBones(int bones) { this.bones = bones; } } |
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Vampire extends Monster { private int bloodCollected; public int getBloodCollected() { return bloodCollected; } public void setBloodCollected(int bloodCollected) { this.bloodCollected = bloodCollected; } } |
Если нам нужно сериализовать любой из экземпляров класса Monster в JSON, а потом десериализовать его же, то нам нужно сохранять информацию о типе сериализованного класса в JSON. Для этого используется аннотация com.fasterxml.jackson.annotation.JsonTypeInfo. Эту аннотацию нужно добавить к базовому классу иерархии, в данном случае к классу Monster:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class") public abstract class Monster { private int health; public int getHealth() { return health; } public void setHealth(int health) { this.health = health; } } |
Обратите внимание на атрибут property, который указывает имя свойства в JSON, в которое будет записано имя типа Java.
Теперь попробуем сериализовать экземпляр класса Skeleton (оформим всё в виде тестов Maven):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Test public void jsonSkeletonSerializationTest() throws IOException { Skeleton skeleton = new Skeleton(); skeleton.setHealth(100); skeleton.setBones(99); ObjectMapper objectMapper = new ObjectMapper(); try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { objectMapper.writeValue(byteArrayOutputStream, skeleton); String expectedJson = new String(Files.readAllBytes( Paths.get("src/test/resources/ru/urvanov/javaexamples" + "/jackson/polymorphicdeserialization" + "/skeleton.json")), "utf-8"); assertJsonEquals(expectedJson, byteArrayOutputStream.toString("utf-8")); } } |
Файл “skeleton.json” имеет такое содержимое:
1 2 3 4 5 |
{ "@class": "ru.urvanov.javaexamples.jackson.polymorphicdeserialization.Skeleton", "health":100, "bones":99 } |
Тест десериализации из этого JSON с помощью Jackson:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Test public void jsonSkeletonDeserializationTest() throws IOException { ObjectMapper objectMapper = new ObjectMapper(); String jsonString = new String(Files.readAllBytes( Paths.get("src/test/resources/ru/urvanov/javaexamples" + "/jackson/polymorphicdeserialization" + "/skeleton.json")), "utf-8"); try (InputStream inputStream = new ByteArrayInputStream( jsonString.getBytes("utf-8"))) { Object object = objectMapper.readValue(inputStream, Monster.class); assertTrue(object instanceof Skeleton); Skeleton skeleton = (Skeleton) object; assertEquals(100, skeleton.getHealth()); assertEquals(99, skeleton.getBones()); } } |
Как видите, в этом тесте Jackson берёт имя типа Java из файла и создаёт экземпляр Skeleton.
Теперь попробуем сериализовать в JSON экземпляр класса Vampire:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Test public void jsonVampireSerializationTest() throws IOException { Vampire vampire = new Vampire(); vampire.setHealth(50); vampire.setBloodCollected(999); ObjectMapper objectMapper = new ObjectMapper(); try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { objectMapper.writeValue(byteArrayOutputStream, vampire); String expectedJson = new String(Files.readAllBytes( Paths.get("src/test/resources/ru/urvanov/javaexamples" + "/jackson/polymorphicdeserialization/" + "vampire.json")), "utf-8"); assertJsonEquals(expectedJson, byteArrayOutputStream.toString("utf-8")); } } |
Файл “vampire.json”:
1 2 3 4 5 |
{ "@class": "ru.urvanov.javaexamples.jackson.polymorphicdeserialization.Vampire", "health":50, "bloodCollected":999 } |
Десериализация класса Vampire из этого JSON-файла:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Test public void jsonVampireDeserializationTest() throws IOException { ObjectMapper objectMapper = new ObjectMapper(); String jsonString = new String(Files.readAllBytes( Paths.get("src/test/resources/ru/urvanov/javaexamples" + "/jackson/polymorphicdeserialization" + "/vampire.json")), "utf-8"); try (InputStream inputStream = new ByteArrayInputStream( jsonString.getBytes("utf-8"))) { Object object = objectMapper.readValue(inputStream, Monster.class); assertTrue(object instanceof Vampire); Vampire vampire = (Vampire) object; assertEquals(50, vampire.getHealth()); assertEquals(999, vampire.getBloodCollected()); } } |
Аннотацию @JsonTypeInfo можно указывать и у полей, для которых нужно сохранить информацию о типе при сериализации в JSON.
Ссылки:
—