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)
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>>() { });
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?
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