maxTrialfire
maxTrialfire

Reputation: 551

Deserialize into polymorphic collection using Jackson without using @JsonTypeInfo

I am using a 3rd party service that spits out JSON which I'm trying to deserialize into Java POJOs. The JSON service can't be changed and I'm using Java 8 and Jackson 2.4.

Here is a simplified version of my problem. I have an interface and two concrete classes below:

public interface Animal {

}

public class Dog implements Animal {

   public String bark;

}

public class Cat implements Animal {

   public String meow;

}

I need to deserialize the following JSON into a List<Animal>:

 {
        "animals": [
     {
       "bark":"bowwow"
     },
     {
      "bark":"woofWoof"
     },
     {
      "meow":"meeeOwww"
     },
     {
      "meow":"hisssss"
     }
    ]
}

I want to be able to determine the concrete java type based on the existence of the meow property (its a Cat) and the bark property (its a Dog) in the JSON. How can make I Jackson do this?

Upvotes: 2

Views: 726

Answers (1)

cassiomolin
cassiomolin

Reputation: 130887

It could be achieved with a custom implementation of StdDeserializer:

public class UniquePropertyPolymorphicDeserializer<T> extends StdDeserializer<T> {

    private Map<String, Class<? extends T>> registry;

    public UniquePropertyPolymorphicDeserializer(Class<T> clazz) {
        super(clazz);
        registry = new HashMap<String, Class<? extends T>>();
    }

    public void register(String uniqueProperty, Class<? extends T> clazz) {
        registry.put(uniqueProperty, clazz);
    }

    @Override
    public T deserialize(JsonParser p, DeserializationContext ctxt) 
            throws IOException, JsonProcessingException {

        Class<? extends T> clazz = null;

        ObjectMapper mapper = (ObjectMapper) p.getCodec();  
        ObjectNode obj = (ObjectNode) mapper.readTree(p);  
        Iterator<Entry<String, JsonNode>> elementsIterator = obj.fields();

        while (elementsIterator.hasNext()) {  
            Entry<String, JsonNode> element = elementsIterator.next();  
            String name = element.getKey();  
            if (registry.containsKey(name)) {  
                clazz = registry.get(name);  
                break;  
            }
        }

        if (clazz == null) {
            throw ctxt.mappingException(
                    "No registered unique properties found "
                    + "for polymorphic deserialization");  
        }

        return mapper.treeToValue(obj, clazz);
    }
}

It can be used as following:

String json = "[{\"bark\":\"bowwow\"},{\"bark\":\"woofWoof\"},{\"meow\":\"meeeOwww\"},{\"meow\":\"hisssss\"}]";

UniquePropertyPolymorphicDeserializer<Animal> deserializer = 
    new UniquePropertyPolymorphicDeserializer<>(Animal.class);

deserializer.register("bark", Dog.class); // if "bark" field is present, then it's a Dog
deserializer.register("meow", Cat.class); // if "meow" field is present, then it's a Cat

SimpleModule module = new SimpleModule("UniquePropertyPolymorphicDeserializer", 
        new Version(1, 0, 0, null, "com.example", "polymorphic-deserializer")); 
module.addDeserializer(Animal.class, deserializer);

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);

Animal[] animals = mapper.readValue(json, Animal[].class);

For more details, have a look here.

Upvotes: 1

Related Questions