(De)-serializing Polymorphic objects with Jackson
/ / Reading Time: 5 MinuteWe’ve all heard about the class Dog extends Animal
concept at some point of our wonderful programming lives, but have you ever heard about how to maintain such polymorphic structure when (de-)serializing the Dog class?
What is Polymorphism?
Theory
With subjects like this there is already a lot of information available on the world-wide web, and explaining polymorphism couldn’t be done better than how Torben Janssen described it on Stackify.com
Polymorphism describes situations in which something occurs in several different forms. In computer science, it describes the concept that you can access objects of different types through the same interface.
That last part is exactly what we want to achieve: deserializing a payload by specifying the interface without knowing the exact implementation that will be provided.
Example
Since you’ve heard the animal example a hundred times already, we’ll use a more real-life example and consider the following list of Messages.
1 | [ |
Implementation
With that we can start defining that interface from which we can access all different types of messages.
1 | interface Message { |
We can use the interface above that defines the message type, or we can define an abstract class like below to prevent duplicating constructor logic in every implementation.
1 | abstract class Message { |
By extending the abstract class we can now define our specific type of messages, starting with the obvious TextMessage.
1 | class TextMessage extends Message { |
Next to text messages we can also share locations, or contacts by sending LocationMessages or ContactMessages.
1 | class LocationMessage extends Message { |
1 | class ContactMessage extends Message { |
Alright, we have got all our models setup but we can’t quite deserialize the json from above yet because Jackson doesn’t know how to map a type to a specific class.
Here’s the trick; we can add the Jackson @JsonTypeInfo
annotation to specify that specific mapping on our Message interface or abstract class.
1 |
|
Let’s step back a bit shall we? We added the @JsonTypeInfo
specifying which property (type) should be used to descriminate the message.
We then add the @JsonSubTypes
annotation to specify the mapping of the property to the respective implementation.
As simple as that, we can now deserialize the json using Jackson’s ObjectMapper.
1 | class Main { |
Result
When we run this we see the following output confirming that we deserialized the json to our specific message classes.
1 | [nl.vreijsenj.messaging.TextMessage@64485a47, nl.vreijsenj.messaging.ContactMessage@25bbf683, nl.vreijsenj.messaging.LocationMessage@6ec8211c] |
Hooray! We could still extend our message class with properties like recipient
, sender
or add abstract methods for the specific classes to implement.