编写多个自定义杰克逊反序列化程序来处理继承的正确方法

时间:2017-03-04 12:55:39

标签: java json serialization jackson json-deserialization

我想用Jackson反序列化一组非常复杂的JSON文档。为了处理继承,我实现了一些自定义反序列化器。

要选择correkt类,我必须检查下一个节点的属性。因此,我读取树,检查属性并选择正确的类。

之后我通过mapper.readerFor(targetClass).readValue(rootNode)阅读JSON。一切都很好,直到这里。

但是当我使用mapper.readerFor(...)时,下一个被叫序列化程序会获得ObjectReader个实例,而不是ObjectMapper。但我需要一个ObjectMapper实例。

我怎么能做得更好?

这是我的一个反序列化器,它导致了我的问题:

public AbstractParametersObject deserialize(JsonParser p, DeserializationContext ctxt) 
    throws IOException, JsonProcessingException {

    Class<? extends AbstractParametersObject> targetClass = null;
    ObjectMapper mapper = (ObjectMapper) p.getCodec();
    ObjectNode root =mapper.readTree(p);
    boolean isReference = root.has("$ref");
    boolean isParameter = root.has("in");

    if (isReference) targetClass = ParameterAsReference.class;
    else if (isParameter) {
        targetClass = Optional.of(root.get("in")).map(JsonNode::asText).map(value -> {
            Class<? extends AbstractParametersObject> effectiveClass = null;

            switch (value) {
                case "body": effectiveClass = BodyParameterObject.class;
                    break;
                case "query": effectiveClass = QueryParameterObject.class;
                    break;
                case "path": effectiveClass = PathParameterObject.class;
                    break;
                case "formData": effectiveClass = FormDataParameterObject.class;
                    break;
                case "header": effectiveClass = HeaderParameterObject.class;
                    break;
            }

            return effectiveClass;
        }).orElseThrow(() -> new IllegalArgumentException("todo"));
    }

    AbstractParametersObject parametersObject = mapper.readerFor(targetClass)
                                                      .readValue(root);
    return parametersObject;
}

2 个答案:

答案 0 :(得分:1)

这可能比你想象的要容易,虽然我不能在没有看到你的一些JSON例子的情况下确定。 Jackson可以直接处理多态,而无需使用@JsonTypeInfo和@JsonSubTypes注释的自定义序列化程序,只要有一个字段的值指示要创建的子类。例如,假设我有两个不同的文档,它们共享一些共同的字段:

{ "type":"square", "name":"my square", "x":12, "y":34, "size":22 }
{ "type":"rectangle", "name":"my rect", "x":9, "y":11, "width":4, "height":9 }

这可以用以下内容注释:

@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, property="type")
@JsonSubTypes({ 
    @Type(name = "square", value = Square.class),
    @Type(name = "rectangle", value = Rectangle.class)
})
public abstract class Shape {
   public String name;
   public int x;
   public int y;
}
public class Square extends Shape {
   public int size;
}
public class Rectangle extends Shape {
   public int width;
   public int height;
}

答案 1 :(得分:1)

所以解决方案非常简单。而是调用mapper.readerFor(targetClass).readValue(root)将节点树反序列化为一个对象,我必须调用mapper.treeToValue(root, targetClass)

以下是我在问题中发布的方法的工作版本:

public AbstractParametersObject deserialize(JsonParser p, DeserializationContext ctxt) 
    throws IOException {

    Class<? extends AbstractParametersObject> targetClass = null;
    ObjectMapper mapper = (ObjectMapper) p.getCodec();
    ObjectNode root =mapper.readTree(p);
    boolean isReference = root.has("$ref");
    boolean isParameter = root.has("in");

    if (isReference) targetClass = ParameterAsReference.class;
    } else if (isParameter) {
        targetClass = Optional.of(root.get("in")).map(JsonNode::asText).map(value -> {
            Class<? extends AbstractParametersObject> effectiveClass = null;

            switch (value) {
                case "body": effectiveClass = BodyParameterObject.class;
                    break;
                case "query": effectiveClass = QueryParameterObject.class;
                    break;
                case "path": effectiveClass = PathParameterObject.class;
                    break;
                case "formData": effectiveClass = FormDataParameterObject.class;
                    break;
                case "header": effectiveClass = HeaderParameterObject.class;
                    break;
            }

            return effectiveClass;
        }).orElseThrow(() -> new IllegalArgumentException("todo"));
    }

    return mapper.treeToValue(root, targetClass);
}