I want to have a Serializer and Deserializer for Class Literals. It should be mapped from / to JSON Strings. And it should also work with generic classes.
I tried writing a custom Deserializer, but it handles all Classes, not just the ones I want:
public class MyDeserializer extends JsonDeserializer<Class<? extends Foo>> {
public Class<? extends Foo> deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String token = jp.getText();
switch(token) {
case "FOO": return Foo.class;
case "BAR": return Bar.class;
}
return null;
}
}
Jackson will call this Deserializer for EVERY Class<?>
. But I just want it to get called for Class<? extends Foo>
.
Btw: I want to register this as a global Deserializer. I don't want to use @JsonDeserialize
everywhere.
How can I get this working properly?
Thanks in advance.
POJO:
public class MyPOJO {
public Class<? extends Foo> fooType;
}
JSON:
{
"fooType": "FOO"
}
ObjectMapper usage:
MyPOJO pojo = new MyPOJO();
pojo.fooType = Foo.class;
ObjectMapper om = new ObjectMapper();
String json = om.writeValueAsString(pojo);
What you try to achieve seems to be impossible due to Type Erasure, indeed at runtime your class MyDeserializer
is seen as a JsonDeserializer<Class>
and your field fooType
is simply of type Class
not of type Class<? extends Foo>
that is actually why your deserializer is called for every Class<?>
.
In your case the best solution would be to annotate your fields of type Class<? extends Foo>
with @JsonDeserialize(using = MyDeserializer.class)
in order to indicate at runtime the deserializer to use but as you don't want to do it, as workaround, assuming that in given class when you have fields of type Class
they will all use the same deserializer (custom or default), you could simply modify your class MyDeserializer
to check first the current bean class to know whether we should use the custom deserializer or the default one.
You would register it for example as next:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("MyModule")
.addDeserializer(Class.class, new MyDeserializer());
mapper.registerModule(module);
The code of your deserializer would then be:
public class MyDeserializer extends JsonDeserializer<Class<?>> {
// The default deserializer for a property of type Class
private final FromStringDeserializer.Std defaultDeserializer;
public MyDeserializer() {
// Set the default deserializer
this.defaultDeserializer = FromStringDeserializer.findDeserializer(Class.class);
}
public Class<?> deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// Check whether we should used the custom or default deserializer
// based on the bean class
if (accept(jp.getCurrentValue().getClass())) {
// The custom deserializer
String token = jp.getText();
switch(token) {
case "FOO": return Foo.class;
case "BAR": return Bar.class;
}
return null;
}
// Call the default deserializer
return (Class<?>) defaultDeserializer.deserialize(jp, ctxt);
}
// Returns true if this bean class has fields of type Class that must
// be deserialized with the custom deserializer, false otherwise
public boolean accept(Class<?> beanClass) {
// Implement your logic here
// You could for example have all the bean classes in a Set and
// check that the provided class is in the Set
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With