From be92a6ebaf8ad52d0b6262cbfdb3257a392570e1 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 26 Jan 2025 11:58:51 -0800 Subject: [PATCH 01/23] Javadoc improvement wrt #4928 --- .../jackson/databind/SerializerProvider.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java index d89e565a22..7eed38b6d3 100644 --- a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java +++ b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java @@ -560,6 +560,9 @@ public abstract WritableObjectId findObjectId(Object forPojo, * Note that serializers produced should NOT handle polymorphic serialization * aspects; separate {@link TypeSerializer} is to be constructed by caller * if and as necessary. + *

+ * Note: this call will also contextualize serializer (call + * {@code serializer.createContextual()}) before returning it. * * @throws JsonMappingException if there are fatal problems with * accessing suitable serializer; including that of not @@ -602,7 +605,8 @@ public JsonSerializer findValueSerializer(Class valueType, BeanProper * This is necessary for accurate handling of external type information, * to handle polymorphic types. *

- * Note: this call will also contextualize serializer before returning it. + * Note: this call will also contextualize serializer (call + * {@code serializer.createContextual()}) before returning it. * * @param property When creating secondary serializers, property for which * serializer is needed: annotations of the property (or bean that contains it) @@ -634,9 +638,10 @@ public JsonSerializer findValueSerializer(JavaType valueType, BeanProper } /** - * Method variant used when we do NOT want contextualization to happen; it will need - * to be handled at a later point, but caller wants to be able to do that - * as needed; sometimes to avoid infinite loops + * Serializer lookup variant used when we do NOT want contextualization to happen; + * while contextualization MUST be handled at some point (many serializers will not be + * in usable state before contextualization), but caller wants to be able to do that + * later; sometimes to avoid infinite loops. * * @since 2.5 */ @@ -663,9 +668,10 @@ public JsonSerializer findValueSerializer(Class valueType) throws Jso } /** - * Method variant used when we do NOT want contextualization to happen; it will need - * to be handled at a later point, but caller wants to be able to do that - * as needed; sometimes to avoid infinite loops + * Serializer lookup variant used when we do NOT want contextualization to happen; + * while contextualization MUST be handled at some point (many serializers will not be + * in usable state before contextualization), but caller wants to be able to do that + * later; sometimes to avoid infinite loops. * * @since 2.5 */ @@ -697,8 +703,10 @@ public JsonSerializer findValueSerializer(JavaType valueType) * certain that this is the primary property value serializer. * * @param valueType Type of values to serialize - * @param property Property that is being handled; will never be null, and its - * type has to match valueType parameter. + * @param property Property that directly refers to value being serialized (optional, + * may be {@code null} for root level serializers). + * Should not be null if property is known. If not null, + * its type must match {@code valueType} argument. * * @since 2.3 */ From 0262bbaca1f747ffe9d809402fd6439df47a3eed Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 26 Jan 2025 12:20:02 -0800 Subject: [PATCH 02/23] Merge tiny clean up from #4929 before PR itself (#4930) --- .../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 25601f88c4..002a265b61 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -1244,10 +1244,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) { @@ -1259,9 +1258,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 603fd9bb3540e775ec5c150a8d11c76331c7d131 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 26 Jan 2025 12:20:02 -0800 Subject: [PATCH 03/23] Merge tiny clean up from #4929 before PR itself (#4930) --- .../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 b1a61bb64314a84837ce4bde6de87a270bdd6b9b Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Mon, 27 Jan 2025 06:01:48 +0900 Subject: [PATCH 04/23] Fixed so that KeyDeserializer set by annotation is not overwritten by KeyDeserializer set in the mapper (#4929) --- release-notes/CREDITS-2.x | 4 ++ release-notes/VERSION-2.x | 3 + .../deser/BasicDeserializerFactory.java | 19 +++--- .../jdk/CustomMapKeyDeserializer4444Test.java | 67 +++++++++++++++++++ 4 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/databind/deser/jdk/CustomMapKeyDeserializer4444Test.java 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); + } +} From f0c97b7ba289c369aa16ebaaf5f79de29baa4968 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 26 Jan 2025 17:19:35 -0800 Subject: [PATCH 05/23] Add test showing fix of #4917 (via jackson-core) (#4931) --- release-notes/VERSION-2.x | 2 + .../deser/jdk/JDKNumberDeserTest.java | 59 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index aa4fdd2e8c..11bb89a8b0 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -8,6 +8,8 @@ Project: jackson-databind #4787: Wrong `String.format()` in `StdDelegatingDeserializer` hides actual error (reported by @Horus1337) +#4917: `BigDecimal` deserialization issue when using `@JsonCreator` + (reported by @dbachdev) 2.17.3 (01-Nov-2024) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java index 6ca6877092..c335f789a5 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java @@ -82,6 +82,34 @@ static class NestedBigDecimalHolder2784 { public BigDecimalHolder2784 holder; } + static class DeserializationIssue4917 { + public DecimalHolder4917 decimalHolder; + public double number; + } + + static class DeserializationIssue4917V2 { + public DecimalHolder4917 decimalHolder; + public int number; + } + + static class DeserializationIssue4917V3 { + public BigDecimal decimal; + public double number; + } + + static class DecimalHolder4917 { + public BigDecimal value; + + private DecimalHolder4917(BigDecimal value) { + this.value = value; + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + static DecimalHolder4917 of(BigDecimal value) { + return new DecimalHolder4917(value); + } + } + /* /********************************************************************** /* Helper classes, serializers/deserializers/resolvers @@ -386,4 +414,35 @@ public void testBigDecimalUnwrapped() throws Exception NestedBigDecimalHolder2784 result = mapper.readValue(JSON, NestedBigDecimalHolder2784.class); assertEquals(new BigDecimal("5.00"), result.holder.value); } + + // [databind#4917] + @Test + public void bigDecimal4917() throws Exception + { + DeserializationIssue4917 issue = MAPPER.readValue( + a2q("{'decimalHolder':100.00,'number':50}"), + DeserializationIssue4917.class); + assertEquals(new BigDecimal("100.00"), issue.decimalHolder.value); + assertEquals(50.0, issue.number); + } + + @Test + public void bigDecimal4917V2() throws Exception + { + DeserializationIssue4917V2 issue = MAPPER.readValue( + a2q("{'decimalHolder':100.00,'number':50}"), + DeserializationIssue4917V2.class); + assertEquals(new BigDecimal("100.00"), issue.decimalHolder.value); + assertEquals(50, issue.number); + } + + @Test + public void bigDecimal4917V3() throws Exception + { + DeserializationIssue4917V3 issue = MAPPER.readValue( + a2q("{'decimal':100.00,'number':50}"), + DeserializationIssue4917V3.class); + assertEquals(new BigDecimal("100.00"), issue.decimal); + assertEquals(50, issue.number); + } } From 94fb3bfa130c32f295ba02cfd9439c0285f80e40 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 27 Jan 2025 17:35:48 -0800 Subject: [PATCH 06/23] Make `ToEmptyObjectSerializer` constructor public --- .../jackson/databind/ser/std/ToEmptyObjectSerializer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/ToEmptyObjectSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/ToEmptyObjectSerializer.java index 5a485029d3..5d9182a189 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/ToEmptyObjectSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/ToEmptyObjectSerializer.java @@ -27,7 +27,8 @@ public class ToEmptyObjectSerializer extends StdSerializer { - protected ToEmptyObjectSerializer(Class raw) { + // `public` since 2.19 + public ToEmptyObjectSerializer(Class raw) { super(raw, false); } From 779ed2822794e2925cf92d1447324f4509e420f2 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 27 Jan 2025 18:36:29 -0800 Subject: [PATCH 07/23] Fix #4933: streamline code slightly (#4935) --- .../jackson/databind/deser/std/StdKeyDeserializer.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java index b42d80c0d0..00405ecd74 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java @@ -194,11 +194,6 @@ protected Object _parse(String key, DeserializationContext ctxt) throws Exceptio case TYPE_DOUBLE: return _parseDouble(key); case TYPE_LOCALE: - try { - return _deser._deserialize(key, ctxt); - } catch (IllegalArgumentException e) { - return _weirdKey(ctxt, key, e); - } case TYPE_CURRENCY: try { return _deser._deserialize(key, ctxt); From 26182273fd935816610cc2350fe7fe36982b180a Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 28 Jan 2025 18:57:31 -0800 Subject: [PATCH 08/23] Fix #4932 (#4937) --- release-notes/VERSION-2.x | 2 ++ .../com/fasterxml/jackson/databind/ObjectMapper.java | 6 ++++++ .../com/fasterxml/jackson/databind/ObjectReader.java | 6 ++++++ .../databind/node/JsonNodeConversionsTest.java | 11 +++++++++++ 4 files changed, 25 insertions(+) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index b961acdd90..745d14b2fc 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -31,6 +31,8 @@ Project: jackson-databind (reported by @dbachdev) #4922: Failing `@JsonMerge` with a custom Map (reported by @nlisker) +#4932: Conversion of `MissingNode` throws `JsonProcessingException` + (reported by @ludgerb) 2.18.2 (27-Nov-2024) diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index adc56b8620..431e68d013 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -4897,6 +4897,9 @@ protected Object _readValue(DeserializationConfig cfg, JsonParser p, result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt); } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) { result = null; + } else if (t == JsonToken.NOT_AVAILABLE) { + // 28-Jan-2025, tatu: [databind#4932] Need to handle this case too + result = null; } else { // pointing to event other than null result = ctxt.readRootValue(p, valueType, _findRootDeserializer(ctxt, valueType), null); } @@ -4921,6 +4924,9 @@ protected Object _readMapAndClose(JsonParser p0, JavaType valueType) result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt); } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) { result = null; + } else if (t == JsonToken.NOT_AVAILABLE) { + // 28-Jan-2025, tatu: [databind#4932] Need to handle this case too + result = null; } else { result = ctxt.readRootValue(p, valueType, _findRootDeserializer(ctxt, valueType), null); diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java index 6035d1d196..c8f07df200 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java @@ -2095,6 +2095,9 @@ protected Object _bind(JsonParser p, Object valueToUpdate) throws IOException } } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) { result = valueToUpdate; + } else if (t == JsonToken.NOT_AVAILABLE) { + // 28-Jan-2025, tatu: [databind#4932] Need to handle this case too + result = valueToUpdate; } else { // pointing to event other than null result = ctxt.readRootValue(p, _valueType, _findRootDeserializer(ctxt), _valueToUpdate); } @@ -2121,6 +2124,9 @@ protected Object _bindAndClose(JsonParser p0) throws IOException } } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) { result = _valueToUpdate; + } else if (t == JsonToken.NOT_AVAILABLE) { + // 28-Jan-2025, tatu: [databind#4932] Need to handle this case too + result = _valueToUpdate; } else { result = ctxt.readRootValue(p, _valueType, _findRootDeserializer(ctxt), _valueToUpdate); } diff --git a/src/test/java/com/fasterxml/jackson/databind/node/JsonNodeConversionsTest.java b/src/test/java/com/fasterxml/jackson/databind/node/JsonNodeConversionsTest.java index e43adf5d32..1ba155e8b8 100644 --- a/src/test/java/com/fasterxml/jackson/databind/node/JsonNodeConversionsTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/node/JsonNodeConversionsTest.java @@ -419,4 +419,15 @@ public void testValueToTree() throws Exception assertEquals(wrapRootMapper.readValue(expected, Map.class), wrapRootMapper.readValue(wrapRootMapper.writeValueAsString(value), Map.class)); assertEquals(wrapRootMapper.readValue(expected, Map.class), wrapRootMapper.readValue(wrapRootMapper.valueToTree(value).toString(), Map.class)); } + + // [databind#4932]: handling of `MissingNode` wrt conversions + @Test + public void treeToValueWithMissingNode4932() throws Exception { + assertNull(MAPPER.treeToValue(MAPPER.nullNode(), Object.class)); + assertNull(MAPPER.treeToValue(MAPPER.missingNode(), Object.class)); + + ObjectReader r = MAPPER.readerFor(Object.class); + assertNull(r.treeToValue(MAPPER.nullNode(), Object.class)); + assertNull(r.treeToValue(MAPPER.missingNode(), Object.class)); + } } From 6c92858fb775e9263e43c0b0d40b4bcfbd97576a Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 28 Jan 2025 20:31:08 -0800 Subject: [PATCH 09/23] Remove override of deprecated methods in test --- .../introspect/JacksonAnnotationIntrospectorTest.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospectorTest.java b/src/test/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospectorTest.java index 253aa4fefd..763a38102b 100644 --- a/src/test/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospectorTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospectorTest.java @@ -141,15 +141,6 @@ static class LcEnumIntrospector extends JacksonAnnotationIntrospector { private static final long serialVersionUID = 1L; - @Override - public String[] findEnumValues(Class enumType, Enum[] enumValues, String[] names) { - // kinda sorta wrong, but for testing's sake... - for (int i = 0, len = enumValues.length; i < len; ++i) { - names[i] = enumValues[i].name().toLowerCase(); - } - return names; - } - @Override public String[] findEnumValues(MapperConfig config, AnnotatedClass annotatedClass, Enum[] enumValues, String[] names) { From 452617d6b2da26bb1e855ae1ef0c2b6e42983511 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 29 Jan 2025 17:09:27 -0800 Subject: [PATCH 10/23] Test fixes wrt #3406 --- .../databind/jsontype/PolymorphicDeserErrorHandlingTest.java | 2 +- .../databind/jsontype/TestPolymorphicWithDefaultImpl.java | 2 +- .../com/fasterxml/jackson/databind/misc/TestBlocking.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/PolymorphicDeserErrorHandlingTest.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/PolymorphicDeserErrorHandlingTest.java index bf2e50b20a..8b22ed8af1 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/PolymorphicDeserErrorHandlingTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/PolymorphicDeserErrorHandlingTest.java @@ -54,7 +54,7 @@ public void testUnknownClassAsSubtype() throws Exception ObjectReader reader = MAPPER.readerFor(BaseUnknownWrapper.class) .without(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE); BaseUnknownWrapper w = reader.readValue(a2q - ("{'value':{'clazz':'com.foobar.Nothing'}}'")); + ("{'value':{'clazz':'com.foobar.Nothing'}}")); assertNotNull(w); } diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl.java index 9c50912155..53a3081d18 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl.java @@ -268,7 +268,7 @@ public void testUnknownClassAsSubtype() throws Exception ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); BaseWrapper w = mapper.readValue(a2q - ("{'value':{'clazz':'com.foobar.Nothing'}}'"), + ("{'value':{'clazz':'com.foobar.Nothing'}}"), BaseWrapper.class); assertNotNull(w); assertNull(w.value); diff --git a/src/test/java/com/fasterxml/jackson/databind/misc/TestBlocking.java b/src/test/java/com/fasterxml/jackson/databind/misc/TestBlocking.java index 6af9695a48..1840d7d6c1 100644 --- a/src/test/java/com/fasterxml/jackson/databind/misc/TestBlocking.java +++ b/src/test/java/com/fasterxml/jackson/databind/misc/TestBlocking.java @@ -1,6 +1,6 @@ package com.fasterxml.jackson.databind.misc; -import java.io.*; +import java.io.IOException; import org.junit.jupiter.api.Test; @@ -24,7 +24,7 @@ public class TestBlocking * quite yet. */ @Test - public void testEagerAdvance() throws IOException + public void testEagerAdvance() throws Exception { ObjectMapper mapper = jsonMapperBuilder() .disable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS) From b672adf6d564e78c5e9bf3ee33f0264f8bc4272b Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 29 Jan 2025 17:09:27 -0800 Subject: [PATCH 11/23] Test fixes wrt #3406 --- .../databind/jsontype/PolymorphicDeserErrorHandlingTest.java | 2 +- .../databind/jsontype/TestPolymorphicWithDefaultImpl.java | 2 +- .../com/fasterxml/jackson/databind/misc/TestBlocking.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/PolymorphicDeserErrorHandlingTest.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/PolymorphicDeserErrorHandlingTest.java index bf2e50b20a..8b22ed8af1 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/PolymorphicDeserErrorHandlingTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/PolymorphicDeserErrorHandlingTest.java @@ -54,7 +54,7 @@ public void testUnknownClassAsSubtype() throws Exception ObjectReader reader = MAPPER.readerFor(BaseUnknownWrapper.class) .without(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE); BaseUnknownWrapper w = reader.readValue(a2q - ("{'value':{'clazz':'com.foobar.Nothing'}}'")); + ("{'value':{'clazz':'com.foobar.Nothing'}}")); assertNotNull(w); } diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl.java index 9c50912155..53a3081d18 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestPolymorphicWithDefaultImpl.java @@ -268,7 +268,7 @@ public void testUnknownClassAsSubtype() throws Exception ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); BaseWrapper w = mapper.readValue(a2q - ("{'value':{'clazz':'com.foobar.Nothing'}}'"), + ("{'value':{'clazz':'com.foobar.Nothing'}}"), BaseWrapper.class); assertNotNull(w); assertNull(w.value); diff --git a/src/test/java/com/fasterxml/jackson/databind/misc/TestBlocking.java b/src/test/java/com/fasterxml/jackson/databind/misc/TestBlocking.java index 4c0e3911e6..dc511d6a33 100644 --- a/src/test/java/com/fasterxml/jackson/databind/misc/TestBlocking.java +++ b/src/test/java/com/fasterxml/jackson/databind/misc/TestBlocking.java @@ -1,6 +1,6 @@ package com.fasterxml.jackson.databind.misc; -import java.io.*; +import java.io.IOException; import org.junit.jupiter.api.Test; @@ -25,7 +25,7 @@ public class TestBlocking * quite yet. */ @Test - public void testEagerAdvance() throws IOException + public void testEagerAdvance() throws Exception { ObjectMapper mapper = new ObjectMapper(); JsonParser jp = createParserUsingReader("[ 1 "); From f85dac0fa471a0130bc3aabcf204086cafaad982 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 29 Jan 2025 17:23:01 -0800 Subject: [PATCH 12/23] Fix last erroneously failing tests wrt #3406 --- .../jackson/databind/jsontype/ext/ExternalTypeIdTest.java | 2 +- .../jsontype/ext/ExternalTypeIdWithCreatorTest.java | 4 ++-- ...pertyCreatorSubtypesExternalPropertyMissingProperty.java | 6 +++--- .../ext/TestSubtypesExternalPropertyMissingProperty.java | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java index 2aa842fd11..5a2161f30d 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java @@ -611,7 +611,7 @@ public Orange(@JsonProperty("name") String name, @JsonProperty("name") String co public void testIssue3008() throws Exception { ObjectReader r = MAPPER.readerFor(Box3008.class); - Box3008 deserOrangeBox = r.readValue("{\"type\":null,\"fruit\":null}}"); + Box3008 deserOrangeBox = r.readValue("{\"type\":null,\"fruit\":null}"); assertNull(deserOrangeBox.fruit); assertNull(deserOrangeBox.type); // error: "expected null, but was:" } diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdWithCreatorTest.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdWithCreatorTest.java index 05aaa15ec0..f2e7667365 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdWithCreatorTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdWithCreatorTest.java @@ -112,7 +112,7 @@ public void testExternalTypeId() throws Exception // [databind#1198] @Test public void testFails() throws Exception { - String json = "{ \"name\": \"foo\", \"attack\":\"right\" } }"; + String json = "{ \"name\": \"foo\", \"attack\":\"right\" } "; Character character = MAPPER.readValue(json, Character.class); @@ -124,7 +124,7 @@ public void testFails() throws Exception { // [databind#1198] @Test public void testWorks() throws Exception { - String json = "{ \"name\": \"foo\", \"preferredAttack\": \"KICK\", \"attack\":\"right\" } }"; + String json = "{ \"name\": \"foo\", \"preferredAttack\": \"KICK\", \"attack\":\"right\" }"; Character character = MAPPER.readValue(json, Character.class); diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestPropertyCreatorSubtypesExternalPropertyMissingProperty.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestPropertyCreatorSubtypesExternalPropertyMissingProperty.java index ff7a46e8fb..69bbd6a38e 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestPropertyCreatorSubtypesExternalPropertyMissingProperty.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestPropertyCreatorSubtypesExternalPropertyMissingProperty.java @@ -104,9 +104,9 @@ public static Orange getOrange(@JsonProperty("name") String name, @JsonProperty( private static final Orange orange = new Orange("Orange", "orange"); private static final Box orangeBox = new Box("orange", orange); private static final String orangeBoxJson = "{\"type\":\"orange\",\"fruit\":{\"name\":\"Orange\",\"color\":\"orange\"}}"; - private static final String orangeBoxNullJson = "{\"type\":\"orange\",\"fruit\":null}}"; - private static final String orangeBoxEmptyJson = "{\"type\":\"orange\",\"fruit\":{}}}"; - private static final String orangeBoxMissingJson = "{\"type\":\"orange\"}}"; + private static final String orangeBoxNullJson = "{\"type\":\"orange\",\"fruit\":null}"; + private static final String orangeBoxEmptyJson = "{\"type\":\"orange\",\"fruit\":{}}"; + private static final String orangeBoxMissingJson = "{\"type\":\"orange\"}"; private static final Apple apple = new Apple("Apple", 16); private static Box appleBox = new Box("apple", apple); diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestSubtypesExternalPropertyMissingProperty.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestSubtypesExternalPropertyMissingProperty.java index e66352a5cc..e08ad6d171 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestSubtypesExternalPropertyMissingProperty.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestSubtypesExternalPropertyMissingProperty.java @@ -101,9 +101,9 @@ public Orange(String name, String c) { private static final Orange orange = new Orange("Orange", "orange"); private static final Box orangeBox = new Box("orange", orange); private static final String orangeBoxJson = "{\"type\":\"orange\",\"fruit\":{\"name\":\"Orange\",\"color\":\"orange\"}}"; - private static final String orangeBoxNullJson = "{\"type\":\"orange\",\"fruit\":null}}"; - private static final String orangeBoxEmptyJson = "{\"type\":\"orange\",\"fruit\":{}}}"; - private static final String orangeBoxMissingJson = "{\"type\":\"orange\"}}"; + private static final String orangeBoxNullJson = "{\"type\":\"orange\",\"fruit\":null}"; + private static final String orangeBoxEmptyJson = "{\"type\":\"orange\",\"fruit\":{}}"; + private static final String orangeBoxMissingJson = "{\"type\":\"orange\"}"; private static final Apple apple = new Apple("Apple", 16); private static final Box appleBox = new Box("apple", apple); From eb71d6834c5d963a9648bf457a825b160b2b7adb Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 29 Jan 2025 17:23:01 -0800 Subject: [PATCH 13/23] Fix last erroneously failing tests wrt #3406 --- .../jackson/databind/jsontype/ext/ExternalTypeIdTest.java | 2 +- .../jsontype/ext/ExternalTypeIdWithCreatorTest.java | 4 ++-- ...pertyCreatorSubtypesExternalPropertyMissingProperty.java | 6 +++--- .../ext/TestSubtypesExternalPropertyMissingProperty.java | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java index 3b9e2ad1ca..5fae592551 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java @@ -610,7 +610,7 @@ public Orange(@JsonProperty("name") String name, @JsonProperty("name") String co public void testIssue3008() throws Exception { ObjectReader r = MAPPER.readerFor(Box3008.class); - Box3008 deserOrangeBox = r.readValue("{\"type\":null,\"fruit\":null}}"); + Box3008 deserOrangeBox = r.readValue("{\"type\":null,\"fruit\":null}"); assertNull(deserOrangeBox.fruit); assertNull(deserOrangeBox.type); // error: "expected null, but was:" } diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdWithCreatorTest.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdWithCreatorTest.java index 7082d02221..f3dc932df7 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdWithCreatorTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdWithCreatorTest.java @@ -111,7 +111,7 @@ public void testExternalTypeId() throws Exception // [databind#1198] @Test public void testFails() throws Exception { - String json = "{ \"name\": \"foo\", \"attack\":\"right\" } }"; + String json = "{ \"name\": \"foo\", \"attack\":\"right\" } "; Character character = MAPPER.readValue(json, Character.class); @@ -123,7 +123,7 @@ public void testFails() throws Exception { // [databind#1198] @Test public void testWorks() throws Exception { - String json = "{ \"name\": \"foo\", \"preferredAttack\": \"KICK\", \"attack\":\"right\" } }"; + String json = "{ \"name\": \"foo\", \"preferredAttack\": \"KICK\", \"attack\":\"right\" }"; Character character = MAPPER.readValue(json, Character.class); diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestPropertyCreatorSubtypesExternalPropertyMissingProperty.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestPropertyCreatorSubtypesExternalPropertyMissingProperty.java index fd6ae0f137..5f208f9f76 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestPropertyCreatorSubtypesExternalPropertyMissingProperty.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestPropertyCreatorSubtypesExternalPropertyMissingProperty.java @@ -104,9 +104,9 @@ public static Orange getOrange(@JsonProperty("name") String name, @JsonProperty( private static final Orange orange = new Orange("Orange", "orange"); private static final Box orangeBox = new Box("orange", orange); private static final String orangeBoxJson = "{\"type\":\"orange\",\"fruit\":{\"name\":\"Orange\",\"color\":\"orange\"}}"; - private static final String orangeBoxNullJson = "{\"type\":\"orange\",\"fruit\":null}}"; - private static final String orangeBoxEmptyJson = "{\"type\":\"orange\",\"fruit\":{}}}"; - private static final String orangeBoxMissingJson = "{\"type\":\"orange\"}}"; + private static final String orangeBoxNullJson = "{\"type\":\"orange\",\"fruit\":null}"; + private static final String orangeBoxEmptyJson = "{\"type\":\"orange\",\"fruit\":{}}"; + private static final String orangeBoxMissingJson = "{\"type\":\"orange\"}"; private static final Apple apple = new Apple("Apple", 16); private static Box appleBox = new Box("apple", apple); diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestSubtypesExternalPropertyMissingProperty.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestSubtypesExternalPropertyMissingProperty.java index e66352a5cc..e08ad6d171 100644 --- a/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestSubtypesExternalPropertyMissingProperty.java +++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/ext/TestSubtypesExternalPropertyMissingProperty.java @@ -101,9 +101,9 @@ public Orange(String name, String c) { private static final Orange orange = new Orange("Orange", "orange"); private static final Box orangeBox = new Box("orange", orange); private static final String orangeBoxJson = "{\"type\":\"orange\",\"fruit\":{\"name\":\"Orange\",\"color\":\"orange\"}}"; - private static final String orangeBoxNullJson = "{\"type\":\"orange\",\"fruit\":null}}"; - private static final String orangeBoxEmptyJson = "{\"type\":\"orange\",\"fruit\":{}}}"; - private static final String orangeBoxMissingJson = "{\"type\":\"orange\"}}"; + private static final String orangeBoxNullJson = "{\"type\":\"orange\",\"fruit\":null}"; + private static final String orangeBoxEmptyJson = "{\"type\":\"orange\",\"fruit\":{}}"; + private static final String orangeBoxMissingJson = "{\"type\":\"orange\"}"; private static final Apple apple = new Apple("Apple", 16); private static final Box appleBox = new Box("apple", apple); From efe5d075117fb06a6ccffb7e3406ab5844ebb986 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 29 Jan 2025 19:33:01 -0800 Subject: [PATCH 14/23] Add test for #4934, minor fix (#4940) --- .../databind/DeserializationContext.java | 4 +- .../databind/DeserializationContextTest.java | 53 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/databind/DeserializationContextTest.java diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java index 108d1966df..d435fe1052 100644 --- a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java +++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java @@ -1074,7 +1074,7 @@ public JsonNode readTree(JsonParser p) throws IOException { */ public T readTreeAsValue(JsonNode n, Class targetType) throws IOException { - if (n == null) { + if (n == null || n.isMissingNode()) { return null; } try (TreeTraversingParser p = _treeAsTokens(n)) { @@ -1098,7 +1098,7 @@ public T readTreeAsValue(JsonNode n, Class targetType) throws IOException */ public T readTreeAsValue(JsonNode n, JavaType targetType) throws IOException { - if (n == null) { + if (n == null || n.isMissingNode()) { return null; } try (TreeTraversingParser p = _treeAsTokens(n)) { diff --git a/src/test/java/com/fasterxml/jackson/databind/DeserializationContextTest.java b/src/test/java/com/fasterxml/jackson/databind/DeserializationContextTest.java new file mode 100644 index 0000000000..96fe5037fe --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/DeserializationContextTest.java @@ -0,0 +1,53 @@ +package com.fasterxml.jackson.databind; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +public class DeserializationContextTest extends DatabindTestUtil +{ + private final ObjectMapper MAPPER = newJsonMapper(); + + static class Bean4934 { + public String value; + } + + // [databind#4934] + @Test + public void testTreeAsValueFromNulls() throws Exception + { + final JsonNodeFactory nodeF = MAPPER.getNodeFactory(); + try (JsonParser p = MAPPER.createParser("abc")) { + DeserializationContext ctxt = MAPPER.readerFor(String.class).createDeserializationContext(p); + + assertNull(ctxt.readTreeAsValue(nodeF.nullNode(), Boolean.class)); + assertEquals(Boolean.FALSE, ctxt.readTreeAsValue(nodeF.nullNode(), Boolean.TYPE)); + + // Only fixed in 2.19: + //assertNull(ctxt.readTreeAsValue(nodeF.nullNode(), Bean4934.class)); + + } + } + + // [databind#4934] + @Test + public void testTreeAsValueFromMissing() throws Exception + { + final JsonNodeFactory nodeF = MAPPER.getNodeFactory(); + try (JsonParser p = MAPPER.createParser("abc")) { + DeserializationContext ctxt = MAPPER.readerFor(String.class).createDeserializationContext(p); + + assertNull(ctxt.readTreeAsValue(nodeF.missingNode(), Boolean.class)); + // Absent becomes `null` for now as well + assertNull(ctxt.readTreeAsValue(nodeF.missingNode(), Boolean.TYPE)); + + // Only fixed in 2.19: + //assertNull(ctxt.readTreeAsValue(nodeF.missingNode(), Bean4934.class)); + } + } +} From 7664e53bc0ad151a0ae60950468eda21e1ffd9c4 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Thu, 30 Jan 2025 17:27:36 -0800 Subject: [PATCH 15/23] Fix #4934 (#4943) --- release-notes/CREDITS-2.x | 5 +++++ release-notes/VERSION-2.x | 3 +++ .../databind/DeserializationContext.java | 20 ++++++++++++++++--- .../databind/DeserializationContextTest.java | 16 ++++++++------- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 704cf55497..1d447c5adb 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -1904,3 +1904,8 @@ Konstantin Maliuga (@badoken) Lars Benedetto (@lbenedetto) * Contributed #4676: Support other enum naming strategies than camelCase (2.19.0) + +Floris Westerman (@FWest98) + * Reported #4934: `DeserializationContext.readTreeAsValue()` handles null nodes + differently from `ObjectMapper.treeToValue()` + (2.19.0) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index a2c2bd3ea4..c4e9de85b2 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -43,6 +43,9 @@ Project: jackson-databind #4869: Add `JsonNode.values()` to replace `elements()` #4896: Coercion shouldn't be necessary for Enums specifying an empty string (reported by @joaocanaverde-blue) +#4934: `DeserializationContext.readTreeAsValue()` handles null nodes + differently from `ObjectMapper.treeToValue()` + (reported by Floris W) 2.18.3 (not yet released) diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java index c22ba26bed..650b3635ab 100644 --- a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java +++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java @@ -989,9 +989,9 @@ public T readValue(JsonParser p, JavaType type) throws IOException { return reportBadDefinition(type, "Could not find JsonDeserializer for type "+ClassUtil.getTypeDescription(type)); } - return (T) deser.deserialize(p, this); + return (T) _readValue(p, deser); } - + /** * Convenience method that may be used by composite or container deserializers, * for reading one-off values for the composite type, taking into account @@ -1023,7 +1023,7 @@ public T readPropertyValue(JsonParser p, BeanProperty prop, JavaType type) t "Could not find JsonDeserializer for type %s (via property %s)", ClassUtil.getTypeDescription(type), ClassUtil.nameOf(prop))); } - return (T) deser.deserialize(p, this); + return (T) _readValue(p, deser); } /** @@ -1116,6 +1116,20 @@ private TreeTraversingParser _treeAsTokens(JsonNode n) throws IOException return p; } + /** + * Helper method that should handle special cases for deserialization; most + * notably handling {@code null} (and possibly absent values). + * + * @since 2.19 + */ + private Object _readValue(JsonParser p, JsonDeserializer deser) throws IOException + { + if (p.hasToken(JsonToken.VALUE_NULL)) { + return deser.getNullValue(this); + } + return deser.deserialize(p, this); + } + /* /********************************************************** /* Methods for problem handling diff --git a/src/test/java/com/fasterxml/jackson/databind/DeserializationContextTest.java b/src/test/java/com/fasterxml/jackson/databind/DeserializationContextTest.java index 96fe5037fe..b919808518 100644 --- a/src/test/java/com/fasterxml/jackson/databind/DeserializationContextTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/DeserializationContextTest.java @@ -11,7 +11,10 @@ public class DeserializationContextTest extends DatabindTestUtil { - private final ObjectMapper MAPPER = newJsonMapper(); + // Not testing "no nulls for primitives", so + private final ObjectMapper MAPPER = jsonMapperBuilder() + .disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) + .build(); static class Bean4934 { public String value; @@ -27,10 +30,9 @@ public void testTreeAsValueFromNulls() throws Exception assertNull(ctxt.readTreeAsValue(nodeF.nullNode(), Boolean.class)); assertEquals(Boolean.FALSE, ctxt.readTreeAsValue(nodeF.nullNode(), Boolean.TYPE)); + assertNull(ctxt.readTreeAsValue(nodeF.nullNode(), String.class)); - // Only fixed in 2.19: - //assertNull(ctxt.readTreeAsValue(nodeF.nullNode(), Bean4934.class)); - + assertNull(ctxt.readTreeAsValue(nodeF.nullNode(), Bean4934.class)); } } @@ -42,12 +44,12 @@ public void testTreeAsValueFromMissing() throws Exception try (JsonParser p = MAPPER.createParser("abc")) { DeserializationContext ctxt = MAPPER.readerFor(String.class).createDeserializationContext(p); - assertNull(ctxt.readTreeAsValue(nodeF.missingNode(), Boolean.class)); // Absent becomes `null` for now as well + assertNull(ctxt.readTreeAsValue(nodeF.missingNode(), Boolean.class)); assertNull(ctxt.readTreeAsValue(nodeF.missingNode(), Boolean.TYPE)); + assertNull(ctxt.readTreeAsValue(nodeF.missingNode(), String.class)); - // Only fixed in 2.19: - //assertNull(ctxt.readTreeAsValue(nodeF.missingNode(), Bean4934.class)); + assertNull(ctxt.readTreeAsValue(nodeF.missingNode(), Bean4934.class)); } } } From 1d3cf21396f4b88c0be87360ef7204fb9f943f62 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 31 Jan 2025 17:35:30 -0800 Subject: [PATCH 16/23] Update Javadoc for `SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS` to reflect upcoming 3.0 change --- .../fasterxml/jackson/databind/SerializationFeature.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java b/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java index a16e5cbeff..88f7d1b300 100644 --- a/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java +++ b/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java @@ -260,11 +260,9 @@ public enum SerializationFeature implements ConfigFeature * representation may mean either simple number, or an array of numbers, * depending on type. *

- * Note: whether {@link java.util.Map} keys are serialized as Strings - * or not is controlled using {@link #WRITE_DATE_KEYS_AS_TIMESTAMPS}. - *

- * Feature is enabled by default, so that period/duration are by default - * serialized as timestamps. + * Feature is enabled by default in Jackson 2.x, so that period/duration values + * are by default serialized as timestamps. + * It will be disabled by default in Jackson 3.x. * * @since 2.5 */ From a204131005008e42d953a929499089f7f5221218 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Feb 2025 14:17:39 -0800 Subject: [PATCH 17/23] Bump the github-actions group with 4 updates (#4945) --- .github/workflows/cifuzz.yml | 2 +- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/dep_build_v2.yml | 2 +- .github/workflows/dep_build_v3.yml | 2 +- .github/workflows/main.yml | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 28d846c09d..086a71291f 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -30,7 +30,7 @@ jobs: dry-run: false language: jvm - name: Upload Crash - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 if: failure() && steps.build.outcome == 'success' with: name: artifacts diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3790515c0b..5af78ea1cf 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,12 +27,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/autobuild@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 diff --git a/.github/workflows/dep_build_v2.yml b/.github/workflows/dep_build_v2.yml index 9b20934a9e..69dc7be980 100644 --- a/.github/workflows/dep_build_v2.yml +++ b/.github/workflows/dep_build_v2.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up JDK - uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0 + uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 with: distribution: 'temurin' java-version: ${{ matrix.java_version }} diff --git a/.github/workflows/dep_build_v3.yml b/.github/workflows/dep_build_v3.yml index bd8d0ab5a0..84767f14a0 100644 --- a/.github/workflows/dep_build_v3.yml +++ b/.github/workflows/dep_build_v3.yml @@ -24,7 +24,7 @@ jobs: with: ref: master - name: Set up JDK - uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0 + uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 with: distribution: 'temurin' java-version: ${{ matrix.java_version }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 522390eb72..f5825ce2b2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -40,7 +40,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up JDK - uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0 + uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 with: distribution: 'temurin' java-version: ${{ matrix.java_version }} @@ -72,7 +72,7 @@ jobs: run: ./mvnw -B -q -ff -ntp test - name: Publish code coverage if: ${{ matrix.release_build && github.event_name != 'pull_request' }} - uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2 + uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1 with: token: ${{ secrets.CODECOV_TOKEN }} file: ./target/site/jacoco/jacoco.xml From 00208a8d71271c239667d00512a251e747866ad7 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 4 Feb 2025 14:03:59 +0100 Subject: [PATCH 18/23] add test case for https://github.com/FasterXML/jackson-core/issues/1397 --- .../deser/jdk/JDKNumberDeserTest.java | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java index 49b30260c3..9d434c21f9 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java @@ -4,6 +4,7 @@ import java.io.StringReader; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -110,6 +111,79 @@ static DecimalHolder4917 of(BigDecimal value) { } } + static class Point { + private Double x; + private Double y; + + public Double getX() { + return x; + } + + public void setX(Double x) { + this.x = x; + } + + public Double getY() { + return y; + } + + public void setY(Double y) { + this.y = y; + } + } + + @JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type", + visible = true) + @JsonSubTypes(@JsonSubTypes.Type(value = CenterResult.class, name = "center")) + static abstract class Result { + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + } + + static class CenterResult extends Result { + private Point center; + + private Double radius; + + public Double getRadius() { + return radius; + } + + public void setRadius(Double radius) { + this.radius = radius; + } + + public Point getCenter() { + return center; + } + + public void setCenter(Point center) { + this.center = center; + } + } + + static class Root { + private Result[] results; + + public Result[] getResults() { + return results; + } + + public void setResults(Result[] results) { + this.results = results; + } + } + /* /********************************************************************** /* Helper classes, serializers/deserializers/resolvers @@ -470,4 +544,23 @@ public void bigDecimal4917V3() throws Exception assertEquals(new BigDecimal("100.00"), issue.decimal); assertEquals(50, issue.number); } + + // https://github.com/FasterXML/jackson-core/issues/1397 + @Test + public void issue1397() throws Exception { + var dataString = a2q("{" + + "'results': [ { " + + "'radius': 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, " + + "'type': 'center', " + + "'center': { " + + "'x': -11.0, " + + "'y': -2.0 } } ] }"); + + Root object = MAPPER.readValue(dataString, Root.class); + + CenterResult result = (CenterResult) Arrays.stream(object.getResults()).findFirst().orElseThrow(); + + System.out.println(result.getCenter().getX()); + System.out.println(result.getCenter().getY()); + } } From e8b456aad98dd43b0dbba08ab6acd70ca7374863 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 4 Feb 2025 14:09:06 +0100 Subject: [PATCH 19/23] Revert "add test case for https://github.com/FasterXML/jackson-core/issues/1397" This reverts commit 00208a8d71271c239667d00512a251e747866ad7. --- .../deser/jdk/JDKNumberDeserTest.java | 93 ------------------- 1 file changed, 93 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java index 9d434c21f9..49b30260c3 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java @@ -4,7 +4,6 @@ import java.io.StringReader; import java.math.BigDecimal; import java.math.BigInteger; -import java.util.Arrays; import java.util.List; import java.util.Map; @@ -111,79 +110,6 @@ static DecimalHolder4917 of(BigDecimal value) { } } - static class Point { - private Double x; - private Double y; - - public Double getX() { - return x; - } - - public void setX(Double x) { - this.x = x; - } - - public Double getY() { - return y; - } - - public void setY(Double y) { - this.y = y; - } - } - - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type", - visible = true) - @JsonSubTypes(@JsonSubTypes.Type(value = CenterResult.class, name = "center")) - static abstract class Result { - private String type; - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - } - - static class CenterResult extends Result { - private Point center; - - private Double radius; - - public Double getRadius() { - return radius; - } - - public void setRadius(Double radius) { - this.radius = radius; - } - - public Point getCenter() { - return center; - } - - public void setCenter(Point center) { - this.center = center; - } - } - - static class Root { - private Result[] results; - - public Result[] getResults() { - return results; - } - - public void setResults(Result[] results) { - this.results = results; - } - } - /* /********************************************************************** /* Helper classes, serializers/deserializers/resolvers @@ -544,23 +470,4 @@ public void bigDecimal4917V3() throws Exception assertEquals(new BigDecimal("100.00"), issue.decimal); assertEquals(50, issue.number); } - - // https://github.com/FasterXML/jackson-core/issues/1397 - @Test - public void issue1397() throws Exception { - var dataString = a2q("{" + - "'results': [ { " + - "'radius': 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, " + - "'type': 'center', " + - "'center': { " + - "'x': -11.0, " + - "'y': -2.0 } } ] }"); - - Root object = MAPPER.readValue(dataString, Root.class); - - CenterResult result = (CenterResult) Arrays.stream(object.getResults()).findFirst().orElseThrow(); - - System.out.println(result.getCenter().getX()); - System.out.println(result.getCenter().getY()); - } } From 2b43b5d5b2e684b674f3e810d90807bb8adafb19 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 4 Feb 2025 16:51:15 -0800 Subject: [PATCH 20/23] Javadoc improvement wrt #4904 --- .../jackson/databind/DeserializationFeature.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java index 595df82fa5..2a5284bcdd 100644 --- a/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java +++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationFeature.java @@ -370,15 +370,15 @@ public enum DeserializationFeature implements ConfigFeature /** * Feature that can be enabled to allow JSON empty String - * value ("") to be bound as `null` for POJOs and other structured + * value ({@code ""}) to be bound as {@code null} for POJOs and other structured * values ({@link java.util.Map}s, {@link java.util.Collection}s). - * If disabled, standard POJOs can only be bound from JSON `null` or + * If disabled, standard POJOs can only be bound from JSON {@code null} or * JSON Object (standard meaning that no custom deserializers or * constructors are defined; both of which can add support for other * kinds of JSON values); if enabled, empty JSON String can be taken * to be equivalent of JSON null. *

- * NOTE: this does NOT apply to scalar values such as booleans, numbers + * NOTE: this does NOT apply to scalar values such as Strings, booleans, numbers * and date/time types; * whether these can be coerced depends on * {@link MapperFeature#ALLOW_COERCION_OF_SCALARS}. @@ -389,13 +389,13 @@ public enum DeserializationFeature implements ConfigFeature /** * Feature that can be enabled to allow empty JSON Array - * value (that is, [ ]) to be bound to POJOs (and - * with 2.9, other values too) as `null`. - * If disabled, standard POJOs can only be bound from JSON `null` or + * value (that is, {@code[ ]} to be bound to POJOs (and + * with 2.9, other values too) as {@code null}. + * If disabled, standard POJOs can only be bound from JSON {@code null} or * JSON Object (standard meaning that no custom deserializers or * constructors are defined; both of which can add support for other * kinds of JSON values); if enabled, empty JSON Array will be taken - * to be equivalent of JSON null. + * to be equivalent of JSON {@code null}. *

* Feature is disabled by default. * From 50e3005adb93e12f2212c5182ea15cf487fafebb Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Wed, 5 Feb 2025 02:03:53 +0100 Subject: [PATCH 21/23] add test case for https://github.com/FasterXML/jackson-core/issues/1397 (#4949) --- .../deser/jdk/JDKNumberDeserTest.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java index 49b30260c3..504d088c92 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/jdk/JDKNumberDeserTest.java @@ -4,6 +4,7 @@ import java.io.StringReader; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -110,6 +111,79 @@ static DecimalHolder4917 of(BigDecimal value) { } } + static class Point { + private Double x; + private Double y; + + public Double getX() { + return x; + } + + public void setX(Double x) { + this.x = x; + } + + public Double getY() { + return y; + } + + public void setY(Double y) { + this.y = y; + } + } + + @JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type", + visible = true) + @JsonSubTypes(@JsonSubTypes.Type(value = CenterResult.class, name = "center")) + static abstract class Result { + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + } + + static class CenterResult extends Result { + private Point center; + + private Double radius; + + public Double getRadius() { + return radius; + } + + public void setRadius(Double radius) { + this.radius = radius; + } + + public Point getCenter() { + return center; + } + + public void setCenter(Point center) { + this.center = center; + } + } + + static class Root { + private Result[] results; + + public Result[] getResults() { + return results; + } + + public void setResults(Result[] results) { + this.results = results; + } + } + /* /********************************************************************** /* Helper classes, serializers/deserializers/resolvers @@ -470,4 +544,22 @@ public void bigDecimal4917V3() throws Exception assertEquals(new BigDecimal("100.00"), issue.decimal); assertEquals(50, issue.number); } + + // https://github.com/FasterXML/jackson-core/issues/1397 + @Test + public void issue1397() throws Exception { + final String dataString = a2q("{ 'results': [ { " + + "'radius': 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, " + + "'type': 'center', " + + "'center': { " + + "'x': -11.0, " + + "'y': -2.0 } } ] }"); + + Root object = MAPPER.readValue(dataString, Root.class); + + CenterResult result = (CenterResult) Arrays.stream(object.getResults()).findFirst().get(); + + assertEquals(-11.0d, result.getCenter().getX()); + assertEquals(-2.0d, result.getCenter().getY()); + } } From a1f50ebef19af2da4c1ba00afd0bb0d2f296635a Mon Sep 17 00:00:00 2001 From: Joren Inghelbrecht <73221968+jin-harmoney@users.noreply.github.com> Date: Fri, 7 Feb 2025 20:38:07 +0100 Subject: [PATCH 22/23] feat: allow clearing all caches (#4954) --- .../databind/DeserializationContext.java | 13 ++++++++++ .../jackson/databind/ObjectMapper.java | 20 ++++++++++++++++ .../jackson/databind/SerializerProvider.java | 18 ++++++++++++++ .../ser/DefaultSerializerProvider.java | 10 -------- .../jackson/databind/ObjectMapperTest.java | 24 +++++++++++++++++++ 5 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java index 650b3635ab..5ec930af1e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java +++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java @@ -707,6 +707,19 @@ public final KeyDeserializer findKeyDeserializer(JavaType keyType, return kd; } + /** + * Method that will drop all dynamically constructed deserializers (ones that + * are counted as result value for {@link DeserializerCache#cachedDeserializersCount}). + * This can be used to remove memory usage (in case some deserializers are + * only used once or so), or to force re-construction of deserializers after + * configuration changes for mapper than owns the provider. + + * @since 2.19 + */ + public void flushCachedDeserializers() { + _cache.flushCachedDeserializers(); + } + /* /********************************************************** /* Public API, ObjectId handling diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index 5907562ec3..e292e6fbae 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -4789,6 +4789,26 @@ public void acceptJsonFormatVisitor(JavaType type, JsonFormatVisitorWrapper visi _serializerProvider(getSerializationConfig()).acceptJsonFormatVisitor(type, visitor); } + /* + /********************************************************** + /* Extended Public API: caches + /********************************************************** + */ + + /** + * Method that will clear all caches. + * This can be used to remove memory usage or to force re-construction cached entries after configuration changes. + * Can also be used to avoid class-loader memory leaks when reloading applications. + + * @since 2.19 + */ + public void clearCaches() { + _rootDeserializers.clear(); + getTypeFactory().clearCache(); + getDeserializationContext().flushCachedDeserializers(); + getSerializerProvider().flushCachedSerializers(); + } + /* /********************************************************** /* Internal factory methods for type ids, overridable diff --git a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java index 914927c81a..a5817b6957 100644 --- a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java +++ b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java @@ -1280,6 +1280,24 @@ public final void defaultSerializeNull(JsonGenerator gen) throws IOException } } + /* + /******************************************************** + /* Cache manipulation + /******************************************************** + */ + + /** + * Method that will drop all serializers currently cached by this provider. + * This can be used to remove memory usage (in case some serializers are + * only used once or so), or to force re-construction of serializers after + * configuration changes for mapper than owns the provider. + + * @since 2.19 + */ + public void flushCachedSerializers() { + _serializerCache.flush(); + } + /* /******************************************************** /* Error reporting diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java b/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java index b930aa857f..119760d055 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java @@ -553,16 +553,6 @@ public int cachedSerializersCount() { return _serializerCache.size(); } - /** - * Method that will drop all serializers currently cached by this provider. - * This can be used to remove memory usage (in case some serializers are - * only used once or so), or to force re-construction of serializers after - * configuration changes for mapper than owns the provider. - */ - public void flushCachedSerializers() { - _serializerCache.flush(); - } - /* /********************************************************** /* Extended API called by ObjectMapper: other diff --git a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java index 4fb454862d..a249682b76 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java @@ -409,6 +409,30 @@ public void testProviderConfig() throws Exception assertEquals(4, m._deserializationContext._cache.cachedDeserializersCount()); } + @Test + public void testClearCaches() throws Exception + { + ObjectMapper m = new ObjectMapper(); + + // Serialize and deserialize to fill caches + final String JSON = "{ \"x\" : 3 }"; + Bean bean = m.readValue(JSON, Bean.class); + m.writeValueAsString("test"); + + // Caches should not be empty + assertNotEquals(0, m._deserializationContext._cache.cachedDeserializersCount()); + assertNotEquals(0, m._rootDeserializers.size()); + assertNotEquals(0, m._serializerProvider.cachedSerializersCount()); + + // Clear caches + m.clearCaches(); + + // Caches should be empty + assertEquals(0, m._deserializationContext._cache.cachedDeserializersCount()); + assertEquals(0, m._rootDeserializers.size()); + assertEquals(0, m._serializerProvider.cachedSerializersCount()); + } + // For [databind#689] @Test public void testCustomDefaultPrettyPrinter() throws Exception From 0578116de55fd92ae654f6b1ac5dc3306efdd670 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 7 Feb 2025 12:08:47 -0800 Subject: [PATCH 23/23] Add release notes wrt #4953, minor tweaks --- release-notes/CREDITS-2.x | 4 ++++ release-notes/VERSION-2.x | 2 ++ .../fasterxml/jackson/databind/ObjectMapper.java | 15 ++++++++------- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 1d447c5adb..9b64535bcc 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -1909,3 +1909,7 @@ Floris Westerman (@FWest98) * Reported #4934: `DeserializationContext.readTreeAsValue()` handles null nodes differently from `ObjectMapper.treeToValue()` (2.19.0) + +Joren Inghelbrecht (@jin-harmoney) + * Contributed #4953: Allow clearing all caches to avoid classloader leaks + (2.19.0) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index c4e9de85b2..c62adf39a7 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -46,6 +46,8 @@ Project: jackson-databind #4934: `DeserializationContext.readTreeAsValue()` handles null nodes differently from `ObjectMapper.treeToValue()` (reported by Floris W) +#4953: Allow clearing all caches to avoid classloader leaks + (contributed by Joren I) 2.18.3 (not yet released) diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index e292e6fbae..01cf16cd12 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -4796,17 +4796,18 @@ public void acceptJsonFormatVisitor(JavaType type, JsonFormatVisitorWrapper visi */ /** - * Method that will clear all caches. - * This can be used to remove memory usage or to force re-construction cached entries after configuration changes. - * Can also be used to avoid class-loader memory leaks when reloading applications. - + * Method that will clear all caches this mapper owns. + *

+ * This method should not be needed in normal operation, but may be + * useful to avoid class-loader memory leaks when reloading applications. + * * @since 2.19 */ public void clearCaches() { _rootDeserializers.clear(); - getTypeFactory().clearCache(); - getDeserializationContext().flushCachedDeserializers(); - getSerializerProvider().flushCachedSerializers(); + _typeFactory.clearCache(); + _deserializationContext.flushCachedDeserializers(); + _serializerProvider.flushCachedSerializers(); } /*