Deserialize JSON with Jackson into Polymorphic Types - A Complete Example is giving me a compile error
As promised, I'm putting an example for how to use annotations to serialize/deserialize polymorphic objects, I based this example in the Animal
class from the tutorial you were reading.
First of all your Animal
class with the Json Annotations for the subclasses.
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "Dog"),
@JsonSubTypes.Type(value = Cat.class, name = "Cat") }
)
public abstract class Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Then your subclasses, Dog
and Cat
.
public class Dog extends Animal {
private String breed;
public Dog() {
}
public Dog(String name, String breed) {
setName(name);
setBreed(breed);
}
public String getBreed() {
return breed;
}
public void setBreed(String breed) {
this.breed = breed;
}
}
public class Cat extends Animal {
public String getFavoriteToy() {
return favoriteToy;
}
public Cat() {}
public Cat(String name, String favoriteToy) {
setName(name);
setFavoriteToy(favoriteToy);
}
public void setFavoriteToy(String favoriteToy) {
this.favoriteToy = favoriteToy;
}
private String favoriteToy;
}
As you can see, there is nothing special for Cat
and Dog
, the only one that know about them is the abstract
class Animal
, so when deserializing, you'll target to Animal
and the ObjectMapper
will return the actual instance as you can see in the following test:
public class Test {
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
Animal myDog = new Dog("ruffus","english shepherd");
Animal myCat = new Cat("goya", "mice");
try {
String dogJson = objectMapper.writeValueAsString(myDog);
System.out.println(dogJson);
Animal deserializedDog = objectMapper.readValue(dogJson, Animal.class);
System.out.println("Deserialized dogJson Class: " + deserializedDog.getClass().getSimpleName());
String catJson = objectMapper.writeValueAsString(myCat);
Animal deseriliazedCat = objectMapper.readValue(catJson, Animal.class);
System.out.println("Deserialized catJson Class: " + deseriliazedCat.getClass().getSimpleName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Output after running the Test
class:
{"@type":"Dog","name":"ruffus","breed":"english shepherd"}
Deserialized dogJson Class: Dog
{"@type":"Cat","name":"goya","favoriteToy":"mice"}
Deserialized catJson Class: Cat
Hope this helps,
Jose Luis
Deserializing polymorphic types with Jackson based on the presence of a unique property
This feels like something @JsonTypeInfo
and @JsonSubTypes
should be used for but I've picked through the docs and none of the properties that can be supplied quite seem to match what you're describing.
You could write a custom deserializer that uses @JsonSubTypes
' "name" and "value" properties in a non-standard way to accomplish what you want. The deserializer and @JsonSubTypes
would be supplied on your base class and the deserializer would use the "name" values to check for the presence of a property and if it exists, then deserialize the JSON into the class supplied in the "value" property. Your classes would then look something like this:
@JsonDeserialize(using = PropertyPresentDeserializer.class)
@JsonSubTypes({
@Type(name = "stringA", value = SubClassA.class),
@Type(name = "stringB", value = SubClassB.class)
})
public abstract class Parent {
private Long id;
...
}
public class SubClassA extends Parent {
private String stringA;
private Integer intA;
...
}
public class SubClassB extends Parent {
private String stringB;
private Integer intB;
...
}
Deserializing Polymorphic Types with @JsonUnwrapped using Jackson
My SinglePolyUnwrappedDeserializer
from this Gist can handle a single polymorphic @JsonUnwrapped
property. It's in Kotlin, but can easily be ported to Java if needed. Example:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes(
JsonSubTypes.Type(value = A::class, name = "a"),
JsonSubTypes.Type(value = B::class, name = "b")
)
abstract class Base
data class A(val x: Int) : Base()
data class B(val y: Boolean) : Base()
@JsonDeserialize(using = SinglePolyUnwrappedDeserializer::class)
data class C(val a: String, @JsonUnwrapped val b: Base)
AFAIK, all combinations of other annotations are supported. The only limitation is that there is exactly one @JsonUnwrapped
property.
If you also need a generic serializer for polymorphic @JsonUnwrapped
, you can write it yourself very easily without any reflection or introspection: just merge the ObjectNode
of the inner object onto the ObjectNode
of the containing object.
Inheritance deserialisation with jackson in java
The answer has indeed been provided in the comments by @hollpolloi with the link: https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization
When you are using inheritence and Jackson you need a way to tell jackson how to differentiate different JSON from each other, and how to decide which object-type (class) each one is. The problem is that Jackson can get confused with similar JSON payloads when there is no attribute that tells jackson what class should be used.
Another good reference q/a is : Deserialize JSON with Jackson into Polymorphic Types - A Complete Example is giving me a compile error
A very simple solution is to annotate your classes with something like the following:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public abstract class Animal {
...
}
See also:
- https://www.logicbig.com/tutorials/misc/jackson/jackson-json-type-info-annotation.html
Jackson Polymorphic Deserialization based on Enum
Fixed!
It works with jackson 2.0!!
How does Jackson deserialize json into a generic type?
Your problem is here:
new TypeReference<T>()
This doesn't do what you expect it to do. Java generics are erased at runtime; therefore the above statement is basically new TypeReference<Object>
.
In other words - the fact that you declared
Person<Child> readPerson = new Person<>();
to expect Child objects is simply not sufficient!
You probably have to pass the specific class Child.class
to the code that maps JSON strings back. For further information, look here.
Related Topics
How to Read File from End to Start (In Reverse Order) in Java
Is There a Good Natural Language Processing Library
How to Generate Jaxb Classes from Xsd
How to Properly Match Varargs in Mockito
How Do Synchronized Static Methods Work in Java and How to Use It for Loading Hibernate Entities
Override Java System.Currenttimemillis for Testing Time Sensitive Code
No Suitable Driver Found for 'Jdbc:Mysql://Localhost:3306/Mysql
Java MVC - How to Divide a Done Text Game into MVC
How to Get a Thread and Heap Dump of a Java Process on Windows That's Not Running in a Console
Given Final Block Not Properly Padded
Java Calendar, Date, and Time Management for a Multi-Timezone Application
How to Go About Formatting 1200 to 1.2K in Java
Where Do I Find a Standard Trie Based Map Implementation in Java
Why Do I Get Java.Lang.Abstractmethoderror When Trying to Load a Blob in the Db
How to Remove the Last Character from a String
Jre 1.7 - Java Version - Returns: Java/Lang/Noclassdeffounderror: Java/Lang/Object