"Hi, buddy!"
"Hey, Diego."
"I see here you've learned the basics of JSON serialization?"
"What do you mean 'basics'? I know a lot!"
"So naive. You don't know the half of it. Ten percent at best."
"You're kidding. What else is there?"
"Deserialization of an object hierarchy (polymorphic deserialization), deserialization of collections, and a whole lot more! The Jackson framework is large and powerful. Honestly, you've only begun to scratch the surface."
"Okay, then tell me about it – I'm all ears."
"I'm really enjoying getting smarter with each lesson!"
"Well, it's my pleasure to help, my robot friend!"
"Are you ready? Then listen up."
"As you've already learned, annotations are used for both serialization and deserialization. In practice, serialization requires far less information than deserialization. For example:"
Java class | JSON |
---|---|
|
|
|
|
|
|
"Instances of Array, ArrayList, LinkedList, and other classes are turned into JSON arrays."
"But when you deserialize a JSON array, what object should you create: an ArrayList or a LinkedList?"
"Right. If a class member is an interface (e.g. public List<Cat> cats), what object should be attributed to it?"
"We can add additional annotations to the field or explicitly indicate the target classes during deserialization. Look at this example:"
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));
}
@JsonAutoDetect
class Cat {
public String name;
public List<Cat> cats = new ArrayList<>();
Cat() {
}
}
"In other words, we can use the second parameter of the mapper.readValue method to pass the list of classes to use during deserialization."
"I like it. That's convenient. So you can deserialize a JSON array into whatever you need, an ArrayList or a LinkedList.
"You also mentioned using annotations. How do you do that?"
"It's easy. For example:"
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);
}
@JsonAutoDetect
class Cat
{
public String name;
@JsonDeserialize(as = ArrayList.class, contentAs = Cat.class)
public List<Cat> cats = new ArrayList<>();
Cat() {
}
}
"We simply add the annotation @JsonDeserialize(as = ArrayList.class, contentAs = Cat.class) to line 5 to indicate which implementation of the List interface to use."
"Ah. I see. That really is quite simple."
"But there's more. Suppose the data type in a List is also an interface! What would you do?"
"Do we use an annotation here too?"
"Yes, the same one. You can also use it to indicate the parameter type. Like this:"
Collection type | How to set the data type |
---|---|
List | @JsonDeserialize(contentAs = ValueTypeImpl.class) |
Map | @JsonDeserialize(keyAs = KeyTypeImpl.class) |
"Cool! There really are a lot of annotations needed for various situations we can't anticipate."
"That's not all. And that brings us to the main course: in real projects, classes quite often inherit the same base class or interface, which is used virtually everywhere. And now imagine that you need to deserialize a data structure containing such classes. For example:"
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());
}
@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" : "Missy", "age" : 5},
{ "name" : "Killer", "age" : 8 , "owner" : "Bill Jeferson"}
]
"Pay attention to the result of serialization."
"We can't deserialize this data into a Java object, since it is essentially indistinguishable from data for other classes."
"There are some distinguishing features: Dog has an owner field."
"Yes, but this field could be null or it could be entirely skipped during serialization."
"Well, can't we specify the data type using annotations that we do know?"
"No. After deserialization, a single collection should have various Cat and Dog objects, as well as a dozen other classes that could inherit from Pet."
"What in the world can you do here?"
"Two things are used here."
"First, a certain field is chosen to distinguish one type from another. If there isn't one, then it is created."
"Second, there are special annotations that let you control the process of «polymorphic deserialization». Here's what you can do:"
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());
}
@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" : "Missy", "age" : 5}
]
}
Using annotations, we indicate that the JSON representation will contain a special field called type that will hold the value cat for the Cat class, and the value dog for the Dog class. This information is enough to properly deserialize an object: during deserialization, the type of the object to be created will be determined by the value of the type field.
"Sometimes the class name is used as the value of the type field (e.g. «com.example.entity.Cat.class»), but this isn't a good practice. How would an external application receiving our JSON know the names of our classes? Worse, classes are sometimes renamed. It is better to use some unique name to identify a specific class."
"Cool! Sigh. I didn't realize deserialization was so complicated. And that there is so much you can fine-tune."
"Yep. These are new concepts for you, but this is the kind of practical knowledge that will make you a genius programmer."
"Amigo is a cool programmer. Cool!"
"OK. Go and take a break."
GO TO FULL VERSION