Deserialize List of polymorphic objects into Object field Deserialize List of polymorphic objects into Object field json json

Deserialize List of polymorphic objects into Object field


Unfortunately deserializer accepts type from the declared type of the field. So if field is Object then it will not deserialize to anything and leave it as Map. There is no simple solution to this because deserializes has no information about what type it should be. One solution is custom mapper like one of the answers is, or you can use custom TaggedObject class instead of Object which is what I used for similar use case:

public class TaggedObject {    @Expose    private String type;    @Expose    private Object value;    @Expose    private Pair<TaggedObject, TaggedObject> pairValue;        @SuppressWarnings("unchecked")    public TaggedObject(Object v) {        this.type = getTypeOf(v);        if (v instanceof Pair) {            this.pairValue = tagPair((Pair<Object, Object>) v);            this.value = null;        } else if ("long".equals(type)){            this.value = "" + v;            this.pairValue = null;        } else {            this.value = v;            this.pairValue = null;        }    }    private static Pair<TaggedObject, TaggedObject> tagPair(Pair<Object, Object> v) {        return new Pair<TaggedObject, TaggedObject>(TaggedObject.tag(v.first), TaggedObject.tag(v.second));    }    private static String getTypeOf(Object v) {        Class<?> cls = v.getClass();        if (cls.equals(Double.class))            return "double";        if (cls.equals(Float.class))            return "float";        if (cls.equals(Integer.class))            return "integer";        if (cls.equals(Long.class))            return "long";        if (cls.equals(Byte.class))            return "byte";        if (cls.equals(Boolean.class))            return "boolean";        if (cls.equals(Short.class))            return "short";        if (cls.equals(String.class))            return "string";        if (cls.equals(Pair.class))            return "pair";        return "";    }    public static TaggedObject tag(Object v) {        if (v == null)            return null;        return new TaggedObject(v);    }    public Object get() {        if (type.equals("pair"))            return new Pair<Object, Object>(                    untag(pairValue.first),                    untag(pairValue.second)                    );        return getAsType(value, type);    }    private static Object getAsType(Object value, String type) {        switch (type) {        case "string" : return value.toString();        case "double" : return value;        case "float"  : return ((Double)value).doubleValue();        case "integer": return ((Double)value).intValue();        case "long"   : {            if (value instanceof Double)                return ((Double)value).longValue();            else                return Long.parseLong((String) value);        }        case "byte"   : return ((Double)value).byteValue();        case "short"  : return ((Double)value).shortValue();        case "boolean": return value;        }        return null;    }    public static Object untag(TaggedObject object) {        if (object != null)            return object.get();        return null;    }}

This is for google gson (that's why there are @Expose annotations) but should work just fine with jackson. I did not include Pair class but you can surely make your own based by the signature or omit it (I needed to serialize pairs).


If you know the type to convert to, you can use the method convertValue of ObjectMapper.

You can convert either a single value or the whole list.

Consider the following example:

public static void main(String[] args) throws IOException {  ObjectMapper objectMapper = Jackson.newObjectMapper()    .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)    .setDefaultPropertyInclusion(Include.NON_NULL);  objectMapper.addMixIn(Animal.class, Mixin.class);  Zoo zoo = objectMapper.readValue(JSON_STRING, Zoo.class);  List<Animal> animals = objectMapper.convertValue(zoo.getAnimals(), new TypeReference<List<Animal>>(){});  for (Object animal : animals) {    System.out.println(animal.getClass());  }}

For your comments, if you are unsure of the actual type that need to be processed, one option that can be useful is enabling Jackson's default typing (with the required precautions).

You can do it like this:

PolymorphicTypeValidator validator = BasicPolymorphicTypeValidator  .builder()  .allowIfBaseType(Animal.class)  .build();ObjectMapper mapper = new ObjectMapper();mapper.activateDefaultTyping(DefaultTyping.NON_FINAL, validator);

Another think you can do is define a custom Deserializer for the field that contains your arbitrary information.

Instead of creating one from scratch, you can reuse some code.

When you define as Object your property, Jackson will assign an instance of the class com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer as itsDeserializer.

In your case, this class will process first the array, so we obtain a List as the main deserialization result.

This List will be composed of instances of the class LinkedHashMap. Why? Because in their implementation, for every JSON object in the array, they generate a LinkedHashMap with keys, the JSON object property names in the order that they appear, and values, an String[] with the JSON object property values.

This process is handled by the mapObject method. If you have some kind of field that allow you to identify the type of Object you are processing, maybe you can define a new Deserializer that extends UntypedObjectDeserializer and overwrites the mapObject method to create, from the Map returned by its parent, the actual, concrete, objects, just by applying Java Bean introspection and property setters.

This last approach can also be implemented with Converters. They are actually very well suited for this two phase process. The idea behind that is the same described in the last paragraph: you will receive a List of Maps, and if you are able to identify the concrete type, you only need to build the corresponding instances from the information received. You can even use ObjectMapper for this last conversion step.


The requirement is that a property of type Object an be deserialized into the correct class.

One could consider using different routes for different types. If this is not an option, and since you have control over the serialized data and to stay as close as possible to your existing code, you could use a JsonDeserialize annotation.

The solution could be to add a 'response_type' to the JSON data.

Since you need to deserialize other types of objects besides list of animals, maybe a more generic name instead of animals make sense. Let's rename it from animals to response.

So instead of

private Object animals;

you will have:

private Object response;

Then your code slightly modified to the above points could look like this:

package jackson.poly;import com.fasterxml.jackson.databind.annotation.JsonDeserialize;public class Zoo {    private String responseType;    @JsonDeserialize(using = ResponseDeserializer.class)    private Object response;    public String getResponseType() { return responseType; }    public Object getResponse() { return response; }}

The ResponseDeserializer could look like this:

package jackson.poly;import com.fasterxml.jackson.core.JsonParser;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.DeserializationContext;import com.fasterxml.jackson.databind.JsonDeserializer;import java.io.IOException;import java.util.Arrays;public class ResponseDeserializer extends JsonDeserializer<Object> {    @Override    public Object deserialize(JsonParser jsonParser,                                  DeserializationContext deserializationContext) throws            IOException, JsonProcessingException {        Zoo parent = (Zoo)deserializationContext.getParser().getParsingContext().getParent().getCurrentValue();        String responseType = parent.getResponseType();        if(responseType.equals("Animals[]")) {            Animal[] animals = jsonParser.readValueAs(Animal[].class);            return Arrays.asList(animals);        }        else if(responseType.equals("Facilities[]")) {            Facility[] facilities = jsonParser.readValueAs(Facility[].class);            return Arrays.asList(facilities);        }        throw new RuntimeException("unknown subtype " + responseType);    }}

Using input data like this (note the response_type property):

private static final String JSON_STRING = "{\n"        + "  \"response_type\": \"Animals[]\",\n"        + "  \"response\": [\n"        + "    {\"name\": \"dog\"},\n"        + "    {\"name\": \"cat\"}\n"        + "  ]\n"        + "}";

a test run of the above program would produce the following output on the debug console:

class jackson.poly.Dogclass jackson.poly.Cat

If you would use something like this as input instead (assuming the corresponding classes exist in a form similar to the animal classes):

private static final String JSON_STRING = "{\n"        + "  \"response_type\": \"Facilities[]\",\n"        + "  \"response\": [\n"        + "    {\"name\": \"house\"},\n"        + "    {\"name\": \"boat\"}\n"        + "  ]\n"        + "}";

you would get

class jackson.poly.Houseclass jackson.poly.Boat

as output.