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:
- 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.
- 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.