Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson JDK8 Data Type and Parameter Names modules don't play together

It seems to me Jackson JDK8 Data Type module occasionally ignores Parameter Names module which seems a little bit surprising, given that both demand JDK8 and solve specific-use cases with regards to JDK8.

The issue here is that I could not find a way to make JSON deserialization work without parameter names specified explicitly (which is what Parameter Names module is supposed to be all about). It is also exhibiting this behaviour only when trying to pass in JDK8-specific type (Optional<T>) in container object constructor (i.e. normally, this works and I have tested that). The code is compiled with javac parameter -parameters.

The question is - how to make it work so that I can leverage the Parameter Names module (i.e. don't need to specify annotation+value in the constructor and let it figure out the property name by argument name)?

I may be mistaken and haven't looked at the code under the bonnet so I'd like to hear if there is something I have missed.

Let's consider this simple example.

Version stack (all latest versions as of this writing):

private val jacksonVer = "2.6.1"
private val jacksonCore: ModuleID = "com.fasterxml.jackson.core" % "jackson-core" % jacksonVer withSources() withJavadoc()
private val jacksonDataBind: ModuleID = "com.fasterxml.jackson.core" % "jackson-databind" % jacksonVer withSources() withJavadoc()
private val jacksonAnnotations: ModuleID = "com.fasterxml.jackson.core" % "jackson-annotations" % jacksonVer withSources() withJavadoc()
private val jacksonParamNames: ModuleID = "com.fasterxml.jackson.module" % "jackson-module-parameter-names" % "2.6.2" withSources() withJavadoc()
private val jacksonJdk8DataType: ModuleID = "com.fasterxml.jackson.datatype" % "jackson-datatype-jdk8" % "2.4.3" withSources() withJavadoc()

Container:

private static class SimpleTest {
    @JsonProperty private Optional<String> s1;
    @JsonProperty private Optional<String> s2;
    @JsonProperty private Map<String, String> map;

    private SimpleTest(@JsonProperty("s1") Optional<String> s1, @JsonProperty("s2") Optional<String> s2, @JsonProperty("map") Map<String, String> map) {
        this.s1 = s1;
        this.s2 = s2;
        this.map = map;
    }

    static SimpleTest of(Optional<String> s1, Optional<String> s2, Map<String, String> m) {
        return new SimpleTest(s1, s2, m);
    }
}

Serialization:

@Test
public void testSer() throws JsonProcessingException {
    SimpleTest test = SimpleTest.of(Optional.of("a"), Optional.empty(), Collections.emptyMap());
    System.out.println(JacksonUtil.getMapper().writeValueAsString(test));
}

Deserialization:

@Test
public void testDeser() throws IOException {
    String json = "{\n" +
            "  \"s1\" : \"a\",\n" +
            "  \"map\" : { }\n" +
            "}";
    JacksonUtil.getMapper().readValue(json, SimpleTest.class);
}

Running testSer() with such a container yields:

{
  "s1" : "a",
  "s2" : null,
  "map" : { }
}

Running testDeser() with input such as this

{
  "s1" : "a",
  "map" : { }
}

also works, and yields expected results (s1 has value, s2 is Optional.empty and map is empty) but only if the container constructor is defined as above. I could not get it work in following combinations:
1)

private SimpleTest(Optional<String> s1, Optional<String> s2, Map<String, String> map) {...}

2)

private SimpleTest(@JsonProperty Optional<String> s1, @JsonProperty Optional<String> s2, @JsonProperty Map<String, String> map) {...}

By rights, both should work but they don't - both approaches yield the following stacktrace:

com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class com._3esi.load.bootstrap.ScratchPad$SimpleTest]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
 at [Source: {
  "s1" : "a",
  "map" : { }
}; line: 2, column: 3]
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1106)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:294)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:131)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3731)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2724)

What am I missing here?

like image 952
quantum Avatar asked Mar 15 '23 17:03

quantum


1 Answers

I think that this is due to one remaining issue with Jackson 2.6, regarding detection of multi-argument constructors: although parameter names are detected, constructor itself is not retained as a candidate without use of @JsonCreator annotation to mark it. This is something that is hoped to be resolved for 2.7 (and was originally supposed to be fixed for 2.6), but for time being it is necessary.

If you add @JsonCreator to constructor and remove @JsonProperty annotations, things should work as expected.

like image 73
StaxMan Avatar answered Apr 26 '23 16:04

StaxMan