Repository navigation
JsonSerialize.keyUsing propagates in values of the Map #2050
Copy link
Copy link
Open
Labels
3.xIssues to be only tackled for Jackson 3.x, not 2.xIssues to be only tackled for Jackson 3.x, not 2.x
Description
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:
object_key_simple_value_serializeput mere value-objects in the map serialized with a custom serializer for keys. This test is OK.object_key_complex_value_serializeput aMap<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"
Metadata
Metadata
Assignees
Labels
3.xIssues to be only tackled for Jackson 3.x, not 2.xIssues to be only tackled for Jackson 3.x, not 2.x
Activity
[-]JsonSerialize.keyUsing propagates in values in the Map[/-][+]JsonSerialize.keyUsing propagates in values of the Map[/+]cowtowncoder commentedon May 24, 2018
Yes. The problem is that annotation for property is visible for all values in nested type, so serializer created for
Objectvalue 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.