From bd41ee282c72ba3908bdf3aa4bf7742e30aacc3d Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 31 Aug 2021 20:52:29 -0700 Subject: [PATCH] Fixed #3214 (I hope ;) ) --- release-notes/VERSION-2.x | 3 +++ .../jackson/databind/JsonDeserializer.java | 27 +++++++++++++++++-- .../databind/deser/NullValueProvider.java | 20 ++++++++++++++ .../deser/impl/PropertyValueBuffer.java | 16 +++++------ .../deser/std/JsonNodeDeserializer.java | 9 +++++++ .../node/AbsentNodeViaCreator3214Test.java} | 6 ++--- 6 files changed, 67 insertions(+), 14 deletions(-) rename src/test/java/com/fasterxml/jackson/{failing/NullJsonNodeViaCreator3214Test.java => databind/node/AbsentNodeViaCreator3214Test.java} (85%) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 6566d5215f..93c7980bbc 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -58,6 +58,9 @@ Project: jackson-databind (contributed by Klaas D) #3193: Add `MapperFeature.APPLY_DEFAULT_VALUES`, initially for Scala module (suggested by Nick B) +#3214: For an absent property Jackson injects `NullNode` instead of `null` to a + JsonNode-typed constructor argument of a `@ConstructorProperties`-annotated constructor + (repored by robvarga@github) #3217: `XMLGregorianCalendar` doesn't work with default typing (reported by Xinzhe Y) #3227: Content `null` handling not working for root values diff --git a/src/main/java/com/fasterxml/jackson/databind/JsonDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/JsonDeserializer.java index 98148f24fa..928eb66e67 100644 --- a/src/main/java/com/fasterxml/jackson/databind/JsonDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/JsonDeserializer.java @@ -315,8 +315,9 @@ public T getNullValue(DeserializationContext ctxt) throws JsonMappingException { * to be called just once (static values), or each time empty value is * needed. *

- * Default implementation indicates that "null value" to use for input null - * is simply Java `null` for all deserializers, unless overridden by sub-classes. + * Default implementation indicates that the "null value" to use for input null + * does not vary across uses so that {@link #getNullValue(DeserializationContext)} + * need not be called more than once per deserializer instance. * This information may be used as optimization. */ @Override @@ -327,6 +328,28 @@ public AccessPattern getNullAccessPattern() { return AccessPattern.CONSTANT; } + /** + * Method called to determine placeholder value to be used for cases + * where no value was obtained from input but we must pass a value + * nonetheless: the common case is that of Creator methods requiring + * passing a value for every parameter. + * Usually this is same as {@link #getNullValue} (which in turn + * is usually simply Java {@code null}), but it can be overridden + * for specific types: most notable scalar types must use "default" + * values. + *

+ * This method needs to be called every time a determination is made. + *

+ * Default implementation simply calls {@link #getNullValue} and + * returns value. + * + * @since 2.13 + */ + @Override + public Object getAbsentValue(DeserializationContext ctxt) throws JsonMappingException { + return getNullValue(ctxt); + } + /* /********************************************************** /* Accessors for other replacement/placeholder values diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/NullValueProvider.java b/src/main/java/com/fasterxml/jackson/databind/deser/NullValueProvider.java index b62834d506..dd39e15522 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/NullValueProvider.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/NullValueProvider.java @@ -31,4 +31,24 @@ public interface NullValueProvider * access null replacement value. */ public AccessPattern getNullAccessPattern(); + + /** + * Method called to determine placeholder value to be used for cases + * where no value was obtained from input but we must pass a value + * nonetheless: the common case is that of Creator methods requiring + * passing a value for every parameter. + * Usually this is same as {@link #getNullValue} (which in turn + * is usually simply Java {@code null}), but it can be overridden + * for specific types: most notable scalar types must use "default" + * values. + *

+ * This method needs to be called every time a determination is made. + *

+ * Default implementation simply calls and returns {@link #getNullValue}. + * + * @since 2.13 + */ + default Object getAbsentValue(DeserializationContext ctxt) throws JsonMappingException { + return getNullValue(ctxt); + } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyValueBuffer.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyValueBuffer.java index f15b4ca89a..f01ff7f808 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyValueBuffer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyValueBuffer.java @@ -4,11 +4,8 @@ import java.util.BitSet; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DatabindException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; + +import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; @@ -202,14 +199,15 @@ protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingExcep } try { // Third: NullValueProvider? (22-Sep-2019, [databind#2458]) - Object nullValue = prop.getNullValueProvider().getNullValue(_context); - if (nullValue != null) { - return nullValue; + // 08-Aug-2021, tatu: consider [databind#3214]; not null but "absent" value... + Object absentValue = prop.getNullValueProvider().getAbsentValue(_context); + if (absentValue != null) { + return absentValue; } // Fourth: default value JsonDeserializer deser = prop.getValueDeserializer(); - return deser.getNullValue(_context); + return deser.getAbsentValue(_context); } catch (DatabindException e) { // [databind#2101]: Include property name, if we have it AnnotatedMember member = prop.getMember(); diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java index 12b525fe0d..f7eb2c3db7 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java @@ -60,6 +60,15 @@ public JsonNode getNullValue(DeserializationContext ctxt) { return ctxt.getNodeFactory().nullNode(); } + /** + * Overridden variant to ensure that absent values are NOT coerced into + * {@code NullNode}s, unlike incoming {@code null} values. + */ + @Override // since 2.13 + public Object getAbsentValue(DeserializationContext ctxt) { + return null; + } + /** * Implementation that will produce types of any JSON nodes; not just one * deserializer is registered to handle (in case of more specialized handler). diff --git a/src/test/java/com/fasterxml/jackson/failing/NullJsonNodeViaCreator3214Test.java b/src/test/java/com/fasterxml/jackson/databind/node/AbsentNodeViaCreator3214Test.java similarity index 85% rename from src/test/java/com/fasterxml/jackson/failing/NullJsonNodeViaCreator3214Test.java rename to src/test/java/com/fasterxml/jackson/databind/node/AbsentNodeViaCreator3214Test.java index 0cb53b2f80..74f893f563 100644 --- a/src/test/java/com/fasterxml/jackson/failing/NullJsonNodeViaCreator3214Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/node/AbsentNodeViaCreator3214Test.java @@ -1,11 +1,11 @@ -package com.fasterxml.jackson.failing; +package com.fasterxml.jackson.databind.node; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; + import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.node.TextNode; -public class NullJsonNodeViaCreator3214Test extends BaseMapTest +public class AbsentNodeViaCreator3214Test extends BaseMapTest { static class Pojo3214 {