Jackson deserialize based on property name

爷,独闯天下 提交于 2019-12-22 10:04:37

问题


I have the following two types of JSON objects:

{"foo": "String value"}

and

{"bar": "String value"}

Both of them represent a specialized type of the same base object. How can I use Jackson for deserializing them ? The type information is only represented by the keys themselves and not the value for any key (almost all examples use the value of the key for determining the type : https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization)


回答1:


Jackson doesn't offer an out of the box solution for that, but it doesn't mean that you are out of luck.


Assuming that your classes implement a common interface or extend a common class, as shown below:

public interface Animal {

}
public class Dog implements Animal {

   private String bark;

   // Default constructor, getters and setters
}
public class Cat implements Animal {

   private String meow;

   // Default constructor, getters and setters
}

You can create a custom deserializer based on the property name. It allows you to define a unique property that will be used to look up the class to perform the deserialization to:

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

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

    public PropertyBasedDeserializer(Class<T> baseClass) {
        super(baseClass);
        deserializationClasses = new HashMap<String, Class<? extends T>>();
    }

    public void register(String property, Class<? extends T> deserializationClass) {
        deserializationClasses.put(property, deserializationClass);
    }

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

        ObjectMapper mapper = (ObjectMapper) p.getCodec();
        JsonNode tree = mapper.readTree(p);

        Class<? extends T> deserializationClass = findDeserializationClass(tree);
        if (deserializationClass == null) {
            throw JsonMappingException.from(ctxt, 
               "No registered unique properties found for polymorphic deserialization");
        }

        return mapper.treeToValue(tree, deserializationClass);
    }

    private Class<? extends T> findDeserializationClass(JsonNode tree) {

        Iterator<Entry<String, JsonNode>> fields = tree.fields();
        Class<? extends T> deserializationClass = null;

        while (fields.hasNext()) {
            Entry<String, JsonNode> field = fields.next();
            String property = field.getKey();
            if (deserializationClasses.containsKey(property)) {
                deserializationClass = deserializationClasses.get(property);
                break;  
            }
        }

        return deserializationClass;
    }
}

Then instantiate and configure the deserializer:

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

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

Add it to a module:

SimpleModule module = new SimpleModule("custom-deserializers", Version.unknownVersion());
module.addDeserializer(Animal.class, deserializer);

Register the module and perform the deserialization as usual:

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

String json = "[{\"bark\":\"bowwow\"}, {\"bark\":\"woofWoof\"}, {\"meow\":\"meeeOwww\"}]";
List<Animal> animals = mapper.readValue(json, new TypeReference<List<Animal>>() { });



回答2:


With fasterxml jackson, you can do this:

abstract class FooOrBar {
    companion object {
        @JvmStatic
        @JsonCreator
        private fun creator(json: Map<String, String>): FooOrBar? {
            return when {
                json.containsKey("foo") -> Foo(json["foo"] as String)
                json.containsKey("bar") -> Foo(json["bar"] as String)
                else -> null
            }
        }
    }
}

class Foo(val foo: String) : FooOrBar() // even can use map delegate if you know what it is
class Bar(val bar: String) : FooOrBar()

It is Kotlin, but you will get the idea.

Note the @JsonCreator is used. the annotated creator function has single argument (which is one kind of the two signatures required by JsonCreator), JSON is deserialized as a Map instance and passed to the creator. From here, you can create your class instance.

----------------UPDATE-------------------------

You can also use JsonNode for the creator function for nested and complex JSON.

private fun creator(json: JsonNode): FooOrBar?



回答3:


You'll have to tell jackson what class you expect:

Foo readValue = mapper.readValue(json, Foo.class);

Bar readValue = mapper.readValue(json, Bar.class);

Otherwise it may be worth using XML in this case if you strong types are necessary for your design.



来源:https://stackoverflow.com/questions/50460950/jackson-deserialize-based-on-property-name

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!