Rohan Peshkar Rohan Peshkar - 2 months ago 9
JSON Question

Jackson - parse different model under same key at runtime

I have a specific json response from server, where under a key the content would be of different models also at a time only one of the model data would be present under the key.

While parsing the response into POJO how can I specify object type at runtime based on other field of contentType on same model.

Following is the code for better understanding of scenario.

Here content_type is type A and so under

"content"
key there would be model for object of class TypeA

"scheduled_content": {

"some_field": "value",
"content_type": "typeA",
"content" : {
"some_field" : "value"
"more_feilds" : "value"
}
}


Here content_type is type B and so under
"content"
key there would be model for object of class TypeB

"scheduled_content": {

"some_field": "value",
"content_type": "typeB",
"content" : {
"some_field_b" : "value"
"more_fields_for_b" : "value"
}
}


How can I write POJO classes to parse such json response?
The type classes are completely different models they don't have any field in common.

Answer

I believe that what you are looking for is called, in Jackson JSON terms, polymorphic deserialization by property name.

Here is how I do it with Jackson 2.1.4:

First create an abstract class ScheduledContent with common members and an abstract method that would operate on the content. Use the JsonTypeInfo annotation to mark the JSON property that would resolve the specific implementation and the JsonSubTypes annotation to register the subtypes by the values of the property previously specified:

import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "content_type")
@JsonSubTypes({
  @JsonSubTypes.Type(name = "typeA", value = ScheduledAContent.class),
  @JsonSubTypes.Type(name = "typeB", value = ScheduledBContent.class)
})
public abstract class ScheduledContent {
  private String someField;

  @JsonSetter("some_field")
  public void setSomeField(String someField) {
    this.someField = someField;
  }

  public abstract void doSomethingWithContent();
}

The subtypes registration can also be done on the ObjectMapper as you will see later.

Then add the specific implementation for the ScheduledAContent class:

public class ScheduledAContent extends ScheduledContent {
    private TypeAContent content;

    public void setContent(TypeAContent content) {
        this.content = content;
    }

    @Override
    public void doSomethingWithContent() {
        System.out.println("someField: " + content.getSomeField());
        System.out.println("anotherField: " + content.getAnotherField());
    }
}

with TypeAContent being:

import com.fasterxml.jackson.annotation.JsonSetter;

public class TypeAContent {
    private String someField;
    private String anotherField;

    @JsonSetter("some_field")
    public void setSomeField(String someField) {
        this.someField = someField;
    }

    public String getSomeField() {
        return someField;
    }

    @JsonSetter("another_field")
    public void setAnotherField(String anotherField) {
        this.anotherField = anotherField;
    }

    public String getAnotherField() {
        return anotherField;
    }
}

and also for the ScheduledBContent class:

public class ScheduledBContent extends ScheduledContent {
    private TypeBContent content;

    public void setContent(TypeBContent content) {
        this.content = content;
    }

    @Override
    public void doSomethingWithContent() {
        System.out.println("someField: " + content.getSomeField());
        System.out.println("anotherField: " + content.getAnotherField());
    }
}

with TypeBContent being:

import com.fasterxml.jackson.annotation.JsonSetter;

public class TypeBContent {
    private String someField;
    private String anotherField;

    @JsonSetter("some_field_b")
    public void setSomeField(String someField) {
        this.someField = someField;
    }

    public String getSomeField() {
        return someField;
    }

    @JsonSetter("another_field_b")
    public void setAnotherField(String anotherField) {
        this.anotherField = anotherField;
    }

    public String getAnotherField() {
        return anotherField;
    }
}

And a simple Test class:

import java.io.IOException;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.NamedType;

public class Test {
    public static void main(String[] args) {
        String jsonA = "{" +
                "\"some_field\": \"main_some_field1\"," +
                "\"content_type\": \"typeA\"," +
                "\"content\" : {" +
                "    \"some_field\" : \"content_some_field\"," +
                "    \"another_field\" : \"content_another_field\"" +
                "}}";

        String jsonB = "{" +
                "\"some_field\": \"main_some_field2\"," +
                "\"content_type\": \"typeB\"," +
                "\"content\" : {" +
                "    \"some_field_b\" : \"content_some_field_b\"," +
                "    \"another_field_b\" : \"content_another_field_b\"" +
                "}}";


        ObjectMapper mapper = new ObjectMapper();

        /*
         * This is another way to register the subTypes if you want to do it dynamically without the use of the
         * JsonSubTypes annotation in the ScheduledContent class
         */
//        mapper.registerSubtypes(new NamedType(ScheduledAContent.class, "typeA"));
//        mapper.registerSubtypes(new NamedType(ScheduledBContent.class, "typeB"));

        try {
            ScheduledContent scheduledAContent = mapper.readValue(jsonA, ScheduledContent.class);
            scheduledAContent.doSomethingWithContent();

            ScheduledContent scheduledBContent = mapper.readValue(jsonB, ScheduledContent.class);
            scheduledBContent.doSomethingWithContent();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

that will produce the output:

someField: content_some_field
anotherField: content_another_field
someField: content_some_field_b
anotherField: content_another_field_b