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, знать, как называются наши классы? К тому же, классы иногда переименовывают. Использование некоего уникального имени для обозначения конкретного класса – предпочтительнее.

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

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

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

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

Комментарии (68)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Максим Li Уровень 44
7 декабря 2025
😎
invoker main Уровень 42
15 октября 2025
чем дальше лес тем шкибиди
Seva Уровень 51 Expert
27 мая 2025
У кого-нибудь получилось десериализовать в объект home json-строку, полученную в последнем примере? В предпоследнем примере десериализация в объект pets проходит без ошибок (исключений). Правда, при этом достать cat и dog из pets не получается. Но в последнем примере ошибка вылезает прямо при десериализации объекта home
{Java_Shark} Уровень 36
28 января 2025
Взрыв мозга)))++
Denis Odesskiy Уровень 47
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 Уровень 33 Student
20 июля 2023
Когда будете задачи решать, надо установить библиотеки. Обязательно при этом поставьте галочки - Установить JavaDoc для библиотек. А то я мучился без документации Ctrl+Q Если не знаете параметров аннотаций, жмите Ctrl+P И давать Jackson без знания аннотаций, всё-таки это не комильфо
Rolik Уровень 41
15 мая 2023
При сериализации JSON строки в Java объект, в классе - модели создавайте не параметризированный List cats, иначе при развертывании Java объекта в строку: @Override public String toString(){} в консоли к каждому имени массива будет лепиться null: [{Муркаnull, и т.д.