简体   繁体   English

如何在 Jackson 中为泛型类型创建自定义反序列化器?

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

Imagine the following scenario:想象以下场景:

class <T> Foo<T> {
    ....
}

class Bar {
    Foo<Something> foo;
}

I want to write a custom Jackson deserializer for Foo.我想为 Foo 编写一个自定义的 Jackson 反序列化器。 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 (eg I need to know that T is Something in that particluar case).为了做到这一点(例如,为了反序列化具有Foo<Something>属性的Bar类),我需要在反序列化时知道Bar使用的Foo<T>的具体类型(例如,我需要知道在那个特定的情况下, TSomething )。

How does one write such a deserializer?如何编写这样的解串器? It should be possible to do it, since Jackson does it with typed collections and maps.应该可以做到这一点,因为 Jackson 使用类型化集合和地图来做到这一点。

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> 1) 在Bar获取声明类型的属性foo并使用它来反序列化Foo<Somehting>

2) Find out at deserialization time that we are deserializing property foo inside class Bar in order to successfully complete step 1) 2) 在反序列化时发现我们正在反序列化类Bar中的属性foo以成功完成步骤 1)

How does one complete 1 and 2 ?如何完成 1 和 2 ?

You can implement a custom JsonDeserializer for your generic type which also implements ContextualDeserializer .你可以为你的泛型类型实现一个自定义的JsonDeserializer ,它也实现了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:我们现在想要反序列化如下所示的 JSON:

{
    "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.实现ContextualDeserializer允许我们根据字段的泛型类型参数为Person类中的每个字段创建一个特定的反序列化器。 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.最好先在此处查看createContextual ,因为 Jackson 将首先调用它。 We read the type of the field out of the BeanProperty (eg Wrapper<String> ) and then extract the first generic type parameter (eg String ).我们从BeanProperty读取字段的类型(例如Wrapper<String> ),然后提取第一个泛型类型参数(例如String )。 We then create a new deserializer and store the inner type as the valueType .然后我们创建一个新的反序列化器并将内部类型存储为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.一旦在这个新创建的反deserialize上调用 deserialize,我们可以简单地要求 Jackson 将值反序列化为内部类型而不是整个包装器类型,并返回一个包含反序列化值的新Wrapper器。

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:如果我们然后尝试反序列化上面的示例 JSON,我们可以看到它按预期工作:

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 . Jackson 文档中有更多关于上下文反序列化器如何工作的详细信息。

If the target itself is a generic type then property will be null, for that you'll need to get the valueTtype from the DeserializationContext:如果目标本身是泛型类型,则属性将为空,为此您需要从 DeserializationContext 获取 valueTtype:

@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
    if (property == null) { //  context is generic
        JMapToListParser parser = new JMapToListParser();
        parser.valueType = ctxt.getContextualType().containedType(0);
        return parser;
    } else {  //  property is generic
        JavaType wrapperType = property.getType();
        JavaType valueType = wrapperType.containedType(0);
        JMapToListParser parser = new JMapToListParser();
        parser.valueType = valueType;
        return parser;
    }
}

This is how you can access/resolve {targetClass} for a Custom Jackson Deserializer.这是您如何访问/解析自定义 Jackson Deserializer 的 {targetClass}。 Of course you need to implement ContextualDeserializer interface for this.当然,您需要为此实现 ContextualDeserializer 接口。

public class WPCustomEntityDeserializer extends JsonDeserializer<Object> 
              implements ContextualDeserializer {

    private Class<?> targetClass;

    @Override
    public Object deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {

        ObjectCodec oc = jp.getCodec();
        JsonNode node = oc.readTree(jp);

        //Your code here to customize deserialization
        // You can access {target class} as targetClass (defined class field here)
        //This should build some {deserializedClasObject}

        return deserializedClasObject;

    }   

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property){
        //Find here the targetClass to be deserialized  
        String targetClassName=ctxt.getContextualType().toCanonical();
        try {
            targetClass = Class.forName(targetClassName);
        } catch (ClassNotFoundException e) {            
            e.printStackTrace();
        }
        return this;
    }
}

For my use case, none of the above solutions worked, so I had to write a custom module.对于我的用例,上述解决方案均无效,因此我必须编写自定义模块。 You can find my implementation on GitHub .你可以在 GitHub 上找到我的实现。

I wanted to write a deserializer that automatically removes blank Strings from Lists.我想编写一个自动从列表中删除空白字符串的反序列化器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM