Search code examples
javajsonserializationjacksonjson-deserialization

Correct way for writing multiple custom Jackson deserializers to handle inheritance


I want to deserialize a quite complicated set of JSON documents with Jackson. To handle inheritance I implemented some custom deserializers.

To choose the correkt class I have to check the properties of the next node. Therefore I read the tree, check the properties and choose the correct class.

After that I read the JSON via mapper.readerFor(targetClass).readValue(rootNode). Everything is fine until here.

But as I use mapper.readerFor(...) the next called serializer gets an ObjectReader instance instead of an ObjectMapper. But I need an ObjectMapper instance.

How could I do it better?

Here is one of my deserializers, which cause my problem:

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

Solution

  • So the solution was quite simple. Instead calling mapper.readerFor(targetClass).readValue(root) to deserialize the node tree into an object I had to call mapper.treeToValue(root, targetClass).

    Here is the working version of the method I posted in my question:

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