Priyanka Sharma
Priyanka Sharma

Reputation: 3

Deserialize multi level polymorphic subtypes using jackson annotations

I want to deserialize 2 level polymorphic subtypes using jackson annotations. Basically I want to convert following json to xml using json annotations and jacksons as follows

{
  "mainSet": {
    "name": "bla bla",
    "myItems": [
      {
        "MySubItem": {
          "id": 1,
          "name": "Value1",
          "itemAProperty1": "Some stuff",
          "itemAProperty2": "Another property value",
          "type": "MySubItemA"
        }
      },
      {
        "MySubItem": {
          "id": 2,
          "name": "Value2",
          "itemBProperty1": 1000,
          "itemBProperty2": "B property",
          "type": "MySubItemB"
        }
      }
    ]
  }
}

And Final Xml i want is

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<mainTag schemaVersion="1" xmlns="http://www.utiba.com/xml/ns/brms/v1"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <name>bla bla</name>
    <MySubItem xsi:type="MySubItemA" id="1" name="value1" itemAProperty1="Some stuff" itemAProperty2="Another property value"/>
    <MySubItem xsi:type="MySubItemB" id="2" name="value2" itemAProperty1=1000 itemAProperty2="B Property"/>
</mainTag>

I have following set of classes - Main class

public abstract class MyItem {

private int id;
private String name;
 //getter and setter
}

Its having abstract subclass

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "MySubItem")
@XmlSeeAlso({
    MySubItemA.class,
    MySubItemB.class,
    MySubItemC.class
})
    public abstract class MySubItem extends MyItem {

    private String itemAProperty1;
    private String itemAProperty2;
     //getter and setter
    }

And MySubItem have concrete subclases say MySubItemA,MySubItemB, MySubItemC

Now Finally, we create a client class that contains a list of objects of the abstract class

 import java.util.ArrayList;
    import java.util.List;

    public class MainSet{
     @XmlElements({
        @XmlElement(name = "MysubItem", type = MySubItem.class),
    })
    private List<MyItem> myItems;

      public List<MyItem> getMyItems() {
        return this.myItems;
    }
    }

I tried creating Mixin classes for MyItem as

@JsonTypeInfo(use=Id.MINIMAL_CLASS, include=As.PROPERTY, property="itemType")
@JsonSubTypes({
   @Type(MySubItem.class)
})

And for and MySubItem

@JsonTypeInfo(use=Id.MINIMAL_CLASS, include=As.PROPERTY, property="type")
@JsonSubTypes({
   @Type(MySubItemA.class)
   @Type(MySubItemB.class)
   @Type(MySubItemC.class)
})

Error i am getting is :

Error parsing JSON from client: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of MySubItem, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
 at [Source: java.io.StringReader@26bd8952; line: 1, column: 498] (through reference chain: bla bla class ["mainSet"]->com.bla.bla["myItems"])

ISSUE : create mixin class for myItems list, having 2 level of abstract subclasses hierarchy

Upvotes: 0

Views: 6350

Answers (2)

Nakul Vashishth
Nakul Vashishth

Reputation: 36

Multiple levels of polymorphic type hierarchy is not supported by Jackson api’s.

You can have a look at : https://github.com/FasterXML/jackson-databind/issues/374

So what you need to do is:

Please create a deserializer (for MySubItem.class say MySubItemDeserializerMixin.class) for and configure that to jsonMapper as we do for other Mixin classes.

mapper.addMixInAnnotations(MySubItem.class, MySubItemDeserializerMixin.class);

The MySubItemDeserializerMixin.java would look like :

@JsonDeserialize(using = MySubItemDeserializer.class)
public abstract class MySubItemDeserializerMixin{
}

You would also need to create a deserializer(MySubItemDeserializer) for MySubItem as specified in MySubItemDeserializerMixin.java.

Now you need to create MySubItemMixin.java which would look like this:

@JsonTypeInfo(use=Id.MINIMAL_CLASS, include=As.PROPERTY, property="type")
@JsonSubTypes({
@Type(MySubItemA.class)
@Type(MySubItemB.class)
@Type(MySubItemC.class)
})

In MySubItemDeserializer you would do something like:

@Override
public MySubItem deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {

    JsonNode node = jsonParser.getCodec().readTree(jsonParser);

    ObjectMapper jsonMapper = new ObjectMapper();

    // Omit null values from the JSON.
    jsonMapper.setSerializationInclusion(Include.NON_NULL);

    // Treat received empty JSON strings as null Java values.
    // Note: doesn't seem to work - using custom deserializer through module
    // below instead.
    jsonMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);

    jsonMapper.addMixInAnnotations(MySubItem.class, MySubItemMixin.class);

    // Enable interpretation of JAXB annotations on our beans (e.g. for 
    // resolving ID/IDREF references).
    jsonMapper.registerModule(new JaxbAnnotationModule());

    MySubItem condition = jsonMapper.readValue(node.toString(), MySubItem.class); 

  }

Hope that resolves your concerns.

Thanks & Regards

Nakul Vashishth

Upvotes: 2

Adam
Adam

Reputation: 2269

Your sub-types annotation is telling JAXB what to do but not telling Jackson, which is what is trying to deserialize your request from JSON. Try adding this to your abstract class, in addition to the TypeInfo:

@JsonSubTypes({
    MySubItemA.class,
    MySubItemB.class,
    MySubItemC.class
})

Upvotes: 0

Related Questions