繁体   English   中英

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

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

想象以下场景:

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

class Bar {
    Foo<Something> foo;
}

我想为 Foo 编写一个自定义的 Jackson 反序列化器。 为了做到这一点(例如,为了反序列化具有Foo<Something>属性的Bar类),我需要在反序列化时知道Bar使用的Foo<T>的具体类型(例如,我需要知道在那个特定的情况下, TSomething )。

如何编写这样的解串器? 应该可以做到这一点,因为 Jackson 使用类型化集合和地图来做到这一点。

说明:

似乎有两个部分可以解决问题:

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

2) 在反序列化时发现我们正在反序列化类Bar中的属性foo以成功完成步骤 1)

如何完成 1 和 2 ?

你可以为你的泛型类型实现一个自定义的JsonDeserializer ,它也实现了ContextualDeserializer

例如,假设我们有以下包含泛型值的简单包装器类型:

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

我们现在想要反序列化如下所示的 JSON:

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

进入一个看起来像这样的类的实例:

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

实现ContextualDeserializer允许我们根据字段的泛型类型参数为Person类中的每个字段创建一个特定的反序列化器。 这允许我们将名称反序列化为字符串,将年龄反序列化为整数。

完整的解串器如下所示:

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;
    }
}

最好先在此处查看createContextual ,因为 Jackson 将首先调用它。 我们从BeanProperty读取字段的类型(例如Wrapper<String> ),然后提取第一个泛型类型参数(例如String )。 然后我们创建一个新的反序列化器并将内部类型存储为valueType

一旦在这个新创建的反deserialize上调用 deserialize,我们可以简单地要求 Jackson 将值反序列化为内部类型而不是整个包装器类型,并返回一个包含反序列化值的新Wrapper器。

为了注册这个自定义反序列化器,我们需要创建一个包含它的模块,并注册该模块:

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

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);

如果我们然后尝试反序列化上面的示例 JSON,我们可以看到它按预期工作:

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

Jackson 文档中有更多关于上下文反序列化器如何工作的详细信息。

如果目标本身是泛型类型,则属性将为空,为此您需要从 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;
    }
}

这是您如何访问/解析自定义 Jackson Deserializer 的 {targetClass}。 当然,您需要为此实现 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;
    }
}

对于我的用例,上述解决方案均无效,因此我必须编写自定义模块。 你可以在 GitHub 上找到我的实现。

我想编写一个自动从列表中删除空白字符串的反序列化器。

暂无
暂无

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

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