Jackson JDK8数据类型和参数名称模块不能一起玩

时间:2015-09-22 18:31:31

标签: json jackson java-8 jackson-modules

在我看来,Jackson JDK8数据类型模块偶尔会忽略参数名称模块,这看起来有点令人惊讶,因为它们都需要JDK8并解决与JDK8相关的特定用例。

这里的问题是我找不到一种方法来使JSON反序列化工作没有显式指定参数名称(这应该是参数名称模块的全部内容)。它只在尝试在容器对象构造函数中传递JDK8特定类型(Optional<T>)时才表现出这种行为(即通常,这有效并且我已经测试过)。代码使用javac参数-parameters编译。

问题是 - 如何让它工作,以便我可以利用参数名称模块(即不需要在构造函数中指定注释+值,让它通过参数名称找出属性名称)?

我可能会误会,并且没有看到发动机罩下的代码,所以我想听听是否有我错过的东西。

让我们考虑一下这个简单的例子。

版本堆栈(撰写本文时的所有最新版本):

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()

容器:

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

序列化:

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

反序列化:

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

使用这样的容器运行testSer()会产生:

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

使用此

等输入运行testDeser()
{
  "s1" : "a",
  "map" : { }
}

也有效,并且会产生预期结果(s1具有值,s2Optional.emptymap为空),但前提是容器构造函数的定义如上所述。我可以让它在以下组合中起作用:
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) {...}

根据权利,两者都应该有效,但他们不会 - 两种方法都会产生以下堆栈跟踪:

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)

我在这里缺少什么?

2 个答案:

答案 0 :(得分:4)

我认为这是由于Jackson 2.6存在的一个问题,关于多参数构造函数的检测:虽然检测到参数名称,但构造函数本身并未保留为候选而不使用@JsonCreator注释来标记它。 这是希望在2.7中得到解决的问题(原本应该在2.6中得到解决),但是暂时还是必要的。

如果您将@JsonCreator添加到构造函数并删除@JsonProperty注释,那么事情应该按预期工作。

答案 1 :(得分:3)

我对Github的答案的CP:

我测试了以下代码,测试通过了:

public class OptionalTest {

    @Test
    public void shouldDeserialize() throws IOException {

        // given
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new Jdk8Module());
        objectMapper.registerModule(new ParameterNamesModule());

        // when
        String json = "{\"s1\":\"a\",\"map\":{}}";
        SimpleTest simpleTest = objectMapper.readValue(json, SimpleTest.class);

        then(simpleTest).isEqualToComparingFieldByField(new SimpleTest(Optional.of("a"), Optional.empty(), new HashMap<>()));
    }

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

        private SimpleTest(Optional<String> s1, Optional<String> s2, 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);
        }
    }
}

请注意,这是针对jackson-parameter-name-modules中的最新状态进行测试的,所有依赖项都设置为2.6.2。