Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use @JacksonInject with @JsonCreator on a top level map

With Jackson json library, it is possible to deserialize object through the use of the @JsonCreator, and be given the "top level" map representing the input json, as follows:

class MyClass {
    final int field;

    @JsonCreator
    public MyClass(Map<String, Object> map) {
        this.field = (int) map.get("theInt");
    }
}

or even on a static factory method:

class MyClass {
    final int field;

    public MyClass(int theInt) {
        this.field = theInt;
    }

    @JsonCreator
    static MyClass create(Map<String, Object> map) {
        return new MyClass((int) map.get("theInt"));
    }
}

The previous examples can process the following kind of json input:

{
    "key1":"value1",
    "key2":"value2",
    "key3":"value3"
}

This is particularly useful in my case because I would like to deserialize a json which structure I don't know. Being given access to what I call the "top level map" makes things simple.

I would like to deserialize my objects this way as it also allows to create immutable objects, instead of using @JsonAnySetter which doesn't permit this, and @JsonProperty which I can't use as I don't know the properties name in advance as I mentioned earlier.

Then to go further, I would like to inject some configuration in my factory method, and Jackson allows this through the use of @JacksonInject and a call to withInjectableValues(InjectableValues) on the ObjectMapper.

This is eventually the kind of code I would like to use:

class MyClass {
    final MyField[] myFields;

    public MyClass(MyField... myFields) {
        this.myFields = myFields;
    }

    @JsonCreator
    static MyClass create(@JacksonInject("conf") Conf conf, Map<String, Object> map) {
        MyFields[] myFields;
        // initialize myFields (or any other object) based on the content of map
        // and also make use of the inject conf
        return new MyClass(myFields);
    }
}

Unfortunately, Jackson throws the following kind of exceptions:

  • when trying the trick on the constructor

JsonMappingException: Argument #1 of constructor [constructor for MyClass, annotations: {JsonCreator=@JsonCreator()}] has no property name annotation; must have name when multiple-paramater constructor annotated as Creator

  • when trying the trick on the factory method

JsonMappingException: Argument #1 of factory method [method create, annotations: {JsonCreator=@JsonCreator()}] has no property name annotation; must have when multiple-paramater static method annotated as Creator

Does anyone know how I could solve the problem?

To sum up the requirements, I need:

  • access to the top level map (don't know the json property names in advance)
  • to create an immutable object (so can't use @JsonAnySetter)
  • to inject some conf to the @JsonCreator decorated constructor or factory method

I cannot change the json input format, which looks like this:

{
    "key1":"value1",
    "key2":"value2",
    "key3":"value3"
}

[EDIT]

This is a known issue: http://jira.codehaus.org/browse/JACKSON-711 (not fixed yet)

like image 482
killy971 Avatar asked Dec 01 '11 09:12

killy971


1 Answers

Right, you would like to both use "delegating" creator (single argument, into which JSON input is first bound) -- different from "property-based" creator where set of named parameters are passed -- and injectable value(s). This should ideally work, but I think it might not work currently. I think there is a Jira entered for this, so you can check it (http://jira.codehaus.org/browse/JACKSON) out.

Just to make sure: are you using version 1.9.2? There have been some fixes in this are since 1.9.0; which at least would give better error message.

like image 92
StaxMan Avatar answered Nov 04 '22 01:11

StaxMan