Jackson Deserializer delegate to next applicable deserializer

Jaagup Kümmel :

I have an external service which I use to query some data. The data will be in one of two formats (first of which is kind of "legacy", but needs to be supported):

{
    "foo": "John Smith"
}

or

{
    "foo": {
        "name": "John Smith",
        "bar": "baz"
    }
}

which I want to map to the following POJO:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Outer {

    private Foo foo;

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Foo {

        String name;
        String bar;

    }

}

Data in the second format (foo is an object) should be deserialized just like any other POJO, but given data in the first format (foo is string), to turn it into an instance of Foo, I want to call new Foo(<foo>, null). To do this, I have created a custom deserializer (@JsonComponent means that this deserializer will be registered with a kinda-global ObjectMapper by spring via Jackson Module interface):

@JsonComponent
public class FooDeserializer extends JsonDeserializer<Outer.Foo> {

    @Override
    public Outer.Foo deserialize(JsonParser parser, DeserializationContext context)
            throws IOException {
        JsonNode node = parser.getCodec().readTree(parser);
        if (node.isTextual()) {
            return new Foo(node.asText(), null);
        }
        return <delegate to next applicable deserializer>;
    }

}

I'm having trouble figuring out how to do the "delegate to next applicable deserializer" part, as every solution I've tried (for example parser.getCodec().treeToValue(node, Outer.Foo.class)) ends up using the same custom deserializer again, causing infinite recursion. Is this even possible?

samabcde :

Credit to schummar answer :How do I call the default deserializer from a custom deserializer in Jackson. Following the above answer,
1. @JsonComponent annotation should be removed from the custom serializer as we need to construct the custom serializer using the default serializer, and this is not supported by @JsonComponent.
2. Register a SimpleModule to the ObjectMapper with a BeanDeserializerModifier and modify the serializer with our custom serializer constructed with the default serializer.
3. In the serialize method of the custom serializer, handle the special case, and delegate the serialization to the default serializer for normal case.

The following code demonstrates how to implement above points.

Main class

import java.io.IOException;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.module.SimpleModule;

public class DelegateDeserializer {
    public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {

        ObjectMapper mapper = new ObjectMapper();

        SimpleModule simpleModule = new SimpleModule();

        simpleModule.setDeserializerModifier(new BeanDeserializerModifier() {
            @Override
            public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc,
                    JsonDeserializer<?> deserializer) {
                if (Outer.Foo.class.isAssignableFrom(beanDesc.getBeanClass())) {
                    return new FooDeserializer(deserializer, beanDesc.getBeanClass());
                }
                return deserializer;
            }
        });

        mapper.registerModule(simpleModule);

        Outer outer1 = mapper.readValue(getType1Json(), Outer.class);
        Outer outer2 = mapper.readValue(getType2Json(), Outer.class);
        System.out.println("deserialize json with object structure:");
        System.out.println(outer1.getFoo().getName());
        System.out.println(outer1.getFoo().getBar());
        System.out.println("deserialize json with string field only:");
        System.out.println(outer2.getFoo().getName());
        System.out.println(outer2.getFoo().getBar());
    }

    private static String getType1Json() {

        return "  {                                                                        "
                + "  \"foo\": {                                                            "
                + "     \"name\": \"John Smith\",                                          "
                + "    \"bar\": \"baz\"                                                    "
                + "   }                                                                    "
                + "}                                                                       ";

    }

    private static String getType2Json() {

        return "  {                                                                        "
                + "  \"foo\": \"John Smith\"                                               "
                + "}                                                                       ";

    }
}

FooDeserializer class

import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import jackson.Outer.Foo;

public class FooDeserializer extends StdDeserializer<Outer.Foo> implements ResolvableDeserializer {

    private static final long serialVersionUID = 1L;
    private final JsonDeserializer<?> defaultDeserializer;

    public FooDeserializer(JsonDeserializer<?> defaultDeserializer, Class<?> clazz) {
        super(clazz);
        this.defaultDeserializer = defaultDeserializer;
    }

    @Override
    public Outer.Foo deserialize(JsonParser parser, DeserializationContext context) throws IOException {
        if (parser.getCurrentToken() == JsonToken.VALUE_STRING) {
            JsonNode node = parser.getCodec().readTree(parser);
            if (node.isTextual()) {
                return new Foo(node.asText(), null);
            }
        }

        return (Foo) defaultDeserializer.deserialize(parser, context);
    }

    @Override
    public void resolve(DeserializationContext ctxt) throws JsonMappingException {
        ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
    }

}

Outer class

 public class Outer {
    private Foo foo;

    public Foo getFoo() {
        return foo;
    }

    public void setFoo(Foo foo) {
        this.foo = foo;
    }

    public static class Foo {
        private String bar;
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getBar() {
            return bar;
        }

        public void setBar(String bar) {
            this.bar = bar;
        }

        public Foo() {
        }

        public Foo(String name, String bar) {
            this.name = name;
            this.bar = bar;
        }
    }

}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=82346&siteId=1