— Привет, дружище!
— Здорово, Диего.
— Я тут смотрю, тебя познакомили с азами сериализации в JSON?
— Почему с азами? Я уже много знаю!
— Святая простота. Да ты и половины не знаешь. Процентов 10 от силы.
— Ух ты. А что там еще осталось?
— Десериализация иерархии объектов (полиморфизм при десериализации), десериализация коллекций, еще много всего. Jackson – большой и мощный фреймворк, а ты с ним, откровенно говоря, едва познакомился.
— Ладно. Тогда расскажи мне о чём-нибудь из этого, а я – послушаю.
Приятно становиться умнее с каждой лекцией!
— Как не помочь другу-роботу? Кто если не я?
Готов? Тогда слушай.
Как ты уже убедился, аннотации используются не только при сериализации, но и при десериализации. На практике для сериализации надо гораздо меньше информации, чем для десериализации. Пример:
Java class | JSON |
---|---|
|
|
|
|
|
|
Объекты типов Array(массив), ArrayList, LinkedList,… заменяются на массив в JSON-формате.
А вот при десериализации неясно, какой объект создать — ArrayList или LinkedList?
— Согласен, если у класса есть поле, и тип поля – это интерфейс (как в случае с public List<Cat> cats), то совсем не ясно, какой именно объект ему присваивать.
— Можно добавить этому полю дополнительные аннотации или оставить jackson-у настройки по умолчанию. Смотри пример:
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());
}
}
class Cat {
public String name;
public List<Cat> cats;
}
Т.е. мы не вмешиваемся и jackson сам определяет классы, которые будут использоваться при десериализации.
— А мне нравится. Удобно. Если конкретная реализация не имеет значения, можно не утруждать себя дополнительными настройками.
Ты еще говорил, что можно воспользоваться аннотациями. Это как?
— Да ничего сложного. Пример:
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());
}
}
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) |
— Круто. Действительно, много нужных аннотаций для разных случаев, о которых заранее и не догадаешься.
— И это еще не все. Сейчас будет самое вкусное. В реальных проектах, классы данных очень часто унаследованы от одного базового класса или интерфейса, который используется практически везде. И вот представь, тебе надо десериализовать структуру данных, которая содержит такие классы. Пример:
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());
}
@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.
— И что же можно тут сделать?
— Тут применяют две вещи.
Во-первых, выделяют некоторое поле, которое используется для того, чтобы отличать один тип от другого. Если его нет – его заводят.
Во-вторых, есть специальные аннотации, которые позволяют управлять процессом «полиморфной десериализации». Вот что можно сделать:
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());
}
@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, знать, как называются наши классы? К тому же, классы иногда переименовывают. Использование некоего уникального имени для обозначения конкретного класса – предпочтительнее.
— Круто! А я и не знал, что десериализация такая сложная вещь. И что столько всего можно настраивать.
— Ага. Это действительно новые для тебя вещи, но именно благодаря таким практическим знаниям, ты скоро станешь крутым программистом.
— Амиго – крутой программист. Круто!
— Ладно. Иди, отдыхай.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ