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

Kre*_*sek 15 java generics json jackson deserialization

想象一下以下场景:

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

class Bar {
    Foo<Something> foo;
}
Run Code Online (Sandbox Code Playgroud)

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

如何编写这样的解串器?应该可以这样做,因为杰克逊用类型集合和地图来做.

澄清:

似乎有2个部分来解决问题:

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

2)在反序列化时找出我们正在对foo类内的属性进行反序列化Bar以便成功完成步骤1)

如何完成1和2?

and*_*ler 37

您可以JsonDeserializer为也实现的泛型类型实现自定义ContextualDeserializer.

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

public static class Wrapper<T> {
    public T value;
}
Run Code Online (Sandbox Code Playgroud)

我们现在想要反序列化看起来像这样的JSON:

{
    "name": "Alice",
    "age": 37
}
Run Code Online (Sandbox Code Playgroud)

进入如下所示的类的实例:

public static class Person {
    public Wrapper<String> name;
    public Wrapper<Integer> age;
}
Run Code Online (Sandbox Code Playgroud)

实现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;
    }
}
Run Code Online (Sandbox Code Playgroud)

最好先看一下createContextual,因为这将首先由杰克逊召集.我们从BeanProperty(例如Wrapper<String>)中读取字段的类型,然后提取第一个泛型类型参数(例如String).然后我们创建一个新的反序列化器并将内部类型存储为valueType.

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

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

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

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
Run Code Online (Sandbox Code Playgroud)

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

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

关于上下文反序列化器如何在Jackson文档中工作的更多细节.

  • 感谢您对这篇文章。对实现我想要的东西非常有帮助。我做了一些与根值一起使用的更改,以防来自 Google 的其他人出现。https://gist.github.com/darylteo/a7be65b539c0d8d3ca0de94d96763f33 (2认同)
  • 谢谢,这确实有帮助上下文类型本身可以是通用的,在这种情况下属性将为空,您需要使用 ctxt.getContextualType 创建 JsonDeserializer (2认同)

Ahm*_*uar 6

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

  • 绝对非常有帮助。我实际上是在 Kotlin 中使用它的,有一些警告。例如,根据您读取值的方式,泛型类型信息可以不完整地传递:`mapper.readValue&lt;Generic&lt;T&gt;&gt;` 保留内部类型 T,而 Java 方法 `mapper.readValue(json, Generic::class .java)` 没有。不知何故明显,但引起了一些 NPE。这个练习教会了我很多关于 JVM 中的泛型的知识。 (2认同)