"Cześć kolego!"

– Hej, Diego.

„Widzę, że nauczyłeś się podstaw serializacji JSON?”

„Co masz na myśli mówiąc „podstawy”? Wiem dużo!”

„Takie naiwne. Nie wiesz nawet połowy. W najlepszym razie dziesięć procent”.

– Żartujesz. Co jeszcze?

„Deserializacja hierarchii obiektów (deserializacja polimorficzna), deserializacja kolekcji i wiele więcej! Struktura Jacksona jest duża i potężna. Szczerze mówiąc, dopiero zacząłeś drapać powierzchownie”.

„Dobrze, więc opowiedz mi o tym – zamieniam się w słuch”.

„Naprawdę cieszę się, że z każdą lekcją staję się mądrzejszy!”

„Cóż, z przyjemnością pomagam, mój przyjacielu robocie!”

„Jesteś gotowy? Więc słuchaj”.

„Jak już się nauczyłeś, adnotacje są używane zarówno do serializacji, jak i deserializacji. W praktyce serializacja wymaga znacznie mniej informacji niż deserializacja. Na przykład:”

klasa Javy JSON
class Cat
{
 public String name = "missy";
 public Cat[] cats = new Cat[0];
}
{
 "name": "missy",
 "cats": []
}
class Cat
{
 public String name = "missy";
 public List cats = new ArrayList<Cat>();
}
{
 "name": "missy",
 "cats": []
}
class Cat
{
 public String name = "missy";
 public List cats = new LinkedList<Cat>();
}
{
 "name": "missy",
 "cats": []
}

„Instancje klas Array, ArrayList, LinkedList i innych klas są przekształcane w tablice JSON”.

„Ale kiedy deserializujesz tablicę JSON, jaki obiekt powinieneś utworzyć: ArrayList czy LinkedList?”

„Racja. Jeśli członkiem klasy jest interfejs (np. public List<Cat> koty ), jaki obiekt należy mu przypisać?”

„Możemy dodać dodatkowe adnotacje do pola lub jawnie wskazać klasy docelowe podczas deserializacji. Spójrz na ten przykład:”

Konwertuj obiekt z JSON
public static void main(String[] args) throws IOException
{
 String jsonString = ""{\"name\":\"Missy\",\"cats\":[{\"name\":\"Timmy\"},{\"name\":\"Killer\"}]}"";
 StringReader reader = new StringReader(jsonString);
 ObjectMapper mapper = new ObjectMapper();
 Cat cat = mapper.readValue(reader, TypeFactory.collectionType(ArrayList.class, Cat.class));
}
Klasa, której obiekty są deserializowane z formatu JSON
@JsonAutoDetect
class Cat {
 public String name;
 public List<Cat> cats = new ArrayList<>();
 Cat() {
 }
}

„Innymi słowy, możemy użyć drugiego parametru metody mapper .readValue , aby przekazać listę klas do użycia podczas deserializacji”.

„Podoba mi się. To wygodne. Więc możesz deserializować tablicę JSON na cokolwiek potrzebujesz, ArrayList lub LinkedList.

„Wspomniałeś także o używaniu adnotacji. Jak to robisz?”

„To proste. Na przykład:”

Konwertuj obiekt z JSON
public static void main(String[] args) throws IOException
{
 String jsonString = ""{\"name\":\"Missy\",\"cats\":[{\"name\":\"Timmy\"},{\"name\":\"Killer\"}]}"";
 StringReader reader = new StringReader(jsonString);

 ObjectMapper mapper = new ObjectMapper();

 Cat cat = mapper.readValue(reader, Cat.class);
}
Klasa, której obiekty są deserializowane z formatu JSON
@JsonAutoDetect
class Cat
{
 public String name;
 @JsonDeserialize(as = ArrayList.class, contentAs = Cat.class)
 public List<Cat> cats = new ArrayList<>();
 Cat() {
 }
}

„Po prostu dodajemy adnotację @JsonDeserialize(as = ArrayList.class, contentAs = Cat.class) do wiersza 5, aby wskazać, której implementacji interfejsu List należy użyć”.

„Ach. Rozumiem. To naprawdę całkiem proste”.

„Ale to nie wszystko. Załóżmy, że typ danych na liście jest również interfejsem! Co byś zrobił?”

„Czy tutaj też używamy adnotacji?”

„Tak, ten sam. Możesz go również użyć do wskazania typu parametru. Na przykład:”

Typ kolekcji Jak ustawić typ danych
Lista @JsonDeserialize(contentAs = ValueTypeImpl.class)
Mapa @JsonDeserialize(keyAs = KeyTypeImpl.class)

„Świetnie! Naprawdę potrzeba wielu adnotacji w różnych sytuacjach, których nie możemy przewidzieć”.

„To nie wszystko. I tu dochodzimy do sedna sprawy: w prawdziwych projektach klasy dość często dziedziczą tę samą klasę bazową lub interfejs, który jest używany praktycznie wszędzie. A teraz wyobraź sobie, że musisz deserializować strukturę danych zawierającą takie klasy. Na przykład:"

Konwertuj obiekt na JSON
public static void main(String[] args) throws IOException
{
 Cat cat = new Cat();
 cat.name = "Missy";
 cat.age = 5;

 Dog dog = new Dog();
 dog.name = "Killer";
 dog.age = 8;
 dog.owner = "Bill Jefferson";

 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());
}
Klasa, której obiekty są konwertowane na format 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;
}
Wynik serializacji i wyjście ekranu:
[
 { "name" : "Missy", "age" : 5},
 { "name" : "Killer", "age" : 8 , "owner" : "Bill Jeferson"}
]

„Zwróć uwagę na wynik serializacji”.

„Nie możemy deserializować tych danych do obiektu Java, ponieważ są one zasadniczo nie do odróżnienia od danych dla innych klas”.

„Istnieje kilka cech wyróżniających: Pies ma pole właściciela”.

„Tak, ale to pole może mieć wartość null lub może zostać całkowicie pominięte podczas serializacji”.

„Cóż, czy nie możemy określić typu danych za pomocą adnotacji, które znamy?”

„Nie. Po deserializacji pojedyncza kolekcja powinna zawierać różne obiekty Kot i Pies, a także tuzin innych klas, które mogłyby dziedziczyć po Zwierzaku”.

– Co, u licha, możesz tu robić?

„Wykorzystywane są tu dwie rzeczy”.

„Najpierw wybiera się określone pole, aby odróżnić jeden typ od drugiego. Jeśli go nie ma, to jest tworzony”.

„Po drugie, istnieją specjalne adnotacje, które pozwalają kontrolować proces «polimorficznej deserializacji». Oto, co możesz zrobić:”

Konwertuj obiekt na JSON
public static void main(String[] args) throws IOException
{
 Cat cat = new Cat();
 cat.name = "Missy";
 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());
}
Klasa, której obiekty są konwertowane na format 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<>();
}
Wynik serializacji i wyjście ekranu:
{
 "pets" : [
 {"type" : "dog", "name" : "Killer", "age" : 8, "owner" : "Bill Jeferson"},
 {"type" : "cat", "name" : "Missy", "age" : 5}
]
}

Za pomocą adnotacji wskazujemy, że reprezentacja JSON będzie zawierała specjalne pole o nazwie type , które będzie zawierało wartość cat dla klasy Cat i wartość dog dla klasy Dog . Ta informacja jest wystarczająca do prawidłowej deserializacji obiektu: podczas deserializacji typ tworzonego obiektu zostanie określony przez wartość pola typu.

„Czasami nazwa klasy jest używana jako wartość pola typu (np. «com.example.entity.Cat.class»), ale nie jest to dobra praktyka. Skąd zewnętrzna aplikacja otrzymująca nasz JSON zna nazwy nasze klasy? Co gorsza, czasami nazwy klas są zmieniane. Lepiej jest użyć unikalnej nazwy, aby zidentyfikować konkretną klasę.

„Fajnie! Westchnienie. Nie zdawałem sobie sprawy, że deserializacja jest tak skomplikowana. I że można tak wiele dostroić”.

„Tak. To są dla ciebie nowe koncepcje, ale to jest rodzaj praktycznej wiedzy, która zrobi z ciebie genialnego programistę”.

"Amigo jest fajnym programistą. Super!"

"OK. Idź i zrób sobie przerwę."