2

My application has two modules: an API A and a visualization component V.

Some API model classes are annotated via org.codehaus.jackson.annotation (Jackson 1.9.13). In one logic flow, objects based on these models are JSON-serialized and delivered to V.

V currently uses com.fasterxml.jackson.core.ObjectMapper (Jackson 2.9.1) for deserializing the received objects. (V does have A as a dependency, and hence Jackson 1.9.13 is also present in the classpath transitively.)

Since Jackson 2.9.1 is trying to deserialize the data into Jackson 1.9.13 annotated classes, some features like custom enum name mappings are not working. For example:

enum Size {

    XL("Extra Large"),
    L("Large"),
    M("Medium"),
    S("Small"),
    XS("Extra Small");

    private String name;

    Size(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @org.codehaus.jackson.annotate.JsonCreator
    public static Size forValue(String value) {
        for (Size size : Size.values()) {
            if (size.getName().equals(value)) {
                return size;
            }
        }
        return null;
    }
}

class Model {
    String name;
    Size size;

    public String getName() {
        return name;
    }

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

    public String getSize() {
        return size.getName();
    }

    public void setSize(Size size) {
        this.size = size;
    }
}

V fails to deserialize objects from the above Model class, with this error:

com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `Size` from String "Medium": value not one of declared Enum instance names: [S, XL, M, XS, L]
 at [Source: (String)"{"name":"foo","size":"Medium"}"; line: 1, column: 22] (through reference chain: Model["size"])

It all makes sense (since 1.9.13 to 2.9.1 is a backward-incompatible change), but what are my options? Will I have to downgrade V's deserialization logic to Jackson 1.9.13? Or, is there any "adaptor" or configuration that I could just "plug in" in order to get the 1.9.13 annotations working under 2.9.1?

(I'd rather not upgrade A to 2.9.1 at this point, because A's overall package size is a major concern; switching to com.fasterxml.jackson.core:jackson-databind will grow A's overall Jackson dependency size from 980 KB to 1.51 MB - unacceptable because the complete core bundle (that builds upon A; does not include V) is currently well under 8 MB in size.)

Janaka Bandara
  • 1,024
  • 1
  • 12
  • 27
  • I guess you could simply recreate the class in V and annotate it with Jackson 2 annotations. I've never tested doing that, but you could also annotate the class with Jackson1 and Jackson2 annotations, even if you're not bundling Jackson2 in A. (i.e. you compile with Jackson2 in the classpath, but you run without it). – JB Nizet Apr 14 '19 at 10:00
  • Thanks @JBNizet! Option 2 sounds interesting, will give it a try... although I too have the suspicion that it might fail, because the annotation class would need to be loaded during the class-load of `Model` – Janaka Bandara Apr 14 '19 at 11:48
  • No, annotations not present in the classpath don't prevent the class to be loaded. – JB Nizet Apr 14 '19 at 11:56

1 Answers1

0

You can customise annotation handling by implementing com.fasterxml.jackson.databind.AnnotationIntrospector class. For example, if we want to allow to use org.codehaus.jackson.annotate.JsonCreator annotation in our model we need to implement handler for it. In other case it will be ignored. Simple implementation could look like below:

class CodehausAnnotationIntrospector extends JacksonAnnotationIntrospector {

    @Override
    public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?> config, Annotated a) {
        JsonCreator.Mode mode = super.findCreatorAnnotation(config, a);
        if (mode != null) {
            return mode;
        }

        org.codehaus.jackson.annotate.JsonCreator ann = _findAnnotation(a, org.codehaus.jackson.annotate.JsonCreator.class);
        if (ann != null) {
            return JsonCreator.Mode.DEFAULT;
        }

        return null;
    }
}

We just simply enable DEFAULT mode in case org.codehaus.jackson.annotate.JsonCreator annotation is used. Below code shows how to register new introspector:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setAnnotationIntrospector(new CodehausAnnotationIntrospector());

        Model model = new Model();
        model.setName("Model1");
        model.setSize(Size.XL);

        String json = mapper.writeValueAsString(model);
        System.out.println(json);
        System.out.println(mapper.readValue(json, Model.class));
    }
}

Above code prints:

{"name":"Model1","size":"Extra Large"}
Model{name='Model1', size=XL}
Michał Ziober
  • 37,175
  • 18
  • 99
  • 146