FasterXML / jackson-datatypes-collections

Jackson project that contains various collection-oriented datatype libraries: Eclipse Collections, Guava, HPPC, PCollections
Apache License 2.0
78 stars 53 forks source link

GuavaMultimapDeserializer throws JsonMappingException: Expecting START_OBJECT, found FIELD_NAME #96

Closed ymwangzq closed 2 years ago

ymwangzq commented 2 years ago

This is my code:

    static class Pojo1 {
        @JsonProperty
        private final ArrayListMultimap<Long, Integer> multimap;

        @JsonCreator
        public Pojo1(ArrayListMultimap<Long, Integer> multimap) {
            this.multimap = multimap;
        }
    }

    @Test
    void testJson1() throws Throwable {
        ObjectMapper mapper = new ObjectMapper().registerModule(new GuavaModule());

        ArrayListMultimap<Long, Integer> multimap = ArrayListMultimap.create();
        multimap.put(1L, 1);
        multimap.put(1L, 2);

        Pojo1 pojo1 = new Pojo1(multimap);

        String json = mapper.writeValueAsString(pojo1);
        System.out.println(json);
        Pojo1 pojo11 = mapper.readValue(json, Pojo1.class);
        System.out.println(pojo11);
    }

This test throws an exception:

com.fasterxml.jackson.databind.JsonMappingException: Expecting START_OBJECT, found FIELD_NAME
 at [Source: (String)"{"multimap":{"1":[1,2]}}"; line: 1, column: 14]

    at com.fasterxml.jackson.datatype.guava.deser.multimap.GuavaMultimapDeserializer.expect(GuavaMultimapDeserializer.java:270)
    at com.fasterxml.jackson.datatype.guava.deser.multimap.GuavaMultimapDeserializer.deserializeContents(GuavaMultimapDeserializer.java:160)
    at com.fasterxml.jackson.datatype.guava.deser.multimap.GuavaMultimapDeserializer.deserialize(GuavaMultimapDeserializer.java:152)
    at com.fasterxml.jackson.datatype.guava.deser.multimap.GuavaMultimapDeserializer.deserialize(GuavaMultimapDeserializer.java:26)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1398)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:351)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:184)
    at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4674)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3629)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3597)
    at com.xiaohongshu.common.utils.jackson.ObjectMapperUtilsTest.testJson1(ObjectMapperUtilsTest.java:97)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:532)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:167)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:114)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:108)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at java.util.ArrayList.forEach(ArrayList.java:1259)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at java.util.ArrayList.forEach(ArrayList.java:1259)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:220)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:188)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:202)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:181)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
    at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

This is my dependencies:

        <dependency>
            <groupId>com.fasterxml.jackson</groupId>
            <artifactId>jackson-bom</artifactId>
            <version>2.13.3</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-guava</artifactId>
        </dependency>
cowtowncoder commented 2 years ago

Sounds like a bug, thank you for reporting this.

cowtowncoder commented 2 years ago

Ah. This is not a bug in deserializer but POJO definition.

The issue is constructor annotation @JsonCreator: when constructor only takes 1 parameter, there are 2 interpretations of how binding should work; and in this case, without more information, code guesses incorrectly that value should be so-called "delegating" (so the parameter value should be the "whole" incoming value and not one of the properties from incoming JSON Object).

Long story short, you need to do it like so:

    static class Pojo1 {
        @JsonProperty
        private final ArrayListMultimap<Long, Integer> multimap;

        @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
        public Pojo1(@JsonProperty("multimap") ArrayListMultimap<Long, Integer> multimap) {
            this.multimap = multimap;
        }

(note: if you are using jackson-module-parameter-names, use of @JsonProperty for creator parameter is optional)

and with that code works as expected.

Exception that you get is not very descriptive so it is bit difficult to figure out unless you happen to know about the complexity of this particular case. I'll see if I can figure out an improvement to error handling here.