From 0bc2f5d0f6b307982390b78aa798e462cc926081 Mon Sep 17 00:00:00 2001 From: Kyrylo Merzlikin Date: Sun, 10 Mar 2024 15:01:00 +0200 Subject: [PATCH 1/4] Fix #2543: Skip delegating creator arguments when collecting properties --- .../introspect/POJOPropertiesCollector.java | 5 +- ...elegatingCreatorImplicitNames2543Test.java | 73 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorImplicitNames2543Test.java diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java index 4f8d64b7a2..9a1669a754 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java @@ -760,7 +760,10 @@ private void _addCreatorParam(Map props, // ...or is a Records canonical constructor boolean isCanonicalConstructor = recordComponentName != null; - if ((creatorMode == null || creatorMode == JsonCreator.Mode.DISABLED) && !isCanonicalConstructor) { + if ((creatorMode == null + || creatorMode == JsonCreator.Mode.DISABLED + || creatorMode == JsonCreator.Mode.DELEGATING) + && !isCanonicalConstructor) { return; } pn = PropertyName.construct(impl); diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorImplicitNames2543Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorImplicitNames2543Test.java new file mode 100644 index 0000000000..d2f7d075fb --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorImplicitNames2543Test.java @@ -0,0 +1,73 @@ +package com.fasterxml.jackson.databind.deser.creators; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.introspect.*; +import org.junit.Test; + +import static com.fasterxml.jackson.annotation.JsonCreator.Mode.DELEGATING; +import static com.fasterxml.jackson.annotation.JsonCreator.Mode.PROPERTIES; +import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.a2q; +import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; + +public class DelegatingCreatorImplicitNames2543Test { + static class Data { + + final String part1; + final String part2; + + // this creator is considered a source of settable bean properties, + // used during deserialization + @JsonCreator(mode = PROPERTIES) + public Data(@JsonProperty("part1") String part1, + @JsonProperty("part2") String part2) { + this.part1 = part1; + this.part2 = part2; + } + + // no properties should be collected from this creator, + // even though it has an argument with an implicit name + @JsonCreator(mode = DELEGATING) + public static Data fromFullData(String fullData) { + String[] parts = fullData.split("\\s+", 2); + return new Data(parts[0], parts[1]); + } + } + + private static class DelegatingCreatorNamedArgumentIntrospector + extends JacksonAnnotationIntrospector { + public String findImplicitPropertyName(AnnotatedMember member) { + if (member instanceof AnnotatedParameter) { + AnnotatedWithParams owner = ((AnnotatedParameter) member).getOwner(); + if (owner instanceof AnnotatedMethod) { + AnnotatedMethod method = (AnnotatedMethod) owner; + if (requireNonNull(method.getAnnotation(JsonCreator.class)).mode() == DELEGATING) + return "fullData"; + } + } + return super.findImplicitPropertyName(member); + } + } + + private static final ObjectMapper MAPPER = new ObjectMapper() + .setAnnotationIntrospector(new DelegatingCreatorNamedArgumentIntrospector()); + + @Test + public void testDeserialization() throws JsonProcessingException { + Data data = MAPPER.readValue(a2q("{'part1':'a','part2':'b'}"), Data.class); + + assertThat(data.part1).isEqualTo("a"); + assertThat(data.part2).isEqualTo("b"); + } + + @Test + public void testDelegatingDeserialization() throws JsonProcessingException { + Data data = MAPPER.readValue(a2q("'a b'"), Data.class); + + assertThat(data.part1).isEqualTo("a"); + assertThat(data.part2).isEqualTo("b"); + } +} From a115ffbacdea4abe6e248a374d8ff522a0627467 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 12 Mar 2024 12:58:49 -0700 Subject: [PATCH 2/4] Minor tweaks for easier merging into 3.0 --- .../introspect/POJOPropertiesCollector.java | 1 + ...elegatingCreatorImplicitNames2543Test.java | 34 ++++++++++++------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java index 9a1669a754..1a30b22fe9 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java @@ -762,6 +762,7 @@ private void _addCreatorParam(Map props, if ((creatorMode == null || creatorMode == JsonCreator.Mode.DISABLED + // 12-Mar-2024: [databind#2543] need to skip for delegating || creatorMode == JsonCreator.Mode.DELEGATING) && !isCanonicalConstructor) { return; diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorImplicitNames2543Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorImplicitNames2543Test.java index d2f7d075fb..ffc40a3f48 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorImplicitNames2543Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/DelegatingCreatorImplicitNames2543Test.java @@ -1,19 +1,25 @@ package com.fasterxml.jackson.databind.deser.creators; +import java.util.Objects; + +import org.junit.Test; + import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonProcessingException; + import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.introspect.*; -import org.junit.Test; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; + +import static org.assertj.core.api.Assertions.assertThat; import static com.fasterxml.jackson.annotation.JsonCreator.Mode.DELEGATING; import static com.fasterxml.jackson.annotation.JsonCreator.Mode.PROPERTIES; -import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.a2q; -import static java.util.Objects.requireNonNull; -import static org.assertj.core.api.Assertions.assertThat; -public class DelegatingCreatorImplicitNames2543Test { +public class DelegatingCreatorImplicitNames2543Test + extends DatabindTestUtil +{ static class Data { final String part1; @@ -37,14 +43,17 @@ public static Data fromFullData(String fullData) { } } - private static class DelegatingCreatorNamedArgumentIntrospector + static class DelegatingCreatorNamedArgumentIntrospector extends JacksonAnnotationIntrospector { + private static final long serialVersionUID = 1L; + + @Override public String findImplicitPropertyName(AnnotatedMember member) { if (member instanceof AnnotatedParameter) { AnnotatedWithParams owner = ((AnnotatedParameter) member).getOwner(); if (owner instanceof AnnotatedMethod) { AnnotatedMethod method = (AnnotatedMethod) owner; - if (requireNonNull(method.getAnnotation(JsonCreator.class)).mode() == DELEGATING) + if (Objects.requireNonNull(method.getAnnotation(JsonCreator.class)).mode() == DELEGATING) return "fullData"; } } @@ -52,11 +61,12 @@ public String findImplicitPropertyName(AnnotatedMember member) { } } - private static final ObjectMapper MAPPER = new ObjectMapper() - .setAnnotationIntrospector(new DelegatingCreatorNamedArgumentIntrospector()); + private static final ObjectMapper MAPPER = JsonMapper.builder() + .annotationIntrospector(new DelegatingCreatorNamedArgumentIntrospector()) + .build(); @Test - public void testDeserialization() throws JsonProcessingException { + public void testDeserialization() throws Exception { Data data = MAPPER.readValue(a2q("{'part1':'a','part2':'b'}"), Data.class); assertThat(data.part1).isEqualTo("a"); @@ -64,7 +74,7 @@ public void testDeserialization() throws JsonProcessingException { } @Test - public void testDelegatingDeserialization() throws JsonProcessingException { + public void testDelegatingDeserialization() throws Exception { Data data = MAPPER.readValue(a2q("'a b'"), Data.class); assertThat(data.part1).isEqualTo("a"); From 4d8551883baad71c4539e4068eb3776c7d6732b2 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 12 Mar 2024 12:59:41 -0700 Subject: [PATCH 3/4] reword comment --- .../jackson/databind/introspect/POJOPropertiesCollector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java index 1a30b22fe9..ba4694a3a3 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java @@ -762,7 +762,7 @@ private void _addCreatorParam(Map props, if ((creatorMode == null || creatorMode == JsonCreator.Mode.DISABLED - // 12-Mar-2024: [databind#2543] need to skip for delegating + // 12-Mar-2024: [databind#2543] need to skip delegating as well || creatorMode == JsonCreator.Mode.DELEGATING) && !isCanonicalConstructor) { return; From 1ae62a9f272b76ba827a02527853cbfd3aa6f5c5 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 12 Mar 2024 14:13:44 -0700 Subject: [PATCH 4/4] Update release notes --- release-notes/CREDITS-2.x | 5 +++++ release-notes/VERSION-2.x | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 0b57855eaa..37c8ef22a6 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -1756,3 +1756,8 @@ Jesper Blomquist (jebl01@github) András Péteri (apeteri@github) * Suggested #4416: Deprecate `JsonNode.asText(String)` (2.17.0) + +Kyrylo Merzlikin (kirmerzlikin@github) + * Contributed fix for #2543: Introspection includes delegating ctor's + only parameter as a property in `BeanDescription` + (2.17.0) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 93a28a36d2..52d2681229 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -12,6 +12,10 @@ Project: jackson-databind #736: `MapperFeature.REQUIRE_SETTERS_FOR_GETTERS` has no effect (reported by @migel) (fix contributed by Joo-Hyuk K) +#2543: Introspection includes delegating ctor's only parameter as + a property in `BeanDescription` + (reported by @nikita2206) + (fix contributed by Kyrylo M) #4160: Deprecate `DefaultTyping.EVERYTHING` in `2.x` and remove in `3.0` (contributed by Joo-Hyuk K) #4194: Add `JsonNodeFeature.FAIL_ON_NAN_TO_BIG_DECIMAL_COERCION` option to