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

2022-09-01 18:09:47

想象一下以下场景:

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

class Bar {
    Foo<Something> foo;
}

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

如何编写这样的反序列化程序?这应该是可能的,因为杰克逊用类型化的集合和地图来做到这一点。

澄清:

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

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

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

如何完成 1 和 2?


答案 1

您可以为泛型类型实现自定义 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;
}

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

完整的反序列化程序如下所示:

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

最好先看看这里,因为杰克逊会先叫这里。我们从中读取字段的类型(例如),然后提取第一个泛型类型参数(例如)。然后,我们创建一个新的反序列化程序,并将内部类型存储为 .createContextualBeanPropertyWrapper<String>StringvalueType

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

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

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


答案 2

如果目标本身是泛型类型,则属性将为 null,为此,您需要从反序列化Context中获取值Ttype:

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