JavaRush /Курсы /Java Collections /JSON serialization frameworks

JSON serialization 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&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 будет определяться тип объекта, который надо создать.

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

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

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

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

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

Комментарии (66)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Seva Уровень 46 Expert
27 мая 2025
У кого-нибудь получилось десериализовать в объект home json-строку, полученную в последнем примере? В предпоследнем примере десериализация в объект pets проходит без ошибок (исключений). Правда, при этом достать cat и dog из pets не получается. Но в последнем примере ошибка вылезает прямо при десериализации объекта home
{Java_Shark} Уровень 36
28 января 2025
Взрыв мозга)))++
Denis Odesskiy Уровень 46
2 октября 2024
В первом примере с кошкой, вы получите строку с хэшем. Чтобы вывести удобочитаемый JSON внесите в код следующие изменения (привожу полный код, вместо кошек тигры😄):

package com.javalearning.example.json;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Deserialize {
    public static void main(String[] args) {
        String jsonString = "{\"name\":\"Shrek\",\"tigers\":[{\"name\":\"Timka\"},{\"name\":\"Killer\"}]}";
        ObjectMapper mapper = new ObjectMapper();
        Tiger tiger;

        try {
            tiger = mapper.readValue(jsonString, Tiger.class);

            String tigerJson = mapper.writeValueAsString(tiger);

            System.out.println(tigerJson);
            System.out.println(tiger.tigers.getClass());
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }
}

package com.javalearning.example.json;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

import java.util.LinkedList;
import java.util.List;

@JsonInclude(JsonInclude.Include.NON_NULL)
class Tiger {
    public String name;
    @JsonDeserialize(as = LinkedList.class)
    public List<Tiger> tigers;
}

вывод:

{
  "name":"Shrek",
  "tigers":[
    {
      "name":"Timka"
    },
    {
      "name":"Killer"
    }
  ]
}

class java.util.LinkedList
Обратите внимание на аннотацию @JsonInclude(JsonInclude.Include.NON_NULL) в классе Tiger. Она позволяет пропустить сериализацию полей со значением null, при этом мы сохраняем конкретную типизацию класса.
29 февраля 2024
Почему нельзя десериализировать список Pet?
Ra Уровень 5 Student
20 июля 2023
Когда будете задачи решать, надо установить библиотеки. Обязательно при этом поставьте галочки - Установить JavaDoc для библиотек. А то я мучился без документации Ctrl+Q Если не знаете параметров аннотаций, жмите Ctrl+P И давать Jackson без знания аннотаций, всё-таки это не комильфо
Rolik Уровень 41
15 мая 2023
При сериализации JSON строки в Java объект, в классе - модели создавайте не параметризированный List cats, иначе при развертывании Java объекта в строку: @Override public String toString(){} в консоли к каждому имени массива будет лепиться null: [{Муркаnull, и т.д.
Siarhei Уровень 46
2 апреля 2023
"о да, довольно просто" "о, ничего сложного" су..аааааа, что тут вообще происходит?????
Роман Уровень 33
14 января 2023
Сделал все как в последнем примере, а результат совсем не тот что у автора. У меня в консоли только: {"pets":[{"type":"cat"},{"type":"dog"}]} ... а сами объекты внутрь не попадают.. может кто-то подсказать, где искать? И еще... у меня все 4 класса в отдельных файлах (Pet, Cat, Dog, House) - в какой какую аннотацию необходимо вписывать? Путем перебирания всего подряд - ничего не выходит.
Artur Shigapov Уровень 27
2 февраля 2023
Ответ от Elvin Yagudin ниже: Я конечно максимально воздерживаюсь от комментариев, ценю труд JavaRush, но тут честно пригорело. В последнем примере мы сериализуем список объектов Cat и Dog, делаем это хитро через класс House. "Так мило, что мы не просто создали массив, а создали домик для животных" - скажет учащийся, но забота о мнимых животных тут на последнем месте, дело в том что аннотация @JsonTypeInfo не работает через сериализацию классов интерфейса List, раз уж взялись за описание достаточно сложных примеров полиморфной сериализации, этот важный нюанс можно указать.