— Привіт, друже!

— Здорово, Дієго.

— Я тут дивлюся, тебе познайомили з азами серіалізації в JSON? Чому із азами? Я вже багато знаю!

— Свята простота. Та ти й половини не знаєш. Відсотків 10 від сили.

— Ух ти. А що там ще залишилося?

— Десеріалізація ієрархії об'єктів (поліморфізм при десеріалізації), десеріалізація колекцій ще багато всього. Jackson - великий і потужний фреймворк, а ти з ним, відверто кажучи, ледве познайомився.

— Гаразд. Тоді розкажи мені про що-небудь з цього, а я – послухаю. >

— Як не допомогти другові-роботу? Хто якщо не я?

Готовий? Тоді слухай.

Як ти вже переконався, анотації використовуються не лише під час серіалізації, а й під час десеріалізації. Насправді для серіалізації треба набагато менше інформації, ніж десеріалізації. Приклад:

Java class JSON
class Cat { public String name = "murka"; public Cat[] cats = новий Cat[0]; }
{ "name": "murka", "cats": [] }
class Cat { public String name = "murka"; public List<Cat> cats = новий ArrayList<>(); }
{ "name": "murka", "cats": [] }
class Cat { public String name = "murka"; public List<Cat> cats = новий LinkedList<>(); }
{ "name": "murka",  "cats": [] }

Об'єкти типів Array(масив), ArrayList, LinkedList,… замінюються на масив у JSON-форматі.

А ось при десеріалізації неясно, який об'єкт створити — ArrayList чи LinkedList?

— Згоден, якщо клас має поле, і тип поля – це інтерфейс (як у випадку з public List<Cat> cats), то зовсім не ясно, який саме об'єкт йому надавати.

— Можна додати цьому полю додаткові інструкції або залишити jackson-у налаштування за промовчанням. Дивись приклад:

Конвертація об'єкта з JSON
 public class Solution { public static void main( String[] args) throws IOException { String jsonString = "{\"name\":\"Murka\",\"cats\":[{\"name\":\ "Timka\"},{\"name\":\"Killer\"}]}"; ObjectMapper mapper = новий ObjectMapper(); Cat cat = mapper.readValue(jsonString, Cat.class ); System.out.println(cat); System.out.println(cat.cats.getClass()); } } 
Клас, об'єкт якого десеріалізується з JSON-формату
 class Cat { public String name; public List<Cat> cats; } 

Тобто. ми не втручаємось і jackson сам визначає класи, які будуть використовуватися при десеріалізації.

— А мені подобається. Зручно. Якщо конкретна реалізація не має значення, можна не турбувати себе додатковими налаштуваннями. Це як?

— Та нічого складного. Приклад:

Конвертація об'єкта з JSON
< code> public class Solution { public static void main(String[] args) throws IOException { String jsonString = "{\"name\":\"Murka\",\"cats\ ":[{\"name\":\"Timka\"},{\"name\":\"Killer\"}]}"; ObjectMapper mapper = новий ObjectMapper(); Cat cat = mapper.readValue(jsonString, Cat.class ); System.out.println(cat); System.out.println(cat.cats.getClass()); } } 
Клас, об'єкт якого десеріалізується з JSON-формату
 class Cat { public String name; @JsonDeserialize(as = LinkedList.class) public List<Cat> cats; } 

У рядку 3 ми просто додали анотацію @JsonDeserialize(as = LinkedList.class), де вказали, яку реалізацію інтерфейсу List використовувати.

— Ага. Зрозуміло. Справді, досить просто.

— Але це ще не все. Тепер уяви, що тип даних у List теж інтерфейс! Що ти робитимеш?

— У нас є анотація і на цей випадок?

— Так, причому та сама. У ній можна вказати ще й тип параметра. Виглядатиме це ось так:

Тип колекції Як встановити тип даних
< strong>List @JsonDeserialize(contentAs=ValueTypeImpl.class)
Map @JsonDeserialize(keyAs=KeyTypeImpl.class)

— Круто. Справді, багато потрібних анотацій для різних випадків, про які заздалегідь і не здогадаєшся.

— І це ще не все. Зараз буде найсмачніше. У реальних проектах класи даних дуже часто успадковані від одного базового класу або інтерфейсу, який використовується практично скрізь. І ось уяви, тобі треба десеріалізувати структуру даних, що містить такі класи. Приклад:

Конвертація об'єкта в JSON
< code>public static void main(String[] args) throws IOException { Cat cat  = новий Cat ; cat.name = "Murka"; cat.age = 5; Dog dog = new Dog(); dog.name = "Killer"; dog.age = 8; dog.owner = "Bill Jeferson"; ArrayList<Pet> pets = новий ArrayList<Pet>(); pets.add(cat); pets.add(dog); StringWriter writer = new StringWriter(); ObjectMapper mapper = new ObjectMapper(); mapper.writeValue(writer, pets); System.out.println(writer.toString()); }
Клас, об'єкт якого конвертує в JSON
@JsonAutoDetect class Pet { public String name; } @JsonAutoDetect class Cat extends Pet { public int age; } @JsonAutoDetect class Dog extends Pet {public int age; public String owner; }
Результат серіалізації та виведення на екран:
[ { "name" : "Murka", "age" : 5}, { "name": "Killer", "age" : 8 , "owner" : "Bill Jeferson"} ] 

Зверніть увагу на результат серіалізації.

Ми не зможемо провести десеріалізацію цих даних назад у Java-об'єкти, т.к. вони фактично невиразні.

— Трохи помітні — у Dog є поле owner.

— Так, але це поле може бути рівним null або взагалі пропускатися при серіалізації.

— А хіба ми не можемо задати тип даних за допомогою відомих нам анотацій?

— Ні. В одній колекції після десеріалізації повинні зберігатися різні об'єкти типу Cat, Dog та ще кілька десятків класів, які можна успадкувати від Pet.

— І що ж тут можна зробити?

— Тут застосовують дві речі.

По-перше, виділяють деяке поле, яке використовується для того, щоб відрізняти один тип від іншого. Якщо його немає – його заводять.

По-друге, є спеціальні інструкції, які дозволяють керувати процесом «поліморфної десеріалізації». Ось що можна зробити:

Конвертація об'єкта в JSON
public static void main(String[] args) throws IOException { Cat cat = new Cat(); cat.name = "Murka"; cat.age = 5; Dog dog = new Dog(); dog.name = "Killer"; dog.age = 8; dog.owner = "Bill Jeferson"; House house = new House(); house.pets.add(dog); house.pets.add(cat); StringWriter writer = new StringWriter(); ObjectMapper mapper = new ObjectMapper(); mapper.writeValue(writer, house); System.out.println(writer.toString()); }
Клас, об'єкт якого конвертує в JSON
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property="type")< /span> @JsonSubTypes({ @JsonSubTypes.Type( value=Cat.class, name="cat"), @JsonSubTypes.Type(value=Dog.class, name="dog") }) class Pet {public String name; } class Cat extends Pet {public int age; } class Dog extends Pet {public int age; public String owner; } class House { public List&ltPet> pets = new ArrayList<>(); }
Результат серіалізації та виведення на екран:
{ "pets" : [ {"type" : "dog","name" : "Killer", "age" : 8, "owner" : "Bill Jeferson"}, {"type " : "cat","name" : "Murka", "age" : 5} ] }

За допомогою анотацій ми вказуємо, що JSON-подання буде містити спеціальне поле type, яке зберігатиме значення cat, для класу Cat та значення dog, для класу Dog. Цієї інформації достатньо, щоб виконати коректну десеріалізацію об'єкта: при десеріалізації за значенням поля type визначатиметься тип об'єкта, який треба створити.

Іноді як значення поля типу використовують ім'я класу (наприклад, «com.example.entity.Cat.class»), але це не дуже добре. Навіщо сторонньому додатку, якому ми надсилаємо JSON, знати, як називаються наші класи? До того ж класи іноді перейменовують. Використання якогось унікального імені для позначення конкретного класу – краще.

— Круто! А я не знав, що десеріалізація така складна річ. І що стільки можна налаштовувати.

— Ага. Це справді нові для тебе речі, але саме завдяки таким практичним знанням, ти скоро станеш крутим програмістом.

— Аміго – крутий програміст. Круто!

— Ладно. Іди, відпочивай.