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

时间:2016-03-22 16:14:32

标签: java json generics jackson deserialization

想象一下以下场景:

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

class Bar {
    Foo<Something> foo;
}

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

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

澄清:

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

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

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

如何完成1和2?

3 个答案:

答案 0 :(得分:32)

您可以为通用类型实现自定义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,因为这将首先由杰克逊召集。我们从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 documentation中工作的更多详细信息。

答案 1 :(得分:2)

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

答案 2 :(得分:2)

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