Reputation: 551
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
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