Skip to content

JsonSerialize.keyUsing propagates in values of the Map #2050

Open

Description

@antechrestos

Hi,

I spotted a strange behaviour using JsonSerialize.keyUsing. I spotted it using the last version 2.9.5.

Here is the test class:

@DisplayName("Demo of jackson JsonSerialize annotation with keyUsing attribute")
public class JacksonKeyUsing {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private static class ObjectKeySerializer extends JsonSerializer<ObjectKey> {

        public void serialize(ObjectKey objectKey,
                              JsonGenerator generator,
                              SerializerProvider provider) throws IOException {
                generator.writeFieldName(objectKey.getId()+": "+objectKey.getName());
        }
    }

    @Data
    private static class ObjectKey {

        private final Integer id;

        private final String name;

        @Builder
        ObjectKey(@NonNull Integer id,
                  @NonNull String name){
            this.id = id;
            this.name = name;
        }
    }

    @Data
    @Builder
    private static class ObjectKeyValues {

        @JsonProperty("attributes")
        @JsonSerialize(keyUsing = ObjectKeySerializer.class)
        @Singular
        private Map<ObjectKey, Object> attributes;


    }

    @Test
    @DisplayName("Serialize a complex key<->simple value Map")
    public void object_key_simple_value_serialize() throws JsonProcessingException {
        // GIVEN
        final ObjectKeyValues data = ObjectKeyValues.builder()
                .attribute(ObjectKey.builder()
                        .id(1)
                        .name("First")
                        .build(),
                        1)
                .attribute(ObjectKey.builder()
                                .id(2)
                                .name("Second")
                                .build(),
                        "2")
                .build();

        // WHEN
        String result = OBJECT_MAPPER.writeValueAsString(data);


        // THEN
        assertThat(result).isEqualTo("{\"attributes\":{\"1: First\":1,\"2: Second\":\"2\"}}");
    }

    @Test
    @DisplayName("Serialize a complex key<->complex value Map")
    public void object_key_complex_value_serialize() throws JsonProcessingException {
        // GIVEN
        final ObjectKeyValues data = ObjectKeyValues.builder()
                .attribute(ObjectKey.builder()
                                .id(1)
                                .name("First")
                                .build(),
                        singletonMap("key", "value"))
                .build();

        // WHEN
        String result = OBJECT_MAPPER.writeValueAsString(data);


        // THEN
        assertThat(result).isEqualTo("{\"attributes\":{\"1: First\":{\"key\":\"value\"}}}");
    }

}

In this class, two tests:

  1. object_key_simple_value_serialize put mere value-objects in the map serialized with a custom serializer for keys. This test is OK.
  2. object_key_complex_value_serialize put a Map<String, String> as a value. This test fails with the following error:
com.fasterxml.jackson.databind.JsonMappingException: java.lang.String cannot be cast to JacksonKeyUsing$ObjectKey (through reference chain: JacksonKeyUsing$ObjectKeyValues["attributes"]->java.util.Collections$SingletonMap["JacksonKeyUsing.ObjectKey(id=1, name=First)"]->java.util.Collections$SingletonMap["key"])

	at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:391)
	at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:351)
	at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:316)
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeFields(MapSerializer.java:721)
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:639)
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:33)
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeFields(MapSerializer.java:718)
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:639)
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:33)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
	at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3893)
	at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3207)
	at JacksonKeyUsing.object_key_complex_value_serialize(JacksonKeyUsing.java:100)
	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:389)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:167)
	at org.junit.jupiter.engine.execution.ThrowableCollector.execute(ThrowableCollector.java:40)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:163)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:110)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.lambda$execute$3(HierarchicalTestExecutor.java:83)
	at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:77)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.lambda$null$2(HierarchicalTestExecutor.java:92)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
	at java.util.Iterator.forEachRemaining(Iterator.java:116)
	at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.lambda$execute$3(HierarchicalTestExecutor.java:92)
	at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:77)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.lambda$null$2(HierarchicalTestExecutor.java:92)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
	at java.util.Iterator.forEachRemaining(Iterator.java:116)
	at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.lambda$execute$3(HierarchicalTestExecutor.java:92)
	at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:77)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:51)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:43)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:74)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to JacksonKeyUsing$ObjectKey
	at JacksonKeyUsing$ObjectKeySerializer.serialize(JacksonKeyUsing.java:26)
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeFields(MapSerializer.java:707)
	... 64 more

My guess is that the JsonSerialize.keyUsing sets a context in the Map serialization and that this context propagates to values too. Hence, if a Map is put as a value of the first Map, it attempts to use the serializer specified to serialize the keys inside the "value Map"

Activity

  1. changed the title [-]JsonSerialize.keyUsing propagates in values in the Map[/-] [+]JsonSerialize.keyUsing propagates in values of the Map[/+] on May 24, 2018
  2. cowtowncoder commented on May 24, 2018

    @cowtowncoder
    Member

    Yes. The problem is that annotation for property is visible for all values in nested type, so serializer created for Object value type will nonetheless see the same annotation, relating to it (as it has no way of knowing it is not immediate descendant).

    Ideally this should not happen, of course.

  3. added
    3.xIssues to be only tackled for Jackson 3.x, not 2.x
    and removed on Sep 12, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.xIssues to be only tackled for Jackson 3.x, not 2.x

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions