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/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 diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/CustomMapKeyDeserializer4444Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/CustomMapKeyDeserializer4444Test.java new file mode 100644 index 0000000000..a12ccb5621 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/CustomMapKeyDeserializer4444Test.java @@ -0,0 +1,67 @@ +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.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 CustomMapKeyDeserializer4444Test extends DatabindTestUtil +{ + @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"); + } + } + + // It is not declared as new TypeReference<> because it causes a compile error in Java 8. + TypeReference> typeRef = new TypeReference>() { + }; + + @Test + void withoutForClass() throws Exception { + ObjectMapper mapper = newJsonMapper(); + 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 Exception { + SimpleModule sm = new SimpleModule(); + sm.addKeyDeserializer(MyKey.class, new ForMapper()); + + ObjectMapper mapper = jsonMapperBuilder().addModule(sm).build(); + Map result = mapper.readValue("{\"foo\":null}", typeRef); + + assertEquals("foo-class", result.keySet().stream().findFirst().get().value); + } +}