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

前端 未结 3 1198
佛祖请我去吃肉
佛祖请我去吃肉 2020-11-29 05:08

Imagine the following scenario:

class  Foo {
    ....
}

class Bar {
    Foo foo;
}

I want to write a cu

3条回答
  •  没有蜡笔的小新
    2020-11-29 05:35

    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 {
        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 name;
        public Wrapper 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> 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) 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.

提交回复
热议问题