How to ignore type information on json arrays during jackson deserialization?

Derek Mok :

I'd like to migrate my ObjectMapper configuration from one that includes type information, to one that does not.

Currently, my ObjectMapper is configured like so:

ObjectMapper objectMapper = new ObjectMapper().enableDefaultTypingAsProperty(
  ObjectMapper.DefaultTyping.NON_FINAL, 
  "@class"
);

Which allows me to deserialize json objects that look like this:

{
  "@class": "com.foo.BarClass",
  "barProp": "bar"
  "barList": [
    [
      "java.util.ArrayList",
      [
        { "@class": "com.foo.BarListElement", ... },
        { "@class": "com.foo.BarListElement", ... },
        ...
      ]
  ],
  ...
}

With the following POJO:

public class BarClass {
  private String barProp;
  // assume BarListElement to be a POJO with sensible getters/setters
  List<BarListElement> barList;
  // assume some other trivial fields and corresponding getters/setters
}

However, I would like remove the call to enableDefaultTypingAsProperty as my application code already knows the class during deserialization, therefore it unnecessarily ties the deserialization code to the fully qualified class name.

I tried to achieve this by removing the call to enableDefaultTypingAsProperty and calling configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) on the ObjectMapper instead, with the intention of ignoring the "@class" property present on the json. Unfortunately, that only works for json that does not contain arrays, since the json array I receive contains the concrete List type information (i.e. the "java.util.ArrayList" element in the array in the example).

I would like to ask if there is any way to configure the ObjectMapper such that the collection type on the json array is also ignored.

Michał Ziober :

You need to customise collection deserialiser which will take care about extra type information. Array with type has a general structure: ["java.util.collectionType", [...]]. To do that we need to use BeanDeserializerModifier and custom CollectionDeserializer. Let's start from custom deserialiser:

class OmitListTypeJsonDeserializer extends CollectionDeserializer {

    public OmitListTypeJsonDeserializer(CollectionDeserializer src) {
        super(src);
    }

    @Override
    public Collection<Object> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        p.nextToken(); // Omit array type
        p.nextToken(); // Omit comma and start array

        Collection<Object> collection = super.deserialize(p, ctxt);

        p.nextToken(); // Omit end of array

        return collection;
    }

    @Override
    public CollectionDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
        return new OmitListTypeJsonDeserializer(super.createContextual(ctxt, property));
    }
}

Bean deserializer modifier looks like below:

class OmitListTypeDeserializerModifier extends BeanDeserializerModifier {

    @Override
    public JsonDeserializer<?> modifyCollectionDeserializer(DeserializationConfig config, CollectionType type, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
        if (deserializer instanceof CollectionDeserializer) {
            return new OmitListTypeJsonDeserializer((CollectionDeserializer) deserializer);
        }

        return deserializer;
    }
}

Example usage:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.std.CollectionDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.CollectionType;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        ObjectMapper typeAwareMapper = new ObjectMapper();
        typeAwareMapper.enable(SerializationFeature.INDENT_OUTPUT);
        typeAwareMapper.enableDefaultTypingAsProperty(ObjectMapper.DefaultTyping.NON_FINAL, "@class");

        String json = typeAwareMapper.writeValueAsString(new BarClass());
        System.out.println(json);

        SimpleModule module = new SimpleModule();
        module.setDeserializerModifier(new OmitListTypeDeserializerModifier());

        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(module);
        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        System.out.println(mapper.readValue(json, BarClass.class));
    }
}

class BarClass {
    private String barProp = "PROP1";
    private List<BarListElement> barList = Arrays.asList(new BarListElement(), new BarListElement());
    private Integer zet = 123;

    // getters, setters
}

class BarListElement {

    private Double sid = Math.random();

    // getters, setters
}

Above code prints below JSON:

{
  "@class" : "com.celoxity.BarClass",
  "barProp" : "PROP1",
  "barList" : [ "java.util.Arrays$ArrayList", [ {
    "@class" : "com.celoxity.BarListElement",
    "sid" : 0.09525693290513237
  }, {
    "@class" : "com.celoxity.BarListElement",
    "sid" : 0.689909415561781
  } ] ],
  "zet" : 123
}

And deserialised object:

BarClass{barProp='PROP1', barList=[BarListElement{sid=0.09525693290513237}, BarListElement{sid=0.689909415561781}], zet=123}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=158588&siteId=1