Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a custom deserializer in Jackson for a generic type?

Tags:

Imagine the following scenario:

class <T> Foo<T> {     .... }  class Bar {     Foo<Something> foo; } 

I want to write a custom Jackson deserializer for Foo. In order to do that (for example, in order to deserialize Bar class that has Foo<Something> property), I need to know the concrete type of Foo<T>, used in Bar, at deserialization time (e.g. I need to know that T is Something in that particluar case).

How does one write such a deserializer? It should be possible to do it, since Jackson does it with typed collections and maps.

Clarifications:

It seems there are 2 parts to solution of the problem:

1) Obtain declared type of property foo inside Bar and use that to deserialize Foo<Somehting>

2) Find out at deserialization time that we are deserializing property foo inside class Bar in order to successfully complete step 1)

How does one complete 1 and 2 ?

like image 290
Krešimir Nesek Avatar asked Mar 22 '16 16:03

Krešimir Nesek


People also ask

What is Jackson deserializer?

Jackson is a powerful and efficient Java library that handles the serialization and deserialization of Java objects and their JSON representations. It's one of the most widely used libraries for this task, and runs under the hood of many other frameworks.

Does Jackson require default constructor?

Jackson uses default (no argument) constructor to create object and then sets value using setters. so you only need @NoArgsConstructor and @Setter.

What is DeserializationContext?

public abstract class DeserializationContext extends DatabindContext implements Serializable. Context for the process of deserialization a single root-level value. Used to allow passing in configuration settings and reusable temporary objects (scrap arrays, containers).


1 Answers

You can implement a custom JsonDeserializer for your generic type which also implements ContextualDeserializer.

For example, suppose we have the following simple wrapper type that contains a generic value:

public static class Wrapper<T> {     public T value; } 

We now want to deserialize JSON that looks like this:

{     "name": "Alice",     "age": 37 } 

into an instance of a class that looks like this:

public static class Person {     public Wrapper<String> name;     public Wrapper<Integer> age; } 

Implementing ContextualDeserializer allows us to create a specific deserializer for each field in the Person class, based on the generic type parameters of the field. This allows us to deserialize the name as a string, and the age as an integer.

The complete deserializer looks like this:

public static class WrapperDeserializer extends JsonDeserializer<Wrapper<?>> implements ContextualDeserializer {     private JavaType valueType;      @Override     public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {         JavaType wrapperType = property.getType();         JavaType valueType = wrapperType.containedType(0);         WrapperDeserializer deserializer = new WrapperDeserializer();         deserializer.valueType = valueType;         return deserializer;     }      @Override     public Wrapper<?> deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException {         Wrapper<?> wrapper = new Wrapper<>();         wrapper.value = ctxt.readValue(parser, valueType);         return wrapper;     } } 

It is best to look at createContextual here first, as this will be called first by Jackson. We read the type of the field out of the BeanProperty (e.g. Wrapper<String>) and then extract the first generic type parameter (e.g. String). We then create a new deserializer and store the inner type as the valueType.

Once deserialize is called on this newly created deserializer, we can simply ask Jackson to deserialize the value as the inner type rather than as the whole wrapper type, and return a new Wrapper containing the deserialized value.

In order to register this custom deserializer, we then need to create a module that contains it, and register that module:

SimpleModule module = new SimpleModule()         .addDeserializer(Wrapper.class, new WrapperDeserializer());  ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(module); 

If we then try to deserialize the example JSON from above, we can see that it works as expected:

Person person = objectMapper.readValue(json, Person.class); System.out.println(person.name.value);  // prints Alice System.out.println(person.age.value);   // prints 37 

There are some more details about how contextual deserializers work in the Jackson documentation.

like image 103
andersschuller Avatar answered Sep 23 '22 21:09

andersschuller