user11160452
user11160452

Reputation: 43

Jackson Polymorphic Deserialization and serialize

I will two different types of in separate calls. I want to deserialize or serialize depending on the business requirement.

one type of json:

{
  "type": "foo",
  "data": [{
    "someCommonProperty": "common property",
    "fooProperty": "foo specific property"
  },{
    "someCommonProperty": "common property1",
    "fooProperty": "foo specific property1"
  }]
}

Another type of json:

{
  "type": "bar",
  "data": [{
    "someCommonProperty": "common property",
    "barProperty": "bar specific property",
    "type": "type1"
  },{
    "someCommonProperty": "common property1",
    "barProperty": "bar specific property1",
    "type": "type1"
  }]
}

I have classes as below

 public class Parent {

    private String type;

    @JsonTypeInfo(use = Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY)
    @JsonSubTypes(value = { 
        @JsonSubTypes.Type(value = Foo.class, name = "foo"),
        @JsonSubTypes.Type(value = Bar.class, name = "bar") 
    })
    private List<AbstractData> data;

    // Getters and setters
 }

 public abstract class AbstractData {

    private String someCommonProperty;

    // Getters and setters
 }

 public class Foo extends AbstractData {

    private String fooProperty;

    // Getters and setters
 }

 public class Bar extends AbstractData {

    private String barProperty;
    private String type;
    // Getters and setters
 }

When I try to deserialize as below, I am getting empty string on writing out java object as json.

ObjectMapper mapper = new ObjectMapper();
Parent parent = mapper.readValue(json, Parent.class);

I get this error:

 com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class com.DemoJsonToJavaDeserialize.AbstractData
 at [Source: C:<projectpath>\target\classes\foo.json; line: 3, column: 12] (through reference chain: com.DemoJsonToJavaDeserialize.Parent["data"]->java.util.ArrayList[0])

Upvotes: 3

Views: 4587

Answers (1)

jmbourdaret
jmbourdaret

Reputation: 261

Option 1

If you have access to the AbstractData class, you might want to use the new 2.12 Jackson Deduction feature :

@JsonSubTypes(value = {
        @JsonSubTypes.Type(value = Foo.class),
        @JsonSubTypes.Type(value = Bar.class)
})
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
public abstract class AbstractData {
    private String someCommonProperty;
   
   // getters setters
}

Then, no need to annotate Foo, Bar and Parent : everything will get instantiated automagically as long as your subclasses have different property names. type json property becomes useless as well.

(If you are using SpringBoot, you can set the jackson-bom.version property to upgrade jackson inside spring boot)

Option 2

the javadoc of JsonTypeInfo.As.EXTERNAL_PROPERTY says : Note also that this mechanism can not be used for container values (arrays, Collections... . I believe this is your problem: you're applying it to a List.

If you can't modify any of the Foo/Barr/AbstractData classes, the classic way is to use a custom Deserializer. On the Parent class :

    @JsonDeserialize(contentUsing = FooBarDeserializer.class)
    private List<AbstractData> data;

And the Deserializer:

public class FooBarDeserializer extends JsonDeserializer<AbstractData> {
    @Override
    public AbstractData deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
        JsonNode node = jsonParser.readValueAsTree();
        if(node.has("fooProperty"))
            return jsonParser.getCodec().treeToValue(node,Foo.class);
        else
            return jsonParser.getCodec().treeToValue(node,Bar.class);
    }
}

If you find a solution that make use of your Parent.type , please share, I'm interested.

Upvotes: 8

Related Questions