'Jackson deserializer for generic type

I need to write a custom deserializer for a class with generics. I couldn't find a way to do this, however I cannot imagine I'm the only one with this problem. As far as I've thought through it, there would be two ways to implement this, yet none of them are implementable:

  1. Providing a Class parameter to the deserializer in the constructor of the deserializer doesn't work, because when registering the deserializer, the relationship between Type.class and the instance of the deserializer with the passed Class instance is lost.

For example:

public class Foo<T> {}
public class FooDeserializer<T> {
    public FooDeserializer(Class<T> type) { ... }
    ...
}
// Boilerplate code...
module.addDeserializer(Foo.class, new FooDeserializer<Bar1>(Bar1.class));
module.addDeserializer(Foo.class, new FooDeserializer<Bar2>(Bar2.class));

This doesn't work, when an ObjectMapper instance gets an instance of Foo, there's no type information of the generic parameter available (type erasure) so it simply chooses the last deserializer registered.

  1. It doesn't help to keep a reference of the generic type in the class because an instantiated version of the class cannot be passed to the deserializer (the interface is readValue(String, Class)).

For example:

String json = "...";
ObjectMapper mapper = ...;
Foo<Bar1> foo = new Foo<>(Bar1.class);
foo = mapper.readValue(json, Foo.class); // Can't pass empty foo instance with Class<?> field containing Bar1.class

Something like this would be needed:

mapper.readValue(json, Foo.class, Bar1.class); // Doesn't exist in jackson

Any suggestions how to do this?

EDIT: I found a way to solve the problem, however it's not a clean solution:

I extend the FooDeserializer with a Class field to save the type of Foo's generic parameter. Then, every time I want to deserialize some json into a new Foo instance, I get a new ObjectMapper instance (I use ObjectMapper#copy on a preconfigured instance from a factory) and pass it a new Module which contains an instance of the FooDeserializer with the class parameter (I know the type at this time). Module, FooDeserializer and the ObjectMapper copy are short living, they only get instantiated for this single deserialization action. As I said, not very clean, but still better than subclassing Foo many times and writing a deserializer for each.

Example:

public class FooDeserializer<T> extends StdDeserializer<T> {
    private Class<T> type;
    public FooDeserializer(Class<T> type) { this.type = type }
    ...
}

// Meanwhile, before deserialization:
ObjectMapper mapper = MyObjectMapperFactory.get().copy();
SimpleModule module = new SimpleModule(new Version(0,0,1,null,null,null);
module.addDeserializer(Foo.class, new FooDeserializer(Bar1.class);
mapper.addModule(module);
Foo<Bar1> x = mapper.readValue(json, Foo.class); 

Probably putting this into a utility method to hide the uglyness.



Solution 1:[1]

I don't think you need to write our own custom deserializer. You can use this syntax to deserialize objects that use generics, taken from another Stack Overflow thread.

mapper.readValue(jsonString, new TypeReference<Data<String>>() {});

Couple sources to help you: Jackson - Deserialize using generic class http://www.tutorialspoint.com/jackson/jackson_data_binding_generics.htm

Solution 2:[2]

You don't need to write a custom deserializer. A generic can be deserialized by putting Jackson annotations on the type that is passed as parameter to the generic class.
Annotate class Bar1 as follows:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
@JsonTypeName("Bar1")
class Bar1 {

}

Now when you deserialize an instance of Foo<Bar1>, Jackson will put type parameter info into JSON. This JSON can then be deserialized into generic class Foo.class just as you would deserialize any other class.

ObjectMapper mapper = ...;
Foo<Bar1> foo = new Foo<Bar1>();
String json = objectMapper.writeValueAsString(foo);
foo = mapper.readValue(json, Foo.class); // no need to specify the type Bar1.

So if every class that can be passed as parameter to Foo has annotations on it, then JSON can be deserialized without knowing the type parameter at compile time.

Please refer Jackson documentation on Polymorphic Deserialization and Annotations.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Community
Solution 2