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

新手上路,请多包涵

想象一下以下场景:

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

class Bar {
    Foo<Something> foo;
}

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

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

说明:

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

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

2)在反序列化时发现我们正在反序列化属性 foo 内部类 Bar 为了成功完成步骤1)

如何完成 1 和 2 ?

原文由 Krešimir Nesek 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.7k
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 在这个新创建的反序列化器上被调用,我们可以简单地要求 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 文档 中有一些关于上下文反序列化器如何工作的更多细节。

原文由 andersschuller 发布,翻译遵循 CC BY-SA 3.0 许可协议

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

原文由 Ahmed Mkaouar 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题