Home > OS >  Jackson serialization/deserialization problems when using @JsonTypeInfo. Can someone explain to me h
Jackson serialization/deserialization problems when using @JsonTypeInfo. Can someone explain to me h

Time:05-16

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);
  • Related