undefined

Json serialize frameworks

Java Collections
3 уровень , 4 лекция
Открыта

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

— Здорово, Диего.

— Я тут смотрю, тебя познакомили с азами сериализации в JSON?

— Почему с азами? Я уже много знаю!

— Святая простота. Да ты и половины не знаешь. Процентов 10 от силы.

— Ух ты. А что там еще осталось?

— Десериализация иерархии объектов (полиморфизм при десериализации), десериализация коллекций, еще много всего. Jackson – большой и мощный фреймворк, а ты с ним, откровенно говоря, едва познакомился.

— Ладно. Тогда расскажи мне о чём-нибудь из этого, а я – послушаю.

Приятно становиться умнее с каждой лекцией!

— Как не помочь другу-роботу? Кто если не я?

Готов? Тогда слушай.

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

Java class JSON
class Cat
{
 public String name = "murka";
 public Cat[] cats = new Cat[0];
}
{
 "name": "murka",
 "cats": []
}
class Cat
{
 public String name = "murka";
 public List<Cat> cats = new ArrayList<>();
}
{
 "name": "murka",
 "cats": []
}
class Cat
{
 public String name = "murka";
 public List<Cat> cats = new 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 = new 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

public class Solution {
    public static void main(String[] args) throws IOException {
        String jsonString = "{\"name\":\"Murka\",\"cats\":[{\"name\":\"Timka\"},{\"name\":\"Killer\"}]}";
        ObjectMapper mapper = new 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 тоже интерфейс! Что ты будешь делать?

— У нас есть аннотация и на этот случай?

— Да, причем та же самая. В ней можно указать еще и тип параметр. Выглядеть это будет вот так:

Тип коллекции Как задать тип данных
List @JsonDeserialize(contentAs=ValueTypeImpl.class)
Map @JsonDeserialize(keyAs=KeyTypeImpl.class)

— Круто. Действительно, много нужных аннотаций для разных случаев, о которых заранее и не догадаешься.

— И это еще не все. Сейчас будет самое вкусное. В реальных проектах, классы данных очень часто унаследованы от одного базового класса или интерфейса, который используется практически везде. И вот представь, тебе надо десериализовать структуру данных, которая содержит такие классы. Пример:

Конвертация объекта в 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";

 ArrayList<Pet> pets = new 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")
@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<Pet> 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 будет определяться тип объекта, который надо создать.

Иногда в качестве значения поля type используют имя класса (например, «com.example.entity.Cat.class»), но это не очень хорошо. Зачем стороннему приложению, которому мы пересылаем JSON, знать, как называются наши классы? К тому же, классы иногда переименовывают. Использование некоего уникального имени для обозначения конкретного класса – предпочтительнее.

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

— Ага. Это действительно новые для тебя вещи, но именно благодаря таким практическим знаниям, ты скоро станешь крутым программистом.

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

— Ладно. Иди, отдыхай.

Комментарии (37)
Чтобы просмотреть все комментарии или оставить свой,
перейдите в полную версию
Ira Tsygarova 36 уровень, Санкт-Петербург
15 февраля 2021
Вижу много вопросов и непонимания с аннотациями На самом деле в них ничего сложного нет Аннотация - это некий флаг (настройка) для фреймворка. По сути, при использовании аннотаций мы явно говорим, что нам нужно, а иногда и как нам это нужно сделать. Это очень похоже на то, как мы имплиментили интерфейсы AutoCloseable, Serializable и т.д. Тем самым говоря java-машине, что "Вот смотри, что класс наш должен уметь"
Valua Sinicyn 41 уровень, Харьков
7 февраля 2021
Ничего не понятно, работаем дальше.
Андрей 41 уровень, Самара
21 января 2021
Ни уровня по аннотациям, ни уровня по рефлексии. А последние темы только на них и построены. Минус респект за такое
Богдан Зінченко 37 уровень, Харьков
27 декабря 2020
Почему, если сеариализовывать не house, а house.pets, то поле type не появляется в элементах массива?
Андрей Шубин 28 уровень, Москва
9 сентября 2020
Чет фигня какая-то написана. Не работают эти подтипы похоже. %)
Денис 35 уровень, Санкт-Петербург
22 августа 2020
После лекции непонятно как пользоваться большинством аннотаций. Приведу код из лекции, который практически не поясняется: @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property="type") @JsonSubTypes({ @JsonSubTypes.Type(value=Cat.class, name="cat"), @JsonSubTypes.Type(value=Dog.class, name="dog") })
Павел Павличенко 35 уровень, Львов
7 августа 2020
С каких пор Диего такой добрый?
Kex 38 уровень, Тольятти Expert
1 июля 2020
Штааааааааа?🤷‍♂️ Почему именно так, где логическое объяснение этой аннотации? List @JsonDeserialize(contentAs=ValueTypeImpl.class) Map @JsonDeserialize(keyAs=KeyTypeImpl.class) Для чего использовать StringWriter если можно работать просто со строкой? Почему мы сделали так ? class House { public List<Pet> pets = new ArrayList<>(); } И анотации хрен расшифруешь @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property="type") @JsonSubTypes({ @JsonSubTypes.Type(value=Cat.class, name="cat"), @JsonSubTypes.Type(value=Dog.class, name="dog") }) короче одни вопросы пойду гуглить тема походу важная и не простая, после прошлых двух расслабило немного а теперь грузанули знатно!
KsArT 41 уровень, Харьков
26 июня 2020
Я крутой программист. Круто!
Denis 41 уровень
18 июня 2020
абалдеть!