From d2a7c4a907c299607fb72ede6124981183ae6871 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 26 Jan 2025 22:14:00 +0900 Subject: [PATCH 1/7] Refactor the process of getting BeanDescription From https://github.com/FasterXML/jackson-databind/blob/eeb5f2b764a505c91fda5c55620e60da30676152/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java#L466 , it seems that only acquisition for JavaType is sufficient. --- .../jackson/databind/deser/BasicDeserializerFactory.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java index 50d6d03055..dfa60cf04e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -1268,10 +1268,9 @@ public KeyDeserializer createKeyDeserializer(DeserializationContext ctxt, throws JsonMappingException { final DeserializationConfig config = ctxt.getConfig(); - BeanDescription beanDesc = null; + final BeanDescription beanDesc = config.introspectClassAnnotations(type); KeyDeserializer deser = null; if (_factoryConfig.hasKeyDeserializers()) { - beanDesc = config.introspectClassAnnotations(type); for (KeyDeserializers d : _factoryConfig.keyDeserializers()) { deser = d.findKeyDeserializer(type, config, beanDesc); if (deser != null) { @@ -1283,9 +1282,6 @@ public KeyDeserializer createKeyDeserializer(DeserializationContext ctxt, // the only non-standard thing is this: if (deser == null) { // [databind#2452]: Support `@JsonDeserialize(keyUsing = ...)` - if (beanDesc == null) { - beanDesc = config.introspectClassAnnotations(type.getRawClass()); - } deser = findKeyDeserializerFromAnnotation(ctxt, beanDesc.getClassInfo()); if (deser == null) { if (type.isEnumType()) { From a7538fb80d5a07b7b0053d98b8ce5e06dd70e2ae Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 26 Jan 2025 22:16:50 +0900 Subject: [PATCH 2/7] Fixed to give priority to KeyDeserializer set by annotation Fixes #4444 --- .../deser/BasicDeserializerFactory.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java index dfa60cf04e..d98af48b86 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -1269,8 +1269,11 @@ public KeyDeserializer createKeyDeserializer(DeserializationContext ctxt, { final DeserializationConfig config = ctxt.getConfig(); final BeanDescription beanDesc = config.introspectClassAnnotations(type); - KeyDeserializer deser = null; - if (_factoryConfig.hasKeyDeserializers()) { + + // [databind#2452]: Support `@JsonDeserialize(keyUsing = ...)` + KeyDeserializer deser = findKeyDeserializerFromAnnotation(ctxt, beanDesc.getClassInfo()); + + if (deser == null && _factoryConfig.hasKeyDeserializers()) { for (KeyDeserializers d : _factoryConfig.keyDeserializers()) { deser = d.findKeyDeserializer(type, config, beanDesc); if (deser != null) { @@ -1281,14 +1284,10 @@ public KeyDeserializer createKeyDeserializer(DeserializationContext ctxt, // the only non-standard thing is this: if (deser == null) { - // [databind#2452]: Support `@JsonDeserialize(keyUsing = ...)` - deser = findKeyDeserializerFromAnnotation(ctxt, beanDesc.getClassInfo()); - if (deser == null) { - if (type.isEnumType()) { - deser = _createEnumKeyDeserializer(ctxt, type); - } else { - deser = StdKeyDeserializers.findStringBasedKeyDeserializer(config, type); - } + if (type.isEnumType()) { + deser = _createEnumKeyDeserializer(ctxt, type); + } else { + deser = StdKeyDeserializers.findStringBasedKeyDeserializer(config, type); } } // and then post-processing From 355dcf6b8b737cdd74b2161a971cab0ca950c0a7 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 26 Jan 2025 22:18:59 +0900 Subject: [PATCH 3/7] Add test for #4444 --- .../ser/TestKeyDeserializerOverwritten.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/test/java/com/fasterxml/jackson/databind/ser/TestKeyDeserializerOverwritten.java diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestKeyDeserializerOverwritten.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestKeyDeserializerOverwritten.java new file mode 100644 index 0000000000..14c68fcab6 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestKeyDeserializerOverwritten.java @@ -0,0 +1,63 @@ +package com.fasterxml.jackson.databind.ser; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.KeyDeserializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.module.SimpleModule; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +// [databind#4444] +public class TestKeyDeserializerOverwritten { + @JsonDeserialize(keyUsing = ForClass.class) + static class MyKey { + private final String value; + + MyKey(String value) { + this.value = value; + } + } + + static class ForClass extends KeyDeserializer { + @Override + public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { + return new MyKey(key + "-class"); + } + } + + static class ForMapper extends KeyDeserializer { + @Override + public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { + return new MyKey(key + "-mapper"); + } + } + + TypeReference> typeRef = new TypeReference<>() {}; + + @Test + void withoutForClass() throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + Map result = mapper.readValue("{\"foo\":null}", typeRef); + + assertEquals("foo-class", result.keySet().stream().findFirst().get().value); + } + + // The KeyDeserializer set by the annotation must not be overwritten by the KeyDeserializer set in the mapper. + @Test + void withForClass() throws JsonProcessingException { + SimpleModule sm = new SimpleModule(); + sm.addKeyDeserializer(MyKey.class, new ForMapper()); + + ObjectMapper mapper = new ObjectMapper().registerModule(sm); + Map result = mapper.readValue("{\"foo\":null}", typeRef); + + assertEquals("foo-class", result.keySet().stream().findFirst().get().value); + } +} From 094c63eac73ca93934794fe792983408d9fd43d0 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 26 Jan 2025 23:37:01 +0900 Subject: [PATCH 4/7] Move test wrt https://github.com/FasterXML/jackson-databind/pull/4929 --- .../databind/{ser => deser}/TestKeyDeserializerOverwritten.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/com/fasterxml/jackson/databind/{ser => deser}/TestKeyDeserializerOverwritten.java (97%) diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestKeyDeserializerOverwritten.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestKeyDeserializerOverwritten.java similarity index 97% rename from src/test/java/com/fasterxml/jackson/databind/ser/TestKeyDeserializerOverwritten.java rename to src/test/java/com/fasterxml/jackson/databind/deser/TestKeyDeserializerOverwritten.java index 14c68fcab6..522d20bcb2 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/TestKeyDeserializerOverwritten.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestKeyDeserializerOverwritten.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.databind.ser; +package com.fasterxml.jackson.databind.deser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; From 37ddcf980fcb3d60870df6cf933bcd88ec47b6a0 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 26 Jan 2025 23:42:41 +0900 Subject: [PATCH 5/7] Resolves compile error in Java 8 --- .../jackson/databind/deser/TestKeyDeserializerOverwritten.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestKeyDeserializerOverwritten.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestKeyDeserializerOverwritten.java index 522d20bcb2..f5853c3c98 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestKeyDeserializerOverwritten.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestKeyDeserializerOverwritten.java @@ -39,7 +39,8 @@ public Object deserializeKey(String key, DeserializationContext ctxt) throws IOE } } - TypeReference> typeRef = new TypeReference<>() {}; + // It is not declared as new TypeReference<> because it causes a compile error in Java 8. + TypeReference> typeRef = new TypeReference>() {}; @Test void withoutForClass() throws JsonProcessingException { From 2fe9822429c727038777166f3b9c6d7b078eb7a3 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 26 Jan 2025 23:45:03 +0900 Subject: [PATCH 6/7] Formatting import order wrt https://github.com/FasterXML/jackson-databind/pull/4929#discussion_r1929789570 --- .../deser/TestKeyDeserializerOverwritten.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestKeyDeserializerOverwritten.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestKeyDeserializerOverwritten.java index f5853c3c98..2dda3dee57 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestKeyDeserializerOverwritten.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestKeyDeserializerOverwritten.java @@ -1,5 +1,9 @@ package com.fasterxml.jackson.databind.deser; +import java.io.IOException; +import java.util.Map; + +import org.junit.jupiter.api.Test; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; @@ -7,10 +11,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.module.SimpleModule; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -40,7 +40,8 @@ public Object deserializeKey(String key, DeserializationContext ctxt) throws IOE } // It is not declared as new TypeReference<> because it causes a compile error in Java 8. - TypeReference> typeRef = new TypeReference>() {}; + TypeReference> typeRef = new TypeReference>() { + }; @Test void withoutForClass() throws JsonProcessingException { From e002abfb980776f6a34f9de77486388892eb26bf Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 26 Jan 2025 12:42:52 -0800 Subject: [PATCH 7/7] Rename test class, add release notes --- release-notes/CREDITS-2.x | 4 ++++ release-notes/VERSION-2.x | 3 +++ .../CustomMapKeyDeserializer4444Test.java} | 16 +++++++++------- 3 files changed, 16 insertions(+), 7 deletions(-) rename src/test/java/com/fasterxml/jackson/databind/deser/{TestKeyDeserializerOverwritten.java => jdk/CustomMapKeyDeserializer4444Test.java} (83%) diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 3d9e3c5bdb..5294a98a4a 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -1850,6 +1850,10 @@ wrongwrong (@k163377) * Reported #4878: When serializing a Map via Converter(StdDelegatingSerializer), a NullPointerException is thrown due to missing key serializer (2.18.3) + * Contributed fix for #4444: The `KeyDeserializer` specified in the class with + `@JsonDeserialize(keyUsing = ...)` is overwritten by the `KeyDeserializer` + specified in the `ObjectMapper`. + (2.18.3) Bernd Ahlers (@bernd) * Reported #4742: Deserialization with Builder, External type id, `@JsonCreator` failing diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 27b95d63e6..d0c505c22f 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -6,6 +6,9 @@ Project: jackson-databind 2.18.3 (not yet released) +#4444: The `KeyDeserializer` specified in the class with `@JsonDeserialize(keyUsing = ...)` + is overwritten by the `KeyDeserializer` specified in the `ObjectMapper`. + (fix by @wrongwrong) #4827: Subclassed Throwable deserialization fails since v2.18.0 - no creator index for property 'cause' (reported by @nilswieber) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestKeyDeserializerOverwritten.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/CustomMapKeyDeserializer4444Test.java similarity index 83% rename from src/test/java/com/fasterxml/jackson/databind/deser/TestKeyDeserializerOverwritten.java rename to src/test/java/com/fasterxml/jackson/databind/deser/jdk/CustomMapKeyDeserializer4444Test.java index 2dda3dee57..a12ccb5621 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestKeyDeserializerOverwritten.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/CustomMapKeyDeserializer4444Test.java @@ -1,21 +1,23 @@ -package com.fasterxml.jackson.databind.deser; +package com.fasterxml.jackson.databind.deser.jdk; import java.io.IOException; import java.util.Map; import org.junit.jupiter.api.Test; -import com.fasterxml.jackson.core.JsonProcessingException; + import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.KeyDeserializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; import static org.junit.jupiter.api.Assertions.assertEquals; // [databind#4444] -public class TestKeyDeserializerOverwritten { +public class CustomMapKeyDeserializer4444Test extends DatabindTestUtil +{ @JsonDeserialize(keyUsing = ForClass.class) static class MyKey { private final String value; @@ -44,8 +46,8 @@ public Object deserializeKey(String key, DeserializationContext ctxt) throws IOE }; @Test - void withoutForClass() throws JsonProcessingException { - ObjectMapper mapper = new ObjectMapper(); + void withoutForClass() throws Exception { + ObjectMapper mapper = newJsonMapper(); Map result = mapper.readValue("{\"foo\":null}", typeRef); assertEquals("foo-class", result.keySet().stream().findFirst().get().value); @@ -53,11 +55,11 @@ void withoutForClass() throws JsonProcessingException { // The KeyDeserializer set by the annotation must not be overwritten by the KeyDeserializer set in the mapper. @Test - void withForClass() throws JsonProcessingException { + void withForClass() throws Exception { SimpleModule sm = new SimpleModule(); sm.addKeyDeserializer(MyKey.class, new ForMapper()); - ObjectMapper mapper = new ObjectMapper().registerModule(sm); + ObjectMapper mapper = jsonMapperBuilder().addModule(sm).build(); Map result = mapper.readValue("{\"foo\":null}", typeRef); assertEquals("foo-class", result.keySet().stream().findFirst().get().value);