diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/FieldProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/FieldProperty.java index b3eb596583..2968d63be1 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/FieldProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/FieldProperty.java @@ -186,6 +186,12 @@ public Object deserializeSetAndReturn(JsonParser p, @Override public void set(Object instance, Object value) throws IOException { + // [databind#4441] 2.17 Fix `@JsonSetter(Nulls.SKIP)` field is not skipped up to this point + if (value == null) { + if (_skipNulls) { + return; + } + } try { _field.set(instance, value); } catch (Exception e) { diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/MethodProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/MethodProperty.java index 69af26514f..33fdf20866 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/MethodProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/MethodProperty.java @@ -178,6 +178,12 @@ public Object deserializeSetAndReturn(JsonParser p, @Override public final void set(Object instance, Object value) throws IOException { + // [databind#4441] 2.17 Fix `@JsonSetter(Nulls.SKIP)` Method is not skipped up to this point + if (value == null) { + if (_skipNulls) { + return; + } + } try { _setter.invoke(instance, value); } catch (Exception e) { diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/SkipNulls4441Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/SkipNulls4441Test.java new file mode 100644 index 0000000000..5c22908b55 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/SkipNulls4441Test.java @@ -0,0 +1,149 @@ +package com.fasterxml.jackson.databind.deser; + +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import org.junit.jupiter.api.Test; + +import java.beans.ConstructorProperties; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import static com.fasterxml.jackson.databind.BaseTest.a2q; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +// [databind#4441] @JsonSetter(nulls = Nulls.SKIP) doesn't work in some situations +public class SkipNulls4441Test { + + static class Middle { + @JsonSetter(nulls = Nulls.SKIP) + private final List listInner = new ArrayList<>(); + private final String field1; + + @ConstructorProperties({"field1"}) + public Middle(String field1) { + this.field1 = field1; + } + + public List getListInner() { + return listInner; + } + + public String getField1() { + return field1; + } + } + + static class Inner { + private final String field1; + + @ConstructorProperties({"field1"}) + public Inner(String field1) { + this.field1 = field1; + } + + public String getField1() { + return field1; + } + } + + static class MiddleSetter { + private List listInner = new ArrayList<>(); + private final String field1; + + @ConstructorProperties({"field1"}) + public MiddleSetter(String field1) { + this.field1 = field1; + } + + @JsonSetter(nulls = Nulls.SKIP) + public void setListInner(List listInner) { + // null passed here + Objects.requireNonNull(listInner); + this.listInner = listInner; + } + + public List getListInner() { + return listInner; + } + + public String getField1() { + return field1; + } + } + + static class InnerSetter { + private final String field1; + + @ConstructorProperties({"field1"}) + public InnerSetter(String field1) { + this.field1 = field1; + } + + public String getField1() { + return field1; + } + } + + private final ObjectMapper objectMapper = JsonMapper.builder().build(); + + private final String NULL_ENDING_JSON = a2q("{" + + " 'field1': 'data', " + + " 'listInner': null " + + "}"); + + private final String NULL_BEGINNING_JSON = a2q("{" + + " 'listInner': null, " + + " 'field1': 'data' " + + "}"); + + @Test + public void testFields() throws Exception { + // Passes + // For some reason, if most-inner "list1" field is null in the end, it works + _testFieldNullSkip(NULL_ENDING_JSON); + // Fails + // But if it's null in the beginning, it doesn't work + _testFieldNullSkip(NULL_BEGINNING_JSON); + } + + @Test + public void testMethods() throws Exception { + // Passes + // For some reason, if most-inner "list1" field is null in the end, it works + _testMethodNullSkip(NULL_ENDING_JSON); + // Fails + // But if it's null in the beginning, it doesn't work + _testMethodNullSkip(NULL_BEGINNING_JSON); + } + + private void _testMethodNullSkip(String s) throws Exception { + MiddleSetter middle = objectMapper.readValue(s, MiddleSetter.class); + + testMiddleSetter(middle); + } + + private void _testFieldNullSkip(String s) throws Exception { + Middle middle = objectMapper.readValue(s, Middle.class); + + testMiddle(middle); + } + + private void testMiddle(Middle middle) { + validateNotNull(middle); + validateNotNull(middle.getField1()); + validateNotNull(middle.getListInner()); + } + + private void testMiddleSetter(MiddleSetter middle) { + validateNotNull(middle); + validateNotNull(middle.getField1()); + validateNotNull(middle.getListInner()); + } + + private static void validateNotNull(Object o) { + assertNotNull(o); + } +}