Say I have these classes
public abstract class Shape {}
public class Circle extends Shape{
private int r;
...
}
public class Main {
public static void main(String [] args) {
String json = "{\"r\": 25 }";
ObjectMapper om = new ObjectMapper();
//WORKS FINE
Circle circle = om.readValue(json, Circle.class);
}
}
Now, say I want to add another class that will contain a list of Shape objects. I understand that to deserialize this class, I will have to add @JsonTypeInfo.
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Circle.class, name = "circle")
})
public abstract class Shape {}
public class Circle extends Shape{...}
public class ListOfShapes {
List<Shape> shapes;
}
public class Main {
public static void main(String [] args) {
String json = "{\"r\": 25 }";
ObjectMapper om = new ObjectMapper();
//DOES NOT WORK
Circle circle = om.readValue(json, Circle.class);
}
}
I am able to successfully serialize the ListOfShapes object now using these annotations but I am no longer able to serialize the circle class. I also noticed that the json schema changed a bit when I serialized the class so I now have an extra key ("type"):
{
"type": "circle",
"r": 5
}
The work around for me at the moment is to change the json string a bit by adding that new key value pair:
String json = "{\"type\": \"circle\", \"r\": 25 }";
Can someone explain to me whats going on here? Why am I not able to serialize the circle object any more? Why does the json schema change, by the addition of a new key value pair?
Thank you for your time.
CodePudding user response:
It's working as expected. When you add the JsonTypeInfo
and JsonSubTypes
annotations to your Shape
class like you have done below:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Circle.class, name = "circle")
})
public abstract class Shape {}
You are informing jackson you are expecting to find a property named type
in every json you want to deserialize to the Shape
class and in case of absence jackson will raise an error, moreover the type
property will be serialized with its value. You are also informing jackson that in case the value associated to the type
property is circle the json has to be deserialized as a Circle
object.
This behaviour is particularly useful when you are not aware of which type of object has to be deserialized or serialized like the example below :
String json = "{\"type\": \"circle\", \"r\": 25 }";
ObjectMapper om = new ObjectMapper();
//I don't know json is a circle object but it can be deserialized as a Shape
Shape circle = om.readValue(json, Shape.class);
System.out.println(circle instanceof Circle); //<-- it prints true
//ok type is present with value circle {"type":"circle","r":25}
System.out.println(mapper.writeValueAsString(circle));
Same approach is used with a ListOfShapes
wrapper class:
String json = "{\"type\": \"circle\", \"r\": 25 }";
ObjectMapper om = new ObjectMapper();
Shape circle = om.readValue(json, Shape.class);
//instantiate the wrapper class
ListOfShapes listOfShapes = new ListOfShapes();
listOfShapes.shapes = List.of(circle);
json = mapper.writeValueAsString(listOfShapes);
//it prints {"shapes":[{"type":"circle","r":25}]}
System.out.println(json);
listOfShapes = om.readValue(json, ListOfShapes.class);
//ok list inside contains the Circle object
System.out.println(listOfShapes);