From 94540f8f6f800d46611091c9886159fdc0007b3e Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 27 Jan 2024 16:55:25 +0900 Subject: [PATCH 01/60] temporarily remove other jdk versions --- .../jackson/databind/jdk9/Java9ListsTest.java | 103 ------ .../failing/RecordUpdate3079FailingTest.java | 25 -- .../databind/jdk17/Java17CollectionsTest.java | 34 -- .../databind/jdk17/RecordPrivate4175Test.java | 27 -- ...orParameterNameAnnotationIntrospector.java | 30 -- .../JsonAliasWithDeduction4327RecordTest.java | 44 --- .../databind/records/RecordBasicsTest.java | 299 ---------------- .../databind/records/RecordCreatorsTest.java | 82 ----- .../RecordDeserialization3897Test.java | 23 -- .../RecordDeserialization3906Test.java | 155 --------- .../records/RecordExplicitCreatorsTest.java | 326 ------------------ .../records/RecordFailingSetter3938Test.java | 42 --- .../RecordIgnoreNonAccessorGetterTest.java | 54 --- .../records/RecordImplicitCreatorsTest.java | 325 ----------------- ...leValueUsePropertiesBasedCreatorsTest.java | 75 ---- .../records/RecordJsonValue3063Test.java | 33 -- .../records/RecordNamingStrategy2992Test.java | 25 -- .../records/RecordNullHandling3847Test.java | 116 ------- .../records/RecordSerializationOrderTest.java | 66 ---- .../records/RecordTypeInfo3342Test.java | 58 ---- .../records/RecordUpdate3079Test.java | 34 -- .../RecordWithIgnoreOverride3992Test.java | 49 --- .../records/RecordWithJsonIgnoreTest.java | 95 ----- .../records/RecordWithJsonNaming3102Test.java | 40 --- .../records/RecordWithJsonSetter2974Test.java | 61 ---- .../databind/jdk21/Java21CollectionsTest.java | 51 --- 26 files changed, 2272 deletions(-) delete mode 100644 src/test-jdk11/java/com/fasterxml/jackson/databind/jdk9/Java9ListsTest.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/failing/RecordUpdate3079FailingTest.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/Java17CollectionsTest.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/RecordPrivate4175Test.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/Jdk8ConstructorParameterNameAnnotationIntrospector.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/JsonAliasWithDeduction4327RecordTest.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordBasicsTest.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordCreatorsTest.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordDeserialization3897Test.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordDeserialization3906Test.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordExplicitCreatorsTest.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordFailingSetter3938Test.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordIgnoreNonAccessorGetterTest.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordImplicitCreatorsTest.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordImplicitSingleValueUsePropertiesBasedCreatorsTest.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordJsonValue3063Test.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordNamingStrategy2992Test.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordNullHandling3847Test.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordSerializationOrderTest.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordTypeInfo3342Test.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordUpdate3079Test.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithIgnoreOverride3992Test.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonIgnoreTest.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonNaming3102Test.java delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonSetter2974Test.java delete mode 100644 src/test-jdk21/java/com/fasterxml/jackson/databind/jdk21/Java21CollectionsTest.java diff --git a/src/test-jdk11/java/com/fasterxml/jackson/databind/jdk9/Java9ListsTest.java b/src/test-jdk11/java/com/fasterxml/jackson/databind/jdk9/Java9ListsTest.java deleted file mode 100644 index c1046a40ac..0000000000 --- a/src/test-jdk11/java/com/fasterxml/jackson/databind/jdk9/Java9ListsTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.fasterxml.jackson.databind.jdk9; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.json.JsonMapper; -import com.fasterxml.jackson.databind.testutil.NoCheckSubTypeValidator; - -// for [databind#2900] -public class Java9ListsTest extends BaseMapTest -{ - private final ObjectMapper MAPPER = JsonMapper.builder() - .activateDefaultTypingAsProperty( - new NoCheckSubTypeValidator(), - ObjectMapper.DefaultTyping.EVERYTHING, - "@class" - ).build(); - - public void testUnmodifiableList() throws Exception - { - final List list = Collections.unmodifiableList(Collections.singletonList("a")); - final String actualJson = MAPPER.writeValueAsString(list); - final List output = MAPPER.readValue(actualJson, List.class); - assertEquals(1, output.size()); - } - - public void testJava9ListOf() throws Exception - { - List list = List.of("a"); -/* { - Class cls = list.getClass(); - com.fasterxml.jackson.databind.JavaType type = MAPPER.constructType(cls); -System.err.println("LIST type: "+type); -System.err.println(" final? "+type.isFinal()); - } - */ - String actualJson = MAPPER.writeValueAsString(list); - List output = MAPPER.readValue(actualJson, List.class); - assertEquals(1, output.size()); - - // and couple of alternatives: - list = List.of("a", "b"); - actualJson = MAPPER.writeValueAsString(list); - output = MAPPER.readValue(actualJson, List.class); - assertEquals(2, output.size()); - - list = List.of("a", "b", "c"); - actualJson = MAPPER.writeValueAsString(list); - output = MAPPER.readValue(actualJson, List.class); - assertEquals(3, output.size()); - - list = List.of(); - actualJson = MAPPER.writeValueAsString(list); - output = MAPPER.readValue(actualJson, List.class); - assertEquals(0, output.size()); - } - - public void testJava9MapOf() throws Exception - { - Map map = Map.of("key", "value"); - String actualJson = MAPPER.writeValueAsString(map); - Map output = MAPPER.readValue(actualJson, Map.class); - assertEquals(1, output.size()); - - // and alternatives - map = Map.of("key", "value", "foo", "bar"); - actualJson = MAPPER.writeValueAsString(map); - output = MAPPER.readValue(actualJson, Map.class); - assertEquals(2, output.size()); - - map = Map.of("key", "value", "foo", "bar", "last", "one"); - actualJson = MAPPER.writeValueAsString(map); - output = MAPPER.readValue(actualJson, Map.class); - assertEquals(3, output.size()); - - map = Map.of(); - actualJson = MAPPER.writeValueAsString(map); - output = MAPPER.readValue(actualJson, Map.class); - assertEquals(0, output.size()); - } - - // [databind#3344] - public void testJava9SetOf() throws Exception - { - Set set = Set.of("a", "b", "c"); - String actualJson = MAPPER.writeValueAsString(set); - Set output = MAPPER.readValue(actualJson, Set.class); - assertTrue(output instanceof Set); - assertEquals(set, output); - } - - public void testJava9ListWrapped() throws Exception - { - final List list = Collections.unmodifiableList(List.of("a")); - final String actualJson = MAPPER.writeValueAsString(list); - final List output = MAPPER.readValue(actualJson, List.class); - assertEquals(1, output.size()); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/failing/RecordUpdate3079FailingTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/failing/RecordUpdate3079FailingTest.java deleted file mode 100644 index 1baa711009..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/failing/RecordUpdate3079FailingTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.fasterxml.jackson.databind.failing; - -import java.util.Collections; - -import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.records.RecordUpdate3079Test; - -// 01-Dec-2022, tatu: Alas, fails on JDK 17 -// see related passing test in RecordUpdate3079Test -public class RecordUpdate3079FailingTest extends BaseMapTest -{ - private final ObjectMapper MAPPER = newJsonMapper(); - - // [databind#3079]: Should be able to Record value directly - public void testDirectRecordUpdate() throws Exception - { - RecordUpdate3079Test.IdNameRecord orig = new RecordUpdate3079Test.IdNameRecord(123, "Bob"); - RecordUpdate3079Test.IdNameRecord result = MAPPER.updateValue(orig, - Collections.singletonMap("id", 137)); - assertNotNull(result); - assertEquals(137, result.id()); - assertEquals("Bob", result.name()); - assertNotSame(orig, result); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/Java17CollectionsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/Java17CollectionsTest.java deleted file mode 100644 index a3bfd8d277..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/Java17CollectionsTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.fasterxml.jackson.databind.jdk17; - -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.json.JsonMapper; -import com.fasterxml.jackson.databind.testutil.NoCheckSubTypeValidator; - -public class Java17CollectionsTest extends BaseMapTest -{ - private final ObjectMapper MAPPER = JsonMapper.builder() - .activateDefaultTypingAsProperty( - new NoCheckSubTypeValidator(), - ObjectMapper.DefaultTyping.EVERYTHING, - "@class" - ).build(); - - // [databind#3404] - public void testJava9StreamOf() throws Exception - { - List input = Stream.of("a", "b", "c").collect(Collectors.toList()); - String actualJson = MAPPER.writeValueAsString(input); - List result = MAPPER.readValue(actualJson, List.class); - assertEquals(input, result); - - input = Stream.of("a", "b", "c").toList(); - actualJson = MAPPER.writeValueAsString(input); - result = MAPPER.readValue(actualJson, List.class); - assertEquals(input, result); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/RecordPrivate4175Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/RecordPrivate4175Test.java deleted file mode 100644 index 0c0729a829..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/RecordPrivate4175Test.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.fasterxml.jackson.databind.jdk17; - -import java.util.Collections; - -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; - -// for [databind#4175] -public class RecordPrivate4175Test extends BaseMapTest -{ - private static record PrivateTextRecord4175(String text) { } - - private final ObjectMapper MAPPER = newJsonMapper(); - - // for [databind#4175] - public void testSerializePrivateTextRecord() throws Exception { - PrivateTextRecord4175 textRecord = new PrivateTextRecord4175("anything"); - String json = MAPPER.writeValueAsString(textRecord); - final Object EXP = Collections.singletonMap("text", "anything"); - assertEquals(EXP, MAPPER.readValue(json, Object.class)); - } - - public void testDeserializePrivateTextRecord() throws Exception { - assertEquals(new PrivateTextRecord4175("anything"), - MAPPER.readValue("{\"text\":\"anything\"}", PrivateTextRecord4175.class)); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/Jdk8ConstructorParameterNameAnnotationIntrospector.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/Jdk8ConstructorParameterNameAnnotationIntrospector.java deleted file mode 100644 index 333fb6208c..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/Jdk8ConstructorParameterNameAnnotationIntrospector.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor; -import com.fasterxml.jackson.databind.introspect.AnnotatedMember; -import com.fasterxml.jackson.databind.introspect.AnnotatedParameter; -import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; - -class Jdk8ConstructorParameterNameAnnotationIntrospector extends JacksonAnnotationIntrospector -{ - private static final long serialVersionUID = 1L; - - @Override - public String findImplicitPropertyName(AnnotatedMember member) { - if (!(member instanceof AnnotatedParameter)) { - return null; - } - AnnotatedParameter parameter = (AnnotatedParameter) member; - if (!(parameter.getOwner() instanceof AnnotatedConstructor)) { - return null; - } - AnnotatedConstructor constructor = (AnnotatedConstructor) parameter.getOwner(); - String parameterName = constructor.getAnnotated().getParameters()[parameter.getIndex()].getName(); - - if (parameterName == null || parameterName.isBlank()) { - throw new IllegalArgumentException("Unable to extract constructor parameter name for: " + member); - } - - return parameterName; - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/JsonAliasWithDeduction4327RecordTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/JsonAliasWithDeduction4327RecordTest.java deleted file mode 100644 index 344bd9536f..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/JsonAliasWithDeduction4327RecordTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.a2q; -import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.jsonMapperBuilder; - -// [databind#4327] JsonAlias should respsect with Polymorphic Deduction -public class JsonAliasWithDeduction4327RecordTest -{ - @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) - @JsonSubTypes({ - @JsonSubTypes.Type(value = DeductionBean1.class), - @JsonSubTypes.Type(value = DeductionBean2.class) - }) - interface Deduction { } - - record DeductionBean1(int x) implements Deduction { } - - record DeductionBean2( - @JsonAlias(value = {"Y", "yy", "ff", "X"}) int y - ) implements Deduction { } - - - private final ObjectMapper mapper = jsonMapperBuilder().build(); - - @ParameterizedTest - @ValueSource(strings = {"Y", "yy", "ff", "X"}) - public void testAliasWithPolymorphicDeduction(String field) throws Exception { - String json = a2q(String.format("{'%s': 2 }", field)); - Deduction value = mapper.readValue(json, Deduction.class); - assertNotNull(value); - assertEquals(2, ((DeductionBean2) value).y()); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordBasicsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordBasicsTest.java deleted file mode 100644 index c2dfb3261d..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordBasicsTest.java +++ /dev/null @@ -1,299 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.annotation.*; - -import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonNaming; -import com.fasterxml.jackson.databind.json.JsonMapper; -import com.fasterxml.jackson.databind.type.TypeFactory; -import com.fasterxml.jackson.databind.util.ClassUtil; -import com.fasterxml.jackson.databind.util.Converter; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -public class RecordBasicsTest extends BaseMapTest -{ - record EmptyRecord() { } - - record SimpleRecord(int id, String name) { } - - record RecordOfRecord(SimpleRecord record) { } - - record RecordWithRename(int id, @JsonProperty("rename")String name) { } - - record RecordWithHeaderInject(int id, @JacksonInject String name) { } - - record RecordWithConstructorInject(int id, String name) { - - RecordWithConstructorInject(int id, @JacksonInject String name) { - this.id = id; - this.name = name; - } - } - - // [databind#2992] - @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) - record SnakeRecord(String myId, String myValue){} - - record RecordWithJsonDeserialize(int id, @JsonDeserialize(converter = StringTrimmer.class) String name) { } - - record RecordSingleWriteOnly(@JsonProperty(access = JsonProperty.Access.WRITE_ONLY) int id) { } - - record RecordSomeWriteOnly( - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) int id, - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) String name, - String email) { - } - - record RecordAllWriteOnly( - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) int id, - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) String name, - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) String email) { - } - - private final ObjectMapper MAPPER = newJsonMapper(); - - /* - /********************************************************************** - /* Test methods, Record type introspection - /********************************************************************** - */ - - public void testClassUtil() { - assertFalse(ClassUtil.isRecordType(getClass())); - - assertTrue(ClassUtil.isRecordType(SimpleRecord.class)); - assertTrue(ClassUtil.isRecordType(RecordOfRecord.class)); - assertTrue(ClassUtil.isRecordType(RecordWithRename.class)); - } - - public void testRecordJavaType() { - assertFalse(MAPPER.constructType(getClass()).isRecordType()); - - assertTrue(MAPPER.constructType(SimpleRecord.class).isRecordType()); - assertTrue(MAPPER.constructType(RecordOfRecord.class).isRecordType()); - assertTrue(MAPPER.constructType(RecordWithRename.class).isRecordType()); - } - - /* - /********************************************************************** - /* Test methods, default reading/writing Record values - /********************************************************************** - */ - - public void testSerializeSimpleRecord() throws Exception { - String json = MAPPER.writeValueAsString(new SimpleRecord(123, "Bob")); - final Object EXP = map("id", Integer.valueOf(123), "name", "Bob"); - assertEquals(EXP, MAPPER.readValue(json, Object.class)); - } - - public void testDeserializeSimpleRecord() throws Exception { - assertEquals(new SimpleRecord(123, "Bob"), - MAPPER.readValue("{\"id\":123,\"name\":\"Bob\"}", SimpleRecord.class)); - } - - public void testSerializeEmptyRecord() throws Exception { - assertEquals("{}", MAPPER.writeValueAsString(new EmptyRecord())); - } - - public void testDeserializeEmptyRecord() throws Exception { - assertEquals(new EmptyRecord(), - MAPPER.readValue("{}", EmptyRecord.class)); - } - - public void testSerializeRecordOfRecord() throws Exception { - RecordOfRecord record = new RecordOfRecord(new SimpleRecord(123, "Bob")); - String json = MAPPER.writeValueAsString(record); - final Object EXP = Collections.singletonMap("record", - map("id", Integer.valueOf(123), "name", "Bob")); - assertEquals(EXP, MAPPER.readValue(json, Object.class)); - } - - public void testDeserializeRecordOfRecord() throws Exception { - assertEquals(new RecordOfRecord(new SimpleRecord(123, "Bob")), - MAPPER.readValue("{\"record\":{\"id\":123,\"name\":\"Bob\"}}", - RecordOfRecord.class)); - } - - /* - /********************************************************************** - /* Test methods, reading/writing Record values with different config - /********************************************************************** - */ - - public void testSerializeSimpleRecord_DisableAnnotationIntrospector() throws Exception { - SimpleRecord record = new SimpleRecord(123, "Bob"); - - JsonMapper mapper = JsonMapper.builder() - .configure(MapperFeature.USE_ANNOTATIONS, false) - .build(); - String json = mapper.writeValueAsString(record); - - assertEquals("{\"id\":123,\"name\":\"Bob\"}", json); - } - - public void testDeserializeSimpleRecord_DisableAnnotationIntrospector() throws Exception { - JsonMapper mapper = JsonMapper.builder() - .configure(MapperFeature.USE_ANNOTATIONS, false) - .build(); - SimpleRecord value = mapper.readValue("{\"id\":123,\"name\":\"Bob\"}", SimpleRecord.class); - - assertEquals(new SimpleRecord(123, "Bob"), value); - } - - /* - /********************************************************************** - /* Test methods, renames, injects - /********************************************************************** - */ - - public void testSerializeJsonRename() throws Exception { - String json = MAPPER.writeValueAsString(new RecordWithRename(123, "Bob")); - final Object EXP = map("id", Integer.valueOf(123), "rename", "Bob"); - assertEquals(EXP, MAPPER.readValue(json, Object.class)); - } - - public void testDeserializeJsonRename() throws Exception { - RecordWithRename value = MAPPER.readValue("{\"id\":123,\"rename\":\"Bob\"}", - RecordWithRename.class); - assertEquals(new RecordWithRename(123, "Bob"), value); - } - - /** - * This test-case is just for documentation purpose: - * GOTCHA: Annotations on header will be propagated to the field, leading to this failure. - * - * @see #testDeserializeConstructorInjectRecord() - */ - public void testDeserializeHeaderInjectRecord_WillFail() throws Exception { - MAPPER.setInjectableValues(new InjectableValues.Std().addValue(String.class, "Bob")); - - try { - MAPPER.readValue("{\"id\":123}", RecordWithHeaderInject.class); - - fail("should not pass"); - } catch (IllegalArgumentException e) { - verifyException(e, "RecordWithHeaderInject#name"); - verifyException(e, "Can not set final java.lang.String field"); - } - } - - public void testDeserializeConstructorInjectRecord() throws Exception { - MAPPER.setInjectableValues(new InjectableValues.Std().addValue(String.class, "Bob")); - - RecordWithConstructorInject value = MAPPER.readValue("{\"id\":123}", RecordWithConstructorInject.class); - assertEquals(new RecordWithConstructorInject(123, "Bob"), value); - } - - /* - /********************************************************************** - /* Test methods, naming strategy - /********************************************************************** - */ - - // [databind#2992] - public void testNamingStrategy() throws Exception - { - SnakeRecord input = new SnakeRecord("123", "value"); - - String json = MAPPER.writeValueAsString(input); - assertEquals("{\"my_id\":\"123\",\"my_value\":\"value\"}", json); - - SnakeRecord output = MAPPER.readValue(json, SnakeRecord.class); - assertEquals(input, output); - } - - /* - /********************************************************************** - /* Test methods, JsonDeserialize - /********************************************************************** - */ - - public void testDeserializeJsonDeserializeRecord() throws Exception { - RecordWithJsonDeserialize value = MAPPER.readValue("{\"id\":123,\"name\":\" Bob \"}", RecordWithJsonDeserialize.class); - - assertEquals(new RecordWithJsonDeserialize(123, "Bob"), value); - } - - /* - /********************************************************************** - /* Test methods, JsonProperty(access=WRITE_ONLY) - /********************************************************************** - */ - - public void testSerialize_SingleWriteOnlyParameter() throws Exception { - String json = MAPPER.writeValueAsString(new RecordSingleWriteOnly(123)); - - assertEquals("{}", json); - } - - // [databind#3897] - public void testDeserialize_SingleWriteOnlyParameter() throws Exception { - RecordSingleWriteOnly value = MAPPER.readValue("{\"id\":123}", RecordSingleWriteOnly.class); - - assertEquals(new RecordSingleWriteOnly(123), value); - } - - public void testSerialize_SomeWriteOnlyParameter() throws Exception { - String json = MAPPER.writeValueAsString(new RecordSomeWriteOnly(123, "Bob", "bob@example.com")); - - assertEquals("{\"email\":\"bob@example.com\"}", json); - } - - public void testDeserialize_SomeWriteOnlyParameter() throws Exception { - RecordSomeWriteOnly value = MAPPER.readValue( - "{\"id\":123,\"name\":\"Bob\",\"email\":\"bob@example.com\"}", - RecordSomeWriteOnly.class); - - assertEquals(new RecordSomeWriteOnly(123, "Bob", "bob@example.com"), value); - } - - public void testSerialize_AllWriteOnlyParameter() throws Exception { - String json = MAPPER.writeValueAsString(new RecordAllWriteOnly(123, "Bob", "bob@example.com")); - - assertEquals("{}", json); - } - - public void testDeserialize_AllWriteOnlyParameter() throws Exception { - RecordAllWriteOnly value = MAPPER.readValue( - "{\"id\":123,\"name\":\"Bob\",\"email\":\"bob@example.com\"}", - RecordAllWriteOnly.class); - - assertEquals(new RecordAllWriteOnly(123, "Bob", "bob@example.com"), value); - } - - /* - /********************************************************************** - /* Internal helper methods - /********************************************************************** - */ - - private Map map(String key1, Object value1, - String key2, Object value2) { - final Map result = new LinkedHashMap<>(); - result.put(key1, value1); - result.put(key2, value2); - return result; - } - - public static class StringTrimmer implements Converter { - - @Override - public String convert(String value) { - return value.trim(); - } - - @Override - public JavaType getInputType(TypeFactory typeFactory) { - return typeFactory.constructType(String.class); - } - - @Override - public JavaType getOutputType(TypeFactory typeFactory) { - return typeFactory.constructType(String.class); - } - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordCreatorsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordCreatorsTest.java deleted file mode 100644 index 558223ec9f..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordCreatorsTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonValue; - -import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; - -public class RecordCreatorsTest extends BaseMapTest -{ - record RecordWithCanonicalCtorOverride(int id, String name) { - public RecordWithCanonicalCtorOverride(int id, String name) { - this.id = id; - this.name = "name"; - } - } - - record RecordWithAltCtor(int id, String name) { - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public RecordWithAltCtor(@JsonProperty("id") int id) { - this(id, "name2"); - } - } - - // [databind#2980] - record RecordWithDelegation(String value) { - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public RecordWithDelegation(String value) { - this.value = "del:"+value; - } - - @JsonValue() - public String getValue() { - return "val:"+value; - } - - public String accessValueForTest() { return value; } - } - - private final ObjectMapper MAPPER = newJsonMapper(); - - /* - /********************************************************************** - /* Test methods, alternate constructors - /********************************************************************** - */ - - public void testDeserializeWithCanonicalCtorOverride() throws Exception { - RecordWithCanonicalCtorOverride value = MAPPER.readValue("{\"id\":123,\"name\":\"Bob\"}", - RecordWithCanonicalCtorOverride.class); - assertEquals(123, value.id()); - assertEquals("name", value.name()); - } - - public void testDeserializeWithAltCtor() throws Exception { - RecordWithAltCtor value = MAPPER.readValue("{\"id\":2812}", - RecordWithAltCtor.class); - assertEquals(2812, value.id()); - assertEquals("name2", value.name()); - - // "Implicit" canonical constructor can no longer be used when there's explicit constructor - try { - MAPPER.readValue("{\"id\":2812,\"name\":\"Bob\"}", - RecordWithAltCtor.class); - fail("should not pass"); - } catch (UnrecognizedPropertyException e) { - verifyException(e, "Unrecognized"); - verifyException(e, "\"name\""); - verifyException(e, "RecordWithAltCtor"); - } - } - - // [databind#2980] - public void testDeserializeWithDelegatingCtor() throws Exception { - RecordWithDelegation value = MAPPER.readValue(q("foobar"), - RecordWithDelegation.class); - assertEquals("del:foobar", value.accessValueForTest()); - - assertEquals(q("val:del:foobar"), MAPPER.writeValueAsString(value)); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordDeserialization3897Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordDeserialization3897Test.java deleted file mode 100644 index 4034f2b272..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordDeserialization3897Test.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.BaseMapTest; - -// [databinding#3897] This is failing test for `Record` class deserialization with single field annotated with -// `JsonProperty.Access.WRITE_ONLY`. Regression from Jackson 2.14.2 -public class RecordDeserialization3897Test extends BaseMapTest { - - record Example( - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - String value - ) {} - - // Passes in 2.14.2, but does not in 2.15.0 - public void testRecordWithWriteOnly() throws Exception { - final String JSON = a2q("{'value':'foo'}"); - - Example result = newJsonMapper().readValue(JSON, Example.class); - - assertEquals("foo", result.value()); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordDeserialization3906Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordDeserialization3906Test.java deleted file mode 100644 index 56456380ed..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordDeserialization3906Test.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.introspect.AnnotatedClass; -import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector; -import com.fasterxml.jackson.databind.introspect.VisibilityChecker; -import com.fasterxml.jackson.databind.module.SimpleModule; - -/** - * Test case that covers both failing-by-regression tests and passing tests. - *

For more details, refer to - * - * [databind#3906]: Regression: 2.15.0 breaks deserialization for records when mapper.setVisibility(ALL, NONE); - */ -public class RecordDeserialization3906Test extends BaseMapTest -{ - record Record3906(String string, int integer) { - } - - @JsonAutoDetect(creatorVisibility = Visibility.NON_PRIVATE) - record Record3906Annotated(String string, int integer) { - } - - record Record3906Creator(String string, int integer) { - @JsonCreator - Record3906Creator { - } - } - - private record PrivateRecord3906(String string, int integer) { - } - - /* - /********************************************************** - /* Failing tests that pass in 2.14 (regression) - /********************************************************** - */ - - // minimal config for reproduction - public void testEmptyJsonToRecordMiminal() throws JsonProcessingException { - ObjectMapper mapper = newJsonMapper(); - mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE); - - Record3906 recordDeser = mapper.readValue("{}", Record3906.class); - - assertEquals(new Record3906(null, 0), recordDeser); - } - - // actual config used reproduction - public void testEmptyJsonToRecordActualImpl() throws JsonProcessingException { - ObjectMapper mapper = newJsonMapper(); - mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE); - mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); - - Record3906 recordDeser = mapper.readValue("{}", Record3906.class); - - assertEquals(new Record3906(null, 0), recordDeser); - } - - /* - /********************************************************** - /* Passing Tests : Suggested work-arounds - /* for future modifications - /********************************************************** - */ - - public void testEmptyJsonToRecordWorkAround() throws JsonProcessingException { - ObjectMapper mapper = newJsonMapper(); - mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE); - mapper.setVisibility(PropertyAccessor.CREATOR, Visibility.ANY); - - Record3906 recordDeser = mapper.readValue("{}", Record3906.class); - - assertEquals(new Record3906(null, 0), recordDeser); - } - - public void testEmptyJsonToRecordCreatorsVisibile() throws JsonProcessingException { - ObjectMapper mapper = newJsonMapper(); - mapper.setVisibility(PropertyAccessor.CREATOR, Visibility.NON_PRIVATE); - - Record3906 recordDeser = mapper.readValue("{}", Record3906.class); - assertEquals(new Record3906(null, 0), recordDeser); - } - - public void testEmptyJsonToRecordUsingModule() throws JsonProcessingException { - ObjectMapper mapper = jsonMapperBuilder().addModule(new SimpleModule() { - @Override - public void setupModule(SetupContext context) { - super.setupModule(context); - context.insertAnnotationIntrospector(new NopAnnotationIntrospector() { - @Override - public VisibilityChecker findAutoDetectVisibility(AnnotatedClass ac, - VisibilityChecker checker) { - return ac.getType().isRecordType() - ? checker.withCreatorVisibility(JsonAutoDetect.Visibility.NON_PRIVATE) - : checker; - } - }); - } - }).build(); - - Record3906 recordDeser = mapper.readValue("{}", Record3906.class); - assertEquals(new Record3906(null, 0), recordDeser); - } - - public void testEmptyJsonToRecordDirectAutoDetectConfig() throws JsonProcessingException { - ObjectMapper mapper = newJsonMapper(); - - Record3906Annotated recordDeser = mapper.readValue("{}", Record3906Annotated.class); - assertEquals(new Record3906Annotated(null, 0), recordDeser); - } - - public void testEmptyJsonToRecordJsonCreator() throws JsonProcessingException { - ObjectMapper mapper = newJsonMapper(); - - Record3906Creator recordDeser = mapper.readValue("{}", Record3906Creator.class); - assertEquals(new Record3906Creator(null, 0), recordDeser); - } - - public void testEmptyJsonToRecordUsingModuleOther() throws JsonProcessingException { - ObjectMapper mapper = jsonMapperBuilder().addModule( - new SimpleModule() { - @Override - public void setupModule(SetupContext context) { - super.setupModule(context); - context.insertAnnotationIntrospector(new NopAnnotationIntrospector() { - @Override - public VisibilityChecker findAutoDetectVisibility(AnnotatedClass ac, - VisibilityChecker checker) { - if (ac.getType() == null) { - return checker; - } - if (!ac.getType().isRecordType()) { - return checker; - } - // If this is a Record, then increase the "creator" visibility again - return checker.withCreatorVisibility(Visibility.ANY); - } - }); - } - }) - .build(); - - assertEquals(new Record3906(null, 0), - mapper.readValue("{}", Record3906.class)); - assertEquals(new PrivateRecord3906(null, 0), - mapper.readValue("{}", PrivateRecord3906.class)); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordExplicitCreatorsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordExplicitCreatorsTest.java deleted file mode 100644 index 18110d5176..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordExplicitCreatorsTest.java +++ /dev/null @@ -1,326 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; -import com.fasterxml.jackson.databind.exc.MismatchedInputException; - -import java.math.BigDecimal; - -public class RecordExplicitCreatorsTest extends BaseMapTest -{ - record RecordWithOneJsonPropertyWithoutJsonCreator(int id, String name) { - - public RecordWithOneJsonPropertyWithoutJsonCreator(@JsonProperty("id_only") int id) { - this(id, "JsonPropertyConstructor"); - } - - public static RecordWithOneJsonPropertyWithoutJsonCreator valueOf(int id) { - return new RecordWithOneJsonPropertyWithoutJsonCreator(id); - } - } - - record RecordWithTwoJsonPropertyWithoutJsonCreator(int id, String name, String email) { - - public RecordWithTwoJsonPropertyWithoutJsonCreator(@JsonProperty("the_id") int id, @JsonProperty("the_email") String email) { - this(id, "TwoJsonPropertyConstructor", email); - } - - public static RecordWithTwoJsonPropertyWithoutJsonCreator valueOf(int id) { - return new RecordWithTwoJsonPropertyWithoutJsonCreator(id, "factory@example.com"); - } - } - - record RecordWithJsonPropertyAndImplicitPropertyWithoutJsonCreator(int id, String name, String email) { - - public RecordWithJsonPropertyAndImplicitPropertyWithoutJsonCreator(@JsonProperty("id_only") int id, String email) { - this(id, "JsonPropertyConstructor", email); - } - } - - record RecordWithJsonPropertyWithJsonCreator(int id, String name) { - - @JsonCreator - public RecordWithJsonPropertyWithJsonCreator(@JsonProperty("id_only") int id) { - this(id, "JsonCreatorConstructor"); - } - - public static RecordWithJsonPropertyWithJsonCreator valueOf(int id) { - return new RecordWithJsonPropertyWithJsonCreator(id); - } - } - - record RecordWithJsonPropertyAndImplicitPropertyWithJsonCreator(int id, String name, String email) { - - @JsonCreator - public RecordWithJsonPropertyAndImplicitPropertyWithJsonCreator(@JsonProperty("id_only") int id, String email) { - this(id, "JsonPropertyConstructor", email); - } - } - - record RecordWithMultiExplicitDelegatingConstructor(int id, String name) { - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public RecordWithMultiExplicitDelegatingConstructor(int id) { - this(id, "IntConstructor"); - } - - @JsonCreator(mode = JsonCreator.Mode.DELEGATING) - public RecordWithMultiExplicitDelegatingConstructor(String id) { - this(Integer.parseInt(id), "StringConstructor"); - } - } - - record RecordWithDisabledJsonCreator(int id, String name) { - - @JsonCreator(mode = JsonCreator.Mode.DISABLED) - RecordWithDisabledJsonCreator { - } - } - - record RecordWithExplicitFactoryMethod(BigDecimal id, String name) { - - @JsonCreator - public static RecordWithExplicitFactoryMethod valueOf(int id) { - return new RecordWithExplicitFactoryMethod(BigDecimal.valueOf(id), "IntFactoryMethod"); - } - - public static RecordWithExplicitFactoryMethod valueOf(double id) { - return new RecordWithExplicitFactoryMethod(BigDecimal.valueOf(id), "DoubleFactoryMethod"); - } - - @JsonCreator - public static RecordWithExplicitFactoryMethod valueOf(String id) { - return new RecordWithExplicitFactoryMethod(BigDecimal.valueOf(Double.parseDouble(id)), "StringFactoryMethod"); - } - } - - private final ObjectMapper MAPPER = jsonMapperBuilder() - .disable(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS) // So that test cases don't have to assert weird error messages - .build(); - - /* - /********************************************************************** - /* Test methods, "implicit" (?) constructor with JsonProperty without JsonCreator - /********************************************************************** - */ - - public void testDeserializeUsingJsonPropertyConstructor_WithoutJsonCreator() throws Exception { - RecordWithOneJsonPropertyWithoutJsonCreator oneJsonPropertyValue = MAPPER.readValue( - "{\"id_only\":123}", - RecordWithOneJsonPropertyWithoutJsonCreator.class); - assertEquals(new RecordWithOneJsonPropertyWithoutJsonCreator(123), oneJsonPropertyValue); - - RecordWithTwoJsonPropertyWithoutJsonCreator twoJsonPropertyValue = MAPPER.readValue( - "{\"the_id\":123,\"the_email\":\"bob@example.com\"}", - RecordWithTwoJsonPropertyWithoutJsonCreator.class); - assertEquals(new RecordWithTwoJsonPropertyWithoutJsonCreator(123, "bob@example.com"), twoJsonPropertyValue); - } - - /** - * Only 1 properties-based creator allowed, so can no longer use the (un-annotated) canonical constructor. - */ - public void testDeserializeUsingCanonicalConstructor_WhenJsonPropertyConstructorExists_WillFail() throws Exception { - try { - MAPPER.readValue( - "{\"id\":123,\"name\":\"Bobby\"}", - RecordWithOneJsonPropertyWithoutJsonCreator.class); - - fail("should not pass"); - } catch (JsonMappingException e) { - verifyException(e, "Unrecognized field \"id\""); - verifyException(e, "RecordWithOneJsonPropertyWithoutJsonCreator"); - verifyException(e, "one known property: \"id_only\""); - } - - try { - MAPPER.readValue( - "{\"id\":123,\"name\":\"Bobby\",\"email\":\"bobby@example.com\"}", - RecordWithTwoJsonPropertyWithoutJsonCreator.class); - - fail("should not pass"); - } catch (JsonMappingException e) { - verifyException(e, "Unrecognized field \"id\""); - verifyException(e, "RecordWithTwoJsonPropertyWithoutJsonCreator"); - verifyException(e, "2 known properties: \"the_id\", \"the_email\""); - } - } - - public void testDeserializeUsingImplicitFactoryMethod_WhenJsonPropertyConstructorExists() throws Exception { - RecordWithOneJsonPropertyWithoutJsonCreator oneJsonPropertyValue = MAPPER.readValue( - "123", - RecordWithOneJsonPropertyWithoutJsonCreator.class); - assertEquals(RecordWithOneJsonPropertyWithoutJsonCreator.valueOf(123), oneJsonPropertyValue); - - RecordWithTwoJsonPropertyWithoutJsonCreator twoJsonPropertyValue = MAPPER.readValue( - "123", - RecordWithTwoJsonPropertyWithoutJsonCreator.class); - assertEquals(RecordWithTwoJsonPropertyWithoutJsonCreator.valueOf(123), twoJsonPropertyValue); - } - - /* - /********************************************************************** - /* Test methods, explicit constructor with JsonProperty with JsonCreator - /********************************************************************** - */ - - public void testDeserializeUsingJsonCreatorConstructor() throws Exception { - RecordWithJsonPropertyWithJsonCreator value = MAPPER.readValue("{\"id_only\":123}", RecordWithJsonPropertyWithJsonCreator.class); - - assertEquals(new RecordWithJsonPropertyWithJsonCreator(123), value); - } - - /** - * Only 1 properties-based creator allowed, so can no longer use the (un-annotated) canonical constructor - */ - public void testDeserializeUsingCanonicalConstructor_WhenJsonCreatorConstructorExists_WillFail() throws Exception { - try { - MAPPER.readValue("{\"id\":123,\"name\":\"Bobby\"}", RecordWithJsonPropertyWithJsonCreator.class); - - fail("should not pass"); - } catch (JsonMappingException e) { - verifyException(e, "Unrecognized field \"id\""); - verifyException(e, "RecordWithJsonPropertyWithJsonCreator"); - verifyException(e, "one known property: \"id_only\""); - } - } - - public void testDeserializeUsingImplicitFactoryMethod_WhenJsonCreatorConstructorExists_WillFail() throws Exception { - try { - MAPPER.readValue("123", RecordWithJsonPropertyWithJsonCreator.class); - - fail("should not pass"); - } catch (MismatchedInputException e) { - verifyException(e, "Cannot construct instance"); - verifyException(e, "RecordWithJsonPropertyWithJsonCreator"); - verifyException(e, "although at least one Creator exists"); - verifyException(e, "no int/Int-argument constructor/factory method"); - } - } - - /* - /********************************************************************** - /* Test methods, multiple explicit delegating constructors - /********************************************************************** - */ - - public void testDeserializeUsingExplicitDelegatingConstructors() throws Exception { - RecordWithMultiExplicitDelegatingConstructor intConstructorValue = MAPPER.readValue("123", RecordWithMultiExplicitDelegatingConstructor.class); - assertEquals(new RecordWithMultiExplicitDelegatingConstructor(123, "IntConstructor"), intConstructorValue); - - RecordWithMultiExplicitDelegatingConstructor stringConstructorValue = MAPPER.readValue("\"123\"", RecordWithMultiExplicitDelegatingConstructor.class); - assertEquals(new RecordWithMultiExplicitDelegatingConstructor(123, "StringConstructor"), stringConstructorValue); - } - - /* - /********************************************************************** - /* Test methods, JsonCreator.mode=DISABLED - /********************************************************************** - */ - - public void testDeserializeUsingDisabledConstructors_WillFail() throws Exception { - try { - MAPPER.readValue("{\"id\":123,\"name\":\"Bobby\"}", RecordWithDisabledJsonCreator.class); - - fail("should not pass"); - } catch (InvalidDefinitionException e) { - verifyException(e, "Cannot construct instance"); - verifyException(e, "RecordWithDisabledJsonCreator"); - verifyException(e, "no Creators, like default constructor, exist"); - verifyException(e, "cannot deserialize from Object value"); - } - - } - - /* - /********************************************************************** - /* Test methods, explicit factory methods - /********************************************************************** - */ - - public void testDeserializeUsingExplicitFactoryMethods() throws Exception { - RecordWithExplicitFactoryMethod intFactoryValue = MAPPER.readValue("123", RecordWithExplicitFactoryMethod.class); - assertEquals(new RecordWithExplicitFactoryMethod(BigDecimal.valueOf(123), "IntFactoryMethod"), intFactoryValue); - - RecordWithExplicitFactoryMethod stringFactoryValue = MAPPER.readValue("\"123.4\"", RecordWithExplicitFactoryMethod.class); - assertEquals(new RecordWithExplicitFactoryMethod(BigDecimal.valueOf(123.4), "StringFactoryMethod"), stringFactoryValue); - } - - /** - * Implicit factory methods detection is only activated when there's no explicit (i.e. annotated - * with {@link JsonCreator}) factory methods. - */ - public void testDeserializeUsingImplicitFactoryMethods_WhenExplicitFactoryMethodsExist_WillFail() throws Exception { - try { - MAPPER.readValue("123.4", RecordWithExplicitFactoryMethod.class); - - fail("should not pass"); - } catch (MismatchedInputException e) { - verifyException(e, "Cannot construct instance"); - verifyException(e, "RecordWithExplicitFactoryMethod"); - verifyException(e, "although at least one Creator exists"); - verifyException(e, "no double/Double-argument constructor/factory"); - } - } - - /** - * Just like how no-arg constructor + setters will still be used to deserialize JSON Object even when - * there's JsonCreator factory method(s) in the JavaBean class. - */ - public void testDeserializeUsingImplicitCanonicalConstructor_WhenFactoryMethodsExist() throws Exception { - RecordWithExplicitFactoryMethod value = MAPPER.readValue("{\"id\":123.4,\"name\":\"CanonicalConstructor\"}", RecordWithExplicitFactoryMethod.class); - - assertEquals(new RecordWithExplicitFactoryMethod(BigDecimal.valueOf(123.4), "CanonicalConstructor"), value); - } - - /* - /********************************************************************** - /* Test methods, implicit parameter names - /********************************************************************** - */ - - public void testDeserializeMultipleConstructorsRecord_WithExplicitAndImplicitParameterNames_WithJsonCreator() throws Exception { - MAPPER.setAnnotationIntrospector(new Jdk8ConstructorParameterNameAnnotationIntrospector()); - - RecordWithJsonPropertyAndImplicitPropertyWithJsonCreator value = MAPPER.readValue( - "{\"id_only\":123,\"email\":\"bob@example.com\"}", - RecordWithJsonPropertyAndImplicitPropertyWithJsonCreator.class); - assertEquals(new RecordWithJsonPropertyAndImplicitPropertyWithJsonCreator(123, "bob@example.com"), value); - } - - /** - * This test-case is just for documentation purpose: - * GOTCHA: The problem is there are two usable constructors: - *

    - *
  1. Canonical constructor
  2. - *
  3. Non-canonical constructor with JsonProperty parameter
  4. - *
- * ...so Jackson-Databind decided NOT to choose any. To overcome this, annotate JsonCreator on the non-canonical - * constructor. - *

- * Similar behaviour is observed if a JavaBean has two usable constructors. - * - * @see #testDeserializeUsingJsonCreatorConstructor() - * @see #testDeserializeUsingCanonicalConstructor_WhenJsonCreatorConstructorExists_WillFail() - */ - public void testDeserializeMultipleConstructorsRecord_WithExplicitAndImplicitParameterNames() throws Exception { - MAPPER.setAnnotationIntrospector(new Jdk8ConstructorParameterNameAnnotationIntrospector()); - - try { - MAPPER.readValue( - "{\"id_only\":123,\"email\":\"bob@example.com\"}", - RecordWithJsonPropertyAndImplicitPropertyWithoutJsonCreator.class); - - fail("should not pass"); - } catch (InvalidDefinitionException e) { - verifyException(e, "Cannot construct instance"); - verifyException(e, "RecordWithJsonPropertyAndImplicitPropertyWithoutJsonCreator"); - verifyException(e, "no Creators, like default constructor, exist"); - verifyException(e, "cannot deserialize from Object value"); - } - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordFailingSetter3938Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordFailingSetter3938Test.java deleted file mode 100644 index 3fe8cfef18..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordFailingSetter3938Test.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.*; - -public class RecordFailingSetter3938Test extends BaseMapTest -{ - private final static String ERROR_3938_PREFIX = "Non-null 'options' not allowed for "; - - // [databind#3938] - interface NoOptionsCommand { - @JsonProperty("options") - default void setOptions(JsonNode value) { - if (value.isNull()) { - return; - } - throw new IllegalArgumentException(ERROR_3938_PREFIX+getClass().getName()); - } - } - - public record Command3938(int id, String filter) implements NoOptionsCommand { } - - private final ObjectMapper MAPPER = newJsonMapper(); - - // [databind#3938]: Should detect and use setters too - public void testFailingSetter3939() throws Exception - { - final ObjectReader R = MAPPER.readerFor(Command3938.class); - - // First, missing value and `null` are fine, as long as we have all fields - assertNotNull(R.readValue(a2q("{'id':1, 'filter':'abc'}"))); - assertNotNull(R.readValue(a2q("{'id':2, 'filter':'abc', 'options':null}"))); - - // But then failure for non-empty Array (f.ex) - try { - R.readValue(a2q("{'id':2,'options':[123]}}")); - fail("Should not pass"); - } catch (DatabindException e) { - verifyException(e, ERROR_3938_PREFIX); - } - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordIgnoreNonAccessorGetterTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordIgnoreNonAccessorGetterTest.java deleted file mode 100644 index af5077c211..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordIgnoreNonAccessorGetterTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; - -public class RecordIgnoreNonAccessorGetterTest extends BaseMapTest { - - // [databind#3628] - interface InterfaceWithGetter { - - String getId(); - - String getName(); - } - - @JsonPropertyOrder({"id", "name", "count"}) // easier to assert when JSON field ordering is always the same - record RecordWithInterfaceWithGetter(String name) implements InterfaceWithGetter { - - @Override - public String getId() { - return "ID:" + name; - } - - @Override - public String getName() { - return name; - } - - // [databind#3895] - public int getCount() { - return 999; - } - } - - private final ObjectMapper MAPPER = newJsonMapper(); - - public void testSerializeIgnoreInterfaceGetter_WithoutUsingVisibilityConfig() throws Exception { - String json = MAPPER.writeValueAsString(new RecordWithInterfaceWithGetter("Bob")); - - assertEquals("{\"id\":\"ID:Bob\",\"name\":\"Bob\",\"count\":999}", json); - } - - public void testSerializeIgnoreInterfaceGetter_UsingVisibilityConfig() throws Exception { - MAPPER.setVisibility(PropertyAccessor.GETTER, Visibility.NONE); - MAPPER.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); - - String json = MAPPER.writeValueAsString(new RecordWithInterfaceWithGetter("Bob")); - - assertEquals("{\"name\":\"Bob\"}", json); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordImplicitCreatorsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordImplicitCreatorsTest.java deleted file mode 100644 index 843c96fe62..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordImplicitCreatorsTest.java +++ /dev/null @@ -1,325 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.annotation.JsonValue; -import com.fasterxml.jackson.databind.BaseMapTest; - -import com.fasterxml.jackson.databind.DatabindException; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.cfg.ConstructorDetector; -import com.fasterxml.jackson.databind.exc.MismatchedInputException; - -import java.math.BigDecimal; - -public class RecordImplicitCreatorsTest extends BaseMapTest -{ - record RecordWithImplicitFactoryMethods(BigDecimal id, String name) { - - public static RecordWithImplicitFactoryMethods valueOf(int id) { - return new RecordWithImplicitFactoryMethods(BigDecimal.valueOf(id), "IntFactoryMethod"); - } - - public static RecordWithImplicitFactoryMethods valueOf(double id) { - return new RecordWithImplicitFactoryMethods(BigDecimal.valueOf(id), "DoubleFactoryMethod"); - } - - public static RecordWithImplicitFactoryMethods valueOf(String id) { - return new RecordWithImplicitFactoryMethods(BigDecimal.valueOf(Double.parseDouble(id)), "StringFactoryMethod"); - } - } - - record RecordWithSingleValueConstructor(int id) { - } - - record RecordWithSingleValueConstructorWithJsonValue(@JsonValue int id) { - } - - record RecordWithSingleValueConstructorWithJsonValueAccessor(int id) { - - @JsonValue - @Override - public int id() { - return id; - } - } - - record RecordWithNonCanonicalConstructor(int id, String name, String email) { - - public RecordWithNonCanonicalConstructor(int id, String email) { - this(id, "NonCanonicalConstructor", email); - } - } - - /** - * Similar to: - *

-     *   public class MyBean {
-     *       ...
-     *       // Single-arg constructor used by delegating creator.
-     *       public MyBean(int id) { ... }
-     *
-     *       // No-arg constructor used by properties-based creator.
-     *       public MyBean() {}
-     *
-     *       // Setters used by properties-based creator.
-     *       public void setId(int id) { ... }
-     *       public void setName(String name) { ... }
-     *   }
-     * 
- */ - record RecordWithAltSingleValueConstructor(int id, String name) { - - public RecordWithAltSingleValueConstructor(int id) { - this(id, "SingleValueConstructor"); - } - } - - private final ObjectMapper MAPPER = newJsonMapper(); - - /* - /********************************************************************** - /* Test methods, implicit factory methods & "implicit" canonical constructor - /********************************************************************** - */ - - public void testDeserializeUsingImplicitIntegerFactoryMethod() throws Exception { - RecordWithImplicitFactoryMethods factoryMethodValue = MAPPER.readValue("123", RecordWithImplicitFactoryMethods.class); - - assertEquals(new RecordWithImplicitFactoryMethods(BigDecimal.valueOf(123), "IntFactoryMethod"), factoryMethodValue); - } - - public void testDeserializeUsingImplicitDoubleFactoryMethod() throws Exception { - RecordWithImplicitFactoryMethods value = MAPPER.readValue("123.4", RecordWithImplicitFactoryMethods.class); - - assertEquals(new RecordWithImplicitFactoryMethods(BigDecimal.valueOf(123.4), "DoubleFactoryMethod"), value); - } - - public void testDeserializeUsingImplicitStringFactoryMethod() throws Exception { - RecordWithImplicitFactoryMethods value = MAPPER.readValue("\"123.4\"", RecordWithImplicitFactoryMethods.class); - - assertEquals(new RecordWithImplicitFactoryMethods(BigDecimal.valueOf(123.4), "StringFactoryMethod"), value); - } - - public void testDeserializeUsingImplicitCanonicalConstructor_WhenImplicitFactoryMethodsExist() throws Exception { - RecordWithImplicitFactoryMethods value = MAPPER.readValue( - "{\"id\":123.4,\"name\":\"CanonicalConstructor\"}", - RecordWithImplicitFactoryMethods.class); - - assertEquals(new RecordWithImplicitFactoryMethods(BigDecimal.valueOf(123.4), "CanonicalConstructor"), value); - } - - public void testDeserializeUsingImplicitFactoryMethod_WithAutoDetectCreatorsDisabled_WillFail() throws Exception { - ObjectMapper mapper = jsonMapperBuilder() - .disable(MapperFeature.AUTO_DETECT_CREATORS) - .build(); - - try { - mapper.readValue("123", RecordWithImplicitFactoryMethods.class); - - fail("should not pass"); - } catch (DatabindException e) { - verifyException(e, "Cannot construct instance"); - verifyException(e, "RecordWithImplicitFactoryMethod"); - verifyException(e, "no int/Int-argument constructor/factory method"); - } - } - - /* - /********************************************************************** - /* Test methods, implicit single-value constructor - /********************************************************************** - */ - - /** - * This test-case is just for documentation purpose: - * GOTCHA: For JavaBean, only having single-value constructor results in implicit delegating creator. But for - * Records, the CANONICAL single-value constructor results in properties-based creator. - *

- * It will result in implicit delegating constructor only when: - *

    - *
  • - * There's NON-CANONICAL single-value constructor - see - * {@link #testDeserializeUsingImplicitDelegatingConstructor()}, or - *
  • - *
  • - * {@code @JsonValue} annotation is used - see - * {@link #testDeserializeUsingImplicitSingleValueConstructor_WithJsonValue()}, - * {@link #testDeserializeUsingImplicitSingleValueConstructor_WithJsonValueAccessor()} - *
  • - *
. - *

- * yihtserns: maybe we can change this to adopt JavaBean's behaviour, but I prefer to not break existing behaviour - * until and unless there's a discussion on this. - */ - public void testDeserializeUsingImplicitSingleValueConstructor() throws Exception { - try { - // Cannot use delegating creator, unlike when dealing with JavaBean - MAPPER.readValue("123", RecordWithSingleValueConstructor.class); - - fail("should not pass"); - } catch (MismatchedInputException e) { - verifyException(e, "Cannot construct instance"); - verifyException(e, "RecordWithSingleValueConstructor"); - verifyException(e, "at least one Creator exists"); - verifyException(e, "no int/Int-argument constructor/factory method"); - } - - // Can only use properties-based creator - RecordWithSingleValueConstructor value = MAPPER.readValue("{\"id\":123}", RecordWithSingleValueConstructor.class); - assertEquals(new RecordWithSingleValueConstructor(123), value); - } - - /** - * This test-case is just for documentation purpose: - * See {@link #testDeserializeUsingImplicitSingleValueConstructor} - */ - public void testDeserializeSingleValueConstructor_WithDelegatingConstructorDetector_WillFail() throws Exception { - MAPPER.setConstructorDetector(ConstructorDetector.USE_DELEGATING); - - try { - // Fail, no delegating creator - MAPPER.readValue("123", RecordWithSingleValueConstructor.class); - - fail("should not pass"); - } catch (MismatchedInputException e) { - verifyException(e, "Cannot construct instance"); - verifyException(e, "RecordWithSingleValueConstructor"); - verifyException(e, "at least one Creator exists"); - verifyException(e, "no int/Int-argument constructor/factory method"); - } - - // Only have properties-based creator - RecordWithSingleValueConstructor value = MAPPER.readValue("{\"id\":123}", RecordWithSingleValueConstructor.class); - assertEquals(new RecordWithSingleValueConstructor(123), value); - } - - /** - * This is just to catch any potential regression. - */ - public void testDeserializeSingleValueConstructor_WithPropertiesBasedConstructorDetector_WillFail() throws Exception { - MAPPER.setConstructorDetector(ConstructorDetector.USE_PROPERTIES_BASED); - - try { - // This should fail - MAPPER.readValue("123", RecordWithSingleValueConstructor.class); - - fail("should not pass"); - } catch (MismatchedInputException e) { - verifyException(e, "Cannot construct instance"); - verifyException(e, "RecordWithSingleValueConstructor"); - verifyException(e, "at least one Creator exists"); - verifyException(e, "no int/Int-argument constructor/factory method"); - } - - // This should work - RecordWithSingleValueConstructor value = MAPPER.readValue("{\"id\":123}", RecordWithSingleValueConstructor.class); - assertEquals(new RecordWithSingleValueConstructor(123), value); - } - - /* - /********************************************************************** - /* Test methods, implicit single-value constructor + @JsonValue - /********************************************************************** - */ - - /** - * [databind#3180] - * This test-case is just for documentation purpose: - * Unlike {@link #testDeserializeUsingImplicitSingleValueConstructor()}, annotating {@code @JsonValue} - * to a Record's header results in a delegating constructor. - */ - public void testDeserializeUsingImplicitSingleValueConstructor_WithJsonValue() throws Exception { - // Can use delegating creator - RecordWithSingleValueConstructorWithJsonValue value = MAPPER.readValue( - "123", - RecordWithSingleValueConstructorWithJsonValue.class); - assertEquals(new RecordWithSingleValueConstructorWithJsonValue(123), value); - - try { - // Can no longer use properties-based creator - MAPPER.readValue("{\"id\":123}", RecordWithSingleValueConstructorWithJsonValue.class); - - fail("should not pass"); - } catch (MismatchedInputException e) { - verifyException(e, "Cannot construct instance"); - verifyException(e, "RecordWithSingleValueConstructorWithJsonValue"); - verifyException(e, "although at least one Creator exists"); - verifyException(e, "cannot deserialize from Object value"); - } - } - - /** - * [databind#3180] - * This test-case is just for documentation purpose: - * Unlike {@link #testDeserializeUsingImplicitSingleValueConstructor()}, annotating {@code @JsonValue} - * to the accessor results in a delegating creator. - */ - public void testDeserializeUsingImplicitSingleValueConstructor_WithJsonValueAccessor() throws Exception { - // Can use delegating creator - RecordWithSingleValueConstructorWithJsonValueAccessor value = MAPPER.readValue( - "123", - RecordWithSingleValueConstructorWithJsonValueAccessor.class); - assertEquals(new RecordWithSingleValueConstructorWithJsonValueAccessor(123), value); - - try { - // Can no longer use properties-based creator - MAPPER.readValue("{\"id\":123}", RecordWithSingleValueConstructorWithJsonValueAccessor.class); - - fail("should not pass"); - } catch (MismatchedInputException e) { - verifyException(e, "Cannot construct instance"); - verifyException(e, "RecordWithSingleValueConstructorWithJsonValueAccessor"); - verifyException(e, "although at least one Creator exists"); - verifyException(e, "cannot deserialize from Object value"); - } - } - - /* - /********************************************************************** - /* Test methods, implicit properties-based + delegating constructor - /********************************************************************** - */ - - public void testDeserializeUsingImplicitPropertiesBasedConstructor() throws Exception { - RecordWithAltSingleValueConstructor value = MAPPER.readValue( - "{\"id\":123,\"name\":\"PropertiesBasedConstructor\"}", - RecordWithAltSingleValueConstructor.class); - - assertEquals(new RecordWithAltSingleValueConstructor(123, "PropertiesBasedConstructor"), value); - } - - /** - * @see #testDeserializeUsingImplicitSingleValueConstructor() - */ - public void testDeserializeUsingImplicitDelegatingConstructor() throws Exception { - RecordWithAltSingleValueConstructor value = MAPPER.readValue("123", RecordWithAltSingleValueConstructor.class); - - assertEquals(new RecordWithAltSingleValueConstructor(123, "SingleValueConstructor"), value); - } - - /* - /********************************************************************** - /* Test methods, implicit parameter names - /********************************************************************** - */ - - public void testDeserializeMultipleConstructorsRecord_WithImplicitParameterNames_WillUseCanonicalConstructor() throws Exception { - MAPPER.setAnnotationIntrospector(new Jdk8ConstructorParameterNameAnnotationIntrospector()); - - RecordWithNonCanonicalConstructor value = MAPPER.readValue( - "{\"id\":123,\"name\":\"Bob\",\"email\":\"bob@example.com\"}", - RecordWithNonCanonicalConstructor.class); - - assertEquals(new RecordWithNonCanonicalConstructor(123, "Bob", "bob@example.com"), value); - } - - public void testDeserializeMultipleConstructorsRecord_WithImplicitParameterNames_WillIgnoreNonCanonicalConstructor() throws Exception { - MAPPER.setAnnotationIntrospector(new Jdk8ConstructorParameterNameAnnotationIntrospector()); - - RecordWithNonCanonicalConstructor value = MAPPER.readValue( - "{\"id\":123,\"email\":\"bob@example.com\"}", - RecordWithNonCanonicalConstructor.class); - - assertEquals(new RecordWithNonCanonicalConstructor(123, null, "bob@example.com"), value); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordImplicitSingleValueUsePropertiesBasedCreatorsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordImplicitSingleValueUsePropertiesBasedCreatorsTest.java deleted file mode 100644 index 9aca999836..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordImplicitSingleValueUsePropertiesBasedCreatorsTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.cfg.ConstructorDetector; -import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; - -public class RecordImplicitSingleValueUsePropertiesBasedCreatorsTest extends BaseMapTest -{ - - record RecordWithMultiValueCanonAndSingleValueAltConstructor(int id, String name) { - - public RecordWithMultiValueCanonAndSingleValueAltConstructor(int id) { - this(id, "AltConstructor"); - } - } - - record RecordWithSingleValueCanonAndMultiValueAltConstructor(String name) { - - public RecordWithSingleValueCanonAndMultiValueAltConstructor(String name, String email) { - this("AltConstructor"); - } - } - - private final ObjectMapper MAPPER = jsonMapperBuilder() - .annotationIntrospector(new Jdk8ConstructorParameterNameAnnotationIntrospector()) - .constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED) - .build(); - - /* - /********************************************************************** - /* Test methods, multi-value canonical constructor + single-value alt constructor - /********************************************************************** - */ - - public void testDeserializeMultipleConstructors_UsingMultiValueCanonicalConstructor() throws Exception { - RecordWithMultiValueCanonAndSingleValueAltConstructor value = MAPPER.readValue( - a2q("{'id':123,'name':'Bob'}"), - RecordWithMultiValueCanonAndSingleValueAltConstructor.class); - - assertEquals(new RecordWithMultiValueCanonAndSingleValueAltConstructor(123, "Bob"), value); - } - - public void testDeserializeMultipleConstructors_WillIgnoreSingleValueAltConstructor() throws Exception { - RecordWithMultiValueCanonAndSingleValueAltConstructor value = MAPPER.readValue( - a2q("{'id':123}"), - RecordWithMultiValueCanonAndSingleValueAltConstructor.class); - - assertEquals(new RecordWithMultiValueCanonAndSingleValueAltConstructor(123, null), value); - } - - /* - /********************************************************************** - /* Test methods, single-value canonical constructor + multi-value alt constructor - /********************************************************************** - */ - - public void testDeserializeMultipleConstructors_UsingSingleValueCanonicalConstructor() throws Exception { - RecordWithSingleValueCanonAndMultiValueAltConstructor value = MAPPER.readValue( - a2q("{'name':'Bob'}"), - RecordWithSingleValueCanonAndMultiValueAltConstructor.class); - - assertEquals(new RecordWithSingleValueCanonAndMultiValueAltConstructor("Bob"), value); - } - - public void testDeserializeMultipleConstructors_WillIgnoreMultiValueAltConstructor() throws Exception { - try { - MAPPER.readValue(a2q("{'name':'Bob','email':'bob@email.com'}"), RecordWithSingleValueCanonAndMultiValueAltConstructor.class); - } catch (UnrecognizedPropertyException e) { - verifyException(e, "Unrecognized"); - verifyException(e, "\"email\""); - verifyException(e, "RecordWithSingleValueCanonAndMultiValueAltConstructor"); - } - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordJsonValue3063Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordJsonValue3063Test.java deleted file mode 100644 index fe6512f2da..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordJsonValue3063Test.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import java.util.Collections; -import java.util.Map; - -import com.fasterxml.jackson.annotation.*; - -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; - -public class RecordJsonValue3063Test extends BaseMapTest -{ - // [databind#3063] - record GetLocations3063(@JsonValue Map nameToLocation) - { - @JsonCreator - public GetLocations3063(Map nameToLocation) - { - this.nameToLocation = nameToLocation; - } - } - - private final ObjectMapper MAPPER = newJsonMapper(); - - // [databind#3063] - public void testRecordWithJsonValue3063() throws Exception - { - Map locations = Collections.singletonMap("a", "locationA"); - String json = MAPPER.writeValueAsString(new GetLocations3063(locations)); - - assertNotNull(json); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordNamingStrategy2992Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordNamingStrategy2992Test.java deleted file mode 100644 index f098009748..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordNamingStrategy2992Test.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -public class RecordNamingStrategy2992Test extends BaseMapTest -{ - @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) - record Record2992(String myId, String myValue) {} - - private final ObjectMapper MAPPER = newJsonMapper(); - - // [databind#2992] - public void testRecordRenaming2992() throws Exception - { - Record2992 src = new Record2992("id", "value"); - String json = MAPPER.writeValueAsString(src); - assertEquals(a2q("{'my_id':'id','my_value':'value'}"), json); - Record2992 after = MAPPER.readValue(json, Record2992.class); - assertEquals(src.myId(), after.myId()); - assertEquals(src.myValue(), after.myValue()); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordNullHandling3847Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordNullHandling3847Test.java deleted file mode 100644 index f7f87f3590..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordNullHandling3847Test.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.cfg.CoercionAction; -import com.fasterxml.jackson.databind.cfg.CoercionInputShape; -import com.fasterxml.jackson.databind.exc.InvalidNullException; -import com.fasterxml.jackson.databind.json.JsonMapper; - -// [databind#3874] -public class RecordNullHandling3847Test extends BaseMapTest { - /* - /********************************************************** - /* Set up - /********************************************************** - */ - - static class Pojo3847 { - public String fieldName; - } - - public record PlainRecord(String fieldName) {} - public record IntRecord(String description, int value) {} - - public record FixedRecord(@JsonProperty("field_name") String fieldName) {} - - /* - /********************************************************** - /* Tests - /********************************************************** - */ - - private final ObjectMapper NULL_MAPPER = JsonMapper.builder() - .defaultSetterInfo(JsonSetter.Value.construct(Nulls.FAIL, Nulls.FAIL)) - .withCoercionConfigDefaults(config -> config.setCoercion(CoercionInputShape.String, - CoercionAction.Fail)) - .build(); - - public void testPojoNullHandlingValid() throws Exception { - Pojo3847 pojo = NULL_MAPPER.readValue(a2q("{'fieldName': 'value'}"), Pojo3847.class); // expected - assertEquals("value", pojo.fieldName); - } - - public void testPojoNullHandlingNullValue() throws Exception { - try { - NULL_MAPPER.readValue(a2q("{'fieldName': null}"), Pojo3847.class); // expected - fail("should expect InvalidNullException"); - } catch (InvalidNullException e) { - verifyException(e, "Invalid `null` value encountered for property \"fieldName\""); - } - } - - public void testPojoNullHandlingEmptyJson() throws Exception { - assertNotNull(NULL_MAPPER.readValue("{}", Pojo3847.class)); - } - - public void testRecordNullHandlingValid() throws Exception { - PlainRecord plainRecord = NULL_MAPPER.readValue(a2q("{'fieldName': 'value'}"), PlainRecord.class); - assertEquals("value", plainRecord.fieldName); - } - - public void testRecordNullHandlingNullValue() throws Exception { - try { - NULL_MAPPER.readValue(a2q("{'fieldName': null}"), PlainRecord.class); - fail("should expect InvalidNullException"); - } catch (InvalidNullException e) { - verifyException(e, "Invalid `null` value encountered for property \"fieldName\""); - } - } - - public void testRecordNullHandlingEmptyJson() throws Exception { - try { - NULL_MAPPER.readValue("{}", PlainRecord.class); - fail("should expect InvalidNullException"); - } catch (InvalidNullException e) { - verifyException(e, "Invalid `null` value encountered for property \"fieldName\""); - } - } - - public void testRecordFixerNullHandlingValid() throws Exception { - FixedRecord fixedRecord = NULL_MAPPER.readValue(a2q("{ 'field_name': 'value' }"), FixedRecord.class); - assertEquals("value", fixedRecord.fieldName); - } - - public void testRecordFixerNullHandlingNullValue() throws Exception { - try { - NULL_MAPPER.readValue(a2q("{ 'field_name': null }"), FixedRecord.class); - fail("should expect InvalidNullException"); - } catch (InvalidNullException e) { - verifyException(e, "Invalid `null` value encountered for property \"field_name\""); - } - } - - public void testRecordFixerNullHandlingEmptyJson() throws Exception { - try { - NULL_MAPPER.readValue("{}", FixedRecord.class); - fail("should expect InvalidNullException"); - } catch (InvalidNullException e) { - verifyException(e, "Invalid `null` value encountered for property \"field_name\""); - } - } - - public void testRecordDefaultNullDeserialization() throws Exception { - PlainRecord pr = new ObjectMapper().readValue("{}", PlainRecord.class); - assertNull(pr.fieldName); - } - - public void testIntRecordDefaultNullDeserialization() throws Exception { - IntRecord ir = new ObjectMapper().readValue("{}", IntRecord.class); - assertNull(ir.description); - assertEquals(0, ir.value); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordSerializationOrderTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordSerializationOrderTest.java deleted file mode 100644 index 494904c3e9..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordSerializationOrderTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; - -public class RecordSerializationOrderTest extends BaseMapTest -{ - record NestedRecordOne(String id, String email, NestedRecordTwo nestedRecordTwo) {} - record NestedRecordOneWithJsonProperty(String id, String email, - @JsonProperty("nestedProperty") NestedRecordTwo nestedRecordTwo) {} - record NestedRecordOneWithJsonPropertyIndex(@JsonProperty(index = 2) String id, - @JsonProperty(index = 0) String email, - @JsonProperty(value = "nestedProperty", index = 1) NestedRecordTwo nestedRecordTwo) {} - - @JsonPropertyOrder({"email", "nestedProperty", "id"}) - record NestedRecordOneWithJsonPropertyOrder(String id, - String email, - @JsonProperty(value = "nestedProperty") NestedRecordTwo nestedRecordTwo) {} - - record NestedRecordTwo(String id, String passport) {} - - private final ObjectMapper MAPPER = newJsonMapper(); - - /* - /********************************************************************** - /* Test methods, alternate constructors - /********************************************************************** - */ - - public void testSerializationOrder() throws Exception { - NestedRecordTwo nestedRecordTwo = new NestedRecordTwo("2", "111110"); - NestedRecordOne nestedRecordOne = new NestedRecordOne("1", "test@records.com", nestedRecordTwo); - final String output = MAPPER.writeValueAsString(nestedRecordOne); - final String expected = "{\"id\":\"1\",\"email\":\"test@records.com\",\"nestedRecordTwo\":{\"id\":\"2\",\"passport\":\"111110\"}}"; - assertEquals(expected, output); - } - - public void testSerializationOrderWithJsonProperty() throws Exception { - NestedRecordTwo nestedRecordTwo = new NestedRecordTwo("2", "111110"); - NestedRecordOneWithJsonProperty nestedRecordOne = - new NestedRecordOneWithJsonProperty("1", "test@records.com", nestedRecordTwo); - final String output = MAPPER.writeValueAsString(nestedRecordOne); - final String expected = "{\"id\":\"1\",\"email\":\"test@records.com\",\"nestedProperty\":{\"id\":\"2\",\"passport\":\"111110\"}}"; - assertEquals(expected, output); - } - - public void testSerializationOrderWithJsonPropertyIndexes() throws Exception { - NestedRecordTwo nestedRecordTwo = new NestedRecordTwo("2", "111110"); - NestedRecordOneWithJsonPropertyIndex nestedRecordOne = - new NestedRecordOneWithJsonPropertyIndex("1", "test@records.com", nestedRecordTwo); - final String output = MAPPER.writeValueAsString(nestedRecordOne); - final String expected = "{\"email\":\"test@records.com\",\"nestedProperty\":{\"id\":\"2\",\"passport\":\"111110\"},\"id\":\"1\"}"; - assertEquals(expected, output); - } - - public void testSerializationOrderWithJsonPropertyOrder() throws Exception { - NestedRecordTwo nestedRecordTwo = new NestedRecordTwo("2", "111110"); - NestedRecordOneWithJsonPropertyOrder nestedRecordOne = - new NestedRecordOneWithJsonPropertyOrder("1", "test@records.com", nestedRecordTwo); - final String output = MAPPER.writeValueAsString(nestedRecordOne); - final String expected = "{\"email\":\"test@records.com\",\"nestedProperty\":{\"id\":\"2\",\"passport\":\"111110\"},\"id\":\"1\"}"; - assertEquals(expected, output); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordTypeInfo3342Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordTypeInfo3342Test.java deleted file mode 100644 index c565832b40..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordTypeInfo3342Test.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; - -// [databind#3102] -public class RecordTypeInfo3342Test extends BaseMapTest -{ - public enum SpiceLevel { - LOW, - HIGH - } - - public interface SpiceTolerance { - } - - public record LowSpiceTolerance(String food) implements SpiceTolerance { - } - - public record HighSpiceTolerance(String food) implements SpiceTolerance { - } - - public record Example( - SpiceLevel level, - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXTERNAL_PROPERTY, - property = "level") - @JsonSubTypes({ - @JsonSubTypes.Type(value = LowSpiceTolerance.class, name = "LOW"), - @JsonSubTypes.Type(value = HighSpiceTolerance.class, name = "HIGH") - }) - SpiceTolerance tolerance) { } - - private final ObjectMapper MAPPER = newJsonMapper(); - - public void testSerializeDeserializeJsonSubType_LOW() throws Exception { - Example record = new Example(SpiceLevel.LOW, new LowSpiceTolerance("Tomato")); - - String json = MAPPER.writeValueAsString(record); - assertEquals("{\"level\":\"LOW\",\"tolerance\":{\"food\":\"Tomato\"}}", json); - - Example value = MAPPER.readValue(json, Example.class); - assertEquals(record, value); - } - - public void testSerializeDeserializeJsonSubType_HIGH() throws Exception { - Example record = new Example(SpiceLevel.HIGH, new HighSpiceTolerance("Chilli")); - - String json = MAPPER.writeValueAsString(record); - assertEquals("{\"level\":\"HIGH\",\"tolerance\":{\"food\":\"Chilli\"}}", json); - - Example value = MAPPER.readValue(json, Example.class); - assertEquals(record, value); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordUpdate3079Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordUpdate3079Test.java deleted file mode 100644 index 09b3f5b227..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordUpdate3079Test.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; - -// see failing test -public class RecordUpdate3079Test extends BaseMapTest -{ - public record IdNameRecord(int id, String name) { } - - static class IdNameWrapper { - public IdNameRecord value; - - protected IdNameWrapper() { } - public IdNameWrapper(IdNameRecord v) { value = v; } - } - - private final ObjectMapper MAPPER = newJsonMapper(); - - // [databind#3079]: also: should be able to Record valued property - public void testRecordAsPropertyUpdate() throws Exception - { - IdNameRecord origRecord = new IdNameRecord(123, "Bob"); - IdNameWrapper orig = new IdNameWrapper(origRecord); - - IdNameWrapper delta = new IdNameWrapper(new IdNameRecord(200, "Gary")); - IdNameWrapper result = MAPPER.updateValue(orig, delta); - - assertEquals(200, result.value.id()); - assertEquals("Gary", result.value.name()); - assertSame(orig, result); - assertNotSame(origRecord, result.value); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithIgnoreOverride3992Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithIgnoreOverride3992Test.java deleted file mode 100644 index b2b31706c1..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithIgnoreOverride3992Test.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import java.util.*; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; - -public class RecordWithIgnoreOverride3992Test extends BaseMapTest -{ - // [databind#3992] - public record HelloRecord(String text, @JsonIgnore Recursion hidden) { - // Before fix: works if this override is removed - // After fix: works either way - @Override - public Recursion hidden() { - return hidden; - } - } - - static class Recursion { - public List all = new ArrayList<>(); - - void add(Recursion recursion) { - all.add(recursion); - } - } - - private final ObjectMapper MAPPER = newJsonMapper(); - - /* - /********************************************************************** - /* Test methods - /********************************************************************** - */ - - // [databind#3992] - public void testHelloRecord() throws Exception { - Recursion beanWithRecursion = new Recursion(); - beanWithRecursion.add(beanWithRecursion); - String json = MAPPER.writer() - .writeValueAsString(new HelloRecord("hello", beanWithRecursion)); - assertEquals(a2q("{'text':'hello'}"), json); - - // Let's check deserialization works too, just in case. - HelloRecord result = MAPPER.readValue(json, HelloRecord.class); - assertNotNull(result); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonIgnoreTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonIgnoreTest.java deleted file mode 100644 index d68e12f62b..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonIgnoreTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; - -public class RecordWithJsonIgnoreTest extends BaseMapTest -{ - record RecordWithIgnore(int id, @JsonIgnore String name) { - } - - record RecordWithIgnoreJsonProperty(int id, @JsonIgnore @JsonProperty("name") String name) { - } - - record RecordWithIgnoreAccessor(int id, String name) { - - @JsonIgnore - @Override - public String name() { - return name; - } - } - - record RecordWithIgnorePrimitiveType(@JsonIgnore int id, String name) { - } - - private final ObjectMapper MAPPER = newJsonMapper(); - - /* - /********************************************************************** - /* Test methods, JsonIgnore - /********************************************************************** - */ - - public void testSerializeJsonIgnoreRecord() throws Exception { - String json = MAPPER.writeValueAsString(new RecordWithIgnore(123, "Bob")); - assertEquals("{\"id\":123}", json); - } - - public void testDeserializeJsonIgnoreRecord() throws Exception { - RecordWithIgnore value = MAPPER.readValue("{\"id\":123,\"name\":\"Bob\"}", - RecordWithIgnore.class); - assertEquals(new RecordWithIgnore(123, null), value); - } - - /* - /********************************************************************** - /* Test methods, JsonIgnore + JsonProperty - /********************************************************************** - */ - - public void testSerializeJsonIgnoreAndJsonPropertyRecord() throws Exception { - String json = MAPPER.writeValueAsString(new RecordWithIgnoreJsonProperty(123, "Bob")); - assertEquals("{\"id\":123}", json); - } - - public void testDeserializeJsonIgnoreAndJsonPropertyRecord() throws Exception { - RecordWithIgnoreJsonProperty value = MAPPER.readValue("{\"id\":123,\"name\":\"Bob\"}", RecordWithIgnoreJsonProperty.class); - assertEquals(new RecordWithIgnoreJsonProperty(123, "Bob"), value); - } - - /* - /********************************************************************** - /* Test methods, JsonIgnore accessor - /********************************************************************** - */ - - public void testSerializeJsonIgnoreAccessorRecord() throws Exception { - String json = MAPPER.writeValueAsString(new RecordWithIgnoreAccessor(123, "Bob")); - assertEquals("{\"id\":123}", json); - } - - public void testDeserializeJsonIgnoreAccessorRecord() throws Exception { - RecordWithIgnoreAccessor value = MAPPER.readValue("{\"id\":123,\"name\":\"Bob\"}", - RecordWithIgnoreAccessor.class); - assertEquals(new RecordWithIgnoreAccessor(123, null), value); - } - - /* - /********************************************************************** - /* Test methods, JsonIgnore parameter of primitive type - /********************************************************************** - */ - - public void testSerializeJsonIgnorePrimitiveTypeRecord() throws Exception { - String json = MAPPER.writeValueAsString(new RecordWithIgnorePrimitiveType(123, "Bob")); - assertEquals("{\"name\":\"Bob\"}", json); - } - - public void testDeserializeJsonIgnorePrimitiveTypeRecord() throws Exception { - RecordWithIgnorePrimitiveType value = MAPPER.readValue("{\"id\":123,\"name\":\"Bob\"}", RecordWithIgnorePrimitiveType.class); - assertEquals(new RecordWithIgnorePrimitiveType(0, "Bob"), value); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonNaming3102Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonNaming3102Test.java deleted file mode 100644 index 3533b6362b..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonNaming3102Test.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.annotation.JsonCreator; - -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectReader; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -public class RecordWithJsonNaming3102Test extends BaseMapTest -{ - @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) - public record SnakeRecord(int id, String toSnakeCase) { - @JsonCreator - public SnakeRecord(int id, String toSnakeCase) { - this.id = id; - this.toSnakeCase = toSnakeCase; - } - } - - private final ObjectMapper MAPPER = newJsonMapper(); - - /* - /********************************************************************** - /* Test methods, Record type introspection - /********************************************************************** - */ - - // [databind#3102] - public void testDeserializeWithJsonNaming() throws Exception - { - final ObjectReader r = MAPPER.readerFor(SnakeRecord.class); - // First, regular case - SnakeRecord value = r.readValue(a2q( - "{'id':123,'to_snake_case':'snakey'}")); - assertEquals(123, value.id); - assertEquals("snakey", value.toSnakeCase); - } -} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonSetter2974Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonSetter2974Test.java deleted file mode 100644 index a54825b83e..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonSetter2974Test.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectReader; -import com.fasterxml.jackson.databind.exc.InvalidNullException; - -public class RecordWithJsonSetter2974Test extends BaseMapTest -{ - record RecordWithNonNullDefs(@JsonSetter(nulls=Nulls.AS_EMPTY) List names, - @JsonSetter(nulls=Nulls.FAIL) Map agesByNames) - { } - - private final ObjectMapper MAPPER = newJsonMapper(); - - /* - /********************************************************************** - /* Test methods, Record type introspection - /********************************************************************** - */ - - // [databind#2974] - public void testDeserializeWithNullAsEmpty() throws Exception - { - final ObjectReader r = MAPPER.readerFor(RecordWithNonNullDefs.class); - // First, regular case - RecordWithNonNullDefs value = r.readValue(a2q( -"{'names':['bob'],'agesByNames':{'bob':39}}")); - assertEquals(1, value.names().size()); - assertEquals("bob", value.names().get(0)); - assertEquals(1, value.agesByNames().size()); - assertEquals(Integer.valueOf(39), value.agesByNames().get("bob")); - - // Then leave out list - value = r.readValue(a2q("{'agesByNames':{'bob':42}}")); - assertNotNull(value.names()); - assertEquals(0, value.names().size()); - assertNotNull(value.agesByNames()); - assertEquals(1, value.agesByNames().size()); - assertEquals(Integer.valueOf(42), value.agesByNames().get("bob")); - } - - // [databind#2974] - public void testDeserializeWithFailForNull() throws Exception - { - final ObjectReader r = MAPPER.readerFor(RecordWithNonNullDefs.class); - // First, regular case - // But attempting to leave out Map ought to fail - try { - /*RecordWithNonNullDefs value =*/ r.readValue(a2q("{'names':['bob']}")); - fail("Should not pass with missing/null 'agesByNames'"); - } catch (InvalidNullException e) { - verifyException(e, "property \"agesByNames\""); - } - } -} diff --git a/src/test-jdk21/java/com/fasterxml/jackson/databind/jdk21/Java21CollectionsTest.java b/src/test-jdk21/java/com/fasterxml/jackson/databind/jdk21/Java21CollectionsTest.java deleted file mode 100644 index 4f2908ef1a..0000000000 --- a/src/test-jdk21/java/com/fasterxml/jackson/databind/jdk21/Java21CollectionsTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.fasterxml.jackson.databind.jdk21; - -import java.util.*; - -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.json.JsonMapper; - -public class Java21CollectionsTest extends BaseMapTest -{ - // [databind#4089] - record SequencedCollections( - SequencedCollection sequencedCollection, - SequencedSet sequencedSet, - SequencedMap sequencedMap) { - } - - public void testSequencedCollectionTypesDeserialize() throws Exception { - String json = """ - { - "sequencedCollection": ["A", "B"], - "sequencedSet": ["C", "D"], - "sequencedMap": {"A": 1, "B": 2} - } - """; - - ObjectMapper objectMapper = JsonMapper.builder().build(); - SequencedCollections value = objectMapper.readValue(json, SequencedCollections.class); - assertEquals(ArrayList.class, value.sequencedCollection.getClass()); - assertEquals(LinkedHashSet.class, value.sequencedSet.getClass()); - assertEquals(LinkedHashMap.class, value.sequencedMap.getClass()); - } - - public void testSequencedCollectionTypesRoundTrip() throws Exception { - ArrayList arrayList = new ArrayList<>(); - arrayList.add("A"); - arrayList.add("B"); - LinkedHashSet linkedHashSet = new LinkedHashSet<>(); - linkedHashSet.add("C"); - linkedHashSet.add("D"); - LinkedHashMap linkedHashMap = new LinkedHashMap<>(); - linkedHashMap.put("A", 1); - linkedHashMap.put("B", 2); - SequencedCollections input = new SequencedCollections(arrayList, linkedHashSet, linkedHashMap); - - ObjectMapper objectMapper = JsonMapper.builder().build(); - String json = objectMapper.writeValueAsString(input); - SequencedCollections value = objectMapper.readValue(json, SequencedCollections.class); - assertEquals(input, value); - } -} From 1faccf0e56e3a1b590a84acce3546d343176ae12 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 3 Feb 2024 15:50:52 +0900 Subject: [PATCH 02/60] Working solution first draft --- .../deser/BasicDeserializerFactory.java | 8 ++- .../databind/deser/BeanDeserializer.java | 9 +++- .../deser/BeanDeserializerFactory.java | 32 ++++++++++++ .../deser/impl/PropertyBasedCreator.java | 8 +++ .../deser/impl/PropertyValueBuffer.java | 17 ++++++ .../failing/AnySetterForCreator562Test.java | 14 +++-- .../JsonAnySetterThroughCreator562Test.java | 52 +++++++++++++++++++ 7 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/failing/JsonAnySetterThroughCreator562Test.java 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 ff060f4f5d..fdd937be33 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -879,8 +879,14 @@ protected void _addExplicitPropertyCreator(DeserializationContext ctxt, */ } name = candidate.findImplicitParamName(i); - _validateNamedPropertyParameter(ctxt, beanDesc, candidate, i, + if (ctxt.getAnnotationIntrospector().hasAnySetter(param)) { + // [databind#562] Any setter can be used... + System.out.println(); + name = PropertyName.construct("leftovers"); + } else { + _validateNamedPropertyParameter(ctxt, beanDesc, candidate, i, name, injectId); + } } properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index 25ae119c6c..9bc05baee6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -497,7 +497,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri // "any property"? if (_anySetter != null) { try { - buffer.bufferAnyProperty(_anySetter, propName, _anySetter.deserialize(p, ctxt)); + buffer.bufferMapProperty(propName, _anySetter.deserialize(p, ctxt)); } catch (Exception e) { wrapAndThrow(e, _beanType.getRawClass(), propName, ctxt); } @@ -523,10 +523,15 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri // We hit END_OBJECT, so: Object bean; try { - bean = creator.build(ctxt, buffer); + if (_anySetter != null) { + bean = creator.buildSimple(ctxt, buffer); + } else { + bean = creator.build(ctxt, buffer); + } } catch (Exception e) { return wrapInstantiationProblem(e, ctxt); } + // 13-Apr-2020, tatu: [databind#2678] need to handle injection here if (_injectables != null) { injectValues(ctxt, bean); diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index 8487b1e049..4cdb3b7bfc 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -546,6 +546,9 @@ protected void addBeanProps(DeserializationContext ctxt, AnnotatedMember anySetter = beanDesc.findAnySetterAccessor(); if (anySetter != null) { builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetter)); + } + else if (ctxt.getAnnotationIntrospector().hasAnySetter(creatorProps[1].getMember())) { + builder.setAnySetter(constructAnySetter(ctxt, beanDesc, creatorProps[1].getMember())); } else { // 23-Jan-2018, tatu: although [databind#1805] would suggest we should block // properties regardless, for now only consider unless there's any setter... @@ -819,6 +822,35 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, valueType, null, mutator, PropertyMetadata.STD_OPTIONAL); + } else if (mutator instanceof AnnotatedParameter){ + AnnotatedParameter af = (AnnotatedParameter) mutator; + // get the type from the content type of the map object + JavaType fieldType = af.getType(); + // 31-Jul-2022, tatu: Not just Maps any more but also JsonNode, so: + if (fieldType.isMapLikeType()) { + fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); + keyType = fieldType.getKeyType(); + valueType = fieldType.getContentType(); + prop = new BeanProperty.Std(PropertyName.construct("stuff"), + fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); + } else if (fieldType.hasRawClass(JsonNode.class) + || fieldType.hasRawClass(ObjectNode.class)) { + fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); + // Deserialize is individual values of ObjectNode, not full ObjectNode, so: + valueType = ctxt.constructType(JsonNode.class); + prop = new BeanProperty.Std(PropertyName.construct(mutator.getName()), + fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); + + // Unlike with more complicated types, here we do not allow any annotation + // overrides etc but instead short-cut handling: + return SettableAnyProperty.constructForJsonNodeField(ctxt, + prop, mutator, valueType, + ctxt.findRootValueDeserializer(valueType)); + } else { + return ctxt.reportBadDefinition(beanDesc.getType(), String.format( + "Unsupported type for any-setter: %s -- only support `Map`s, `JsonNode` and `ObjectNode` ", + ClassUtil.getTypeDescription(fieldType))); + } } else if (isField) { AnnotatedField af = (AnnotatedField) mutator; // get the type from the content type of the map object diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java index 5225baaab6..c82564fb86 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java @@ -197,10 +197,18 @@ public PropertyValueBuffer startBuilding(JsonParser p, DeserializationContext ct return new PropertyValueBuffer(p, ctxt, _propertyCount, oir); } + public Object buildSimple(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException + { + return _valueInstantiator.createFromObjectWith(ctxt, + _allProperties, buffer); + } + public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException { Object bean = _valueInstantiator.createFromObjectWith(ctxt, _allProperties, buffer); + + // returning null isn't quite legal, but let's let caller deal with that if (bean != null) { // Object Id to handle? 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 5d323381a0..35f371fe51 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 @@ -2,12 +2,15 @@ import java.io.IOException; import java.util.BitSet; +import java.util.HashMap; +import java.util.Map; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; +import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; /** @@ -176,6 +179,20 @@ public Object[] getParameters(SettableBeanProperty[] props) } } } + // check if we have anySetter Param + // if we do, parse from buffer and set + Map param = new HashMap<>(); + for (PropertyValue next = buffered(); next != null; next = next.next) { + try { + next.assign(param); + } catch (IOException e) { + // TODO : Wrap properly + throw new JsonMappingException("TODO: Wrap"); + } + } + // find the "FIRST" creator Prop with JsonAnySetter + // Then assign value to it + _creatorParameters[1] = param; return _creatorParameters; } diff --git a/src/test/java/com/fasterxml/jackson/failing/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/failing/AnySetterForCreator562Test.java index 1b1d080b3f..2232407308 100644 --- a/src/test/java/com/fasterxml/jackson/failing/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/failing/AnySetterForCreator562Test.java @@ -1,6 +1,6 @@ package com.fasterxml.jackson.failing; -import java.util.Collections; +import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; @@ -36,12 +36,16 @@ public POJO562(@JsonProperty("a") String a, @Test public void testAnySetterViaCreator562() throws Exception { + Map expected = new HashMap<>(); + expected.put("b", Integer.valueOf(42)); + expected.put("c", Integer.valueOf(111)); + POJO562 pojo = MAPPER.readValue(a2q( - "{'a':'value', 'b':42}" + "{'a':'value', 'b':42, 'c': 111}" ), POJO562.class); - assertEquals(pojo.a, "value"); - assertEquals(Collections.singletonMap("b", Integer.valueOf(42)), - pojo.stuff); + + assertEquals("value", pojo.a); + assertEquals(expected, pojo.stuff); } } diff --git a/src/test/java/com/fasterxml/jackson/failing/JsonAnySetterThroughCreator562Test.java b/src/test/java/com/fasterxml/jackson/failing/JsonAnySetterThroughCreator562Test.java new file mode 100644 index 0000000000..a72d153dca --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/failing/JsonAnySetterThroughCreator562Test.java @@ -0,0 +1,52 @@ +package com.fasterxml.jackson.failing; + +import java.util.HashMap; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.newJsonMapper; + +public class JsonAnySetterThroughCreator562Test { + + static class MyClass { + public String field; + public HashMap anySetter; + + @JsonCreator + public MyClass( + @JsonProperty("field") String field, + @JsonAnySetter HashMap anySetter + ) { + this.field = field; + this.anySetter = anySetter; + } + } + + @Test + void testJsonAnySetterOnRecord() throws Exception { + String json = + "{\n" + + " \"field\": \"value\",\n" + + " \"unmapped1\": \"value1\",\n" + + " \"unmapped2\": \"value2\"\n" + + "}"; + HashMap expected = new HashMap<>(); + expected.put("unmapped1", "value1"); + expected.put("unmapped2", "value2"); + + + ObjectMapper objectMapper = newJsonMapper(); + + MyClass deserialized = objectMapper.readValue(json, MyClass.class); + + assertEquals("value", deserialized.field); + assertEquals(expected, deserialized.anySetter); + } +} From 8930a4e729c250d478e6986c524f59a990fe362f Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 3 Feb 2024 15:52:15 +0900 Subject: [PATCH 03/60] Revert "temporarily remove other jdk versions" This reverts commit 94540f8f6f800d46611091c9886159fdc0007b3e. --- .../jackson/databind/jdk9/Java9ListsTest.java | 103 ++++++ .../failing/RecordUpdate3079FailingTest.java | 25 ++ .../databind/jdk17/Java17CollectionsTest.java | 34 ++ .../databind/jdk17/RecordPrivate4175Test.java | 27 ++ ...orParameterNameAnnotationIntrospector.java | 30 ++ .../JsonAliasWithDeduction4327RecordTest.java | 44 +++ .../databind/records/RecordBasicsTest.java | 299 ++++++++++++++++ .../databind/records/RecordCreatorsTest.java | 82 +++++ .../RecordDeserialization3897Test.java | 23 ++ .../RecordDeserialization3906Test.java | 155 +++++++++ .../records/RecordExplicitCreatorsTest.java | 326 ++++++++++++++++++ .../records/RecordFailingSetter3938Test.java | 42 +++ .../RecordIgnoreNonAccessorGetterTest.java | 54 +++ .../records/RecordImplicitCreatorsTest.java | 325 +++++++++++++++++ ...leValueUsePropertiesBasedCreatorsTest.java | 75 ++++ .../records/RecordJsonValue3063Test.java | 33 ++ .../records/RecordNamingStrategy2992Test.java | 25 ++ .../records/RecordNullHandling3847Test.java | 116 +++++++ .../records/RecordSerializationOrderTest.java | 66 ++++ .../records/RecordTypeInfo3342Test.java | 58 ++++ .../records/RecordUpdate3079Test.java | 34 ++ .../RecordWithIgnoreOverride3992Test.java | 49 +++ .../records/RecordWithJsonIgnoreTest.java | 95 +++++ .../records/RecordWithJsonNaming3102Test.java | 40 +++ .../records/RecordWithJsonSetter2974Test.java | 61 ++++ .../databind/jdk21/Java21CollectionsTest.java | 51 +++ 26 files changed, 2272 insertions(+) create mode 100644 src/test-jdk11/java/com/fasterxml/jackson/databind/jdk9/Java9ListsTest.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/failing/RecordUpdate3079FailingTest.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/Java17CollectionsTest.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/RecordPrivate4175Test.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/Jdk8ConstructorParameterNameAnnotationIntrospector.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/JsonAliasWithDeduction4327RecordTest.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordBasicsTest.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordCreatorsTest.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordDeserialization3897Test.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordDeserialization3906Test.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordExplicitCreatorsTest.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordFailingSetter3938Test.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordIgnoreNonAccessorGetterTest.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordImplicitCreatorsTest.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordImplicitSingleValueUsePropertiesBasedCreatorsTest.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordJsonValue3063Test.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordNamingStrategy2992Test.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordNullHandling3847Test.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordSerializationOrderTest.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordTypeInfo3342Test.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordUpdate3079Test.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithIgnoreOverride3992Test.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonIgnoreTest.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonNaming3102Test.java create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonSetter2974Test.java create mode 100644 src/test-jdk21/java/com/fasterxml/jackson/databind/jdk21/Java21CollectionsTest.java diff --git a/src/test-jdk11/java/com/fasterxml/jackson/databind/jdk9/Java9ListsTest.java b/src/test-jdk11/java/com/fasterxml/jackson/databind/jdk9/Java9ListsTest.java new file mode 100644 index 0000000000..c1046a40ac --- /dev/null +++ b/src/test-jdk11/java/com/fasterxml/jackson/databind/jdk9/Java9ListsTest.java @@ -0,0 +1,103 @@ +package com.fasterxml.jackson.databind.jdk9; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.testutil.NoCheckSubTypeValidator; + +// for [databind#2900] +public class Java9ListsTest extends BaseMapTest +{ + private final ObjectMapper MAPPER = JsonMapper.builder() + .activateDefaultTypingAsProperty( + new NoCheckSubTypeValidator(), + ObjectMapper.DefaultTyping.EVERYTHING, + "@class" + ).build(); + + public void testUnmodifiableList() throws Exception + { + final List list = Collections.unmodifiableList(Collections.singletonList("a")); + final String actualJson = MAPPER.writeValueAsString(list); + final List output = MAPPER.readValue(actualJson, List.class); + assertEquals(1, output.size()); + } + + public void testJava9ListOf() throws Exception + { + List list = List.of("a"); +/* { + Class cls = list.getClass(); + com.fasterxml.jackson.databind.JavaType type = MAPPER.constructType(cls); +System.err.println("LIST type: "+type); +System.err.println(" final? "+type.isFinal()); + } + */ + String actualJson = MAPPER.writeValueAsString(list); + List output = MAPPER.readValue(actualJson, List.class); + assertEquals(1, output.size()); + + // and couple of alternatives: + list = List.of("a", "b"); + actualJson = MAPPER.writeValueAsString(list); + output = MAPPER.readValue(actualJson, List.class); + assertEquals(2, output.size()); + + list = List.of("a", "b", "c"); + actualJson = MAPPER.writeValueAsString(list); + output = MAPPER.readValue(actualJson, List.class); + assertEquals(3, output.size()); + + list = List.of(); + actualJson = MAPPER.writeValueAsString(list); + output = MAPPER.readValue(actualJson, List.class); + assertEquals(0, output.size()); + } + + public void testJava9MapOf() throws Exception + { + Map map = Map.of("key", "value"); + String actualJson = MAPPER.writeValueAsString(map); + Map output = MAPPER.readValue(actualJson, Map.class); + assertEquals(1, output.size()); + + // and alternatives + map = Map.of("key", "value", "foo", "bar"); + actualJson = MAPPER.writeValueAsString(map); + output = MAPPER.readValue(actualJson, Map.class); + assertEquals(2, output.size()); + + map = Map.of("key", "value", "foo", "bar", "last", "one"); + actualJson = MAPPER.writeValueAsString(map); + output = MAPPER.readValue(actualJson, Map.class); + assertEquals(3, output.size()); + + map = Map.of(); + actualJson = MAPPER.writeValueAsString(map); + output = MAPPER.readValue(actualJson, Map.class); + assertEquals(0, output.size()); + } + + // [databind#3344] + public void testJava9SetOf() throws Exception + { + Set set = Set.of("a", "b", "c"); + String actualJson = MAPPER.writeValueAsString(set); + Set output = MAPPER.readValue(actualJson, Set.class); + assertTrue(output instanceof Set); + assertEquals(set, output); + } + + public void testJava9ListWrapped() throws Exception + { + final List list = Collections.unmodifiableList(List.of("a")); + final String actualJson = MAPPER.writeValueAsString(list); + final List output = MAPPER.readValue(actualJson, List.class); + assertEquals(1, output.size()); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/failing/RecordUpdate3079FailingTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/failing/RecordUpdate3079FailingTest.java new file mode 100644 index 0000000000..1baa711009 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/failing/RecordUpdate3079FailingTest.java @@ -0,0 +1,25 @@ +package com.fasterxml.jackson.databind.failing; + +import java.util.Collections; + +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.records.RecordUpdate3079Test; + +// 01-Dec-2022, tatu: Alas, fails on JDK 17 +// see related passing test in RecordUpdate3079Test +public class RecordUpdate3079FailingTest extends BaseMapTest +{ + private final ObjectMapper MAPPER = newJsonMapper(); + + // [databind#3079]: Should be able to Record value directly + public void testDirectRecordUpdate() throws Exception + { + RecordUpdate3079Test.IdNameRecord orig = new RecordUpdate3079Test.IdNameRecord(123, "Bob"); + RecordUpdate3079Test.IdNameRecord result = MAPPER.updateValue(orig, + Collections.singletonMap("id", 137)); + assertNotNull(result); + assertEquals(137, result.id()); + assertEquals("Bob", result.name()); + assertNotSame(orig, result); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/Java17CollectionsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/Java17CollectionsTest.java new file mode 100644 index 0000000000..a3bfd8d277 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/Java17CollectionsTest.java @@ -0,0 +1,34 @@ +package com.fasterxml.jackson.databind.jdk17; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.testutil.NoCheckSubTypeValidator; + +public class Java17CollectionsTest extends BaseMapTest +{ + private final ObjectMapper MAPPER = JsonMapper.builder() + .activateDefaultTypingAsProperty( + new NoCheckSubTypeValidator(), + ObjectMapper.DefaultTyping.EVERYTHING, + "@class" + ).build(); + + // [databind#3404] + public void testJava9StreamOf() throws Exception + { + List input = Stream.of("a", "b", "c").collect(Collectors.toList()); + String actualJson = MAPPER.writeValueAsString(input); + List result = MAPPER.readValue(actualJson, List.class); + assertEquals(input, result); + + input = Stream.of("a", "b", "c").toList(); + actualJson = MAPPER.writeValueAsString(input); + result = MAPPER.readValue(actualJson, List.class); + assertEquals(input, result); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/RecordPrivate4175Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/RecordPrivate4175Test.java new file mode 100644 index 0000000000..0c0729a829 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/jdk17/RecordPrivate4175Test.java @@ -0,0 +1,27 @@ +package com.fasterxml.jackson.databind.jdk17; + +import java.util.Collections; + +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; + +// for [databind#4175] +public class RecordPrivate4175Test extends BaseMapTest +{ + private static record PrivateTextRecord4175(String text) { } + + private final ObjectMapper MAPPER = newJsonMapper(); + + // for [databind#4175] + public void testSerializePrivateTextRecord() throws Exception { + PrivateTextRecord4175 textRecord = new PrivateTextRecord4175("anything"); + String json = MAPPER.writeValueAsString(textRecord); + final Object EXP = Collections.singletonMap("text", "anything"); + assertEquals(EXP, MAPPER.readValue(json, Object.class)); + } + + public void testDeserializePrivateTextRecord() throws Exception { + assertEquals(new PrivateTextRecord4175("anything"), + MAPPER.readValue("{\"text\":\"anything\"}", PrivateTextRecord4175.class)); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/Jdk8ConstructorParameterNameAnnotationIntrospector.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/Jdk8ConstructorParameterNameAnnotationIntrospector.java new file mode 100644 index 0000000000..333fb6208c --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/Jdk8ConstructorParameterNameAnnotationIntrospector.java @@ -0,0 +1,30 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor; +import com.fasterxml.jackson.databind.introspect.AnnotatedMember; +import com.fasterxml.jackson.databind.introspect.AnnotatedParameter; +import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; + +class Jdk8ConstructorParameterNameAnnotationIntrospector extends JacksonAnnotationIntrospector +{ + private static final long serialVersionUID = 1L; + + @Override + public String findImplicitPropertyName(AnnotatedMember member) { + if (!(member instanceof AnnotatedParameter)) { + return null; + } + AnnotatedParameter parameter = (AnnotatedParameter) member; + if (!(parameter.getOwner() instanceof AnnotatedConstructor)) { + return null; + } + AnnotatedConstructor constructor = (AnnotatedConstructor) parameter.getOwner(); + String parameterName = constructor.getAnnotated().getParameters()[parameter.getIndex()].getName(); + + if (parameterName == null || parameterName.isBlank()) { + throw new IllegalArgumentException("Unable to extract constructor parameter name for: " + member); + } + + return parameterName; + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/JsonAliasWithDeduction4327RecordTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/JsonAliasWithDeduction4327RecordTest.java new file mode 100644 index 0000000000..344bd9536f --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/JsonAliasWithDeduction4327RecordTest.java @@ -0,0 +1,44 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.a2q; +import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.jsonMapperBuilder; + +// [databind#4327] JsonAlias should respsect with Polymorphic Deduction +public class JsonAliasWithDeduction4327RecordTest +{ + @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) + @JsonSubTypes({ + @JsonSubTypes.Type(value = DeductionBean1.class), + @JsonSubTypes.Type(value = DeductionBean2.class) + }) + interface Deduction { } + + record DeductionBean1(int x) implements Deduction { } + + record DeductionBean2( + @JsonAlias(value = {"Y", "yy", "ff", "X"}) int y + ) implements Deduction { } + + + private final ObjectMapper mapper = jsonMapperBuilder().build(); + + @ParameterizedTest + @ValueSource(strings = {"Y", "yy", "ff", "X"}) + public void testAliasWithPolymorphicDeduction(String field) throws Exception { + String json = a2q(String.format("{'%s': 2 }", field)); + Deduction value = mapper.readValue(json, Deduction.class); + assertNotNull(value); + assertEquals(2, ((DeductionBean2) value).y()); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordBasicsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordBasicsTest.java new file mode 100644 index 0000000000..c2dfb3261d --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordBasicsTest.java @@ -0,0 +1,299 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.annotation.*; + +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.type.TypeFactory; +import com.fasterxml.jackson.databind.util.ClassUtil; +import com.fasterxml.jackson.databind.util.Converter; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +public class RecordBasicsTest extends BaseMapTest +{ + record EmptyRecord() { } + + record SimpleRecord(int id, String name) { } + + record RecordOfRecord(SimpleRecord record) { } + + record RecordWithRename(int id, @JsonProperty("rename")String name) { } + + record RecordWithHeaderInject(int id, @JacksonInject String name) { } + + record RecordWithConstructorInject(int id, String name) { + + RecordWithConstructorInject(int id, @JacksonInject String name) { + this.id = id; + this.name = name; + } + } + + // [databind#2992] + @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) + record SnakeRecord(String myId, String myValue){} + + record RecordWithJsonDeserialize(int id, @JsonDeserialize(converter = StringTrimmer.class) String name) { } + + record RecordSingleWriteOnly(@JsonProperty(access = JsonProperty.Access.WRITE_ONLY) int id) { } + + record RecordSomeWriteOnly( + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) int id, + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) String name, + String email) { + } + + record RecordAllWriteOnly( + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) int id, + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) String name, + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) String email) { + } + + private final ObjectMapper MAPPER = newJsonMapper(); + + /* + /********************************************************************** + /* Test methods, Record type introspection + /********************************************************************** + */ + + public void testClassUtil() { + assertFalse(ClassUtil.isRecordType(getClass())); + + assertTrue(ClassUtil.isRecordType(SimpleRecord.class)); + assertTrue(ClassUtil.isRecordType(RecordOfRecord.class)); + assertTrue(ClassUtil.isRecordType(RecordWithRename.class)); + } + + public void testRecordJavaType() { + assertFalse(MAPPER.constructType(getClass()).isRecordType()); + + assertTrue(MAPPER.constructType(SimpleRecord.class).isRecordType()); + assertTrue(MAPPER.constructType(RecordOfRecord.class).isRecordType()); + assertTrue(MAPPER.constructType(RecordWithRename.class).isRecordType()); + } + + /* + /********************************************************************** + /* Test methods, default reading/writing Record values + /********************************************************************** + */ + + public void testSerializeSimpleRecord() throws Exception { + String json = MAPPER.writeValueAsString(new SimpleRecord(123, "Bob")); + final Object EXP = map("id", Integer.valueOf(123), "name", "Bob"); + assertEquals(EXP, MAPPER.readValue(json, Object.class)); + } + + public void testDeserializeSimpleRecord() throws Exception { + assertEquals(new SimpleRecord(123, "Bob"), + MAPPER.readValue("{\"id\":123,\"name\":\"Bob\"}", SimpleRecord.class)); + } + + public void testSerializeEmptyRecord() throws Exception { + assertEquals("{}", MAPPER.writeValueAsString(new EmptyRecord())); + } + + public void testDeserializeEmptyRecord() throws Exception { + assertEquals(new EmptyRecord(), + MAPPER.readValue("{}", EmptyRecord.class)); + } + + public void testSerializeRecordOfRecord() throws Exception { + RecordOfRecord record = new RecordOfRecord(new SimpleRecord(123, "Bob")); + String json = MAPPER.writeValueAsString(record); + final Object EXP = Collections.singletonMap("record", + map("id", Integer.valueOf(123), "name", "Bob")); + assertEquals(EXP, MAPPER.readValue(json, Object.class)); + } + + public void testDeserializeRecordOfRecord() throws Exception { + assertEquals(new RecordOfRecord(new SimpleRecord(123, "Bob")), + MAPPER.readValue("{\"record\":{\"id\":123,\"name\":\"Bob\"}}", + RecordOfRecord.class)); + } + + /* + /********************************************************************** + /* Test methods, reading/writing Record values with different config + /********************************************************************** + */ + + public void testSerializeSimpleRecord_DisableAnnotationIntrospector() throws Exception { + SimpleRecord record = new SimpleRecord(123, "Bob"); + + JsonMapper mapper = JsonMapper.builder() + .configure(MapperFeature.USE_ANNOTATIONS, false) + .build(); + String json = mapper.writeValueAsString(record); + + assertEquals("{\"id\":123,\"name\":\"Bob\"}", json); + } + + public void testDeserializeSimpleRecord_DisableAnnotationIntrospector() throws Exception { + JsonMapper mapper = JsonMapper.builder() + .configure(MapperFeature.USE_ANNOTATIONS, false) + .build(); + SimpleRecord value = mapper.readValue("{\"id\":123,\"name\":\"Bob\"}", SimpleRecord.class); + + assertEquals(new SimpleRecord(123, "Bob"), value); + } + + /* + /********************************************************************** + /* Test methods, renames, injects + /********************************************************************** + */ + + public void testSerializeJsonRename() throws Exception { + String json = MAPPER.writeValueAsString(new RecordWithRename(123, "Bob")); + final Object EXP = map("id", Integer.valueOf(123), "rename", "Bob"); + assertEquals(EXP, MAPPER.readValue(json, Object.class)); + } + + public void testDeserializeJsonRename() throws Exception { + RecordWithRename value = MAPPER.readValue("{\"id\":123,\"rename\":\"Bob\"}", + RecordWithRename.class); + assertEquals(new RecordWithRename(123, "Bob"), value); + } + + /** + * This test-case is just for documentation purpose: + * GOTCHA: Annotations on header will be propagated to the field, leading to this failure. + * + * @see #testDeserializeConstructorInjectRecord() + */ + public void testDeserializeHeaderInjectRecord_WillFail() throws Exception { + MAPPER.setInjectableValues(new InjectableValues.Std().addValue(String.class, "Bob")); + + try { + MAPPER.readValue("{\"id\":123}", RecordWithHeaderInject.class); + + fail("should not pass"); + } catch (IllegalArgumentException e) { + verifyException(e, "RecordWithHeaderInject#name"); + verifyException(e, "Can not set final java.lang.String field"); + } + } + + public void testDeserializeConstructorInjectRecord() throws Exception { + MAPPER.setInjectableValues(new InjectableValues.Std().addValue(String.class, "Bob")); + + RecordWithConstructorInject value = MAPPER.readValue("{\"id\":123}", RecordWithConstructorInject.class); + assertEquals(new RecordWithConstructorInject(123, "Bob"), value); + } + + /* + /********************************************************************** + /* Test methods, naming strategy + /********************************************************************** + */ + + // [databind#2992] + public void testNamingStrategy() throws Exception + { + SnakeRecord input = new SnakeRecord("123", "value"); + + String json = MAPPER.writeValueAsString(input); + assertEquals("{\"my_id\":\"123\",\"my_value\":\"value\"}", json); + + SnakeRecord output = MAPPER.readValue(json, SnakeRecord.class); + assertEquals(input, output); + } + + /* + /********************************************************************** + /* Test methods, JsonDeserialize + /********************************************************************** + */ + + public void testDeserializeJsonDeserializeRecord() throws Exception { + RecordWithJsonDeserialize value = MAPPER.readValue("{\"id\":123,\"name\":\" Bob \"}", RecordWithJsonDeserialize.class); + + assertEquals(new RecordWithJsonDeserialize(123, "Bob"), value); + } + + /* + /********************************************************************** + /* Test methods, JsonProperty(access=WRITE_ONLY) + /********************************************************************** + */ + + public void testSerialize_SingleWriteOnlyParameter() throws Exception { + String json = MAPPER.writeValueAsString(new RecordSingleWriteOnly(123)); + + assertEquals("{}", json); + } + + // [databind#3897] + public void testDeserialize_SingleWriteOnlyParameter() throws Exception { + RecordSingleWriteOnly value = MAPPER.readValue("{\"id\":123}", RecordSingleWriteOnly.class); + + assertEquals(new RecordSingleWriteOnly(123), value); + } + + public void testSerialize_SomeWriteOnlyParameter() throws Exception { + String json = MAPPER.writeValueAsString(new RecordSomeWriteOnly(123, "Bob", "bob@example.com")); + + assertEquals("{\"email\":\"bob@example.com\"}", json); + } + + public void testDeserialize_SomeWriteOnlyParameter() throws Exception { + RecordSomeWriteOnly value = MAPPER.readValue( + "{\"id\":123,\"name\":\"Bob\",\"email\":\"bob@example.com\"}", + RecordSomeWriteOnly.class); + + assertEquals(new RecordSomeWriteOnly(123, "Bob", "bob@example.com"), value); + } + + public void testSerialize_AllWriteOnlyParameter() throws Exception { + String json = MAPPER.writeValueAsString(new RecordAllWriteOnly(123, "Bob", "bob@example.com")); + + assertEquals("{}", json); + } + + public void testDeserialize_AllWriteOnlyParameter() throws Exception { + RecordAllWriteOnly value = MAPPER.readValue( + "{\"id\":123,\"name\":\"Bob\",\"email\":\"bob@example.com\"}", + RecordAllWriteOnly.class); + + assertEquals(new RecordAllWriteOnly(123, "Bob", "bob@example.com"), value); + } + + /* + /********************************************************************** + /* Internal helper methods + /********************************************************************** + */ + + private Map map(String key1, Object value1, + String key2, Object value2) { + final Map result = new LinkedHashMap<>(); + result.put(key1, value1); + result.put(key2, value2); + return result; + } + + public static class StringTrimmer implements Converter { + + @Override + public String convert(String value) { + return value.trim(); + } + + @Override + public JavaType getInputType(TypeFactory typeFactory) { + return typeFactory.constructType(String.class); + } + + @Override + public JavaType getOutputType(TypeFactory typeFactory) { + return typeFactory.constructType(String.class); + } + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordCreatorsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordCreatorsTest.java new file mode 100644 index 0000000000..558223ec9f --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordCreatorsTest.java @@ -0,0 +1,82 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; + +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; + +public class RecordCreatorsTest extends BaseMapTest +{ + record RecordWithCanonicalCtorOverride(int id, String name) { + public RecordWithCanonicalCtorOverride(int id, String name) { + this.id = id; + this.name = "name"; + } + } + + record RecordWithAltCtor(int id, String name) { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public RecordWithAltCtor(@JsonProperty("id") int id) { + this(id, "name2"); + } + } + + // [databind#2980] + record RecordWithDelegation(String value) { + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public RecordWithDelegation(String value) { + this.value = "del:"+value; + } + + @JsonValue() + public String getValue() { + return "val:"+value; + } + + public String accessValueForTest() { return value; } + } + + private final ObjectMapper MAPPER = newJsonMapper(); + + /* + /********************************************************************** + /* Test methods, alternate constructors + /********************************************************************** + */ + + public void testDeserializeWithCanonicalCtorOverride() throws Exception { + RecordWithCanonicalCtorOverride value = MAPPER.readValue("{\"id\":123,\"name\":\"Bob\"}", + RecordWithCanonicalCtorOverride.class); + assertEquals(123, value.id()); + assertEquals("name", value.name()); + } + + public void testDeserializeWithAltCtor() throws Exception { + RecordWithAltCtor value = MAPPER.readValue("{\"id\":2812}", + RecordWithAltCtor.class); + assertEquals(2812, value.id()); + assertEquals("name2", value.name()); + + // "Implicit" canonical constructor can no longer be used when there's explicit constructor + try { + MAPPER.readValue("{\"id\":2812,\"name\":\"Bob\"}", + RecordWithAltCtor.class); + fail("should not pass"); + } catch (UnrecognizedPropertyException e) { + verifyException(e, "Unrecognized"); + verifyException(e, "\"name\""); + verifyException(e, "RecordWithAltCtor"); + } + } + + // [databind#2980] + public void testDeserializeWithDelegatingCtor() throws Exception { + RecordWithDelegation value = MAPPER.readValue(q("foobar"), + RecordWithDelegation.class); + assertEquals("del:foobar", value.accessValueForTest()); + + assertEquals(q("val:del:foobar"), MAPPER.writeValueAsString(value)); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordDeserialization3897Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordDeserialization3897Test.java new file mode 100644 index 0000000000..4034f2b272 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordDeserialization3897Test.java @@ -0,0 +1,23 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.BaseMapTest; + +// [databinding#3897] This is failing test for `Record` class deserialization with single field annotated with +// `JsonProperty.Access.WRITE_ONLY`. Regression from Jackson 2.14.2 +public class RecordDeserialization3897Test extends BaseMapTest { + + record Example( + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + String value + ) {} + + // Passes in 2.14.2, but does not in 2.15.0 + public void testRecordWithWriteOnly() throws Exception { + final String JSON = a2q("{'value':'foo'}"); + + Example result = newJsonMapper().readValue(JSON, Example.class); + + assertEquals("foo", result.value()); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordDeserialization3906Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordDeserialization3906Test.java new file mode 100644 index 0000000000..56456380ed --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordDeserialization3906Test.java @@ -0,0 +1,155 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.introspect.AnnotatedClass; +import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector; +import com.fasterxml.jackson.databind.introspect.VisibilityChecker; +import com.fasterxml.jackson.databind.module.SimpleModule; + +/** + * Test case that covers both failing-by-regression tests and passing tests. + *

For more details, refer to + * + * [databind#3906]: Regression: 2.15.0 breaks deserialization for records when mapper.setVisibility(ALL, NONE); + */ +public class RecordDeserialization3906Test extends BaseMapTest +{ + record Record3906(String string, int integer) { + } + + @JsonAutoDetect(creatorVisibility = Visibility.NON_PRIVATE) + record Record3906Annotated(String string, int integer) { + } + + record Record3906Creator(String string, int integer) { + @JsonCreator + Record3906Creator { + } + } + + private record PrivateRecord3906(String string, int integer) { + } + + /* + /********************************************************** + /* Failing tests that pass in 2.14 (regression) + /********************************************************** + */ + + // minimal config for reproduction + public void testEmptyJsonToRecordMiminal() throws JsonProcessingException { + ObjectMapper mapper = newJsonMapper(); + mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE); + + Record3906 recordDeser = mapper.readValue("{}", Record3906.class); + + assertEquals(new Record3906(null, 0), recordDeser); + } + + // actual config used reproduction + public void testEmptyJsonToRecordActualImpl() throws JsonProcessingException { + ObjectMapper mapper = newJsonMapper(); + mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE); + mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); + + Record3906 recordDeser = mapper.readValue("{}", Record3906.class); + + assertEquals(new Record3906(null, 0), recordDeser); + } + + /* + /********************************************************** + /* Passing Tests : Suggested work-arounds + /* for future modifications + /********************************************************** + */ + + public void testEmptyJsonToRecordWorkAround() throws JsonProcessingException { + ObjectMapper mapper = newJsonMapper(); + mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE); + mapper.setVisibility(PropertyAccessor.CREATOR, Visibility.ANY); + + Record3906 recordDeser = mapper.readValue("{}", Record3906.class); + + assertEquals(new Record3906(null, 0), recordDeser); + } + + public void testEmptyJsonToRecordCreatorsVisibile() throws JsonProcessingException { + ObjectMapper mapper = newJsonMapper(); + mapper.setVisibility(PropertyAccessor.CREATOR, Visibility.NON_PRIVATE); + + Record3906 recordDeser = mapper.readValue("{}", Record3906.class); + assertEquals(new Record3906(null, 0), recordDeser); + } + + public void testEmptyJsonToRecordUsingModule() throws JsonProcessingException { + ObjectMapper mapper = jsonMapperBuilder().addModule(new SimpleModule() { + @Override + public void setupModule(SetupContext context) { + super.setupModule(context); + context.insertAnnotationIntrospector(new NopAnnotationIntrospector() { + @Override + public VisibilityChecker findAutoDetectVisibility(AnnotatedClass ac, + VisibilityChecker checker) { + return ac.getType().isRecordType() + ? checker.withCreatorVisibility(JsonAutoDetect.Visibility.NON_PRIVATE) + : checker; + } + }); + } + }).build(); + + Record3906 recordDeser = mapper.readValue("{}", Record3906.class); + assertEquals(new Record3906(null, 0), recordDeser); + } + + public void testEmptyJsonToRecordDirectAutoDetectConfig() throws JsonProcessingException { + ObjectMapper mapper = newJsonMapper(); + + Record3906Annotated recordDeser = mapper.readValue("{}", Record3906Annotated.class); + assertEquals(new Record3906Annotated(null, 0), recordDeser); + } + + public void testEmptyJsonToRecordJsonCreator() throws JsonProcessingException { + ObjectMapper mapper = newJsonMapper(); + + Record3906Creator recordDeser = mapper.readValue("{}", Record3906Creator.class); + assertEquals(new Record3906Creator(null, 0), recordDeser); + } + + public void testEmptyJsonToRecordUsingModuleOther() throws JsonProcessingException { + ObjectMapper mapper = jsonMapperBuilder().addModule( + new SimpleModule() { + @Override + public void setupModule(SetupContext context) { + super.setupModule(context); + context.insertAnnotationIntrospector(new NopAnnotationIntrospector() { + @Override + public VisibilityChecker findAutoDetectVisibility(AnnotatedClass ac, + VisibilityChecker checker) { + if (ac.getType() == null) { + return checker; + } + if (!ac.getType().isRecordType()) { + return checker; + } + // If this is a Record, then increase the "creator" visibility again + return checker.withCreatorVisibility(Visibility.ANY); + } + }); + } + }) + .build(); + + assertEquals(new Record3906(null, 0), + mapper.readValue("{}", Record3906.class)); + assertEquals(new PrivateRecord3906(null, 0), + mapper.readValue("{}", PrivateRecord3906.class)); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordExplicitCreatorsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordExplicitCreatorsTest.java new file mode 100644 index 0000000000..18110d5176 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordExplicitCreatorsTest.java @@ -0,0 +1,326 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; + +import java.math.BigDecimal; + +public class RecordExplicitCreatorsTest extends BaseMapTest +{ + record RecordWithOneJsonPropertyWithoutJsonCreator(int id, String name) { + + public RecordWithOneJsonPropertyWithoutJsonCreator(@JsonProperty("id_only") int id) { + this(id, "JsonPropertyConstructor"); + } + + public static RecordWithOneJsonPropertyWithoutJsonCreator valueOf(int id) { + return new RecordWithOneJsonPropertyWithoutJsonCreator(id); + } + } + + record RecordWithTwoJsonPropertyWithoutJsonCreator(int id, String name, String email) { + + public RecordWithTwoJsonPropertyWithoutJsonCreator(@JsonProperty("the_id") int id, @JsonProperty("the_email") String email) { + this(id, "TwoJsonPropertyConstructor", email); + } + + public static RecordWithTwoJsonPropertyWithoutJsonCreator valueOf(int id) { + return new RecordWithTwoJsonPropertyWithoutJsonCreator(id, "factory@example.com"); + } + } + + record RecordWithJsonPropertyAndImplicitPropertyWithoutJsonCreator(int id, String name, String email) { + + public RecordWithJsonPropertyAndImplicitPropertyWithoutJsonCreator(@JsonProperty("id_only") int id, String email) { + this(id, "JsonPropertyConstructor", email); + } + } + + record RecordWithJsonPropertyWithJsonCreator(int id, String name) { + + @JsonCreator + public RecordWithJsonPropertyWithJsonCreator(@JsonProperty("id_only") int id) { + this(id, "JsonCreatorConstructor"); + } + + public static RecordWithJsonPropertyWithJsonCreator valueOf(int id) { + return new RecordWithJsonPropertyWithJsonCreator(id); + } + } + + record RecordWithJsonPropertyAndImplicitPropertyWithJsonCreator(int id, String name, String email) { + + @JsonCreator + public RecordWithJsonPropertyAndImplicitPropertyWithJsonCreator(@JsonProperty("id_only") int id, String email) { + this(id, "JsonPropertyConstructor", email); + } + } + + record RecordWithMultiExplicitDelegatingConstructor(int id, String name) { + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public RecordWithMultiExplicitDelegatingConstructor(int id) { + this(id, "IntConstructor"); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public RecordWithMultiExplicitDelegatingConstructor(String id) { + this(Integer.parseInt(id), "StringConstructor"); + } + } + + record RecordWithDisabledJsonCreator(int id, String name) { + + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + RecordWithDisabledJsonCreator { + } + } + + record RecordWithExplicitFactoryMethod(BigDecimal id, String name) { + + @JsonCreator + public static RecordWithExplicitFactoryMethod valueOf(int id) { + return new RecordWithExplicitFactoryMethod(BigDecimal.valueOf(id), "IntFactoryMethod"); + } + + public static RecordWithExplicitFactoryMethod valueOf(double id) { + return new RecordWithExplicitFactoryMethod(BigDecimal.valueOf(id), "DoubleFactoryMethod"); + } + + @JsonCreator + public static RecordWithExplicitFactoryMethod valueOf(String id) { + return new RecordWithExplicitFactoryMethod(BigDecimal.valueOf(Double.parseDouble(id)), "StringFactoryMethod"); + } + } + + private final ObjectMapper MAPPER = jsonMapperBuilder() + .disable(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS) // So that test cases don't have to assert weird error messages + .build(); + + /* + /********************************************************************** + /* Test methods, "implicit" (?) constructor with JsonProperty without JsonCreator + /********************************************************************** + */ + + public void testDeserializeUsingJsonPropertyConstructor_WithoutJsonCreator() throws Exception { + RecordWithOneJsonPropertyWithoutJsonCreator oneJsonPropertyValue = MAPPER.readValue( + "{\"id_only\":123}", + RecordWithOneJsonPropertyWithoutJsonCreator.class); + assertEquals(new RecordWithOneJsonPropertyWithoutJsonCreator(123), oneJsonPropertyValue); + + RecordWithTwoJsonPropertyWithoutJsonCreator twoJsonPropertyValue = MAPPER.readValue( + "{\"the_id\":123,\"the_email\":\"bob@example.com\"}", + RecordWithTwoJsonPropertyWithoutJsonCreator.class); + assertEquals(new RecordWithTwoJsonPropertyWithoutJsonCreator(123, "bob@example.com"), twoJsonPropertyValue); + } + + /** + * Only 1 properties-based creator allowed, so can no longer use the (un-annotated) canonical constructor. + */ + public void testDeserializeUsingCanonicalConstructor_WhenJsonPropertyConstructorExists_WillFail() throws Exception { + try { + MAPPER.readValue( + "{\"id\":123,\"name\":\"Bobby\"}", + RecordWithOneJsonPropertyWithoutJsonCreator.class); + + fail("should not pass"); + } catch (JsonMappingException e) { + verifyException(e, "Unrecognized field \"id\""); + verifyException(e, "RecordWithOneJsonPropertyWithoutJsonCreator"); + verifyException(e, "one known property: \"id_only\""); + } + + try { + MAPPER.readValue( + "{\"id\":123,\"name\":\"Bobby\",\"email\":\"bobby@example.com\"}", + RecordWithTwoJsonPropertyWithoutJsonCreator.class); + + fail("should not pass"); + } catch (JsonMappingException e) { + verifyException(e, "Unrecognized field \"id\""); + verifyException(e, "RecordWithTwoJsonPropertyWithoutJsonCreator"); + verifyException(e, "2 known properties: \"the_id\", \"the_email\""); + } + } + + public void testDeserializeUsingImplicitFactoryMethod_WhenJsonPropertyConstructorExists() throws Exception { + RecordWithOneJsonPropertyWithoutJsonCreator oneJsonPropertyValue = MAPPER.readValue( + "123", + RecordWithOneJsonPropertyWithoutJsonCreator.class); + assertEquals(RecordWithOneJsonPropertyWithoutJsonCreator.valueOf(123), oneJsonPropertyValue); + + RecordWithTwoJsonPropertyWithoutJsonCreator twoJsonPropertyValue = MAPPER.readValue( + "123", + RecordWithTwoJsonPropertyWithoutJsonCreator.class); + assertEquals(RecordWithTwoJsonPropertyWithoutJsonCreator.valueOf(123), twoJsonPropertyValue); + } + + /* + /********************************************************************** + /* Test methods, explicit constructor with JsonProperty with JsonCreator + /********************************************************************** + */ + + public void testDeserializeUsingJsonCreatorConstructor() throws Exception { + RecordWithJsonPropertyWithJsonCreator value = MAPPER.readValue("{\"id_only\":123}", RecordWithJsonPropertyWithJsonCreator.class); + + assertEquals(new RecordWithJsonPropertyWithJsonCreator(123), value); + } + + /** + * Only 1 properties-based creator allowed, so can no longer use the (un-annotated) canonical constructor + */ + public void testDeserializeUsingCanonicalConstructor_WhenJsonCreatorConstructorExists_WillFail() throws Exception { + try { + MAPPER.readValue("{\"id\":123,\"name\":\"Bobby\"}", RecordWithJsonPropertyWithJsonCreator.class); + + fail("should not pass"); + } catch (JsonMappingException e) { + verifyException(e, "Unrecognized field \"id\""); + verifyException(e, "RecordWithJsonPropertyWithJsonCreator"); + verifyException(e, "one known property: \"id_only\""); + } + } + + public void testDeserializeUsingImplicitFactoryMethod_WhenJsonCreatorConstructorExists_WillFail() throws Exception { + try { + MAPPER.readValue("123", RecordWithJsonPropertyWithJsonCreator.class); + + fail("should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot construct instance"); + verifyException(e, "RecordWithJsonPropertyWithJsonCreator"); + verifyException(e, "although at least one Creator exists"); + verifyException(e, "no int/Int-argument constructor/factory method"); + } + } + + /* + /********************************************************************** + /* Test methods, multiple explicit delegating constructors + /********************************************************************** + */ + + public void testDeserializeUsingExplicitDelegatingConstructors() throws Exception { + RecordWithMultiExplicitDelegatingConstructor intConstructorValue = MAPPER.readValue("123", RecordWithMultiExplicitDelegatingConstructor.class); + assertEquals(new RecordWithMultiExplicitDelegatingConstructor(123, "IntConstructor"), intConstructorValue); + + RecordWithMultiExplicitDelegatingConstructor stringConstructorValue = MAPPER.readValue("\"123\"", RecordWithMultiExplicitDelegatingConstructor.class); + assertEquals(new RecordWithMultiExplicitDelegatingConstructor(123, "StringConstructor"), stringConstructorValue); + } + + /* + /********************************************************************** + /* Test methods, JsonCreator.mode=DISABLED + /********************************************************************** + */ + + public void testDeserializeUsingDisabledConstructors_WillFail() throws Exception { + try { + MAPPER.readValue("{\"id\":123,\"name\":\"Bobby\"}", RecordWithDisabledJsonCreator.class); + + fail("should not pass"); + } catch (InvalidDefinitionException e) { + verifyException(e, "Cannot construct instance"); + verifyException(e, "RecordWithDisabledJsonCreator"); + verifyException(e, "no Creators, like default constructor, exist"); + verifyException(e, "cannot deserialize from Object value"); + } + + } + + /* + /********************************************************************** + /* Test methods, explicit factory methods + /********************************************************************** + */ + + public void testDeserializeUsingExplicitFactoryMethods() throws Exception { + RecordWithExplicitFactoryMethod intFactoryValue = MAPPER.readValue("123", RecordWithExplicitFactoryMethod.class); + assertEquals(new RecordWithExplicitFactoryMethod(BigDecimal.valueOf(123), "IntFactoryMethod"), intFactoryValue); + + RecordWithExplicitFactoryMethod stringFactoryValue = MAPPER.readValue("\"123.4\"", RecordWithExplicitFactoryMethod.class); + assertEquals(new RecordWithExplicitFactoryMethod(BigDecimal.valueOf(123.4), "StringFactoryMethod"), stringFactoryValue); + } + + /** + * Implicit factory methods detection is only activated when there's no explicit (i.e. annotated + * with {@link JsonCreator}) factory methods. + */ + public void testDeserializeUsingImplicitFactoryMethods_WhenExplicitFactoryMethodsExist_WillFail() throws Exception { + try { + MAPPER.readValue("123.4", RecordWithExplicitFactoryMethod.class); + + fail("should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot construct instance"); + verifyException(e, "RecordWithExplicitFactoryMethod"); + verifyException(e, "although at least one Creator exists"); + verifyException(e, "no double/Double-argument constructor/factory"); + } + } + + /** + * Just like how no-arg constructor + setters will still be used to deserialize JSON Object even when + * there's JsonCreator factory method(s) in the JavaBean class. + */ + public void testDeserializeUsingImplicitCanonicalConstructor_WhenFactoryMethodsExist() throws Exception { + RecordWithExplicitFactoryMethod value = MAPPER.readValue("{\"id\":123.4,\"name\":\"CanonicalConstructor\"}", RecordWithExplicitFactoryMethod.class); + + assertEquals(new RecordWithExplicitFactoryMethod(BigDecimal.valueOf(123.4), "CanonicalConstructor"), value); + } + + /* + /********************************************************************** + /* Test methods, implicit parameter names + /********************************************************************** + */ + + public void testDeserializeMultipleConstructorsRecord_WithExplicitAndImplicitParameterNames_WithJsonCreator() throws Exception { + MAPPER.setAnnotationIntrospector(new Jdk8ConstructorParameterNameAnnotationIntrospector()); + + RecordWithJsonPropertyAndImplicitPropertyWithJsonCreator value = MAPPER.readValue( + "{\"id_only\":123,\"email\":\"bob@example.com\"}", + RecordWithJsonPropertyAndImplicitPropertyWithJsonCreator.class); + assertEquals(new RecordWithJsonPropertyAndImplicitPropertyWithJsonCreator(123, "bob@example.com"), value); + } + + /** + * This test-case is just for documentation purpose: + * GOTCHA: The problem is there are two usable constructors: + *

    + *
  1. Canonical constructor
  2. + *
  3. Non-canonical constructor with JsonProperty parameter
  4. + *
+ * ...so Jackson-Databind decided NOT to choose any. To overcome this, annotate JsonCreator on the non-canonical + * constructor. + *

+ * Similar behaviour is observed if a JavaBean has two usable constructors. + * + * @see #testDeserializeUsingJsonCreatorConstructor() + * @see #testDeserializeUsingCanonicalConstructor_WhenJsonCreatorConstructorExists_WillFail() + */ + public void testDeserializeMultipleConstructorsRecord_WithExplicitAndImplicitParameterNames() throws Exception { + MAPPER.setAnnotationIntrospector(new Jdk8ConstructorParameterNameAnnotationIntrospector()); + + try { + MAPPER.readValue( + "{\"id_only\":123,\"email\":\"bob@example.com\"}", + RecordWithJsonPropertyAndImplicitPropertyWithoutJsonCreator.class); + + fail("should not pass"); + } catch (InvalidDefinitionException e) { + verifyException(e, "Cannot construct instance"); + verifyException(e, "RecordWithJsonPropertyAndImplicitPropertyWithoutJsonCreator"); + verifyException(e, "no Creators, like default constructor, exist"); + verifyException(e, "cannot deserialize from Object value"); + } + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordFailingSetter3938Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordFailingSetter3938Test.java new file mode 100644 index 0000000000..3fe8cfef18 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordFailingSetter3938Test.java @@ -0,0 +1,42 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.*; + +public class RecordFailingSetter3938Test extends BaseMapTest +{ + private final static String ERROR_3938_PREFIX = "Non-null 'options' not allowed for "; + + // [databind#3938] + interface NoOptionsCommand { + @JsonProperty("options") + default void setOptions(JsonNode value) { + if (value.isNull()) { + return; + } + throw new IllegalArgumentException(ERROR_3938_PREFIX+getClass().getName()); + } + } + + public record Command3938(int id, String filter) implements NoOptionsCommand { } + + private final ObjectMapper MAPPER = newJsonMapper(); + + // [databind#3938]: Should detect and use setters too + public void testFailingSetter3939() throws Exception + { + final ObjectReader R = MAPPER.readerFor(Command3938.class); + + // First, missing value and `null` are fine, as long as we have all fields + assertNotNull(R.readValue(a2q("{'id':1, 'filter':'abc'}"))); + assertNotNull(R.readValue(a2q("{'id':2, 'filter':'abc', 'options':null}"))); + + // But then failure for non-empty Array (f.ex) + try { + R.readValue(a2q("{'id':2,'options':[123]}}")); + fail("Should not pass"); + } catch (DatabindException e) { + verifyException(e, ERROR_3938_PREFIX); + } + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordIgnoreNonAccessorGetterTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordIgnoreNonAccessorGetterTest.java new file mode 100644 index 0000000000..af5077c211 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordIgnoreNonAccessorGetterTest.java @@ -0,0 +1,54 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class RecordIgnoreNonAccessorGetterTest extends BaseMapTest { + + // [databind#3628] + interface InterfaceWithGetter { + + String getId(); + + String getName(); + } + + @JsonPropertyOrder({"id", "name", "count"}) // easier to assert when JSON field ordering is always the same + record RecordWithInterfaceWithGetter(String name) implements InterfaceWithGetter { + + @Override + public String getId() { + return "ID:" + name; + } + + @Override + public String getName() { + return name; + } + + // [databind#3895] + public int getCount() { + return 999; + } + } + + private final ObjectMapper MAPPER = newJsonMapper(); + + public void testSerializeIgnoreInterfaceGetter_WithoutUsingVisibilityConfig() throws Exception { + String json = MAPPER.writeValueAsString(new RecordWithInterfaceWithGetter("Bob")); + + assertEquals("{\"id\":\"ID:Bob\",\"name\":\"Bob\",\"count\":999}", json); + } + + public void testSerializeIgnoreInterfaceGetter_UsingVisibilityConfig() throws Exception { + MAPPER.setVisibility(PropertyAccessor.GETTER, Visibility.NONE); + MAPPER.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); + + String json = MAPPER.writeValueAsString(new RecordWithInterfaceWithGetter("Bob")); + + assertEquals("{\"name\":\"Bob\"}", json); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordImplicitCreatorsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordImplicitCreatorsTest.java new file mode 100644 index 0000000000..843c96fe62 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordImplicitCreatorsTest.java @@ -0,0 +1,325 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.databind.BaseMapTest; + +import com.fasterxml.jackson.databind.DatabindException; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.cfg.ConstructorDetector; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; + +import java.math.BigDecimal; + +public class RecordImplicitCreatorsTest extends BaseMapTest +{ + record RecordWithImplicitFactoryMethods(BigDecimal id, String name) { + + public static RecordWithImplicitFactoryMethods valueOf(int id) { + return new RecordWithImplicitFactoryMethods(BigDecimal.valueOf(id), "IntFactoryMethod"); + } + + public static RecordWithImplicitFactoryMethods valueOf(double id) { + return new RecordWithImplicitFactoryMethods(BigDecimal.valueOf(id), "DoubleFactoryMethod"); + } + + public static RecordWithImplicitFactoryMethods valueOf(String id) { + return new RecordWithImplicitFactoryMethods(BigDecimal.valueOf(Double.parseDouble(id)), "StringFactoryMethod"); + } + } + + record RecordWithSingleValueConstructor(int id) { + } + + record RecordWithSingleValueConstructorWithJsonValue(@JsonValue int id) { + } + + record RecordWithSingleValueConstructorWithJsonValueAccessor(int id) { + + @JsonValue + @Override + public int id() { + return id; + } + } + + record RecordWithNonCanonicalConstructor(int id, String name, String email) { + + public RecordWithNonCanonicalConstructor(int id, String email) { + this(id, "NonCanonicalConstructor", email); + } + } + + /** + * Similar to: + *

+     *   public class MyBean {
+     *       ...
+     *       // Single-arg constructor used by delegating creator.
+     *       public MyBean(int id) { ... }
+     *
+     *       // No-arg constructor used by properties-based creator.
+     *       public MyBean() {}
+     *
+     *       // Setters used by properties-based creator.
+     *       public void setId(int id) { ... }
+     *       public void setName(String name) { ... }
+     *   }
+     * 
+ */ + record RecordWithAltSingleValueConstructor(int id, String name) { + + public RecordWithAltSingleValueConstructor(int id) { + this(id, "SingleValueConstructor"); + } + } + + private final ObjectMapper MAPPER = newJsonMapper(); + + /* + /********************************************************************** + /* Test methods, implicit factory methods & "implicit" canonical constructor + /********************************************************************** + */ + + public void testDeserializeUsingImplicitIntegerFactoryMethod() throws Exception { + RecordWithImplicitFactoryMethods factoryMethodValue = MAPPER.readValue("123", RecordWithImplicitFactoryMethods.class); + + assertEquals(new RecordWithImplicitFactoryMethods(BigDecimal.valueOf(123), "IntFactoryMethod"), factoryMethodValue); + } + + public void testDeserializeUsingImplicitDoubleFactoryMethod() throws Exception { + RecordWithImplicitFactoryMethods value = MAPPER.readValue("123.4", RecordWithImplicitFactoryMethods.class); + + assertEquals(new RecordWithImplicitFactoryMethods(BigDecimal.valueOf(123.4), "DoubleFactoryMethod"), value); + } + + public void testDeserializeUsingImplicitStringFactoryMethod() throws Exception { + RecordWithImplicitFactoryMethods value = MAPPER.readValue("\"123.4\"", RecordWithImplicitFactoryMethods.class); + + assertEquals(new RecordWithImplicitFactoryMethods(BigDecimal.valueOf(123.4), "StringFactoryMethod"), value); + } + + public void testDeserializeUsingImplicitCanonicalConstructor_WhenImplicitFactoryMethodsExist() throws Exception { + RecordWithImplicitFactoryMethods value = MAPPER.readValue( + "{\"id\":123.4,\"name\":\"CanonicalConstructor\"}", + RecordWithImplicitFactoryMethods.class); + + assertEquals(new RecordWithImplicitFactoryMethods(BigDecimal.valueOf(123.4), "CanonicalConstructor"), value); + } + + public void testDeserializeUsingImplicitFactoryMethod_WithAutoDetectCreatorsDisabled_WillFail() throws Exception { + ObjectMapper mapper = jsonMapperBuilder() + .disable(MapperFeature.AUTO_DETECT_CREATORS) + .build(); + + try { + mapper.readValue("123", RecordWithImplicitFactoryMethods.class); + + fail("should not pass"); + } catch (DatabindException e) { + verifyException(e, "Cannot construct instance"); + verifyException(e, "RecordWithImplicitFactoryMethod"); + verifyException(e, "no int/Int-argument constructor/factory method"); + } + } + + /* + /********************************************************************** + /* Test methods, implicit single-value constructor + /********************************************************************** + */ + + /** + * This test-case is just for documentation purpose: + * GOTCHA: For JavaBean, only having single-value constructor results in implicit delegating creator. But for + * Records, the CANONICAL single-value constructor results in properties-based creator. + *

+ * It will result in implicit delegating constructor only when: + *

    + *
  • + * There's NON-CANONICAL single-value constructor - see + * {@link #testDeserializeUsingImplicitDelegatingConstructor()}, or + *
  • + *
  • + * {@code @JsonValue} annotation is used - see + * {@link #testDeserializeUsingImplicitSingleValueConstructor_WithJsonValue()}, + * {@link #testDeserializeUsingImplicitSingleValueConstructor_WithJsonValueAccessor()} + *
  • + *
. + *

+ * yihtserns: maybe we can change this to adopt JavaBean's behaviour, but I prefer to not break existing behaviour + * until and unless there's a discussion on this. + */ + public void testDeserializeUsingImplicitSingleValueConstructor() throws Exception { + try { + // Cannot use delegating creator, unlike when dealing with JavaBean + MAPPER.readValue("123", RecordWithSingleValueConstructor.class); + + fail("should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot construct instance"); + verifyException(e, "RecordWithSingleValueConstructor"); + verifyException(e, "at least one Creator exists"); + verifyException(e, "no int/Int-argument constructor/factory method"); + } + + // Can only use properties-based creator + RecordWithSingleValueConstructor value = MAPPER.readValue("{\"id\":123}", RecordWithSingleValueConstructor.class); + assertEquals(new RecordWithSingleValueConstructor(123), value); + } + + /** + * This test-case is just for documentation purpose: + * See {@link #testDeserializeUsingImplicitSingleValueConstructor} + */ + public void testDeserializeSingleValueConstructor_WithDelegatingConstructorDetector_WillFail() throws Exception { + MAPPER.setConstructorDetector(ConstructorDetector.USE_DELEGATING); + + try { + // Fail, no delegating creator + MAPPER.readValue("123", RecordWithSingleValueConstructor.class); + + fail("should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot construct instance"); + verifyException(e, "RecordWithSingleValueConstructor"); + verifyException(e, "at least one Creator exists"); + verifyException(e, "no int/Int-argument constructor/factory method"); + } + + // Only have properties-based creator + RecordWithSingleValueConstructor value = MAPPER.readValue("{\"id\":123}", RecordWithSingleValueConstructor.class); + assertEquals(new RecordWithSingleValueConstructor(123), value); + } + + /** + * This is just to catch any potential regression. + */ + public void testDeserializeSingleValueConstructor_WithPropertiesBasedConstructorDetector_WillFail() throws Exception { + MAPPER.setConstructorDetector(ConstructorDetector.USE_PROPERTIES_BASED); + + try { + // This should fail + MAPPER.readValue("123", RecordWithSingleValueConstructor.class); + + fail("should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot construct instance"); + verifyException(e, "RecordWithSingleValueConstructor"); + verifyException(e, "at least one Creator exists"); + verifyException(e, "no int/Int-argument constructor/factory method"); + } + + // This should work + RecordWithSingleValueConstructor value = MAPPER.readValue("{\"id\":123}", RecordWithSingleValueConstructor.class); + assertEquals(new RecordWithSingleValueConstructor(123), value); + } + + /* + /********************************************************************** + /* Test methods, implicit single-value constructor + @JsonValue + /********************************************************************** + */ + + /** + * [databind#3180] + * This test-case is just for documentation purpose: + * Unlike {@link #testDeserializeUsingImplicitSingleValueConstructor()}, annotating {@code @JsonValue} + * to a Record's header results in a delegating constructor. + */ + public void testDeserializeUsingImplicitSingleValueConstructor_WithJsonValue() throws Exception { + // Can use delegating creator + RecordWithSingleValueConstructorWithJsonValue value = MAPPER.readValue( + "123", + RecordWithSingleValueConstructorWithJsonValue.class); + assertEquals(new RecordWithSingleValueConstructorWithJsonValue(123), value); + + try { + // Can no longer use properties-based creator + MAPPER.readValue("{\"id\":123}", RecordWithSingleValueConstructorWithJsonValue.class); + + fail("should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot construct instance"); + verifyException(e, "RecordWithSingleValueConstructorWithJsonValue"); + verifyException(e, "although at least one Creator exists"); + verifyException(e, "cannot deserialize from Object value"); + } + } + + /** + * [databind#3180] + * This test-case is just for documentation purpose: + * Unlike {@link #testDeserializeUsingImplicitSingleValueConstructor()}, annotating {@code @JsonValue} + * to the accessor results in a delegating creator. + */ + public void testDeserializeUsingImplicitSingleValueConstructor_WithJsonValueAccessor() throws Exception { + // Can use delegating creator + RecordWithSingleValueConstructorWithJsonValueAccessor value = MAPPER.readValue( + "123", + RecordWithSingleValueConstructorWithJsonValueAccessor.class); + assertEquals(new RecordWithSingleValueConstructorWithJsonValueAccessor(123), value); + + try { + // Can no longer use properties-based creator + MAPPER.readValue("{\"id\":123}", RecordWithSingleValueConstructorWithJsonValueAccessor.class); + + fail("should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, "Cannot construct instance"); + verifyException(e, "RecordWithSingleValueConstructorWithJsonValueAccessor"); + verifyException(e, "although at least one Creator exists"); + verifyException(e, "cannot deserialize from Object value"); + } + } + + /* + /********************************************************************** + /* Test methods, implicit properties-based + delegating constructor + /********************************************************************** + */ + + public void testDeserializeUsingImplicitPropertiesBasedConstructor() throws Exception { + RecordWithAltSingleValueConstructor value = MAPPER.readValue( + "{\"id\":123,\"name\":\"PropertiesBasedConstructor\"}", + RecordWithAltSingleValueConstructor.class); + + assertEquals(new RecordWithAltSingleValueConstructor(123, "PropertiesBasedConstructor"), value); + } + + /** + * @see #testDeserializeUsingImplicitSingleValueConstructor() + */ + public void testDeserializeUsingImplicitDelegatingConstructor() throws Exception { + RecordWithAltSingleValueConstructor value = MAPPER.readValue("123", RecordWithAltSingleValueConstructor.class); + + assertEquals(new RecordWithAltSingleValueConstructor(123, "SingleValueConstructor"), value); + } + + /* + /********************************************************************** + /* Test methods, implicit parameter names + /********************************************************************** + */ + + public void testDeserializeMultipleConstructorsRecord_WithImplicitParameterNames_WillUseCanonicalConstructor() throws Exception { + MAPPER.setAnnotationIntrospector(new Jdk8ConstructorParameterNameAnnotationIntrospector()); + + RecordWithNonCanonicalConstructor value = MAPPER.readValue( + "{\"id\":123,\"name\":\"Bob\",\"email\":\"bob@example.com\"}", + RecordWithNonCanonicalConstructor.class); + + assertEquals(new RecordWithNonCanonicalConstructor(123, "Bob", "bob@example.com"), value); + } + + public void testDeserializeMultipleConstructorsRecord_WithImplicitParameterNames_WillIgnoreNonCanonicalConstructor() throws Exception { + MAPPER.setAnnotationIntrospector(new Jdk8ConstructorParameterNameAnnotationIntrospector()); + + RecordWithNonCanonicalConstructor value = MAPPER.readValue( + "{\"id\":123,\"email\":\"bob@example.com\"}", + RecordWithNonCanonicalConstructor.class); + + assertEquals(new RecordWithNonCanonicalConstructor(123, null, "bob@example.com"), value); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordImplicitSingleValueUsePropertiesBasedCreatorsTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordImplicitSingleValueUsePropertiesBasedCreatorsTest.java new file mode 100644 index 0000000000..9aca999836 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordImplicitSingleValueUsePropertiesBasedCreatorsTest.java @@ -0,0 +1,75 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.cfg.ConstructorDetector; +import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; + +public class RecordImplicitSingleValueUsePropertiesBasedCreatorsTest extends BaseMapTest +{ + + record RecordWithMultiValueCanonAndSingleValueAltConstructor(int id, String name) { + + public RecordWithMultiValueCanonAndSingleValueAltConstructor(int id) { + this(id, "AltConstructor"); + } + } + + record RecordWithSingleValueCanonAndMultiValueAltConstructor(String name) { + + public RecordWithSingleValueCanonAndMultiValueAltConstructor(String name, String email) { + this("AltConstructor"); + } + } + + private final ObjectMapper MAPPER = jsonMapperBuilder() + .annotationIntrospector(new Jdk8ConstructorParameterNameAnnotationIntrospector()) + .constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED) + .build(); + + /* + /********************************************************************** + /* Test methods, multi-value canonical constructor + single-value alt constructor + /********************************************************************** + */ + + public void testDeserializeMultipleConstructors_UsingMultiValueCanonicalConstructor() throws Exception { + RecordWithMultiValueCanonAndSingleValueAltConstructor value = MAPPER.readValue( + a2q("{'id':123,'name':'Bob'}"), + RecordWithMultiValueCanonAndSingleValueAltConstructor.class); + + assertEquals(new RecordWithMultiValueCanonAndSingleValueAltConstructor(123, "Bob"), value); + } + + public void testDeserializeMultipleConstructors_WillIgnoreSingleValueAltConstructor() throws Exception { + RecordWithMultiValueCanonAndSingleValueAltConstructor value = MAPPER.readValue( + a2q("{'id':123}"), + RecordWithMultiValueCanonAndSingleValueAltConstructor.class); + + assertEquals(new RecordWithMultiValueCanonAndSingleValueAltConstructor(123, null), value); + } + + /* + /********************************************************************** + /* Test methods, single-value canonical constructor + multi-value alt constructor + /********************************************************************** + */ + + public void testDeserializeMultipleConstructors_UsingSingleValueCanonicalConstructor() throws Exception { + RecordWithSingleValueCanonAndMultiValueAltConstructor value = MAPPER.readValue( + a2q("{'name':'Bob'}"), + RecordWithSingleValueCanonAndMultiValueAltConstructor.class); + + assertEquals(new RecordWithSingleValueCanonAndMultiValueAltConstructor("Bob"), value); + } + + public void testDeserializeMultipleConstructors_WillIgnoreMultiValueAltConstructor() throws Exception { + try { + MAPPER.readValue(a2q("{'name':'Bob','email':'bob@email.com'}"), RecordWithSingleValueCanonAndMultiValueAltConstructor.class); + } catch (UnrecognizedPropertyException e) { + verifyException(e, "Unrecognized"); + verifyException(e, "\"email\""); + verifyException(e, "RecordWithSingleValueCanonAndMultiValueAltConstructor"); + } + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordJsonValue3063Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordJsonValue3063Test.java new file mode 100644 index 0000000000..fe6512f2da --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordJsonValue3063Test.java @@ -0,0 +1,33 @@ +package com.fasterxml.jackson.databind.records; + +import java.util.Collections; +import java.util.Map; + +import com.fasterxml.jackson.annotation.*; + +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class RecordJsonValue3063Test extends BaseMapTest +{ + // [databind#3063] + record GetLocations3063(@JsonValue Map nameToLocation) + { + @JsonCreator + public GetLocations3063(Map nameToLocation) + { + this.nameToLocation = nameToLocation; + } + } + + private final ObjectMapper MAPPER = newJsonMapper(); + + // [databind#3063] + public void testRecordWithJsonValue3063() throws Exception + { + Map locations = Collections.singletonMap("a", "locationA"); + String json = MAPPER.writeValueAsString(new GetLocations3063(locations)); + + assertNotNull(json); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordNamingStrategy2992Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordNamingStrategy2992Test.java new file mode 100644 index 0000000000..f098009748 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordNamingStrategy2992Test.java @@ -0,0 +1,25 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +public class RecordNamingStrategy2992Test extends BaseMapTest +{ + @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) + record Record2992(String myId, String myValue) {} + + private final ObjectMapper MAPPER = newJsonMapper(); + + // [databind#2992] + public void testRecordRenaming2992() throws Exception + { + Record2992 src = new Record2992("id", "value"); + String json = MAPPER.writeValueAsString(src); + assertEquals(a2q("{'my_id':'id','my_value':'value'}"), json); + Record2992 after = MAPPER.readValue(json, Record2992.class); + assertEquals(src.myId(), after.myId()); + assertEquals(src.myValue(), after.myValue()); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordNullHandling3847Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordNullHandling3847Test.java new file mode 100644 index 0000000000..f7f87f3590 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordNullHandling3847Test.java @@ -0,0 +1,116 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.cfg.CoercionAction; +import com.fasterxml.jackson.databind.cfg.CoercionInputShape; +import com.fasterxml.jackson.databind.exc.InvalidNullException; +import com.fasterxml.jackson.databind.json.JsonMapper; + +// [databind#3874] +public class RecordNullHandling3847Test extends BaseMapTest { + /* + /********************************************************** + /* Set up + /********************************************************** + */ + + static class Pojo3847 { + public String fieldName; + } + + public record PlainRecord(String fieldName) {} + public record IntRecord(String description, int value) {} + + public record FixedRecord(@JsonProperty("field_name") String fieldName) {} + + /* + /********************************************************** + /* Tests + /********************************************************** + */ + + private final ObjectMapper NULL_MAPPER = JsonMapper.builder() + .defaultSetterInfo(JsonSetter.Value.construct(Nulls.FAIL, Nulls.FAIL)) + .withCoercionConfigDefaults(config -> config.setCoercion(CoercionInputShape.String, + CoercionAction.Fail)) + .build(); + + public void testPojoNullHandlingValid() throws Exception { + Pojo3847 pojo = NULL_MAPPER.readValue(a2q("{'fieldName': 'value'}"), Pojo3847.class); // expected + assertEquals("value", pojo.fieldName); + } + + public void testPojoNullHandlingNullValue() throws Exception { + try { + NULL_MAPPER.readValue(a2q("{'fieldName': null}"), Pojo3847.class); // expected + fail("should expect InvalidNullException"); + } catch (InvalidNullException e) { + verifyException(e, "Invalid `null` value encountered for property \"fieldName\""); + } + } + + public void testPojoNullHandlingEmptyJson() throws Exception { + assertNotNull(NULL_MAPPER.readValue("{}", Pojo3847.class)); + } + + public void testRecordNullHandlingValid() throws Exception { + PlainRecord plainRecord = NULL_MAPPER.readValue(a2q("{'fieldName': 'value'}"), PlainRecord.class); + assertEquals("value", plainRecord.fieldName); + } + + public void testRecordNullHandlingNullValue() throws Exception { + try { + NULL_MAPPER.readValue(a2q("{'fieldName': null}"), PlainRecord.class); + fail("should expect InvalidNullException"); + } catch (InvalidNullException e) { + verifyException(e, "Invalid `null` value encountered for property \"fieldName\""); + } + } + + public void testRecordNullHandlingEmptyJson() throws Exception { + try { + NULL_MAPPER.readValue("{}", PlainRecord.class); + fail("should expect InvalidNullException"); + } catch (InvalidNullException e) { + verifyException(e, "Invalid `null` value encountered for property \"fieldName\""); + } + } + + public void testRecordFixerNullHandlingValid() throws Exception { + FixedRecord fixedRecord = NULL_MAPPER.readValue(a2q("{ 'field_name': 'value' }"), FixedRecord.class); + assertEquals("value", fixedRecord.fieldName); + } + + public void testRecordFixerNullHandlingNullValue() throws Exception { + try { + NULL_MAPPER.readValue(a2q("{ 'field_name': null }"), FixedRecord.class); + fail("should expect InvalidNullException"); + } catch (InvalidNullException e) { + verifyException(e, "Invalid `null` value encountered for property \"field_name\""); + } + } + + public void testRecordFixerNullHandlingEmptyJson() throws Exception { + try { + NULL_MAPPER.readValue("{}", FixedRecord.class); + fail("should expect InvalidNullException"); + } catch (InvalidNullException e) { + verifyException(e, "Invalid `null` value encountered for property \"field_name\""); + } + } + + public void testRecordDefaultNullDeserialization() throws Exception { + PlainRecord pr = new ObjectMapper().readValue("{}", PlainRecord.class); + assertNull(pr.fieldName); + } + + public void testIntRecordDefaultNullDeserialization() throws Exception { + IntRecord ir = new ObjectMapper().readValue("{}", IntRecord.class); + assertNull(ir.description); + assertEquals(0, ir.value); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordSerializationOrderTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordSerializationOrderTest.java new file mode 100644 index 0000000000..494904c3e9 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordSerializationOrderTest.java @@ -0,0 +1,66 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class RecordSerializationOrderTest extends BaseMapTest +{ + record NestedRecordOne(String id, String email, NestedRecordTwo nestedRecordTwo) {} + record NestedRecordOneWithJsonProperty(String id, String email, + @JsonProperty("nestedProperty") NestedRecordTwo nestedRecordTwo) {} + record NestedRecordOneWithJsonPropertyIndex(@JsonProperty(index = 2) String id, + @JsonProperty(index = 0) String email, + @JsonProperty(value = "nestedProperty", index = 1) NestedRecordTwo nestedRecordTwo) {} + + @JsonPropertyOrder({"email", "nestedProperty", "id"}) + record NestedRecordOneWithJsonPropertyOrder(String id, + String email, + @JsonProperty(value = "nestedProperty") NestedRecordTwo nestedRecordTwo) {} + + record NestedRecordTwo(String id, String passport) {} + + private final ObjectMapper MAPPER = newJsonMapper(); + + /* + /********************************************************************** + /* Test methods, alternate constructors + /********************************************************************** + */ + + public void testSerializationOrder() throws Exception { + NestedRecordTwo nestedRecordTwo = new NestedRecordTwo("2", "111110"); + NestedRecordOne nestedRecordOne = new NestedRecordOne("1", "test@records.com", nestedRecordTwo); + final String output = MAPPER.writeValueAsString(nestedRecordOne); + final String expected = "{\"id\":\"1\",\"email\":\"test@records.com\",\"nestedRecordTwo\":{\"id\":\"2\",\"passport\":\"111110\"}}"; + assertEquals(expected, output); + } + + public void testSerializationOrderWithJsonProperty() throws Exception { + NestedRecordTwo nestedRecordTwo = new NestedRecordTwo("2", "111110"); + NestedRecordOneWithJsonProperty nestedRecordOne = + new NestedRecordOneWithJsonProperty("1", "test@records.com", nestedRecordTwo); + final String output = MAPPER.writeValueAsString(nestedRecordOne); + final String expected = "{\"id\":\"1\",\"email\":\"test@records.com\",\"nestedProperty\":{\"id\":\"2\",\"passport\":\"111110\"}}"; + assertEquals(expected, output); + } + + public void testSerializationOrderWithJsonPropertyIndexes() throws Exception { + NestedRecordTwo nestedRecordTwo = new NestedRecordTwo("2", "111110"); + NestedRecordOneWithJsonPropertyIndex nestedRecordOne = + new NestedRecordOneWithJsonPropertyIndex("1", "test@records.com", nestedRecordTwo); + final String output = MAPPER.writeValueAsString(nestedRecordOne); + final String expected = "{\"email\":\"test@records.com\",\"nestedProperty\":{\"id\":\"2\",\"passport\":\"111110\"},\"id\":\"1\"}"; + assertEquals(expected, output); + } + + public void testSerializationOrderWithJsonPropertyOrder() throws Exception { + NestedRecordTwo nestedRecordTwo = new NestedRecordTwo("2", "111110"); + NestedRecordOneWithJsonPropertyOrder nestedRecordOne = + new NestedRecordOneWithJsonPropertyOrder("1", "test@records.com", nestedRecordTwo); + final String output = MAPPER.writeValueAsString(nestedRecordOne); + final String expected = "{\"email\":\"test@records.com\",\"nestedProperty\":{\"id\":\"2\",\"passport\":\"111110\"},\"id\":\"1\"}"; + assertEquals(expected, output); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordTypeInfo3342Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordTypeInfo3342Test.java new file mode 100644 index 0000000000..c565832b40 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordTypeInfo3342Test.java @@ -0,0 +1,58 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; + +// [databind#3102] +public class RecordTypeInfo3342Test extends BaseMapTest +{ + public enum SpiceLevel { + LOW, + HIGH + } + + public interface SpiceTolerance { + } + + public record LowSpiceTolerance(String food) implements SpiceTolerance { + } + + public record HighSpiceTolerance(String food) implements SpiceTolerance { + } + + public record Example( + SpiceLevel level, + @JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXTERNAL_PROPERTY, + property = "level") + @JsonSubTypes({ + @JsonSubTypes.Type(value = LowSpiceTolerance.class, name = "LOW"), + @JsonSubTypes.Type(value = HighSpiceTolerance.class, name = "HIGH") + }) + SpiceTolerance tolerance) { } + + private final ObjectMapper MAPPER = newJsonMapper(); + + public void testSerializeDeserializeJsonSubType_LOW() throws Exception { + Example record = new Example(SpiceLevel.LOW, new LowSpiceTolerance("Tomato")); + + String json = MAPPER.writeValueAsString(record); + assertEquals("{\"level\":\"LOW\",\"tolerance\":{\"food\":\"Tomato\"}}", json); + + Example value = MAPPER.readValue(json, Example.class); + assertEquals(record, value); + } + + public void testSerializeDeserializeJsonSubType_HIGH() throws Exception { + Example record = new Example(SpiceLevel.HIGH, new HighSpiceTolerance("Chilli")); + + String json = MAPPER.writeValueAsString(record); + assertEquals("{\"level\":\"HIGH\",\"tolerance\":{\"food\":\"Chilli\"}}", json); + + Example value = MAPPER.readValue(json, Example.class); + assertEquals(record, value); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordUpdate3079Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordUpdate3079Test.java new file mode 100644 index 0000000000..09b3f5b227 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordUpdate3079Test.java @@ -0,0 +1,34 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; + +// see failing test +public class RecordUpdate3079Test extends BaseMapTest +{ + public record IdNameRecord(int id, String name) { } + + static class IdNameWrapper { + public IdNameRecord value; + + protected IdNameWrapper() { } + public IdNameWrapper(IdNameRecord v) { value = v; } + } + + private final ObjectMapper MAPPER = newJsonMapper(); + + // [databind#3079]: also: should be able to Record valued property + public void testRecordAsPropertyUpdate() throws Exception + { + IdNameRecord origRecord = new IdNameRecord(123, "Bob"); + IdNameWrapper orig = new IdNameWrapper(origRecord); + + IdNameWrapper delta = new IdNameWrapper(new IdNameRecord(200, "Gary")); + IdNameWrapper result = MAPPER.updateValue(orig, delta); + + assertEquals(200, result.value.id()); + assertEquals("Gary", result.value.name()); + assertSame(orig, result); + assertNotSame(origRecord, result.value); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithIgnoreOverride3992Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithIgnoreOverride3992Test.java new file mode 100644 index 0000000000..b2b31706c1 --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithIgnoreOverride3992Test.java @@ -0,0 +1,49 @@ +package com.fasterxml.jackson.databind.records; + +import java.util.*; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class RecordWithIgnoreOverride3992Test extends BaseMapTest +{ + // [databind#3992] + public record HelloRecord(String text, @JsonIgnore Recursion hidden) { + // Before fix: works if this override is removed + // After fix: works either way + @Override + public Recursion hidden() { + return hidden; + } + } + + static class Recursion { + public List all = new ArrayList<>(); + + void add(Recursion recursion) { + all.add(recursion); + } + } + + private final ObjectMapper MAPPER = newJsonMapper(); + + /* + /********************************************************************** + /* Test methods + /********************************************************************** + */ + + // [databind#3992] + public void testHelloRecord() throws Exception { + Recursion beanWithRecursion = new Recursion(); + beanWithRecursion.add(beanWithRecursion); + String json = MAPPER.writer() + .writeValueAsString(new HelloRecord("hello", beanWithRecursion)); + assertEquals(a2q("{'text':'hello'}"), json); + + // Let's check deserialization works too, just in case. + HelloRecord result = MAPPER.readValue(json, HelloRecord.class); + assertNotNull(result); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonIgnoreTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonIgnoreTest.java new file mode 100644 index 0000000000..d68e12f62b --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonIgnoreTest.java @@ -0,0 +1,95 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class RecordWithJsonIgnoreTest extends BaseMapTest +{ + record RecordWithIgnore(int id, @JsonIgnore String name) { + } + + record RecordWithIgnoreJsonProperty(int id, @JsonIgnore @JsonProperty("name") String name) { + } + + record RecordWithIgnoreAccessor(int id, String name) { + + @JsonIgnore + @Override + public String name() { + return name; + } + } + + record RecordWithIgnorePrimitiveType(@JsonIgnore int id, String name) { + } + + private final ObjectMapper MAPPER = newJsonMapper(); + + /* + /********************************************************************** + /* Test methods, JsonIgnore + /********************************************************************** + */ + + public void testSerializeJsonIgnoreRecord() throws Exception { + String json = MAPPER.writeValueAsString(new RecordWithIgnore(123, "Bob")); + assertEquals("{\"id\":123}", json); + } + + public void testDeserializeJsonIgnoreRecord() throws Exception { + RecordWithIgnore value = MAPPER.readValue("{\"id\":123,\"name\":\"Bob\"}", + RecordWithIgnore.class); + assertEquals(new RecordWithIgnore(123, null), value); + } + + /* + /********************************************************************** + /* Test methods, JsonIgnore + JsonProperty + /********************************************************************** + */ + + public void testSerializeJsonIgnoreAndJsonPropertyRecord() throws Exception { + String json = MAPPER.writeValueAsString(new RecordWithIgnoreJsonProperty(123, "Bob")); + assertEquals("{\"id\":123}", json); + } + + public void testDeserializeJsonIgnoreAndJsonPropertyRecord() throws Exception { + RecordWithIgnoreJsonProperty value = MAPPER.readValue("{\"id\":123,\"name\":\"Bob\"}", RecordWithIgnoreJsonProperty.class); + assertEquals(new RecordWithIgnoreJsonProperty(123, "Bob"), value); + } + + /* + /********************************************************************** + /* Test methods, JsonIgnore accessor + /********************************************************************** + */ + + public void testSerializeJsonIgnoreAccessorRecord() throws Exception { + String json = MAPPER.writeValueAsString(new RecordWithIgnoreAccessor(123, "Bob")); + assertEquals("{\"id\":123}", json); + } + + public void testDeserializeJsonIgnoreAccessorRecord() throws Exception { + RecordWithIgnoreAccessor value = MAPPER.readValue("{\"id\":123,\"name\":\"Bob\"}", + RecordWithIgnoreAccessor.class); + assertEquals(new RecordWithIgnoreAccessor(123, null), value); + } + + /* + /********************************************************************** + /* Test methods, JsonIgnore parameter of primitive type + /********************************************************************** + */ + + public void testSerializeJsonIgnorePrimitiveTypeRecord() throws Exception { + String json = MAPPER.writeValueAsString(new RecordWithIgnorePrimitiveType(123, "Bob")); + assertEquals("{\"name\":\"Bob\"}", json); + } + + public void testDeserializeJsonIgnorePrimitiveTypeRecord() throws Exception { + RecordWithIgnorePrimitiveType value = MAPPER.readValue("{\"id\":123,\"name\":\"Bob\"}", RecordWithIgnorePrimitiveType.class); + assertEquals(new RecordWithIgnorePrimitiveType(0, "Bob"), value); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonNaming3102Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonNaming3102Test.java new file mode 100644 index 0000000000..3533b6362b --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonNaming3102Test.java @@ -0,0 +1,40 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.annotation.JsonCreator; + +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +public class RecordWithJsonNaming3102Test extends BaseMapTest +{ + @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) + public record SnakeRecord(int id, String toSnakeCase) { + @JsonCreator + public SnakeRecord(int id, String toSnakeCase) { + this.id = id; + this.toSnakeCase = toSnakeCase; + } + } + + private final ObjectMapper MAPPER = newJsonMapper(); + + /* + /********************************************************************** + /* Test methods, Record type introspection + /********************************************************************** + */ + + // [databind#3102] + public void testDeserializeWithJsonNaming() throws Exception + { + final ObjectReader r = MAPPER.readerFor(SnakeRecord.class); + // First, regular case + SnakeRecord value = r.readValue(a2q( + "{'id':123,'to_snake_case':'snakey'}")); + assertEquals(123, value.id); + assertEquals("snakey", value.toSnakeCase); + } +} diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonSetter2974Test.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonSetter2974Test.java new file mode 100644 index 0000000000..a54825b83e --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordWithJsonSetter2974Test.java @@ -0,0 +1,61 @@ +package com.fasterxml.jackson.databind.records; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.exc.InvalidNullException; + +public class RecordWithJsonSetter2974Test extends BaseMapTest +{ + record RecordWithNonNullDefs(@JsonSetter(nulls=Nulls.AS_EMPTY) List names, + @JsonSetter(nulls=Nulls.FAIL) Map agesByNames) + { } + + private final ObjectMapper MAPPER = newJsonMapper(); + + /* + /********************************************************************** + /* Test methods, Record type introspection + /********************************************************************** + */ + + // [databind#2974] + public void testDeserializeWithNullAsEmpty() throws Exception + { + final ObjectReader r = MAPPER.readerFor(RecordWithNonNullDefs.class); + // First, regular case + RecordWithNonNullDefs value = r.readValue(a2q( +"{'names':['bob'],'agesByNames':{'bob':39}}")); + assertEquals(1, value.names().size()); + assertEquals("bob", value.names().get(0)); + assertEquals(1, value.agesByNames().size()); + assertEquals(Integer.valueOf(39), value.agesByNames().get("bob")); + + // Then leave out list + value = r.readValue(a2q("{'agesByNames':{'bob':42}}")); + assertNotNull(value.names()); + assertEquals(0, value.names().size()); + assertNotNull(value.agesByNames()); + assertEquals(1, value.agesByNames().size()); + assertEquals(Integer.valueOf(42), value.agesByNames().get("bob")); + } + + // [databind#2974] + public void testDeserializeWithFailForNull() throws Exception + { + final ObjectReader r = MAPPER.readerFor(RecordWithNonNullDefs.class); + // First, regular case + // But attempting to leave out Map ought to fail + try { + /*RecordWithNonNullDefs value =*/ r.readValue(a2q("{'names':['bob']}")); + fail("Should not pass with missing/null 'agesByNames'"); + } catch (InvalidNullException e) { + verifyException(e, "property \"agesByNames\""); + } + } +} diff --git a/src/test-jdk21/java/com/fasterxml/jackson/databind/jdk21/Java21CollectionsTest.java b/src/test-jdk21/java/com/fasterxml/jackson/databind/jdk21/Java21CollectionsTest.java new file mode 100644 index 0000000000..4f2908ef1a --- /dev/null +++ b/src/test-jdk21/java/com/fasterxml/jackson/databind/jdk21/Java21CollectionsTest.java @@ -0,0 +1,51 @@ +package com.fasterxml.jackson.databind.jdk21; + +import java.util.*; + +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; + +public class Java21CollectionsTest extends BaseMapTest +{ + // [databind#4089] + record SequencedCollections( + SequencedCollection sequencedCollection, + SequencedSet sequencedSet, + SequencedMap sequencedMap) { + } + + public void testSequencedCollectionTypesDeserialize() throws Exception { + String json = """ + { + "sequencedCollection": ["A", "B"], + "sequencedSet": ["C", "D"], + "sequencedMap": {"A": 1, "B": 2} + } + """; + + ObjectMapper objectMapper = JsonMapper.builder().build(); + SequencedCollections value = objectMapper.readValue(json, SequencedCollections.class); + assertEquals(ArrayList.class, value.sequencedCollection.getClass()); + assertEquals(LinkedHashSet.class, value.sequencedSet.getClass()); + assertEquals(LinkedHashMap.class, value.sequencedMap.getClass()); + } + + public void testSequencedCollectionTypesRoundTrip() throws Exception { + ArrayList arrayList = new ArrayList<>(); + arrayList.add("A"); + arrayList.add("B"); + LinkedHashSet linkedHashSet = new LinkedHashSet<>(); + linkedHashSet.add("C"); + linkedHashSet.add("D"); + LinkedHashMap linkedHashMap = new LinkedHashMap<>(); + linkedHashMap.put("A", 1); + linkedHashMap.put("B", 2); + SequencedCollections input = new SequencedCollections(arrayList, linkedHashSet, linkedHashMap); + + ObjectMapper objectMapper = JsonMapper.builder().build(); + String json = objectMapper.writeValueAsString(input); + SequencedCollections value = objectMapper.readValue(json, SequencedCollections.class); + assertEquals(input, value); + } +} From 5280004568986ddc1a5ebb9666938c49b5db72ae Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 3 Feb 2024 15:53:35 +0900 Subject: [PATCH 04/60] Delete JsonAnySetterThroughCreator562Test.java --- .../JsonAnySetterThroughCreator562Test.java | 52 ------------------- 1 file changed, 52 deletions(-) delete mode 100644 src/test/java/com/fasterxml/jackson/failing/JsonAnySetterThroughCreator562Test.java diff --git a/src/test/java/com/fasterxml/jackson/failing/JsonAnySetterThroughCreator562Test.java b/src/test/java/com/fasterxml/jackson/failing/JsonAnySetterThroughCreator562Test.java deleted file mode 100644 index a72d153dca..0000000000 --- a/src/test/java/com/fasterxml/jackson/failing/JsonAnySetterThroughCreator562Test.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.fasterxml.jackson.failing; - -import java.util.HashMap; - -import org.junit.jupiter.api.Test; - -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.newJsonMapper; - -public class JsonAnySetterThroughCreator562Test { - - static class MyClass { - public String field; - public HashMap anySetter; - - @JsonCreator - public MyClass( - @JsonProperty("field") String field, - @JsonAnySetter HashMap anySetter - ) { - this.field = field; - this.anySetter = anySetter; - } - } - - @Test - void testJsonAnySetterOnRecord() throws Exception { - String json = - "{\n" + - " \"field\": \"value\",\n" + - " \"unmapped1\": \"value1\",\n" + - " \"unmapped2\": \"value2\"\n" + - "}"; - HashMap expected = new HashMap<>(); - expected.put("unmapped1", "value1"); - expected.put("unmapped2", "value2"); - - - ObjectMapper objectMapper = newJsonMapper(); - - MyClass deserialized = objectMapper.readValue(json, MyClass.class); - - assertEquals("value", deserialized.field); - assertEquals(expected, deserialized.anySetter); - } -} From 99a882b5317566a2627d9621a49ba5128942c8e4 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 3 Feb 2024 21:43:11 +0900 Subject: [PATCH 05/60] Remove hard coded field names --- .../jackson/databind/deser/BasicDeserializerFactory.java | 5 ++--- 1 file changed, 2 insertions(+), 3 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 fdd937be33..809104a125 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -879,10 +879,9 @@ protected void _addExplicitPropertyCreator(DeserializationContext ctxt, */ } name = candidate.findImplicitParamName(i); + // [databind#562] Any setter can be used... if (ctxt.getAnnotationIntrospector().hasAnySetter(param)) { - // [databind#562] Any setter can be used... - System.out.println(); - name = PropertyName.construct("leftovers"); + // no-op } else { _validateNamedPropertyParameter(ctxt, beanDesc, candidate, i, name, injectId); From a840e56ba4087e2a9350cbe1b38c2b8522085cd8 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 3 Feb 2024 23:08:35 +0900 Subject: [PATCH 06/60] Clean Up --- .../deser/BasicDeserializerFactory.java | 3 +- .../deser/BeanDeserializerFactory.java | 23 ++++++++++-- .../deser/impl/PropertyBasedCreator.java | 2 -- .../deser/impl/PropertyValueBuffer.java | 36 +++++++++++++------ 4 files changed, 47 insertions(+), 17 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 809104a125..385b1656cb 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -880,7 +880,8 @@ protected void _addExplicitPropertyCreator(DeserializationContext ctxt, } name = candidate.findImplicitParamName(i); // [databind#562] Any setter can be used... - if (ctxt.getAnnotationIntrospector().hasAnySetter(param)) { + Boolean hasAnySetter = ctxt.getAnnotationIntrospector().hasAnySetter(param); + if (hasAnySetter != null && hasAnySetter) { // no-op } else { _validateNamedPropertyParameter(ctxt, beanDesc, candidate, i, diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index 4cdb3b7bfc..e87496e660 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -544,11 +544,11 @@ protected void addBeanProps(DeserializationContext ctxt, // Also, do we have a fallback "any" setter? AnnotatedMember anySetter = beanDesc.findAnySetterAccessor(); + AnnotatedMember creatorPropWithAnySetter = _findCreatorPropWithAnySetter(ctxt, creatorProps); if (anySetter != null) { builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetter)); - } - else if (ctxt.getAnnotationIntrospector().hasAnySetter(creatorProps[1].getMember())) { - builder.setAnySetter(constructAnySetter(ctxt, beanDesc, creatorProps[1].getMember())); + } else if (creatorPropWithAnySetter != null) { + builder.setAnySetter(constructAnySetter(ctxt, beanDesc, creatorPropWithAnySetter)); } else { // 23-Jan-2018, tatu: although [databind#1805] would suggest we should block // properties regardless, for now only consider unless there's any setter... @@ -664,6 +664,23 @@ else if (ctxt.getAnnotationIntrospector().hasAnySetter(creatorProps[1].getMember } } + private AnnotatedMember _findCreatorPropWithAnySetter(DeserializationContext ctxt, SettableBeanProperty[] creatorProps) { + if (creatorProps != null) { + AnnotationIntrospector ai = ctxt.getAnnotationIntrospector(); + for (SettableBeanProperty prop : creatorProps) { + AnnotatedMember m = prop.getMember(); + if (m != null) { + Boolean hasAnySetter = ai.hasAnySetter(m); + if (hasAnySetter != null && hasAnySetter) { + return prop.getMember(); + } + } + + } + } + return null; + } + private boolean _isSetterlessType(Class rawType) { // May also need to consider getters // for Map/Collection properties; but with lowest precedence diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java index c82564fb86..14488e88c2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java @@ -207,8 +207,6 @@ public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) thr { Object bean = _valueInstantiator.createFromObjectWith(ctxt, _allProperties, buffer); - - // returning null isn't quite legal, but let's let caller deal with that if (bean != null) { // Object Id to handle? 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 35f371fe51..f3bd2708fc 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 @@ -179,20 +179,34 @@ public Object[] getParameters(SettableBeanProperty[] props) } } } + + // [databind#562]: Respect @JsonAnySetter in @JsonCreator // check if we have anySetter Param - // if we do, parse from buffer and set - Map param = new HashMap<>(); - for (PropertyValue next = buffered(); next != null; next = next.next) { - try { - next.assign(param); - } catch (IOException e) { - // TODO : Wrap properly - throw new JsonMappingException("TODO: Wrap"); + AnnotationIntrospector ai = _context.getAnnotationIntrospector(); + for (int i = 0; i < props.length; i++) { + SettableBeanProperty prop = props[i]; + AnnotatedMember member = prop.getMember(); + if (member == null) { + continue; + } + Boolean hasAnySetter = ai.hasAnySetter(member); + if (hasAnySetter == null || !hasAnySetter) { + continue; + } + // So we have prop with anySetter. Should be Map-like, so let's assign such? + // Assign all remaining values to the map + Map param = new HashMap<>(); + for (PropertyValue next = buffered(); next != null; next = next.next) { + try { + next.assign(param); + } catch (IOException e) { + _context.reportInputMismatch(prop, e.getMessage()); + } } + // assign it then return + _creatorParameters[i] = param; + break; } - // find the "FIRST" creator Prop with JsonAnySetter - // Then assign value to it - _creatorParameters[1] = param; return _creatorParameters; } From 75c23f1528f80a45dadc414bcccc020469f0c39e Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 3 Feb 2024 23:23:22 +0900 Subject: [PATCH 07/60] Clean up unused _anySetter --- .../databind/deser/BeanDeserializer.java | 23 ++++++++- .../deser/BeanDeserializerFactory.java | 49 ------------------- .../deser/impl/PropertyBasedCreator.java | 3 +- 3 files changed, 23 insertions(+), 52 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index 9bc05baee6..96a6ce8c63 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.cfg.CoercionAction; import com.fasterxml.jackson.databind.deser.impl.*; import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring; +import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil; import com.fasterxml.jackson.databind.util.NameTransformer; @@ -520,10 +521,13 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri unknown.copyCurrentStructure(p); } + + Collection creatorProps = creator.properties(); + AnnotatedMember anySetter = _findCreatorPropWithAnySetter(ctxt, creatorProps.toArray(new SettableBeanProperty[creatorProps.size()])); // We hit END_OBJECT, so: Object bean; try { - if (_anySetter != null) { + if (anySetter != null) { bean = creator.buildSimple(ctxt, buffer); } else { bean = creator.build(ctxt, buffer); @@ -553,6 +557,23 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri return bean; } + + private AnnotatedMember _findCreatorPropWithAnySetter(DeserializationContext ctxt, SettableBeanProperty[] creatorProps) { + if (creatorProps != null) { + AnnotationIntrospector ai = ctxt.getAnnotationIntrospector(); + for (SettableBeanProperty prop : creatorProps) { + AnnotatedMember m = prop.getMember(); + if (m != null) { + Boolean hasAnySetter = ai.hasAnySetter(m); + if (hasAnySetter != null && hasAnySetter) { + return prop.getMember(); + } + } + + } + } + return null; + } /** * @since 2.8 */ diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index e87496e660..8487b1e049 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -544,11 +544,8 @@ protected void addBeanProps(DeserializationContext ctxt, // Also, do we have a fallback "any" setter? AnnotatedMember anySetter = beanDesc.findAnySetterAccessor(); - AnnotatedMember creatorPropWithAnySetter = _findCreatorPropWithAnySetter(ctxt, creatorProps); if (anySetter != null) { builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetter)); - } else if (creatorPropWithAnySetter != null) { - builder.setAnySetter(constructAnySetter(ctxt, beanDesc, creatorPropWithAnySetter)); } else { // 23-Jan-2018, tatu: although [databind#1805] would suggest we should block // properties regardless, for now only consider unless there's any setter... @@ -664,23 +661,6 @@ protected void addBeanProps(DeserializationContext ctxt, } } - private AnnotatedMember _findCreatorPropWithAnySetter(DeserializationContext ctxt, SettableBeanProperty[] creatorProps) { - if (creatorProps != null) { - AnnotationIntrospector ai = ctxt.getAnnotationIntrospector(); - for (SettableBeanProperty prop : creatorProps) { - AnnotatedMember m = prop.getMember(); - if (m != null) { - Boolean hasAnySetter = ai.hasAnySetter(m); - if (hasAnySetter != null && hasAnySetter) { - return prop.getMember(); - } - } - - } - } - return null; - } - private boolean _isSetterlessType(Class rawType) { // May also need to consider getters // for Map/Collection properties; but with lowest precedence @@ -839,35 +819,6 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, valueType, null, mutator, PropertyMetadata.STD_OPTIONAL); - } else if (mutator instanceof AnnotatedParameter){ - AnnotatedParameter af = (AnnotatedParameter) mutator; - // get the type from the content type of the map object - JavaType fieldType = af.getType(); - // 31-Jul-2022, tatu: Not just Maps any more but also JsonNode, so: - if (fieldType.isMapLikeType()) { - fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); - keyType = fieldType.getKeyType(); - valueType = fieldType.getContentType(); - prop = new BeanProperty.Std(PropertyName.construct("stuff"), - fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); - } else if (fieldType.hasRawClass(JsonNode.class) - || fieldType.hasRawClass(ObjectNode.class)) { - fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); - // Deserialize is individual values of ObjectNode, not full ObjectNode, so: - valueType = ctxt.constructType(JsonNode.class); - prop = new BeanProperty.Std(PropertyName.construct(mutator.getName()), - fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); - - // Unlike with more complicated types, here we do not allow any annotation - // overrides etc but instead short-cut handling: - return SettableAnyProperty.constructForJsonNodeField(ctxt, - prop, mutator, valueType, - ctxt.findRootValueDeserializer(valueType)); - } else { - return ctxt.reportBadDefinition(beanDesc.getType(), String.format( - "Unsupported type for any-setter: %s -- only support `Map`s, `JsonNode` and `ObjectNode` ", - ClassUtil.getTypeDescription(fieldType))); - } } else if (isField) { AnnotatedField af = (AnnotatedField) mutator; // get the type from the content type of the map object diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java index 14488e88c2..042aea09a2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java @@ -205,8 +205,7 @@ public Object buildSimple(DeserializationContext ctxt, PropertyValueBuffer buffe public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException { - Object bean = _valueInstantiator.createFromObjectWith(ctxt, - _allProperties, buffer); + Object bean = buildSimple(ctxt, buffer); // returning null isn't quite legal, but let's let caller deal with that if (bean != null) { // Object Id to handle? From 2b7d22f468ebd801b05f67c79fcba45dac224c0b Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 3 Feb 2024 23:34:36 +0900 Subject: [PATCH 08/60] Add back required property _anySetter implementation --- .../databind/deser/BeanDeserializer.java | 23 +----------- .../deser/BeanDeserializerFactory.java | 36 +++++++++++++++++++ .../deser/impl/PropertyBasedCreator.java | 3 +- 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index 96a6ce8c63..9bc05baee6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -8,7 +8,6 @@ import com.fasterxml.jackson.databind.cfg.CoercionAction; import com.fasterxml.jackson.databind.deser.impl.*; import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring; -import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil; import com.fasterxml.jackson.databind.util.NameTransformer; @@ -521,13 +520,10 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri unknown.copyCurrentStructure(p); } - - Collection creatorProps = creator.properties(); - AnnotatedMember anySetter = _findCreatorPropWithAnySetter(ctxt, creatorProps.toArray(new SettableBeanProperty[creatorProps.size()])); // We hit END_OBJECT, so: Object bean; try { - if (anySetter != null) { + if (_anySetter != null) { bean = creator.buildSimple(ctxt, buffer); } else { bean = creator.build(ctxt, buffer); @@ -557,23 +553,6 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri return bean; } - - private AnnotatedMember _findCreatorPropWithAnySetter(DeserializationContext ctxt, SettableBeanProperty[] creatorProps) { - if (creatorProps != null) { - AnnotationIntrospector ai = ctxt.getAnnotationIntrospector(); - for (SettableBeanProperty prop : creatorProps) { - AnnotatedMember m = prop.getMember(); - if (m != null) { - Boolean hasAnySetter = ai.hasAnySetter(m); - if (hasAnySetter != null && hasAnySetter) { - return prop.getMember(); - } - } - - } - } - return null; - } /** * @since 2.8 */ diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index 8487b1e049..4f4461625c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -544,8 +544,11 @@ protected void addBeanProps(DeserializationContext ctxt, // Also, do we have a fallback "any" setter? AnnotatedMember anySetter = beanDesc.findAnySetterAccessor(); + AnnotatedMember creatorPropWithAnySetter = _findCreatorPropWithAnySetter(ctxt, creatorProps); if (anySetter != null) { builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetter)); + } else if (creatorPropWithAnySetter != null) { + builder.setAnySetter(constructAnySetter(ctxt, beanDesc, creatorPropWithAnySetter)); } else { // 23-Jan-2018, tatu: although [databind#1805] would suggest we should block // properties regardless, for now only consider unless there's any setter... @@ -661,6 +664,23 @@ protected void addBeanProps(DeserializationContext ctxt, } } + private AnnotatedMember _findCreatorPropWithAnySetter(DeserializationContext ctxt, SettableBeanProperty[] creatorProps) { + if (creatorProps != null) { + AnnotationIntrospector ai = ctxt.getAnnotationIntrospector(); + for (SettableBeanProperty prop : creatorProps) { + AnnotatedMember m = prop.getMember(); + if (m != null) { + Boolean hasAnySetter = ai.hasAnySetter(m); + if (hasAnySetter != null && hasAnySetter) { + return prop.getMember(); + } + } + + } + } + return null; + } + private boolean _isSetterlessType(Class rawType) { // May also need to consider getters // for Map/Collection properties; but with lowest precedence @@ -819,6 +839,22 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, valueType, null, mutator, PropertyMetadata.STD_OPTIONAL); + } else if (mutator instanceof AnnotatedParameter){ + AnnotatedParameter af = (AnnotatedParameter) mutator; + // get the type from the content type of the map object + JavaType fieldType = af.getType(); + // 31-Jul-2022, tatu: Not just Maps any more but also JsonNode, so: + if (fieldType.isMapLikeType()) { + fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); + keyType = fieldType.getKeyType(); + valueType = fieldType.getContentType(); + prop = new BeanProperty.Std(PropertyName.construct("stuff"), + fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); + } else { + return ctxt.reportBadDefinition(beanDesc.getType(), String.format( + "Unsupported type for any-setter: %s -- only support `Map`s, `JsonNode` and `ObjectNode` ", + ClassUtil.getTypeDescription(fieldType))); + } } else if (isField) { AnnotatedField af = (AnnotatedField) mutator; // get the type from the content type of the map object diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java index 042aea09a2..14488e88c2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java @@ -205,7 +205,8 @@ public Object buildSimple(DeserializationContext ctxt, PropertyValueBuffer buffe public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException { - Object bean = buildSimple(ctxt, buffer); + Object bean = _valueInstantiator.createFromObjectWith(ctxt, + _allProperties, buffer); // returning null isn't quite legal, but let's let caller deal with that if (bean != null) { // Object Id to handle? From 1a9afab17f397e176ba9cda6dbf4da6de700c5f5 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 4 Feb 2024 18:13:39 +0900 Subject: [PATCH 09/60] Add `Boolean _ hasAnySetter` in --- .../databind/deser/CreatorProperty.java | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 5e7ce8c98d..1b2d7c399a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -76,19 +76,40 @@ public class CreatorProperty protected boolean _ignorable; /** - * @since 2.11 + * Marker flag to indicate that current property is used to handle "any setter" via `@JsonAnySetter`. + * + * @since 2.18 + */ + protected final Boolean _hasAnySetter; + + /** + * @since 2.18 */ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperName, TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, - PropertyMetadata metadata) + PropertyMetadata metadata, Boolean hasAnySetter) { super(name, type, wrapperName, typeDeser, contextAnnotations, metadata); _annotated = param; _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; + _hasAnySetter = hasAnySetter; + } + + /** + * @since 2.11 + * @deprecated Since 2.18. use factory later version instead. + */ + protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperName, + TypeDeserializer typeDeser, + Annotations contextAnnotations, AnnotatedParameter param, + int index, JacksonInject.Value injectable, + PropertyMetadata metadata) + { + this(name, type, wrapperName, typeDeser, contextAnnotations, param, index, injectable, metadata, null); } /** @@ -125,6 +146,21 @@ public CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperNam * * @since 2.11 */ + public static CreatorProperty construct(PropertyName name, JavaType type, PropertyName wrapperName, + TypeDeserializer typeDeser, + Annotations contextAnnotations, AnnotatedParameter param, + int index, JacksonInject.Value injectable, + PropertyMetadata metadata, + Boolean hasAnySetter) + { + return new CreatorProperty(name, type, wrapperName, typeDeser, contextAnnotations, + param, index, injectable, metadata, hasAnySetter); + } + + /** + * @since 2.11 + * @deprecated Since 2.18. use later version instead. + */ public static CreatorProperty construct(PropertyName name, JavaType type, PropertyName wrapperName, TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, @@ -132,7 +168,7 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper PropertyMetadata metadata) { return new CreatorProperty(name, type, wrapperName, typeDeser, contextAnnotations, - param, index, injectable, metadata); + param, index, injectable, metadata, null); } /** @@ -145,6 +181,7 @@ protected CreatorProperty(CreatorProperty src, PropertyName newName) { _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; + _hasAnySetter = src._hasAnySetter; // [databind#562] Since 2.18 } protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, @@ -155,6 +192,7 @@ protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; + _hasAnySetter = src._hasAnySetter; // [databind#562] Since 2.18 } @Override @@ -320,6 +358,13 @@ public boolean isInjectionOnly() { // public boolean isInjectionOnly() { return false; } + /** + * @since 2.18 + */ + public boolean hasAnySetter() { + return Boolean.TRUE.equals(_hasAnySetter); + } + /* /********************************************************** /* Overridden methods, other From 2b2e0aa969da449ad0c07e45188902dda170197e Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 4 Feb 2024 18:14:05 +0900 Subject: [PATCH 10/60] Use hasAnySetter from CreatorProperty --- .../deser/BasicDeserializerFactory.java | 29 +++++++++++++++---- .../deser/impl/PropertyValueBuffer.java | 17 +++++------ 2 files changed, 30 insertions(+), 16 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 385b1656cb..aef762f723 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -867,6 +867,8 @@ protected void _addExplicitPropertyCreator(DeserializationContext ctxt, JacksonInject.Value injectId = candidate.injection(i); AnnotatedParameter param = candidate.parameter(i); PropertyName name = candidate.paramName(i); + Boolean hasAnySetter = null; + if (name == null) { // 21-Sep-2017, tatu: Looks like we want to block accidental use of Unwrapped, // as that will not work with Creators well at all @@ -879,16 +881,17 @@ protected void _addExplicitPropertyCreator(DeserializationContext ctxt, */ } name = candidate.findImplicitParamName(i); - // [databind#562] Any setter can be used... - Boolean hasAnySetter = ctxt.getAnnotationIntrospector().hasAnySetter(param); - if (hasAnySetter != null && hasAnySetter) { + // [databind#562] Allow @JsonAnySetter in creators. Introspection is done here only because of + // _validateNamedPropertyParameter() check. Otherwise, in constructCreatorProperty seems appropriate.... + hasAnySetter = ctxt.getAnnotationIntrospector().hasAnySetter(param); + if (Boolean.TRUE.equals(hasAnySetter)) { // no-op } else { _validateNamedPropertyParameter(ctxt, beanDesc, candidate, i, name, injectId); } } - properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId); + properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId, hasAnySetter); } creators.addPropertyCreator(candidate.creator(), true, properties); } @@ -1193,7 +1196,8 @@ protected void _reportUnwrappedCreatorProperty(DeserializationContext ctxt, protected SettableBeanProperty constructCreatorProperty(DeserializationContext ctxt, BeanDescription beanDesc, PropertyName name, int index, AnnotatedParameter param, - JacksonInject.Value injectable) + JacksonInject.Value injectable, + Boolean hasAnySetter) throws JsonMappingException { final DeserializationConfig config = ctxt.getConfig(); @@ -1231,7 +1235,7 @@ protected SettableBeanProperty constructCreatorProperty(DeserializationContext c // so it is not called directly here SettableBeanProperty prop = CreatorProperty.construct(name, type, property.getWrapperName(), typeDeser, beanDesc.getClassAnnotations(), param, index, injectable, - metadata); + metadata, hasAnySetter); JsonDeserializer deser = findDeserializerFromAnnotation(ctxt, param); if (deser == null) { deser = type.getValueHandler(); @@ -1244,6 +1248,19 @@ protected SettableBeanProperty constructCreatorProperty(DeserializationContext c return prop; } + /** + * @deprecated Since 2.18, use {@link #constructCreatorProperty(DeserializationContext, BeanDescription, + * PropertyName, int, AnnotatedParameter, JacksonInject.Value, Boolean)} instead. + */ + protected SettableBeanProperty constructCreatorProperty(DeserializationContext ctxt, + BeanDescription beanDesc, PropertyName name, int index, + AnnotatedParameter param, + JacksonInject.Value injectable) + throws JsonMappingException + { + return constructCreatorProperty(ctxt, beanDesc, name, index, param, injectable, null); + } + private PropertyName _findParamName(AnnotatedParameter param, AnnotationIntrospector intr) { if (intr != null) { 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 f3bd2708fc..159ad15e69 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 @@ -8,6 +8,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.deser.CreatorProperty; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference; @@ -180,17 +181,12 @@ public Object[] getParameters(SettableBeanProperty[] props) } } - // [databind#562]: Respect @JsonAnySetter in @JsonCreator - // check if we have anySetter Param - AnnotationIntrospector ai = _context.getAnnotationIntrospector(); - for (int i = 0; i < props.length; i++) { - SettableBeanProperty prop = props[i]; - AnnotatedMember member = prop.getMember(); - if (member == null) { + // [databind#562] Since 2.18 : Respect @JsonAnySetter in @JsonCreator + for (int i = 0; i < _creatorParameters.length; i++) { + if (!(props[i] instanceof CreatorProperty cp)) { continue; } - Boolean hasAnySetter = ai.hasAnySetter(member); - if (hasAnySetter == null || !hasAnySetter) { + if (!cp.hasAnySetter()) { continue; } // So we have prop with anySetter. Should be Map-like, so let's assign such? @@ -200,13 +196,14 @@ public Object[] getParameters(SettableBeanProperty[] props) try { next.assign(param); } catch (IOException e) { - _context.reportInputMismatch(prop, e.getMessage()); + _context.reportInputMismatch(cp, e.getMessage()); } } // assign it then return _creatorParameters[i] = param; break; } + return _creatorParameters; } From 6a892f870da6bbd39145ce1c0c8f890c921f4b95 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 4 Feb 2024 19:41:16 +0900 Subject: [PATCH 11/60] Remove unncessary anySetter construction --- .../deser/BeanDeserializerFactory.java | 20 ------------------- .../databind/deser/CreatorProperty.java | 18 ++++++++--------- .../deser/impl/PropertyValueBuffer.java | 11 ++++------ 3 files changed, 13 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index 4f4461625c..3021a4f6bb 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -544,11 +544,8 @@ protected void addBeanProps(DeserializationContext ctxt, // Also, do we have a fallback "any" setter? AnnotatedMember anySetter = beanDesc.findAnySetterAccessor(); - AnnotatedMember creatorPropWithAnySetter = _findCreatorPropWithAnySetter(ctxt, creatorProps); if (anySetter != null) { builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetter)); - } else if (creatorPropWithAnySetter != null) { - builder.setAnySetter(constructAnySetter(ctxt, beanDesc, creatorPropWithAnySetter)); } else { // 23-Jan-2018, tatu: although [databind#1805] would suggest we should block // properties regardless, for now only consider unless there's any setter... @@ -838,23 +835,6 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, prop = new BeanProperty.Std(PropertyName.construct(mutator.getName()), valueType, null, mutator, PropertyMetadata.STD_OPTIONAL); - - } else if (mutator instanceof AnnotatedParameter){ - AnnotatedParameter af = (AnnotatedParameter) mutator; - // get the type from the content type of the map object - JavaType fieldType = af.getType(); - // 31-Jul-2022, tatu: Not just Maps any more but also JsonNode, so: - if (fieldType.isMapLikeType()) { - fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); - keyType = fieldType.getKeyType(); - valueType = fieldType.getContentType(); - prop = new BeanProperty.Std(PropertyName.construct("stuff"), - fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); - } else { - return ctxt.reportBadDefinition(beanDesc.getType(), String.format( - "Unsupported type for any-setter: %s -- only support `Map`s, `JsonNode` and `ObjectNode` ", - ClassUtil.getTypeDescription(fieldType))); - } } else if (isField) { AnnotatedField af = (AnnotatedField) mutator; // get the type from the content type of the map object diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 1b2d7c399a..14114b75c7 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -80,7 +80,7 @@ public class CreatorProperty * * @since 2.18 */ - protected final Boolean _hasAnySetter; + protected final Boolean _isAnySetterProp; /** * @since 2.18 @@ -89,14 +89,14 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, - PropertyMetadata metadata, Boolean hasAnySetter) + PropertyMetadata metadata, Boolean isAnySetterProp) { super(name, type, wrapperName, typeDeser, contextAnnotations, metadata); _annotated = param; _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; - _hasAnySetter = hasAnySetter; + _isAnySetterProp = isAnySetterProp; } /** @@ -151,10 +151,10 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, PropertyMetadata metadata, - Boolean hasAnySetter) + Boolean isAnySetterProp) { return new CreatorProperty(name, type, wrapperName, typeDeser, contextAnnotations, - param, index, injectable, metadata, hasAnySetter); + param, index, injectable, metadata, isAnySetterProp); } /** @@ -181,7 +181,7 @@ protected CreatorProperty(CreatorProperty src, PropertyName newName) { _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _hasAnySetter = src._hasAnySetter; // [databind#562] Since 2.18 + _isAnySetterProp = src._isAnySetterProp; // [databind#562] Since 2.18 } protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, @@ -192,7 +192,7 @@ protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _hasAnySetter = src._hasAnySetter; // [databind#562] Since 2.18 + _isAnySetterProp = src._isAnySetterProp; // [databind#562] Since 2.18 } @Override @@ -361,8 +361,8 @@ public boolean isInjectionOnly() { /** * @since 2.18 */ - public boolean hasAnySetter() { - return Boolean.TRUE.equals(_hasAnySetter); + public boolean isAnySetterProp() { + return Boolean.TRUE.equals(_isAnySetterProp); } /* 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 159ad15e69..15cde4b634 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 @@ -11,7 +11,6 @@ import com.fasterxml.jackson.databind.deser.CreatorProperty; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; -import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; /** @@ -183,14 +182,12 @@ public Object[] getParameters(SettableBeanProperty[] props) // [databind#562] Since 2.18 : Respect @JsonAnySetter in @JsonCreator for (int i = 0; i < _creatorParameters.length; i++) { - if (!(props[i] instanceof CreatorProperty cp)) { + if (!(props[i] instanceof CreatorProperty cp) + || (!cp.isAnySetterProp()) + || (!cp.getType().isMapLikeType()) + ) { continue; } - if (!cp.hasAnySetter()) { - continue; - } - // So we have prop with anySetter. Should be Map-like, so let's assign such? - // Assign all remaining values to the map Map param = new HashMap<>(); for (PropertyValue next = buffered(); next != null; next = next.next) { try { From 765ed3a1afb94104e3fae010aa18800e5554ceaa Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 4 Feb 2024 19:41:34 +0900 Subject: [PATCH 12/60] Revert "Remove unncessary anySetter construction" This reverts commit 6a892f870da6bbd39145ce1c0c8f890c921f4b95. --- .../deser/BeanDeserializerFactory.java | 20 +++++++++++++++++++ .../databind/deser/CreatorProperty.java | 18 ++++++++--------- .../deser/impl/PropertyValueBuffer.java | 11 ++++++---- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index 3021a4f6bb..4f4461625c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -544,8 +544,11 @@ protected void addBeanProps(DeserializationContext ctxt, // Also, do we have a fallback "any" setter? AnnotatedMember anySetter = beanDesc.findAnySetterAccessor(); + AnnotatedMember creatorPropWithAnySetter = _findCreatorPropWithAnySetter(ctxt, creatorProps); if (anySetter != null) { builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetter)); + } else if (creatorPropWithAnySetter != null) { + builder.setAnySetter(constructAnySetter(ctxt, beanDesc, creatorPropWithAnySetter)); } else { // 23-Jan-2018, tatu: although [databind#1805] would suggest we should block // properties regardless, for now only consider unless there's any setter... @@ -835,6 +838,23 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, prop = new BeanProperty.Std(PropertyName.construct(mutator.getName()), valueType, null, mutator, PropertyMetadata.STD_OPTIONAL); + + } else if (mutator instanceof AnnotatedParameter){ + AnnotatedParameter af = (AnnotatedParameter) mutator; + // get the type from the content type of the map object + JavaType fieldType = af.getType(); + // 31-Jul-2022, tatu: Not just Maps any more but also JsonNode, so: + if (fieldType.isMapLikeType()) { + fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); + keyType = fieldType.getKeyType(); + valueType = fieldType.getContentType(); + prop = new BeanProperty.Std(PropertyName.construct("stuff"), + fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); + } else { + return ctxt.reportBadDefinition(beanDesc.getType(), String.format( + "Unsupported type for any-setter: %s -- only support `Map`s, `JsonNode` and `ObjectNode` ", + ClassUtil.getTypeDescription(fieldType))); + } } else if (isField) { AnnotatedField af = (AnnotatedField) mutator; // get the type from the content type of the map object diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 14114b75c7..1b2d7c399a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -80,7 +80,7 @@ public class CreatorProperty * * @since 2.18 */ - protected final Boolean _isAnySetterProp; + protected final Boolean _hasAnySetter; /** * @since 2.18 @@ -89,14 +89,14 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, - PropertyMetadata metadata, Boolean isAnySetterProp) + PropertyMetadata metadata, Boolean hasAnySetter) { super(name, type, wrapperName, typeDeser, contextAnnotations, metadata); _annotated = param; _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; - _isAnySetterProp = isAnySetterProp; + _hasAnySetter = hasAnySetter; } /** @@ -151,10 +151,10 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, PropertyMetadata metadata, - Boolean isAnySetterProp) + Boolean hasAnySetter) { return new CreatorProperty(name, type, wrapperName, typeDeser, contextAnnotations, - param, index, injectable, metadata, isAnySetterProp); + param, index, injectable, metadata, hasAnySetter); } /** @@ -181,7 +181,7 @@ protected CreatorProperty(CreatorProperty src, PropertyName newName) { _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _isAnySetterProp = src._isAnySetterProp; // [databind#562] Since 2.18 + _hasAnySetter = src._hasAnySetter; // [databind#562] Since 2.18 } protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, @@ -192,7 +192,7 @@ protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _isAnySetterProp = src._isAnySetterProp; // [databind#562] Since 2.18 + _hasAnySetter = src._hasAnySetter; // [databind#562] Since 2.18 } @Override @@ -361,8 +361,8 @@ public boolean isInjectionOnly() { /** * @since 2.18 */ - public boolean isAnySetterProp() { - return Boolean.TRUE.equals(_isAnySetterProp); + public boolean hasAnySetter() { + return Boolean.TRUE.equals(_hasAnySetter); } /* 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 15cde4b634..159ad15e69 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 @@ -11,6 +11,7 @@ import com.fasterxml.jackson.databind.deser.CreatorProperty; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; +import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; /** @@ -182,12 +183,14 @@ public Object[] getParameters(SettableBeanProperty[] props) // [databind#562] Since 2.18 : Respect @JsonAnySetter in @JsonCreator for (int i = 0; i < _creatorParameters.length; i++) { - if (!(props[i] instanceof CreatorProperty cp) - || (!cp.isAnySetterProp()) - || (!cp.getType().isMapLikeType()) - ) { + if (!(props[i] instanceof CreatorProperty cp)) { continue; } + if (!cp.hasAnySetter()) { + continue; + } + // So we have prop with anySetter. Should be Map-like, so let's assign such? + // Assign all remaining values to the map Map param = new HashMap<>(); for (PropertyValue next = buffered(); next != null; next = next.next) { try { From 57d467a4ae5eb593aa1ab7236a0cd2a50f06f487 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 4 Feb 2024 19:44:04 +0900 Subject: [PATCH 13/60] Put back isAnySetterXxx methods and fields --- .../databind/deser/CreatorProperty.java | 18 +++++++++--------- .../deser/impl/PropertyValueBuffer.java | 3 +-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 1b2d7c399a..95e23afaf4 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -80,7 +80,7 @@ public class CreatorProperty * * @since 2.18 */ - protected final Boolean _hasAnySetter; + protected final Boolean _isAnySetterProp; /** * @since 2.18 @@ -89,14 +89,14 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, - PropertyMetadata metadata, Boolean hasAnySetter) + PropertyMetadata metadata, Boolean isAnySetterProp) { super(name, type, wrapperName, typeDeser, contextAnnotations, metadata); _annotated = param; _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; - _hasAnySetter = hasAnySetter; + _isAnySetterProp = isAnySetterProp; } /** @@ -151,10 +151,10 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, PropertyMetadata metadata, - Boolean hasAnySetter) + Boolean isAnySetter) { return new CreatorProperty(name, type, wrapperName, typeDeser, contextAnnotations, - param, index, injectable, metadata, hasAnySetter); + param, index, injectable, metadata, isAnySetter); } /** @@ -181,7 +181,7 @@ protected CreatorProperty(CreatorProperty src, PropertyName newName) { _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _hasAnySetter = src._hasAnySetter; // [databind#562] Since 2.18 + _isAnySetterProp = src._isAnySetterProp; // [databind#562] Since 2.18 } protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, @@ -192,7 +192,7 @@ protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _hasAnySetter = src._hasAnySetter; // [databind#562] Since 2.18 + _isAnySetterProp = src._isAnySetterProp; // [databind#562] Since 2.18 } @Override @@ -361,8 +361,8 @@ public boolean isInjectionOnly() { /** * @since 2.18 */ - public boolean hasAnySetter() { - return Boolean.TRUE.equals(_hasAnySetter); + public boolean isAnySetterProp() { + return Boolean.TRUE.equals(_isAnySetterProp); } /* 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 159ad15e69..62a660ae6e 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 @@ -11,7 +11,6 @@ import com.fasterxml.jackson.databind.deser.CreatorProperty; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; -import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; /** @@ -186,7 +185,7 @@ public Object[] getParameters(SettableBeanProperty[] props) if (!(props[i] instanceof CreatorProperty cp)) { continue; } - if (!cp.hasAnySetter()) { + if (!cp.isAnySetterProp()) { continue; } // So we have prop with anySetter. Should be Map-like, so let's assign such? From 2ae2dca68c27b84c44d76f02f5450249fd9aadc3 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 4 Feb 2024 21:00:21 +0900 Subject: [PATCH 14/60] Fix JDK 8 error --- .../deser/impl/PropertyValueBuffer.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) 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 62a660ae6e..c829f30620 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 @@ -182,25 +182,25 @@ public Object[] getParameters(SettableBeanProperty[] props) // [databind#562] Since 2.18 : Respect @JsonAnySetter in @JsonCreator for (int i = 0; i < _creatorParameters.length; i++) { - if (!(props[i] instanceof CreatorProperty cp)) { + if (!(props[i] instanceof CreatorProperty)) { continue; } - if (!cp.isAnySetterProp()) { - continue; - } - // So we have prop with anySetter. Should be Map-like, so let's assign such? - // Assign all remaining values to the map - Map param = new HashMap<>(); - for (PropertyValue next = buffered(); next != null; next = next.next) { - try { - next.assign(param); - } catch (IOException e) { - _context.reportInputMismatch(cp, e.getMessage()); + CreatorProperty cp = (CreatorProperty) props[i]; + if (cp.isAnySetterProp() && !cp.getType().isMapLikeType()) { + // So we have prop with anySetter. Should be Map-like, so let's assign such? + // Assign all remaining values to the map + Map param = new HashMap<>(); + for (PropertyValue next = buffered(); next != null; next = next.next) { + try { + next.assign(param); + } catch (IOException e) { + _context.reportInputMismatch(cp, e.getMessage()); + } } + // assign it then return + _creatorParameters[i] = param; + break; } - // assign it then return - _creatorParameters[i] = param; - break; } return _creatorParameters; From 7da980a5eb51e241c41fb68bb6b81d4c86554973 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 9 Feb 2024 18:58:27 +0900 Subject: [PATCH 15/60] Move test dir --- .../deser/creators}/AnySetterForCreator562Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/com/fasterxml/jackson/{failing => databind/deser/creators}/AnySetterForCreator562Test.java (95%) diff --git a/src/test/java/com/fasterxml/jackson/failing/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java similarity index 95% rename from src/test/java/com/fasterxml/jackson/failing/AnySetterForCreator562Test.java rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java index 2232407308..e6812ec285 100644 --- a/src/test/java/com/fasterxml/jackson/failing/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.failing; +package com.fasterxml.jackson.databind.deser.creators; import java.util.HashMap; import java.util.Map; From 09d4c82ed6311e3154ba9a1407333690fe169b7d Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 9 Feb 2024 18:58:37 +0900 Subject: [PATCH 16/60] Fix error --- .../jackson/databind/deser/impl/PropertyValueBuffer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c829f30620..dd992933ef 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 @@ -186,7 +186,7 @@ public Object[] getParameters(SettableBeanProperty[] props) continue; } CreatorProperty cp = (CreatorProperty) props[i]; - if (cp.isAnySetterProp() && !cp.getType().isMapLikeType()) { + if (cp.isAnySetterProp() && cp.getType().isMapLikeType()) { // So we have prop with anySetter. Should be Map-like, so let's assign such? // Assign all remaining values to the map Map param = new HashMap<>(); From 9baad775dcf771e6b80a192c097aee332d483a13 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 9 Feb 2024 21:47:43 +0900 Subject: [PATCH 17/60] Add new SettableAnyProperty impl --- .../databind/deser/BeanDeserializer.java | 2 +- .../deser/BeanDeserializerFactory.java | 6 ++ .../databind/deser/CreatorProperty.java | 8 ++- .../databind/deser/SettableAnyProperty.java | 67 +++++++++++++++++++ .../databind/deser/ValueInstantiator.java | 7 ++ .../deser/impl/PropertyBasedCreator.java | 5 +- .../deser/impl/PropertyValueBuffer.java | 32 ++++----- 7 files changed, 108 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index 9bc05baee6..5afb027840 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -524,7 +524,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri Object bean; try { if (_anySetter != null) { - bean = creator.buildSimple(ctxt, buffer); + bean = creator.buildWithAnySetter(ctxt, buffer, _anySetter); } else { bean = creator.build(ctxt, buffer); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index 4f4461625c..a91a96938f 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -828,6 +828,7 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, JavaType keyType; JavaType valueType; final boolean isField = mutator instanceof AnnotatedField; + boolean isMapParam = false; if (mutator instanceof AnnotatedMethod) { // we know it's a 2-arg method, second arg is the value @@ -850,6 +851,7 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, valueType = fieldType.getContentType(); prop = new BeanProperty.Std(PropertyName.construct("stuff"), fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); + isMapParam = true; } else { return ctxt.reportBadDefinition(beanDesc.getType(), String.format( "Unsupported type for any-setter: %s -- only support `Map`s, `JsonNode` and `ObjectNode` ", @@ -916,6 +918,10 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, return SettableAnyProperty.constructForMapField(ctxt, prop, mutator, valueType, keyDeser, deser, typeDeser); } + if (isMapParam) { + return SettableAnyProperty.constructForMapParameter(ctxt, + prop, mutator, valueType, keyDeser, deser, typeDeser); + } return SettableAnyProperty.constructForMethod(ctxt, prop, mutator, valueType, keyDeser, deser, typeDeser); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 95e23afaf4..2501918d8b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.lang.annotation.Annotation; +import java.util.Map; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.core.JsonParser; @@ -96,7 +97,7 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; - _isAnySetterProp = isAnySetterProp; + _isAnySetterProp = isAnySetterProp; // [databind#562] Since 2.18 } /** @@ -399,4 +400,9 @@ private void _reportMissingSetter(JsonParser p, DeserializationContext ctxt) thr throw InvalidDefinitionException.from(p, msg, getType()); } } + + public Map createAndBuildMap(DeserializationContext context, SettableAnyProperty anySetter) throws IOException { + SettableAnyProperty.MapParameterAnyProperty mapParap = (SettableAnyProperty.MapParameterAnyProperty) anySetter; + return mapParap.initMap(context); + } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java index b7aafa205b..22607ff32f 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java @@ -117,6 +117,28 @@ public static SettableAnyProperty constructForJsonNodeField(DeserializationConte ctxt.getNodeFactory()); } + /** + * @since 2.18 + */ + public static SettableAnyProperty constructForMapParameter(DeserializationContext ctxt, + BeanProperty property, + AnnotatedMember field, JavaType valueType, + KeyDeserializer keyDeser, + JsonDeserializer valueDeser, TypeDeserializer typeDeser) + { + Class mapType = field.getRawType(); + // 02-Aug-2022, tatu: Ideally would be resolved to a concrete type by caller but + // alas doesn't appear to happen. Nor does `BasicDeserializerFactory` expose method + // for finding default or explicit mappings. + if (mapType == Map.class) { + mapType = LinkedHashMap.class; + } + ValueInstantiator vi = JDKValueInstantiators.findStdValueInstantiator(ctxt.getConfig(), mapType); + return new MapParameterAnyProperty(property, field, valueType, + keyDeser, valueDeser, typeDeser, + vi); + } + // Abstract @since 2.14 public abstract SettableAnyProperty withValueDeserializer(JsonDeserializer deser); @@ -437,4 +459,49 @@ public SettableAnyProperty withValueDeserializer(JsonDeserializer deser) return this; } } + + /** + * @since 2.18 + */ + protected static class MapParameterAnyProperty extends SettableAnyProperty + implements java.io.Serializable + { + private static final long serialVersionUID = 1L; + + protected final ValueInstantiator _valueInstantiator; + + public MapParameterAnyProperty(BeanProperty property, + AnnotatedMember field, JavaType valueType, + KeyDeserializer keyDeser, + JsonDeserializer valueDeser, TypeDeserializer typeDeser, + ValueInstantiator inst) { + super(property, field, valueType, + keyDeser, valueDeser, typeDeser); + _valueInstantiator = inst; + } + + @Override + public SettableAnyProperty withValueDeserializer(JsonDeserializer deser) { + return new MapParameterAnyProperty(_property, _setter, _type, + _keyDeserializer, deser, _valueTypeDeserializer, + _valueInstantiator); + } + + @SuppressWarnings("unchecked") + @Override + protected void _set(Object instance, Object propName, Object value) throws Exception { + throw new UnsupportedOperationException("Cannot set any properties for constructor parameter of type `Map`"); + } + + protected Map initMap(DeserializationContext ctxt) + throws IOException + { + if (_valueInstantiator == null) { + throw JsonMappingException.from(ctxt, String.format( + "Cannot create an instance of %s for use as \"any-setter\" '%s'", + ClassUtil.nameOf(_type.getRawClass()), _property.getName())); + } + return (Map) _valueInstantiator.createUsingDefault(ctxt); + } + } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java b/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java index 648bdcc3d3..ffd17a77ee 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java @@ -301,6 +301,13 @@ public Object createFromObjectWith(DeserializationContext ctxt, return createFromObjectWith(ctxt, buffer.getParameters(props)); } + public Object createFromObjectWith(DeserializationContext ctxt, + SettableBeanProperty[] props, PropertyValueBuffer buffer, SettableAnyProperty anySetter) + throws IOException + { + return createFromObjectWith(ctxt, buffer.getParametersWithAnySetter(props, anySetter)); + } + /** * Method to called to create value instance from JSON Object using * an intermediate "delegate" value to pass to createor method diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java index 14488e88c2..544db0f544 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.deser.SettableAnyProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; import com.fasterxml.jackson.databind.deser.ValueInstantiator; @@ -197,10 +198,10 @@ public PropertyValueBuffer startBuilding(JsonParser p, DeserializationContext ct return new PropertyValueBuffer(p, ctxt, _propertyCount, oir); } - public Object buildSimple(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException + public Object buildWithAnySetter(DeserializationContext ctxt, PropertyValueBuffer buffer, SettableAnyProperty anySetter) throws IOException { return _valueInstantiator.createFromObjectWith(ctxt, - _allProperties, buffer); + _allProperties, buffer, anySetter); } public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException 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 dd992933ef..005e13429c 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 @@ -2,7 +2,6 @@ import java.io.IOException; import java.util.BitSet; -import java.util.HashMap; import java.util.Map; import com.fasterxml.jackson.core.JsonParser; @@ -179,30 +178,33 @@ public Object[] getParameters(SettableBeanProperty[] props) } } } + return _creatorParameters; + } + public Object[] getParametersWithAnySetter(SettableBeanProperty[] props, SettableAnyProperty anySetter) + throws JsonMappingException + { + getParameters(props); // [databind#562] Since 2.18 : Respect @JsonAnySetter in @JsonCreator for (int i = 0; i < _creatorParameters.length; i++) { - if (!(props[i] instanceof CreatorProperty)) { + if (!(props[i] instanceof CreatorProperty) || !((CreatorProperty) props[i]).isAnySetterProp()) { continue; } CreatorProperty cp = (CreatorProperty) props[i]; - if (cp.isAnySetterProp() && cp.getType().isMapLikeType()) { - // So we have prop with anySetter. Should be Map-like, so let's assign such? - // Assign all remaining values to the map - Map param = new HashMap<>(); + Object param = null; + try { + // cp.initMap(_context); + Map map = cp.createAndBuildMap(_context, anySetter); for (PropertyValue next = buffered(); next != null; next = next.next) { - try { - next.assign(param); - } catch (IOException e) { - _context.reportInputMismatch(cp, e.getMessage()); - } + next.assign(map); } - // assign it then return - _creatorParameters[i] = param; - break; + param = map; + } catch (IOException e) { + _context.reportInputMismatch(cp, e.getMessage()); } + _creatorParameters[i] = param; + break; } - return _creatorParameters; } From 81a88032250d16bd068bbc5ca4277bd11a47e25f Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 9 Feb 2024 22:05:48 +0900 Subject: [PATCH 18/60] Remove hard coded prop name --- .../jackson/databind/deser/BeanDeserializerFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index a91a96938f..04bed93c9b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -849,7 +849,7 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); keyType = fieldType.getKeyType(); valueType = fieldType.getContentType(); - prop = new BeanProperty.Std(PropertyName.construct("stuff"), + prop = new BeanProperty.Std(PropertyName.NO_NAME, fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); isMapParam = true; } else { From 742a589c6bafb49e82b44a4d2eb5b4e82b3d13c2 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 9 Feb 2024 22:26:51 +0900 Subject: [PATCH 19/60] Update javadoc and clean comments --- .../jackson/databind/deser/CreatorProperty.java | 15 ++++++++++++--- .../databind/deser/impl/PropertyValueBuffer.java | 3 +-- .../creators/AnySetterForCreator562Test.java | 4 ++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 2501918d8b..171bbafd29 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -144,8 +144,9 @@ public CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperNam * method parameter; used for accessing annotations of the property * @param injectable Information about injectable value, if any * @param index Index of this property within creator invocation - * - * @since 2.11 + * @param isAnySetter Marker flag to indicate that current property is used to handle "any setter" via `@JsonAnySetter`. + * + * @since 2.18 */ public static CreatorProperty construct(PropertyName name, JavaType type, PropertyName wrapperName, TypeDeserializer typeDeser, @@ -401,7 +402,15 @@ private void _reportMissingSetter(JsonParser p, DeserializationContext ctxt) thr } } - public Map createAndBuildMap(DeserializationContext context, SettableAnyProperty anySetter) throws IOException { + /** + * @since 2.18 + */ + public Map initMap(DeserializationContext context, SettableAnyProperty anySetter) + throws IOException + { + if (!isAnySetterProp()) { + throw new IllegalStateException("Cannot create Map for non-AnySetter creator property"); + } SettableAnyProperty.MapParameterAnyProperty mapParap = (SettableAnyProperty.MapParameterAnyProperty) anySetter; return mapParap.initMap(context); } 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 005e13429c..41c99a7eda 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 @@ -193,8 +193,7 @@ public Object[] getParametersWithAnySetter(SettableBeanProperty[] props, Settabl CreatorProperty cp = (CreatorProperty) props[i]; Object param = null; try { - // cp.initMap(_context); - Map map = cp.createAndBuildMap(_context, anySetter); + Map map = cp.initMap(_context, anySetter); for (PropertyValue next = buffered(); next != null; next = next.next) { next.assign(map); } diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java index e6812ec285..5c6c9b66ff 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java @@ -23,8 +23,8 @@ static class POJO562 @JsonCreator public POJO562(@JsonProperty("a") String a, - @JsonAnySetter Map - leftovers) { + @JsonAnySetter Map leftovers + ) { this.a = a; stuff = leftovers; } From 010849a32f073c2376eb672da153a15a2c8769ee Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 9 Feb 2024 16:52:35 -0800 Subject: [PATCH 20/60] Minor clean up, streamlining --- .../deser/BeanDeserializerFactory.java | 4 +-- .../databind/deser/CreatorProperty.java | 11 ++++---- .../databind/deser/SettableAnyProperty.java | 3 ++- .../deser/impl/PropertyValueBuffer.java | 26 +++++++++++-------- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index 04bed93c9b..99266ef839 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -670,12 +670,10 @@ private AnnotatedMember _findCreatorPropWithAnySetter(DeserializationContext ctx for (SettableBeanProperty prop : creatorProps) { AnnotatedMember m = prop.getMember(); if (m != null) { - Boolean hasAnySetter = ai.hasAnySetter(m); - if (hasAnySetter != null && hasAnySetter) { + if (Boolean.TRUE.equals(ai.hasAnySetter(m))) { return prop.getMember(); } } - } } return null; diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 171bbafd29..df882c4f92 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -81,7 +81,7 @@ public class CreatorProperty * * @since 2.18 */ - protected final Boolean _isAnySetterProp; + protected final boolean _isAnySetterProp; /** * @since 2.18 @@ -97,13 +97,14 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; - _isAnySetterProp = isAnySetterProp; // [databind#562] Since 2.18 + _isAnySetterProp = Boolean.TRUE.equals(isAnySetterProp); // [databind#562] Since 2.18 } /** * @since 2.11 * @deprecated Since 2.18. use factory later version instead. */ + @Deprecated protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperName, TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, @@ -163,6 +164,7 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper * @since 2.11 * @deprecated Since 2.18. use later version instead. */ + @Deprecated public static CreatorProperty construct(PropertyName name, JavaType type, PropertyName wrapperName, TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, @@ -364,7 +366,7 @@ public boolean isInjectionOnly() { * @since 2.18 */ public boolean isAnySetterProp() { - return Boolean.TRUE.equals(_isAnySetterProp); + return _isAnySetterProp; } /* @@ -411,7 +413,6 @@ public Map initMap(DeserializationContext context, SettableAnyPr if (!isAnySetterProp()) { throw new IllegalStateException("Cannot create Map for non-AnySetter creator property"); } - SettableAnyProperty.MapParameterAnyProperty mapParap = (SettableAnyProperty.MapParameterAnyProperty) anySetter; - return mapParap.initMap(context); + return ((SettableAnyProperty.MapParameterAnyProperty) anySetter).initMap(context); } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java index 22607ff32f..0cb94f5d04 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java @@ -487,15 +487,16 @@ public SettableAnyProperty withValueDeserializer(JsonDeserializer deser) _valueInstantiator); } - @SuppressWarnings("unchecked") @Override protected void _set(Object instance, Object propName, Object value) throws Exception { throw new UnsupportedOperationException("Cannot set any properties for constructor parameter of type `Map`"); } + @SuppressWarnings("unchecked") protected Map initMap(DeserializationContext ctxt) throws IOException { + // This really should have been caught earlier if (_valueInstantiator == null) { throw JsonMappingException.from(ctxt, String.format( "Cannot create an instance of %s for use as \"any-setter\" '%s'", 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 41c99a7eda..247048691f 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 @@ -149,28 +149,29 @@ public Object getParameter(SettableBeanProperty prop) public Object[] getParameters(SettableBeanProperty[] props) throws JsonMappingException { + final Object[] creatorParams = _creatorParameters; // quick check to see if anything else is needed if (_paramsNeeded > 0) { if (_paramsSeenBig == null) { int mask = _paramsSeen; // not optimal, could use `Integer.trailingZeroes()`, but for now should not // really matter for common cases - for (int ix = 0, len = _creatorParameters.length; ix < len; ++ix, mask >>= 1) { + for (int ix = 0, len = creatorParams.length; ix < len; ++ix, mask >>= 1) { if ((mask & 1) == 0) { - _creatorParameters[ix] = _findMissing(props[ix]); + creatorParams[ix] = _findMissing(props[ix]); } } } else { - final int len = _creatorParameters.length; + final int len = creatorParams.length; for (int ix = 0; (ix = _paramsSeenBig.nextClearBit(ix)) < len; ++ix) { - _creatorParameters[ix] = _findMissing(props[ix]); + creatorParams[ix] = _findMissing(props[ix]); } } } if (_context.isEnabled(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES)) { for (int ix = 0; ix < props.length; ++ix) { - if (_creatorParameters[ix] == null) { + if (creatorParams[ix] == null) { SettableBeanProperty prop = props[ix]; _context.reportInputMismatch(prop, "Null value for creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES` enabled", @@ -178,19 +179,22 @@ public Object[] getParameters(SettableBeanProperty[] props) } } } - return _creatorParameters; + return creatorParams; } public Object[] getParametersWithAnySetter(SettableBeanProperty[] props, SettableAnyProperty anySetter) throws JsonMappingException { - getParameters(props); + final Object[] creatorParams = getParameters(props); // [databind#562] Since 2.18 : Respect @JsonAnySetter in @JsonCreator - for (int i = 0; i < _creatorParameters.length; i++) { - if (!(props[i] instanceof CreatorProperty) || !((CreatorProperty) props[i]).isAnySetterProp()) { + for (int i = 0; i < creatorParams.length; i++) { + if (!(props[i] instanceof CreatorProperty)) { continue; } CreatorProperty cp = (CreatorProperty) props[i]; + if (!cp.isAnySetterProp()) { + continue; + } Object param = null; try { Map map = cp.initMap(_context, anySetter); @@ -201,10 +205,10 @@ public Object[] getParametersWithAnySetter(SettableBeanProperty[] props, Settabl } catch (IOException e) { _context.reportInputMismatch(cp, e.getMessage()); } - _creatorParameters[i] = param; + creatorParams[i] = param; break; } - return _creatorParameters; + return creatorParams; } protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingException From 10540a8378108ef996a55d75468fefab7f1ea61e Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 9 Feb 2024 17:09:01 -0800 Subject: [PATCH 21/60] Add verification there's only one any-setter --- .../deser/BasicDeserializerFactory.java | 23 +++++++++++----- .../databind/deser/CreatorProperty.java | 11 ++++---- .../creators/AnySetterForCreator562Test.java | 26 +++++++++++++++++++ 3 files changed, 48 insertions(+), 12 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 aef762f723..ecb486b224 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -862,12 +862,12 @@ protected void _addExplicitPropertyCreator(DeserializationContext ctxt, { final int paramCount = candidate.paramCount(); SettableBeanProperty[] properties = new SettableBeanProperty[paramCount]; + int anySetterIndex = -1; for (int i = 0; i < paramCount; ++i) { JacksonInject.Value injectId = candidate.injection(i); AnnotatedParameter param = candidate.parameter(i); PropertyName name = candidate.paramName(i); - Boolean hasAnySetter = null; if (name == null) { // 21-Sep-2017, tatu: Looks like we want to block accidental use of Unwrapped, @@ -883,15 +883,21 @@ protected void _addExplicitPropertyCreator(DeserializationContext ctxt, name = candidate.findImplicitParamName(i); // [databind#562] Allow @JsonAnySetter in creators. Introspection is done here only because of // _validateNamedPropertyParameter() check. Otherwise, in constructCreatorProperty seems appropriate.... - hasAnySetter = ctxt.getAnnotationIntrospector().hasAnySetter(param); - if (Boolean.TRUE.equals(hasAnySetter)) { + if (Boolean.TRUE.equals(ctxt.getAnnotationIntrospector().hasAnySetter(param))) { + if (anySetterIndex >= 0) { + ctxt.reportBadTypeDefinition(beanDesc, + "More than one 'any-setter' specified (parameters #%d vs #%d)", + anySetterIndex, i); + } + anySetterIndex = i; // no-op } else { _validateNamedPropertyParameter(ctxt, beanDesc, candidate, i, name, injectId); } } - properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId, hasAnySetter); + properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId, + anySetterIndex >= 0); } creators.addPropertyCreator(candidate.creator(), true, properties); } @@ -1192,12 +1198,14 @@ protected void _reportUnwrappedCreatorProperty(DeserializationContext ctxt, * Method that will construct a property object that represents * a logical property passed via Creator (constructor or static * factory method) + * + * @since 2.18 */ protected SettableBeanProperty constructCreatorProperty(DeserializationContext ctxt, BeanDescription beanDesc, PropertyName name, int index, AnnotatedParameter param, JacksonInject.Value injectable, - Boolean hasAnySetter) + boolean hasAnySetter) throws JsonMappingException { final DeserializationConfig config = ctxt.getConfig(); @@ -1250,15 +1258,16 @@ protected SettableBeanProperty constructCreatorProperty(DeserializationContext c /** * @deprecated Since 2.18, use {@link #constructCreatorProperty(DeserializationContext, BeanDescription, - * PropertyName, int, AnnotatedParameter, JacksonInject.Value, Boolean)} instead. + * PropertyName, int, AnnotatedParameter, JacksonInject.Value, boolean)} instead. */ + @Deprecated protected SettableBeanProperty constructCreatorProperty(DeserializationContext ctxt, BeanDescription beanDesc, PropertyName name, int index, AnnotatedParameter param, JacksonInject.Value injectable) throws JsonMappingException { - return constructCreatorProperty(ctxt, beanDesc, name, index, param, injectable, null); + return constructCreatorProperty(ctxt, beanDesc, name, index, param, injectable, false); } private PropertyName _findParamName(AnnotatedParameter param, AnnotationIntrospector intr) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index df882c4f92..0b73d6a2fb 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -90,14 +90,14 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, - PropertyMetadata metadata, Boolean isAnySetterProp) + PropertyMetadata metadata, boolean isAnySetterProp) { super(name, type, wrapperName, typeDeser, contextAnnotations, metadata); _annotated = param; _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; - _isAnySetterProp = Boolean.TRUE.equals(isAnySetterProp); // [databind#562] Since 2.18 + _isAnySetterProp = isAnySetterProp; // [databind#562] Since 2.18 } /** @@ -111,7 +111,8 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper int index, JacksonInject.Value injectable, PropertyMetadata metadata) { - this(name, type, wrapperName, typeDeser, contextAnnotations, param, index, injectable, metadata, null); + this(name, type, wrapperName, typeDeser, contextAnnotations, param, index, injectable, + metadata, false); } /** @@ -154,7 +155,7 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, PropertyMetadata metadata, - Boolean isAnySetter) + boolean isAnySetter) { return new CreatorProperty(name, type, wrapperName, typeDeser, contextAnnotations, param, index, injectable, metadata, isAnySetter); @@ -172,7 +173,7 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper PropertyMetadata metadata) { return new CreatorProperty(name, type, wrapperName, typeDeser, contextAnnotations, - param, index, injectable, metadata, null); + param, index, injectable, metadata, false); } /** diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java index 5c6c9b66ff..3fa24b9239 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java @@ -8,9 +8,11 @@ import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; public class AnySetterForCreator562Test extends DatabindTestUtil { @@ -30,6 +32,17 @@ public POJO562(@JsonProperty("a") String a, } } + // [databind#562]: failing cacse + static class MultipleAny562 + { + @JsonCreator + public MultipleAny562(@JsonProperty("a") String a, + @JsonAnySetter Map leftovers, + @JsonAnySetter Map leftovers2) { + throw new Error("Should never get here!"); + } + } + private final ObjectMapper MAPPER = newJsonMapper(); // [databind#562] @@ -48,4 +61,17 @@ public void testAnySetterViaCreator562() throws Exception assertEquals("value", pojo.a); assertEquals(expected, pojo.stuff); } + + // [databind#562] + @Test + public void testAnySetterViaCreator562FailForDup() throws Exception + { + try { + MAPPER.readValue("{}", MultipleAny562.class); + fail("Should not pass"); + } catch (InvalidDefinitionException e) { + verifyException(e, "Invalid type definition"); + verifyException(e, "More than one 'any-setter'"); + } + } } From 1458882393558cddb0b90ce6fa5bd3e95811bff2 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 10 Feb 2024 10:32:38 +0900 Subject: [PATCH 22/60] Test case for disabled(@JsonAnySetter) (rain check? --- .../creators/AnySetterForCreator562Test.java | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java index 3fa24b9239..7fbe5e40e5 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java @@ -32,7 +32,7 @@ public POJO562(@JsonProperty("a") String a, } } - // [databind#562]: failing cacse + // [databind#562]: failing case static class MultipleAny562 { @JsonCreator @@ -43,6 +43,22 @@ public MultipleAny562(@JsonProperty("a") String a, } } + // [databind#562] + static class PojoWithDisabled + { + String a; + + Map stuff; + + @JsonCreator + public PojoWithDisabled(@JsonProperty("a") String a, + @JsonAnySetter(enabled = false) Map leftovers + ) { + this.a = a; + stuff = leftovers; + } + } + private final ObjectMapper MAPPER = newJsonMapper(); // [databind#562] @@ -74,4 +90,18 @@ public void testAnySetterViaCreator562FailForDup() throws Exception verifyException(e, "More than one 'any-setter'"); } } + + // [databind#562] + @Test + public void testAnySetterViaCreator562Disabled() throws Exception + { + try { + MAPPER.readValue(a2q("{'a':'value', 'b':42, 'c': 111}"), + PojoWithDisabled.class); + fail(); + } catch (InvalidDefinitionException e) { + verifyException(e, "Invalid type definition for type"); + verifyException(e, "has no property name (and is not Injectable): can not use as property-based Creator"); + } + } } From bddb0427a4f40d3cddb73d31e7c369695755415f Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 10 Feb 2024 10:32:56 +0900 Subject: [PATCH 23/60] Require non-null _valueInstantiator during construction --- .../jackson/databind/deser/SettableAnyProperty.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java index 0cb94f5d04..a103246ee2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.*; @@ -477,7 +478,7 @@ public MapParameterAnyProperty(BeanProperty property, ValueInstantiator inst) { super(property, field, valueType, keyDeser, valueDeser, typeDeser); - _valueInstantiator = inst; + _valueInstantiator = Objects.requireNonNull(inst, "ValueInstantiator for MapParameterAnyProperty cannot be `null`"); } @Override @@ -496,12 +497,6 @@ protected void _set(Object instance, Object propName, Object value) throws Excep protected Map initMap(DeserializationContext ctxt) throws IOException { - // This really should have been caught earlier - if (_valueInstantiator == null) { - throw JsonMappingException.from(ctxt, String.format( - "Cannot create an instance of %s for use as \"any-setter\" '%s'", - ClassUtil.nameOf(_type.getRawClass()), _property.getName())); - } return (Map) _valueInstantiator.createUsingDefault(ctxt); } } From 5c1f30d499c01c7a3266f7c598e52cf41243cb26 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 10 Feb 2024 10:51:39 +0900 Subject: [PATCH 24/60] Add test with DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES --- .../creators/AnySetterForCreator562Test.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java index 7fbe5e40e5..bc0c89ef9d 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java @@ -78,6 +78,24 @@ public void testAnySetterViaCreator562() throws Exception assertEquals(expected, pojo.stuff); } + // [databind#562] + @Test + public void testNoAnySetterContentTest562() throws Exception + { + ObjectMapper mapper = jsonMapperBuilder() + .enable(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES) + .build(); + + Map expected = new HashMap<>(); + expected.put("b", Integer.valueOf(42)); + expected.put("c", Integer.valueOf(111)); + + POJO562 pojo = mapper.readValue(a2q("{'a':'value'}"), POJO562.class); + + assertEquals("value", pojo.a); + assertEquals(expected, pojo.stuff); + } + // [databind#562] @Test public void testAnySetterViaCreator562FailForDup() throws Exception From bdc23573fb62861f3c828157b81a36882de3617e Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 10 Feb 2024 11:02:03 +0900 Subject: [PATCH 25/60] Fix tests --- .../creators/AnySetterForCreator562Test.java | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java index bc0c89ef9d..79ac3271d1 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -80,20 +81,37 @@ public void testAnySetterViaCreator562() throws Exception // [databind#562] @Test - public void testNoAnySetterContentTest562() throws Exception + public void testWithFailureConfigs562() throws Exception { - ObjectMapper mapper = jsonMapperBuilder() - .enable(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES) - .build(); + ObjectMapper failOnNullMapper = jsonMapperBuilder() + .enable(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES).build(); - Map expected = new HashMap<>(); - expected.put("b", Integer.valueOf(42)); - expected.put("c", Integer.valueOf(111)); + try { + failOnNullMapper.readValue(a2q("{'a':'value'}"), POJO562.class); + fail("Should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, "Null value for creator property"); + } - POJO562 pojo = mapper.readValue(a2q("{'a':'value'}"), POJO562.class); + ObjectMapper failOnMissingMapper = jsonMapperBuilder() + .enable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES).build(); + try { + failOnMissingMapper.readValue(a2q("{'a':'value'}"), POJO562.class); + fail("Should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, "Missing creator property"); + } - assertEquals("value", pojo.a); - assertEquals(expected, pojo.stuff); + ObjectMapper failOnBothMapper = jsonMapperBuilder() + .enable(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES) + .enable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES) + .build(); + try { + failOnBothMapper.readValue(a2q("{'a':'value'}"), POJO562.class); + fail("Should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, "Missing creator property"); + } } // [databind#562] From 47ac3f760a844f8759f04c1f91cca4c8bdd9ac6e Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 10 Feb 2024 11:02:38 +0900 Subject: [PATCH 26/60] Update AnySetterForCreator562Test.java --- .../databind/deser/creators/AnySetterForCreator562Test.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java index 79ac3271d1..516ea58ae4 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java @@ -90,7 +90,7 @@ public void testWithFailureConfigs562() throws Exception failOnNullMapper.readValue(a2q("{'a':'value'}"), POJO562.class); fail("Should not pass"); } catch (MismatchedInputException e) { - verifyException(e, "Null value for creator property"); + verifyException(e, "Null value for creator property ''"); } ObjectMapper failOnMissingMapper = jsonMapperBuilder() @@ -99,7 +99,7 @@ public void testWithFailureConfigs562() throws Exception failOnMissingMapper.readValue(a2q("{'a':'value'}"), POJO562.class); fail("Should not pass"); } catch (MismatchedInputException e) { - verifyException(e, "Missing creator property"); + verifyException(e, "Missing creator property ''"); } ObjectMapper failOnBothMapper = jsonMapperBuilder() @@ -110,7 +110,7 @@ public void testWithFailureConfigs562() throws Exception failOnBothMapper.readValue(a2q("{'a':'value'}"), POJO562.class); fail("Should not pass"); } catch (MismatchedInputException e) { - verifyException(e, "Missing creator property"); + verifyException(e, "Missing creator property ''"); } } From 8612433174444c364f704fa6a82c802baba3f959 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 10 Feb 2024 14:41:07 +0900 Subject: [PATCH 27/60] Implement version 2 Add quick check before iteration start --- .../deser/BasicDeserializerFactory.java | 2 +- .../databind/deser/BeanDeserializer.java | 13 +- .../databind/deser/CreatorProperty.java | 10 +- .../databind/deser/SettableAnyProperty.java | 4 +- .../databind/deser/ValueInstantiator.java | 7 -- .../deser/impl/PropertyBasedCreator.java | 31 ++++- .../deser/impl/PropertyValueBuffer.java | 116 ++++++++++-------- 7 files changed, 102 insertions(+), 81 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 ecb486b224..fcfa81236d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -1199,7 +1199,7 @@ protected void _reportUnwrappedCreatorProperty(DeserializationContext ctxt, * a logical property passed via Creator (constructor or static * factory method) * - * @since 2.18 + * @since 2.19 */ protected SettableBeanProperty constructCreatorProperty(DeserializationContext ctxt, BeanDescription beanDesc, PropertyName name, int index, diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index 5afb027840..cf2555b7a7 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -415,7 +415,9 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri throws IOException { final PropertyBasedCreator creator = _propertyBasedCreator; - PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, _objectIdReader); + PropertyValueBuffer buffer = (_anySetter != null) + ? creator.startBuildingWithAnySetter(p, ctxt, _objectIdReader, _anySetter) + : creator.startBuilding(p, ctxt, _objectIdReader); TokenBuffer unknown = null; final Class activeView = _needViewProcesing ? ctxt.getActiveView() : null; @@ -495,9 +497,10 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri continue; } // "any property"? + if (_anySetter != null) { try { - buffer.bufferMapProperty(propName, _anySetter.deserialize(p, ctxt)); + buffer.bufferWithAnySetter(ctxt, p, creator, propName); } catch (Exception e) { wrapAndThrow(e, _beanType.getRawClass(), propName, ctxt); } @@ -523,11 +526,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri // We hit END_OBJECT, so: Object bean; try { - if (_anySetter != null) { - bean = creator.buildWithAnySetter(ctxt, buffer, _anySetter); - } else { - bean = creator.build(ctxt, buffer); - } + bean = creator.build(ctxt, buffer); } catch (Exception e) { return wrapInstantiationProblem(e, ctxt); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 0b73d6a2fb..a7070c6fdc 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -79,12 +79,12 @@ public class CreatorProperty /** * Marker flag to indicate that current property is used to handle "any setter" via `@JsonAnySetter`. * - * @since 2.18 + * @since 2.19 */ protected final boolean _isAnySetterProp; /** - * @since 2.18 + * @since 2.19 */ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperName, TypeDeserializer typeDeser, @@ -148,7 +148,7 @@ public CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperNam * @param index Index of this property within creator invocation * @param isAnySetter Marker flag to indicate that current property is used to handle "any setter" via `@JsonAnySetter`. * - * @since 2.18 + * @since 2.19 */ public static CreatorProperty construct(PropertyName name, JavaType type, PropertyName wrapperName, TypeDeserializer typeDeser, @@ -364,7 +364,7 @@ public boolean isInjectionOnly() { // public boolean isInjectionOnly() { return false; } /** - * @since 2.18 + * @since 2.19 */ public boolean isAnySetterProp() { return _isAnySetterProp; @@ -406,7 +406,7 @@ private void _reportMissingSetter(JsonParser p, DeserializationContext ctxt) thr } /** - * @since 2.18 + * @since 2.19 */ public Map initMap(DeserializationContext context, SettableAnyProperty anySetter) throws IOException diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java index a103246ee2..f639881fd1 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java @@ -119,7 +119,7 @@ public static SettableAnyProperty constructForJsonNodeField(DeserializationConte } /** - * @since 2.18 + * @since 2.19 */ public static SettableAnyProperty constructForMapParameter(DeserializationContext ctxt, BeanProperty property, @@ -462,7 +462,7 @@ public SettableAnyProperty withValueDeserializer(JsonDeserializer deser) } /** - * @since 2.18 + * @since 2.19 */ protected static class MapParameterAnyProperty extends SettableAnyProperty implements java.io.Serializable diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java b/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java index ffd17a77ee..648bdcc3d3 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java @@ -301,13 +301,6 @@ public Object createFromObjectWith(DeserializationContext ctxt, return createFromObjectWith(ctxt, buffer.getParameters(props)); } - public Object createFromObjectWith(DeserializationContext ctxt, - SettableBeanProperty[] props, PropertyValueBuffer buffer, SettableAnyProperty anySetter) - throws IOException - { - return createFromObjectWith(ctxt, buffer.getParametersWithAnySetter(props, anySetter)); - } - /** * Method to called to create value instance from JSON Object using * an intermediate "delegate" value to pass to createor method diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java index 544db0f544..132958d387 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.deser.CreatorProperty; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; import com.fasterxml.jackson.databind.deser.ValueInstantiator; @@ -188,6 +189,16 @@ public SettableBeanProperty findCreatorProperty(int propertyIndex) { /********************************************************** */ + /** + * Method called when starting to build a bean instance. + * + * @since 2.19 (added SettableAnyProperty parameter) + */ + public PropertyValueBuffer startBuildingWithAnySetter(JsonParser p, DeserializationContext ctxt, + ObjectIdReader oir, SettableAnyProperty anySetter) { + return new PropertyValueBuffer(p, ctxt, _propertyCount, oir, anySetter); + } + /** * Method called when starting to build a bean instance. * @@ -198,12 +209,6 @@ public PropertyValueBuffer startBuilding(JsonParser p, DeserializationContext ct return new PropertyValueBuffer(p, ctxt, _propertyCount, oir); } - public Object buildWithAnySetter(DeserializationContext ctxt, PropertyValueBuffer buffer, SettableAnyProperty anySetter) throws IOException - { - return _valueInstantiator.createFromObjectWith(ctxt, - _allProperties, buffer, anySetter); - } - public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException { Object bean = _valueInstantiator.createFromObjectWith(ctxt, @@ -221,6 +226,20 @@ public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) thr return bean; } + public CreatorProperty findAnySetterProp() { + for (SettableBeanProperty prop : _allProperties) { + if (!(prop instanceof CreatorProperty)) { + continue; + } + CreatorProperty cp = (CreatorProperty) prop; + if (!cp.isAnySetterProp()) { + continue; + } + return cp; + } + return null; + } + /* /********************************************************** /* Helper classes 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 247048691f..3a30b07329 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 @@ -5,7 +5,6 @@ import java.util.Map; import com.fasterxml.jackson.core.JsonParser; - import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.CreatorProperty; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; @@ -19,8 +18,7 @@ * and hence need buffering before instance (that will have properties * to assign values to) is constructed. */ -public class PropertyValueBuffer -{ +public class PropertyValueBuffer { /* /********************************************************** /* Configuration @@ -77,6 +75,20 @@ public class PropertyValueBuffer */ protected Object _idValue; + /** + * [databind#562]: Allow use of `@JsonAnySetter` in `@JsonCreator` + * + * @since 2.19 + */ + protected SettableAnyProperty _anySetter; + + /** + * [databind#562]: Allow use of `@JsonAnySetter` in `@JsonCreator` + * + * @since 2.19 + */ + private Map _anySetterMap; + /* /********************************************************** /* Life-cycle @@ -84,8 +96,7 @@ public class PropertyValueBuffer */ public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramCount, - ObjectIdReader oir) - { + ObjectIdReader oir, SettableAnyProperty anySetter) { _parser = p; _context = ctxt; _paramsNeeded = paramCount; @@ -96,6 +107,12 @@ public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramC } else { _paramsSeenBig = new BitSet(); } + _anySetter = anySetter; + } + + public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramCount, + ObjectIdReader oir) { + this(p, ctxt, paramCount, oir, null); } /** @@ -104,8 +121,7 @@ public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramC * * @since 2.8 */ - public final boolean hasParameter(SettableBeanProperty prop) - { + public final boolean hasParameter(SettableBeanProperty prop) { if (_paramsSeenBig == null) { return ((_paramsSeen >> prop.getCreatorIndex()) & 1) == 1; } @@ -123,8 +139,7 @@ public final boolean hasParameter(SettableBeanProperty prop) * @since 2.8 */ public Object getParameter(SettableBeanProperty prop) - throws JsonMappingException - { + throws JsonMappingException { Object value; if (hasParameter(prop)) { value = _creatorParameters[prop.getCreatorIndex()]; @@ -147,8 +162,7 @@ public Object getParameter(SettableBeanProperty prop) * then whole JSON Object has been processed, */ public Object[] getParameters(SettableBeanProperty[] props) - throws JsonMappingException - { + throws JsonMappingException { final Object[] creatorParams = _creatorParameters; // quick check to see if anything else is needed if (_paramsNeeded > 0) { @@ -174,60 +188,39 @@ public Object[] getParameters(SettableBeanProperty[] props) if (creatorParams[ix] == null) { SettableBeanProperty prop = props[ix]; _context.reportInputMismatch(prop, - "Null value for creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES` enabled", - prop.getName(), props[ix].getCreatorIndex()); + "Null value for creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES` enabled", + prop.getName(), props[ix].getCreatorIndex()); } } } - return creatorParams; - } - - public Object[] getParametersWithAnySetter(SettableBeanProperty[] props, SettableAnyProperty anySetter) - throws JsonMappingException - { - final Object[] creatorParams = getParameters(props); // [databind#562] Since 2.18 : Respect @JsonAnySetter in @JsonCreator - for (int i = 0; i < creatorParams.length; i++) { - if (!(props[i] instanceof CreatorProperty)) { - continue; - } - CreatorProperty cp = (CreatorProperty) props[i]; - if (!cp.isAnySetterProp()) { - continue; - } - Object param = null; - try { - Map map = cp.initMap(_context, anySetter); - for (PropertyValue next = buffered(); next != null; next = next.next) { - next.assign(map); + if (_anySetter != null) { + for (int i = 0; i < creatorParams.length; i++) { + if (props[i].getMember() == _anySetter.getProperty().getMember()) { + creatorParams[i] = _anySetterMap; + break; } - param = map; - } catch (IOException e) { - _context.reportInputMismatch(cp, e.getMessage()); } - creatorParams[i] = param; - break; } return creatorParams; } - protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingException - { + protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingException { // First: do we have injectable value? Object injectableValueId = prop.getInjectableValueId(); if (injectableValueId != null) { return _context.findInjectableValue(prop.getInjectableValueId(), - prop, null); + prop, null); } // Second: required? if (prop.isRequired()) { _context.reportInputMismatch(prop, "Missing required creator property '%s' (index %d)", - prop.getName(), prop.getCreatorIndex()); + prop.getName(), prop.getCreatorIndex()); } if (_context.isEnabled(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES)) { _context.reportInputMismatch(prop, - "Missing creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES` enabled", - prop.getName(), prop.getCreatorIndex()); + "Missing creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES` enabled", + prop.getName(), prop.getCreatorIndex()); } try { // Third: NullValueProvider? (22-Sep-2019, [databind#2458]) @@ -262,8 +255,7 @@ protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingExcep * * @since 2.1 */ - public boolean readIdProperty(String propName) throws IOException - { + public boolean readIdProperty(String propName) throws IOException { if ((_objectIdReader != null) && propName.equals(_objectIdReader.propertyName.getSimpleName())) { _idValue = _objectIdReader.readObjectReference(_parser, _context); return true; @@ -274,8 +266,7 @@ public boolean readIdProperty(String propName) throws IOException /** * Helper method called to handle Object Id value collected earlier, if any */ - public Object handleIdValue(final DeserializationContext ctxt, Object bean) throws IOException - { + public Object handleIdValue(final DeserializationContext ctxt, Object bean) throws IOException { if (_objectIdReader != null) { if (_idValue != null) { ReadableObjectId roid = ctxt.findObjectId(_idValue, _objectIdReader.generator, _objectIdReader.resolver); @@ -293,20 +284,22 @@ public Object handleIdValue(final DeserializationContext ctxt, Object bean) thro return bean; } - protected PropertyValue buffered() { return _buffered; } + protected PropertyValue buffered() { + return _buffered; + } - public boolean isComplete() { return _paramsNeeded <= 0; } + public boolean isComplete() { + return _paramsNeeded <= 0; + } /** * Method called to buffer value for given property, as well as check whether * we now have values for all (creator) properties that we expect to get values for. * * @return True if we have received all creator parameters - * * @since 2.6 */ - public boolean assignParameter(SettableBeanProperty prop, Object value) - { + public boolean assignParameter(SettableBeanProperty prop, Object value) { final int ix = prop.getCreatorIndex(); _creatorParameters[ix] = value; if (_paramsSeenBig == null) { @@ -341,4 +334,21 @@ public void bufferAnyProperty(SettableAnyProperty prop, String propName, Object public void bufferMapProperty(Object key, Object value) { _buffered = new PropertyValue.Map(_buffered, value, key); } + + /** + * @since 2.19 + */ + public void bufferWithAnySetter(DeserializationContext ctxt, JsonParser p, PropertyBasedCreator creator, String propName) + throws IOException { + // Only called once, to initialize map + if (_anySetterMap == null) { + CreatorProperty cp = creator.findAnySetterProp(); + try { + _anySetterMap = cp.initMap(_context, _anySetter); + } catch (IOException e) { + _context.reportInputMismatch(cp, e.getMessage()); + } + } + _anySetterMap.put(propName, _anySetter.deserialize(p, ctxt)); + } } From 5d694656d627924d6c9e3a9c10ac603df79c6056 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 10 Feb 2024 15:13:53 +0900 Subject: [PATCH 28/60] Clean up changes --- .../deser/BasicDeserializerFactory.java | 5 +- .../databind/deser/BeanDeserializer.java | 3 +- .../databind/deser/CreatorProperty.java | 21 +++--- .../deser/impl/PropertyBasedCreator.java | 4 +- .../deser/impl/PropertyValueBuffer.java | 68 ++++++++++++------- 5 files changed, 56 insertions(+), 45 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 fcfa81236d..f3cdefdcba 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -881,8 +881,7 @@ protected void _addExplicitPropertyCreator(DeserializationContext ctxt, */ } name = candidate.findImplicitParamName(i); - // [databind#562] Allow @JsonAnySetter in creators. Introspection is done here only because of - // _validateNamedPropertyParameter() check. Otherwise, in constructCreatorProperty seems appropriate.... + // [databind#562] Allow @JsonAnySetter in creators if (Boolean.TRUE.equals(ctxt.getAnnotationIntrospector().hasAnySetter(param))) { if (anySetterIndex >= 0) { ctxt.reportBadTypeDefinition(beanDesc, @@ -1257,7 +1256,7 @@ protected SettableBeanProperty constructCreatorProperty(DeserializationContext c } /** - * @deprecated Since 2.18, use {@link #constructCreatorProperty(DeserializationContext, BeanDescription, + * @deprecated since 2.19, use {@link #constructCreatorProperty(DeserializationContext, BeanDescription, * PropertyName, int, AnnotatedParameter, JacksonInject.Value, boolean)} instead. */ @Deprecated diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index cf2555b7a7..b0da6dc3b2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -500,7 +500,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri if (_anySetter != null) { try { - buffer.bufferWithAnySetter(ctxt, p, creator, propName); + buffer.bufferAnySetter(ctxt, p, creator, propName); } catch (Exception e) { wrapAndThrow(e, _beanType.getRawClass(), propName, ctxt); } @@ -530,7 +530,6 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri } catch (Exception e) { return wrapInstantiationProblem(e, ctxt); } - // 13-Apr-2020, tatu: [databind#2678] need to handle injection here if (_injectables != null) { injectValues(ctxt, bean); diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index a7070c6fdc..79a8e2dc2b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -81,7 +81,7 @@ public class CreatorProperty * * @since 2.19 */ - protected final boolean _isAnySetterProp; + protected final boolean _isAnySetter; /** * @since 2.19 @@ -90,19 +90,19 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, - PropertyMetadata metadata, boolean isAnySetterProp) + PropertyMetadata metadata, boolean isAnySetter) { super(name, type, wrapperName, typeDeser, contextAnnotations, metadata); _annotated = param; _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; - _isAnySetterProp = isAnySetterProp; // [databind#562] Since 2.18 + _isAnySetter = isAnySetter; // [databind#562] since 2.19 } /** * @since 2.11 - * @deprecated Since 2.18. use factory later version instead. + * @deprecated since 2.19. use factory later version instead. */ @Deprecated protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperName, @@ -163,7 +163,7 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper /** * @since 2.11 - * @deprecated Since 2.18. use later version instead. + * @deprecated since 2.19. use later version instead. */ @Deprecated public static CreatorProperty construct(PropertyName name, JavaType type, PropertyName wrapperName, @@ -186,7 +186,7 @@ protected CreatorProperty(CreatorProperty src, PropertyName newName) { _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _isAnySetterProp = src._isAnySetterProp; // [databind#562] Since 2.18 + _isAnySetter = src._isAnySetter; // [databind#562] since 2.19 } protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, @@ -197,7 +197,7 @@ protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _isAnySetterProp = src._isAnySetterProp; // [databind#562] Since 2.18 + _isAnySetter = src._isAnySetter; // [databind#562] since 2.19 } @Override @@ -366,8 +366,8 @@ public boolean isInjectionOnly() { /** * @since 2.19 */ - public boolean isAnySetterProp() { - return _isAnySetterProp; + public boolean isAnySetter() { + return _isAnySetter; } /* @@ -411,9 +411,6 @@ private void _reportMissingSetter(JsonParser p, DeserializationContext ctxt) thr public Map initMap(DeserializationContext context, SettableAnyProperty anySetter) throws IOException { - if (!isAnySetterProp()) { - throw new IllegalStateException("Cannot create Map for non-AnySetter creator property"); - } return ((SettableAnyProperty.MapParameterAnyProperty) anySetter).initMap(context); } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java index 132958d387..cd6121c9dc 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java @@ -226,13 +226,13 @@ public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) thr return bean; } - public CreatorProperty findAnySetterProp() { + public CreatorProperty findAnySetterProperty() { for (SettableBeanProperty prop : _allProperties) { if (!(prop instanceof CreatorProperty)) { continue; } CreatorProperty cp = (CreatorProperty) prop; - if (!cp.isAnySetterProp()) { + if (!cp.isAnySetter()) { continue; } return cp; 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 3a30b07329..5e99a02b62 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 @@ -5,6 +5,7 @@ import java.util.Map; import com.fasterxml.jackson.core.JsonParser; + import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.CreatorProperty; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; @@ -18,7 +19,8 @@ * and hence need buffering before instance (that will have properties * to assign values to) is constructed. */ -public class PropertyValueBuffer { +public class PropertyValueBuffer +{ /* /********************************************************** /* Configuration @@ -80,7 +82,7 @@ public class PropertyValueBuffer { * * @since 2.19 */ - protected SettableAnyProperty _anySetter; + protected final SettableAnyProperty _anySetter; /** * [databind#562]: Allow use of `@JsonAnySetter` in `@JsonCreator` @@ -96,7 +98,8 @@ public class PropertyValueBuffer { */ public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramCount, - ObjectIdReader oir, SettableAnyProperty anySetter) { + ObjectIdReader oir, SettableAnyProperty anySetter) + { _parser = p; _context = ctxt; _paramsNeeded = paramCount; @@ -110,8 +113,13 @@ public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramC _anySetter = anySetter; } + /** + * + * @deprecated Since 2.19, use later version instead. + */ public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramCount, - ObjectIdReader oir) { + ObjectIdReader oir) + { this(p, ctxt, paramCount, oir, null); } @@ -121,7 +129,8 @@ public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramC * * @since 2.8 */ - public final boolean hasParameter(SettableBeanProperty prop) { + public final boolean hasParameter(SettableBeanProperty prop) + { if (_paramsSeenBig == null) { return ((_paramsSeen >> prop.getCreatorIndex()) & 1) == 1; } @@ -139,7 +148,8 @@ public final boolean hasParameter(SettableBeanProperty prop) { * @since 2.8 */ public Object getParameter(SettableBeanProperty prop) - throws JsonMappingException { + throws JsonMappingException + { Object value; if (hasParameter(prop)) { value = _creatorParameters[prop.getCreatorIndex()]; @@ -162,7 +172,8 @@ public Object getParameter(SettableBeanProperty prop) * then whole JSON Object has been processed, */ public Object[] getParameters(SettableBeanProperty[] props) - throws JsonMappingException { + throws JsonMappingException + { final Object[] creatorParams = _creatorParameters; // quick check to see if anything else is needed if (_paramsNeeded > 0) { @@ -188,12 +199,12 @@ public Object[] getParameters(SettableBeanProperty[] props) if (creatorParams[ix] == null) { SettableBeanProperty prop = props[ix]; _context.reportInputMismatch(prop, - "Null value for creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES` enabled", - prop.getName(), props[ix].getCreatorIndex()); + "Null value for creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES` enabled", + prop.getName(), props[ix].getCreatorIndex()); } } } - // [databind#562] Since 2.18 : Respect @JsonAnySetter in @JsonCreator + // [databind#562] since 2.19 : Respect @JsonAnySetter in @JsonCreator if (_anySetter != null) { for (int i = 0; i < creatorParams.length; i++) { if (props[i].getMember() == _anySetter.getProperty().getMember()) { @@ -205,22 +216,23 @@ public Object[] getParameters(SettableBeanProperty[] props) return creatorParams; } - protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingException { + protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingException + { // First: do we have injectable value? Object injectableValueId = prop.getInjectableValueId(); if (injectableValueId != null) { return _context.findInjectableValue(prop.getInjectableValueId(), - prop, null); + prop, null); } // Second: required? if (prop.isRequired()) { _context.reportInputMismatch(prop, "Missing required creator property '%s' (index %d)", - prop.getName(), prop.getCreatorIndex()); + prop.getName(), prop.getCreatorIndex()); } if (_context.isEnabled(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES)) { _context.reportInputMismatch(prop, - "Missing creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES` enabled", - prop.getName(), prop.getCreatorIndex()); + "Missing creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES` enabled", + prop.getName(), prop.getCreatorIndex()); } try { // Third: NullValueProvider? (22-Sep-2019, [databind#2458]) @@ -255,7 +267,8 @@ protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingExcep * * @since 2.1 */ - public boolean readIdProperty(String propName) throws IOException { + public boolean readIdProperty(String propName) throws IOException + { if ((_objectIdReader != null) && propName.equals(_objectIdReader.propertyName.getSimpleName())) { _idValue = _objectIdReader.readObjectReference(_parser, _context); return true; @@ -266,7 +279,8 @@ public boolean readIdProperty(String propName) throws IOException { /** * Helper method called to handle Object Id value collected earlier, if any */ - public Object handleIdValue(final DeserializationContext ctxt, Object bean) throws IOException { + public Object handleIdValue(final DeserializationContext ctxt, Object bean) throws IOException + { if (_objectIdReader != null) { if (_idValue != null) { ReadableObjectId roid = ctxt.findObjectId(_idValue, _objectIdReader.generator, _objectIdReader.resolver); @@ -284,22 +298,20 @@ public Object handleIdValue(final DeserializationContext ctxt, Object bean) thro return bean; } - protected PropertyValue buffered() { - return _buffered; - } + protected PropertyValue buffered() { return _buffered; } - public boolean isComplete() { - return _paramsNeeded <= 0; - } + public boolean isComplete() { return _paramsNeeded <= 0; } /** * Method called to buffer value for given property, as well as check whether * we now have values for all (creator) properties that we expect to get values for. * * @return True if we have received all creator parameters + * * @since 2.6 */ - public boolean assignParameter(SettableBeanProperty prop, Object value) { + public boolean assignParameter(SettableBeanProperty prop, Object value) + { final int ix = prop.getCreatorIndex(); _creatorParameters[ix] = value; if (_paramsSeenBig == null) { @@ -338,11 +350,15 @@ public void bufferMapProperty(Object key, Object value) { /** * @since 2.19 */ - public void bufferWithAnySetter(DeserializationContext ctxt, JsonParser p, PropertyBasedCreator creator, String propName) + public void bufferAnySetter(DeserializationContext ctxt, JsonParser p, PropertyBasedCreator creator, String propName) throws IOException { // Only called once, to initialize map if (_anySetterMap == null) { - CreatorProperty cp = creator.findAnySetterProp(); + CreatorProperty cp = creator.findAnySetterProperty(); + if (cp == null) { + ctxt.reportBadDefinition(creator.getClass(), + "Invalid configuration: no creator property with 'any-setter' annotation found"); + } try { _anySetterMap = cp.initMap(_context, _anySetter); } catch (IOException e) { From 7a55942047a2b662d36182a62bf9786ff48a21fb Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 27 Jan 2024 16:55:25 +0900 Subject: [PATCH 29/60] temporarily remove other jdk versions --- .../JsonAliasWithDeduction4327RecordTest.java | 44 ------------------- 1 file changed, 44 deletions(-) delete mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/JsonAliasWithDeduction4327RecordTest.java diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/JsonAliasWithDeduction4327RecordTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/JsonAliasWithDeduction4327RecordTest.java deleted file mode 100644 index 344bd9536f..0000000000 --- a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/JsonAliasWithDeduction4327RecordTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.fasterxml.jackson.databind.records; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.a2q; -import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.jsonMapperBuilder; - -// [databind#4327] JsonAlias should respsect with Polymorphic Deduction -public class JsonAliasWithDeduction4327RecordTest -{ - @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) - @JsonSubTypes({ - @JsonSubTypes.Type(value = DeductionBean1.class), - @JsonSubTypes.Type(value = DeductionBean2.class) - }) - interface Deduction { } - - record DeductionBean1(int x) implements Deduction { } - - record DeductionBean2( - @JsonAlias(value = {"Y", "yy", "ff", "X"}) int y - ) implements Deduction { } - - - private final ObjectMapper mapper = jsonMapperBuilder().build(); - - @ParameterizedTest - @ValueSource(strings = {"Y", "yy", "ff", "X"}) - public void testAliasWithPolymorphicDeduction(String field) throws Exception { - String json = a2q(String.format("{'%s': 2 }", field)); - Deduction value = mapper.readValue(json, Deduction.class); - assertNotNull(value); - assertEquals(2, ((DeductionBean2) value).y()); - } -} From ff2250032d362a43bb5f840addc9d8bbc8e0382b Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 3 Feb 2024 15:50:52 +0900 Subject: [PATCH 30/60] Working solution first draft --- .../deser/BasicDeserializerFactory.java | 8 ++- .../databind/deser/BeanDeserializer.java | 9 +++- .../deser/BeanDeserializerFactory.java | 32 ++++++++++++ .../deser/impl/PropertyBasedCreator.java | 8 +++ .../deser/impl/PropertyValueBuffer.java | 17 ++++++ .../failing/AnySetterForCreator562Test.java | 11 ++-- .../JsonAnySetterThroughCreator562Test.java | 52 +++++++++++++++++++ 7 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/failing/JsonAnySetterThroughCreator562Test.java 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 95c7c73f18..9472fee918 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -526,8 +526,14 @@ private void _addExplicitPropertyCreator(DeserializationContext ctxt, _reportUnwrappedCreatorProperty(ctxt, beanDesc, param); } name = candidate.findImplicitParamName(i); - _validateNamedPropertyParameter(ctxt, beanDesc, candidate, i, + if (ctxt.getAnnotationIntrospector().hasAnySetter(param)) { + // [databind#562] Any setter can be used... + System.out.println(); + name = PropertyName.construct("leftovers"); + } else { + _validateNamedPropertyParameter(ctxt, beanDesc, candidate, i, name, injectId); + } } properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index cf744fe359..4e6a9e9ff1 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -497,7 +497,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri // "any property"? if (_anySetter != null) { try { - buffer.bufferAnyProperty(_anySetter, propName, _anySetter.deserialize(p, ctxt)); + buffer.bufferMapProperty(propName, _anySetter.deserialize(p, ctxt)); } catch (Exception e) { wrapAndThrow(e, _beanType.getRawClass(), propName, ctxt); } @@ -523,10 +523,15 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri // We hit END_OBJECT, so: Object bean; try { - bean = creator.build(ctxt, buffer); + if (_anySetter != null) { + bean = creator.buildSimple(ctxt, buffer); + } else { + bean = creator.build(ctxt, buffer); + } } catch (Exception e) { return wrapInstantiationProblem(e, ctxt); } + // 13-Apr-2020, tatu: [databind#2678] need to handle injection here if (_injectables != null) { injectValues(ctxt, bean); diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index 03f4d26f42..3b0bb4f5a0 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -546,6 +546,9 @@ protected void addBeanProps(DeserializationContext ctxt, AnnotatedMember anySetter = beanDesc.findAnySetterAccessor(); if (anySetter != null) { builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetter)); + } + else if (ctxt.getAnnotationIntrospector().hasAnySetter(creatorProps[1].getMember())) { + builder.setAnySetter(constructAnySetter(ctxt, beanDesc, creatorProps[1].getMember())); } else { // 23-Jan-2018, tatu: although [databind#1805] would suggest we should block // properties regardless, for now only consider unless there's any setter... @@ -819,6 +822,35 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, valueType, null, mutator, PropertyMetadata.STD_OPTIONAL); + } else if (mutator instanceof AnnotatedParameter){ + AnnotatedParameter af = (AnnotatedParameter) mutator; + // get the type from the content type of the map object + JavaType fieldType = af.getType(); + // 31-Jul-2022, tatu: Not just Maps any more but also JsonNode, so: + if (fieldType.isMapLikeType()) { + fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); + keyType = fieldType.getKeyType(); + valueType = fieldType.getContentType(); + prop = new BeanProperty.Std(PropertyName.construct("stuff"), + fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); + } else if (fieldType.hasRawClass(JsonNode.class) + || fieldType.hasRawClass(ObjectNode.class)) { + fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); + // Deserialize is individual values of ObjectNode, not full ObjectNode, so: + valueType = ctxt.constructType(JsonNode.class); + prop = new BeanProperty.Std(PropertyName.construct(mutator.getName()), + fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); + + // Unlike with more complicated types, here we do not allow any annotation + // overrides etc but instead short-cut handling: + return SettableAnyProperty.constructForJsonNodeField(ctxt, + prop, mutator, valueType, + ctxt.findRootValueDeserializer(valueType)); + } else { + return ctxt.reportBadDefinition(beanDesc.getType(), String.format( + "Unsupported type for any-setter: %s -- only support `Map`s, `JsonNode` and `ObjectNode` ", + ClassUtil.getTypeDescription(fieldType))); + } } else if (isField) { AnnotatedField af = (AnnotatedField) mutator; // get the type from the content type of the map object diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java index 5225baaab6..c82564fb86 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java @@ -197,10 +197,18 @@ public PropertyValueBuffer startBuilding(JsonParser p, DeserializationContext ct return new PropertyValueBuffer(p, ctxt, _propertyCount, oir); } + public Object buildSimple(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException + { + return _valueInstantiator.createFromObjectWith(ctxt, + _allProperties, buffer); + } + public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException { Object bean = _valueInstantiator.createFromObjectWith(ctxt, _allProperties, buffer); + + // returning null isn't quite legal, but let's let caller deal with that if (bean != null) { // Object Id to handle? 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 5d323381a0..35f371fe51 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 @@ -2,12 +2,15 @@ import java.io.IOException; import java.util.BitSet; +import java.util.HashMap; +import java.util.Map; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; +import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; /** @@ -176,6 +179,20 @@ public Object[] getParameters(SettableBeanProperty[] props) } } } + // check if we have anySetter Param + // if we do, parse from buffer and set + Map param = new HashMap<>(); + for (PropertyValue next = buffered(); next != null; next = next.next) { + try { + next.assign(param); + } catch (IOException e) { + // TODO : Wrap properly + throw new JsonMappingException("TODO: Wrap"); + } + } + // find the "FIRST" creator Prop with JsonAnySetter + // Then assign value to it + _creatorParameters[1] = param; return _creatorParameters; } diff --git a/src/test/java/com/fasterxml/jackson/failing/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/failing/AnySetterForCreator562Test.java index eca7692a78..a9725045b0 100644 --- a/src/test/java/com/fasterxml/jackson/failing/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/failing/AnySetterForCreator562Test.java @@ -1,6 +1,6 @@ package com.fasterxml.jackson.failing; -import java.util.Collections; +import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; @@ -37,12 +37,15 @@ public POJO562(@JsonProperty("a") String a, @Test void anySetterViaCreator562() throws Exception { + Map expected = new HashMap<>(); + expected.put("b", Integer.valueOf(42)); + expected.put("c", Integer.valueOf(111)); + POJO562 pojo = MAPPER.readValue(a2q( - "{'a':'value', 'b':42}" + "{'a':'value', 'b':42, 'c': 111}" ), POJO562.class); assertEquals("value", pojo.a); - assertEquals(Collections.singletonMap("b", Integer.valueOf(42)), - pojo.stuff); + assertEquals(expected, pojo.stuff); } } diff --git a/src/test/java/com/fasterxml/jackson/failing/JsonAnySetterThroughCreator562Test.java b/src/test/java/com/fasterxml/jackson/failing/JsonAnySetterThroughCreator562Test.java new file mode 100644 index 0000000000..a72d153dca --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/failing/JsonAnySetterThroughCreator562Test.java @@ -0,0 +1,52 @@ +package com.fasterxml.jackson.failing; + +import java.util.HashMap; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.newJsonMapper; + +public class JsonAnySetterThroughCreator562Test { + + static class MyClass { + public String field; + public HashMap anySetter; + + @JsonCreator + public MyClass( + @JsonProperty("field") String field, + @JsonAnySetter HashMap anySetter + ) { + this.field = field; + this.anySetter = anySetter; + } + } + + @Test + void testJsonAnySetterOnRecord() throws Exception { + String json = + "{\n" + + " \"field\": \"value\",\n" + + " \"unmapped1\": \"value1\",\n" + + " \"unmapped2\": \"value2\"\n" + + "}"; + HashMap expected = new HashMap<>(); + expected.put("unmapped1", "value1"); + expected.put("unmapped2", "value2"); + + + ObjectMapper objectMapper = newJsonMapper(); + + MyClass deserialized = objectMapper.readValue(json, MyClass.class); + + assertEquals("value", deserialized.field); + assertEquals(expected, deserialized.anySetter); + } +} From e2670e64e0de9358dedc81796d365ddd2200259d Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 3 Feb 2024 15:52:15 +0900 Subject: [PATCH 31/60] Revert "temporarily remove other jdk versions" This reverts commit 94540f8f6f800d46611091c9886159fdc0007b3e. --- .../JsonAliasWithDeduction4327RecordTest.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/test-jdk17/java/com/fasterxml/jackson/databind/records/JsonAliasWithDeduction4327RecordTest.java diff --git a/src/test-jdk17/java/com/fasterxml/jackson/databind/records/JsonAliasWithDeduction4327RecordTest.java b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/JsonAliasWithDeduction4327RecordTest.java new file mode 100644 index 0000000000..344bd9536f --- /dev/null +++ b/src/test-jdk17/java/com/fasterxml/jackson/databind/records/JsonAliasWithDeduction4327RecordTest.java @@ -0,0 +1,44 @@ +package com.fasterxml.jackson.databind.records; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.a2q; +import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.jsonMapperBuilder; + +// [databind#4327] JsonAlias should respsect with Polymorphic Deduction +public class JsonAliasWithDeduction4327RecordTest +{ + @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) + @JsonSubTypes({ + @JsonSubTypes.Type(value = DeductionBean1.class), + @JsonSubTypes.Type(value = DeductionBean2.class) + }) + interface Deduction { } + + record DeductionBean1(int x) implements Deduction { } + + record DeductionBean2( + @JsonAlias(value = {"Y", "yy", "ff", "X"}) int y + ) implements Deduction { } + + + private final ObjectMapper mapper = jsonMapperBuilder().build(); + + @ParameterizedTest + @ValueSource(strings = {"Y", "yy", "ff", "X"}) + public void testAliasWithPolymorphicDeduction(String field) throws Exception { + String json = a2q(String.format("{'%s': 2 }", field)); + Deduction value = mapper.readValue(json, Deduction.class); + assertNotNull(value); + assertEquals(2, ((DeductionBean2) value).y()); + } +} From 05a4c0b34d38a1a50d05845b0ce8427e27a82011 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 3 Feb 2024 15:53:35 +0900 Subject: [PATCH 32/60] Delete JsonAnySetterThroughCreator562Test.java --- .../JsonAnySetterThroughCreator562Test.java | 52 ------------------- 1 file changed, 52 deletions(-) delete mode 100644 src/test/java/com/fasterxml/jackson/failing/JsonAnySetterThroughCreator562Test.java diff --git a/src/test/java/com/fasterxml/jackson/failing/JsonAnySetterThroughCreator562Test.java b/src/test/java/com/fasterxml/jackson/failing/JsonAnySetterThroughCreator562Test.java deleted file mode 100644 index a72d153dca..0000000000 --- a/src/test/java/com/fasterxml/jackson/failing/JsonAnySetterThroughCreator562Test.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.fasterxml.jackson.failing; - -import java.util.HashMap; - -import org.junit.jupiter.api.Test; - -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.newJsonMapper; - -public class JsonAnySetterThroughCreator562Test { - - static class MyClass { - public String field; - public HashMap anySetter; - - @JsonCreator - public MyClass( - @JsonProperty("field") String field, - @JsonAnySetter HashMap anySetter - ) { - this.field = field; - this.anySetter = anySetter; - } - } - - @Test - void testJsonAnySetterOnRecord() throws Exception { - String json = - "{\n" + - " \"field\": \"value\",\n" + - " \"unmapped1\": \"value1\",\n" + - " \"unmapped2\": \"value2\"\n" + - "}"; - HashMap expected = new HashMap<>(); - expected.put("unmapped1", "value1"); - expected.put("unmapped2", "value2"); - - - ObjectMapper objectMapper = newJsonMapper(); - - MyClass deserialized = objectMapper.readValue(json, MyClass.class); - - assertEquals("value", deserialized.field); - assertEquals(expected, deserialized.anySetter); - } -} From dd53092c6a415042de31a8fc816f27b54583a820 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 3 Feb 2024 21:43:11 +0900 Subject: [PATCH 33/60] Remove hard coded field names --- .../jackson/databind/deser/BasicDeserializerFactory.java | 5 ++--- 1 file changed, 2 insertions(+), 3 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 9472fee918..bfc29f5ff6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -526,10 +526,9 @@ private void _addExplicitPropertyCreator(DeserializationContext ctxt, _reportUnwrappedCreatorProperty(ctxt, beanDesc, param); } name = candidate.findImplicitParamName(i); + // [databind#562] Any setter can be used... if (ctxt.getAnnotationIntrospector().hasAnySetter(param)) { - // [databind#562] Any setter can be used... - System.out.println(); - name = PropertyName.construct("leftovers"); + // no-op } else { _validateNamedPropertyParameter(ctxt, beanDesc, candidate, i, name, injectId); From 49f8b9597bb4f58c8448cc74eb0d2c041ec21679 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 3 Feb 2024 23:08:35 +0900 Subject: [PATCH 34/60] Clean Up --- .../deser/BasicDeserializerFactory.java | 3 +- .../deser/BeanDeserializerFactory.java | 23 ++++++++++-- .../deser/impl/PropertyBasedCreator.java | 2 -- .../deser/impl/PropertyValueBuffer.java | 36 +++++++++++++------ 4 files changed, 47 insertions(+), 17 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 bfc29f5ff6..2e587c15a2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -527,7 +527,8 @@ private void _addExplicitPropertyCreator(DeserializationContext ctxt, } name = candidate.findImplicitParamName(i); // [databind#562] Any setter can be used... - if (ctxt.getAnnotationIntrospector().hasAnySetter(param)) { + Boolean hasAnySetter = ctxt.getAnnotationIntrospector().hasAnySetter(param); + if (hasAnySetter != null && hasAnySetter) { // no-op } else { _validateNamedPropertyParameter(ctxt, beanDesc, candidate, i, diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index 3b0bb4f5a0..f955abbbe5 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -544,11 +544,11 @@ protected void addBeanProps(DeserializationContext ctxt, // Also, do we have a fallback "any" setter? AnnotatedMember anySetter = beanDesc.findAnySetterAccessor(); + AnnotatedMember creatorPropWithAnySetter = _findCreatorPropWithAnySetter(ctxt, creatorProps); if (anySetter != null) { builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetter)); - } - else if (ctxt.getAnnotationIntrospector().hasAnySetter(creatorProps[1].getMember())) { - builder.setAnySetter(constructAnySetter(ctxt, beanDesc, creatorProps[1].getMember())); + } else if (creatorPropWithAnySetter != null) { + builder.setAnySetter(constructAnySetter(ctxt, beanDesc, creatorPropWithAnySetter)); } else { // 23-Jan-2018, tatu: although [databind#1805] would suggest we should block // properties regardless, for now only consider unless there's any setter... @@ -664,6 +664,23 @@ else if (ctxt.getAnnotationIntrospector().hasAnySetter(creatorProps[1].getMember } } + private AnnotatedMember _findCreatorPropWithAnySetter(DeserializationContext ctxt, SettableBeanProperty[] creatorProps) { + if (creatorProps != null) { + AnnotationIntrospector ai = ctxt.getAnnotationIntrospector(); + for (SettableBeanProperty prop : creatorProps) { + AnnotatedMember m = prop.getMember(); + if (m != null) { + Boolean hasAnySetter = ai.hasAnySetter(m); + if (hasAnySetter != null && hasAnySetter) { + return prop.getMember(); + } + } + + } + } + return null; + } + private boolean _isSetterlessType(Class rawType) { // May also need to consider getters // for Map/Collection properties; but with lowest precedence diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java index c82564fb86..14488e88c2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java @@ -207,8 +207,6 @@ public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) thr { Object bean = _valueInstantiator.createFromObjectWith(ctxt, _allProperties, buffer); - - // returning null isn't quite legal, but let's let caller deal with that if (bean != null) { // Object Id to handle? 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 35f371fe51..f3bd2708fc 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 @@ -179,20 +179,34 @@ public Object[] getParameters(SettableBeanProperty[] props) } } } + + // [databind#562]: Respect @JsonAnySetter in @JsonCreator // check if we have anySetter Param - // if we do, parse from buffer and set - Map param = new HashMap<>(); - for (PropertyValue next = buffered(); next != null; next = next.next) { - try { - next.assign(param); - } catch (IOException e) { - // TODO : Wrap properly - throw new JsonMappingException("TODO: Wrap"); + AnnotationIntrospector ai = _context.getAnnotationIntrospector(); + for (int i = 0; i < props.length; i++) { + SettableBeanProperty prop = props[i]; + AnnotatedMember member = prop.getMember(); + if (member == null) { + continue; + } + Boolean hasAnySetter = ai.hasAnySetter(member); + if (hasAnySetter == null || !hasAnySetter) { + continue; + } + // So we have prop with anySetter. Should be Map-like, so let's assign such? + // Assign all remaining values to the map + Map param = new HashMap<>(); + for (PropertyValue next = buffered(); next != null; next = next.next) { + try { + next.assign(param); + } catch (IOException e) { + _context.reportInputMismatch(prop, e.getMessage()); + } } + // assign it then return + _creatorParameters[i] = param; + break; } - // find the "FIRST" creator Prop with JsonAnySetter - // Then assign value to it - _creatorParameters[1] = param; return _creatorParameters; } From b4d03e356de7ff79511f0fcf8a6c1bb5b93e9391 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 3 Feb 2024 23:23:22 +0900 Subject: [PATCH 35/60] Clean up unused _anySetter --- .../databind/deser/BeanDeserializer.java | 23 ++++++++- .../deser/BeanDeserializerFactory.java | 49 ------------------- .../deser/impl/PropertyBasedCreator.java | 3 +- 3 files changed, 23 insertions(+), 52 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index 4e6a9e9ff1..8bab211ad9 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.cfg.CoercionAction; import com.fasterxml.jackson.databind.deser.impl.*; import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring; +import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil; import com.fasterxml.jackson.databind.util.NameTransformer; @@ -520,10 +521,13 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri unknown.copyCurrentStructure(p); } + + Collection creatorProps = creator.properties(); + AnnotatedMember anySetter = _findCreatorPropWithAnySetter(ctxt, creatorProps.toArray(new SettableBeanProperty[creatorProps.size()])); // We hit END_OBJECT, so: Object bean; try { - if (_anySetter != null) { + if (anySetter != null) { bean = creator.buildSimple(ctxt, buffer); } else { bean = creator.build(ctxt, buffer); @@ -553,6 +557,23 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri return bean; } + + private AnnotatedMember _findCreatorPropWithAnySetter(DeserializationContext ctxt, SettableBeanProperty[] creatorProps) { + if (creatorProps != null) { + AnnotationIntrospector ai = ctxt.getAnnotationIntrospector(); + for (SettableBeanProperty prop : creatorProps) { + AnnotatedMember m = prop.getMember(); + if (m != null) { + Boolean hasAnySetter = ai.hasAnySetter(m); + if (hasAnySetter != null && hasAnySetter) { + return prop.getMember(); + } + } + + } + } + return null; + } /** * @since 2.8 */ diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index f955abbbe5..03f4d26f42 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -544,11 +544,8 @@ protected void addBeanProps(DeserializationContext ctxt, // Also, do we have a fallback "any" setter? AnnotatedMember anySetter = beanDesc.findAnySetterAccessor(); - AnnotatedMember creatorPropWithAnySetter = _findCreatorPropWithAnySetter(ctxt, creatorProps); if (anySetter != null) { builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetter)); - } else if (creatorPropWithAnySetter != null) { - builder.setAnySetter(constructAnySetter(ctxt, beanDesc, creatorPropWithAnySetter)); } else { // 23-Jan-2018, tatu: although [databind#1805] would suggest we should block // properties regardless, for now only consider unless there's any setter... @@ -664,23 +661,6 @@ protected void addBeanProps(DeserializationContext ctxt, } } - private AnnotatedMember _findCreatorPropWithAnySetter(DeserializationContext ctxt, SettableBeanProperty[] creatorProps) { - if (creatorProps != null) { - AnnotationIntrospector ai = ctxt.getAnnotationIntrospector(); - for (SettableBeanProperty prop : creatorProps) { - AnnotatedMember m = prop.getMember(); - if (m != null) { - Boolean hasAnySetter = ai.hasAnySetter(m); - if (hasAnySetter != null && hasAnySetter) { - return prop.getMember(); - } - } - - } - } - return null; - } - private boolean _isSetterlessType(Class rawType) { // May also need to consider getters // for Map/Collection properties; but with lowest precedence @@ -839,35 +819,6 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, valueType, null, mutator, PropertyMetadata.STD_OPTIONAL); - } else if (mutator instanceof AnnotatedParameter){ - AnnotatedParameter af = (AnnotatedParameter) mutator; - // get the type from the content type of the map object - JavaType fieldType = af.getType(); - // 31-Jul-2022, tatu: Not just Maps any more but also JsonNode, so: - if (fieldType.isMapLikeType()) { - fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); - keyType = fieldType.getKeyType(); - valueType = fieldType.getContentType(); - prop = new BeanProperty.Std(PropertyName.construct("stuff"), - fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); - } else if (fieldType.hasRawClass(JsonNode.class) - || fieldType.hasRawClass(ObjectNode.class)) { - fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); - // Deserialize is individual values of ObjectNode, not full ObjectNode, so: - valueType = ctxt.constructType(JsonNode.class); - prop = new BeanProperty.Std(PropertyName.construct(mutator.getName()), - fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); - - // Unlike with more complicated types, here we do not allow any annotation - // overrides etc but instead short-cut handling: - return SettableAnyProperty.constructForJsonNodeField(ctxt, - prop, mutator, valueType, - ctxt.findRootValueDeserializer(valueType)); - } else { - return ctxt.reportBadDefinition(beanDesc.getType(), String.format( - "Unsupported type for any-setter: %s -- only support `Map`s, `JsonNode` and `ObjectNode` ", - ClassUtil.getTypeDescription(fieldType))); - } } else if (isField) { AnnotatedField af = (AnnotatedField) mutator; // get the type from the content type of the map object diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java index 14488e88c2..042aea09a2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java @@ -205,8 +205,7 @@ public Object buildSimple(DeserializationContext ctxt, PropertyValueBuffer buffe public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException { - Object bean = _valueInstantiator.createFromObjectWith(ctxt, - _allProperties, buffer); + Object bean = buildSimple(ctxt, buffer); // returning null isn't quite legal, but let's let caller deal with that if (bean != null) { // Object Id to handle? From 426f3035a22bc7f7f7db53eae242ba08f17db6ab Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 3 Feb 2024 23:34:36 +0900 Subject: [PATCH 36/60] Add back required property _anySetter implementation --- .../databind/deser/BeanDeserializer.java | 23 +----------- .../deser/BeanDeserializerFactory.java | 36 +++++++++++++++++++ .../deser/impl/PropertyBasedCreator.java | 3 +- 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index 8bab211ad9..4e6a9e9ff1 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -8,7 +8,6 @@ import com.fasterxml.jackson.databind.cfg.CoercionAction; import com.fasterxml.jackson.databind.deser.impl.*; import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring; -import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil; import com.fasterxml.jackson.databind.util.NameTransformer; @@ -521,13 +520,10 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri unknown.copyCurrentStructure(p); } - - Collection creatorProps = creator.properties(); - AnnotatedMember anySetter = _findCreatorPropWithAnySetter(ctxt, creatorProps.toArray(new SettableBeanProperty[creatorProps.size()])); // We hit END_OBJECT, so: Object bean; try { - if (anySetter != null) { + if (_anySetter != null) { bean = creator.buildSimple(ctxt, buffer); } else { bean = creator.build(ctxt, buffer); @@ -557,23 +553,6 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri return bean; } - - private AnnotatedMember _findCreatorPropWithAnySetter(DeserializationContext ctxt, SettableBeanProperty[] creatorProps) { - if (creatorProps != null) { - AnnotationIntrospector ai = ctxt.getAnnotationIntrospector(); - for (SettableBeanProperty prop : creatorProps) { - AnnotatedMember m = prop.getMember(); - if (m != null) { - Boolean hasAnySetter = ai.hasAnySetter(m); - if (hasAnySetter != null && hasAnySetter) { - return prop.getMember(); - } - } - - } - } - return null; - } /** * @since 2.8 */ diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index 03f4d26f42..c3189970e8 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -544,8 +544,11 @@ protected void addBeanProps(DeserializationContext ctxt, // Also, do we have a fallback "any" setter? AnnotatedMember anySetter = beanDesc.findAnySetterAccessor(); + AnnotatedMember creatorPropWithAnySetter = _findCreatorPropWithAnySetter(ctxt, creatorProps); if (anySetter != null) { builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetter)); + } else if (creatorPropWithAnySetter != null) { + builder.setAnySetter(constructAnySetter(ctxt, beanDesc, creatorPropWithAnySetter)); } else { // 23-Jan-2018, tatu: although [databind#1805] would suggest we should block // properties regardless, for now only consider unless there's any setter... @@ -661,6 +664,23 @@ protected void addBeanProps(DeserializationContext ctxt, } } + private AnnotatedMember _findCreatorPropWithAnySetter(DeserializationContext ctxt, SettableBeanProperty[] creatorProps) { + if (creatorProps != null) { + AnnotationIntrospector ai = ctxt.getAnnotationIntrospector(); + for (SettableBeanProperty prop : creatorProps) { + AnnotatedMember m = prop.getMember(); + if (m != null) { + Boolean hasAnySetter = ai.hasAnySetter(m); + if (hasAnySetter != null && hasAnySetter) { + return prop.getMember(); + } + } + + } + } + return null; + } + private boolean _isSetterlessType(Class rawType) { // May also need to consider getters // for Map/Collection properties; but with lowest precedence @@ -819,6 +839,22 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, valueType, null, mutator, PropertyMetadata.STD_OPTIONAL); + } else if (mutator instanceof AnnotatedParameter){ + AnnotatedParameter af = (AnnotatedParameter) mutator; + // get the type from the content type of the map object + JavaType fieldType = af.getType(); + // 31-Jul-2022, tatu: Not just Maps any more but also JsonNode, so: + if (fieldType.isMapLikeType()) { + fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); + keyType = fieldType.getKeyType(); + valueType = fieldType.getContentType(); + prop = new BeanProperty.Std(PropertyName.construct("stuff"), + fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); + } else { + return ctxt.reportBadDefinition(beanDesc.getType(), String.format( + "Unsupported type for any-setter: %s -- only support `Map`s, `JsonNode` and `ObjectNode` ", + ClassUtil.getTypeDescription(fieldType))); + } } else if (isField) { AnnotatedField af = (AnnotatedField) mutator; // get the type from the content type of the map object diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java index 042aea09a2..14488e88c2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java @@ -205,7 +205,8 @@ public Object buildSimple(DeserializationContext ctxt, PropertyValueBuffer buffe public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException { - Object bean = buildSimple(ctxt, buffer); + Object bean = _valueInstantiator.createFromObjectWith(ctxt, + _allProperties, buffer); // returning null isn't quite legal, but let's let caller deal with that if (bean != null) { // Object Id to handle? From 201c5eaa52b050f2d84886aeec689a0b24f64195 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 4 Feb 2024 18:13:39 +0900 Subject: [PATCH 37/60] Add `Boolean _ hasAnySetter` in --- .../databind/deser/CreatorProperty.java | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 5e7ce8c98d..1b2d7c399a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -76,19 +76,40 @@ public class CreatorProperty protected boolean _ignorable; /** - * @since 2.11 + * Marker flag to indicate that current property is used to handle "any setter" via `@JsonAnySetter`. + * + * @since 2.18 + */ + protected final Boolean _hasAnySetter; + + /** + * @since 2.18 */ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperName, TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, - PropertyMetadata metadata) + PropertyMetadata metadata, Boolean hasAnySetter) { super(name, type, wrapperName, typeDeser, contextAnnotations, metadata); _annotated = param; _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; + _hasAnySetter = hasAnySetter; + } + + /** + * @since 2.11 + * @deprecated Since 2.18. use factory later version instead. + */ + protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperName, + TypeDeserializer typeDeser, + Annotations contextAnnotations, AnnotatedParameter param, + int index, JacksonInject.Value injectable, + PropertyMetadata metadata) + { + this(name, type, wrapperName, typeDeser, contextAnnotations, param, index, injectable, metadata, null); } /** @@ -125,6 +146,21 @@ public CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperNam * * @since 2.11 */ + public static CreatorProperty construct(PropertyName name, JavaType type, PropertyName wrapperName, + TypeDeserializer typeDeser, + Annotations contextAnnotations, AnnotatedParameter param, + int index, JacksonInject.Value injectable, + PropertyMetadata metadata, + Boolean hasAnySetter) + { + return new CreatorProperty(name, type, wrapperName, typeDeser, contextAnnotations, + param, index, injectable, metadata, hasAnySetter); + } + + /** + * @since 2.11 + * @deprecated Since 2.18. use later version instead. + */ public static CreatorProperty construct(PropertyName name, JavaType type, PropertyName wrapperName, TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, @@ -132,7 +168,7 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper PropertyMetadata metadata) { return new CreatorProperty(name, type, wrapperName, typeDeser, contextAnnotations, - param, index, injectable, metadata); + param, index, injectable, metadata, null); } /** @@ -145,6 +181,7 @@ protected CreatorProperty(CreatorProperty src, PropertyName newName) { _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; + _hasAnySetter = src._hasAnySetter; // [databind#562] Since 2.18 } protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, @@ -155,6 +192,7 @@ protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; + _hasAnySetter = src._hasAnySetter; // [databind#562] Since 2.18 } @Override @@ -320,6 +358,13 @@ public boolean isInjectionOnly() { // public boolean isInjectionOnly() { return false; } + /** + * @since 2.18 + */ + public boolean hasAnySetter() { + return Boolean.TRUE.equals(_hasAnySetter); + } + /* /********************************************************** /* Overridden methods, other From a247d02e3fcceb61da52562e48bc4bdd96ba8046 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 4 Feb 2024 18:14:05 +0900 Subject: [PATCH 38/60] Use hasAnySetter from CreatorProperty --- .../deser/BasicDeserializerFactory.java | 51 ++++++++++++++++--- .../deser/impl/PropertyValueBuffer.java | 17 +++---- 2 files changed, 52 insertions(+), 16 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 2e587c15a2..708c29fabc 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -518,6 +518,8 @@ private void _addExplicitPropertyCreator(DeserializationContext ctxt, JacksonInject.Value injectId = candidate.injection(i); AnnotatedParameter param = candidate.parameter(i); PropertyName name = candidate.paramName(i); + Boolean hasAnySetter = null; + if (name == null) { // 21-Sep-2017, tatu: Looks like we want to block accidental use of Unwrapped, // as that will not work with Creators well at all @@ -526,16 +528,17 @@ private void _addExplicitPropertyCreator(DeserializationContext ctxt, _reportUnwrappedCreatorProperty(ctxt, beanDesc, param); } name = candidate.findImplicitParamName(i); - // [databind#562] Any setter can be used... - Boolean hasAnySetter = ctxt.getAnnotationIntrospector().hasAnySetter(param); - if (hasAnySetter != null && hasAnySetter) { + // [databind#562] Allow @JsonAnySetter in creators. Introspection is done here only because of + // _validateNamedPropertyParameter() check. Otherwise, in constructCreatorProperty seems appropriate.... + hasAnySetter = ctxt.getAnnotationIntrospector().hasAnySetter(param); + if (Boolean.TRUE.equals(hasAnySetter)) { // no-op } else { _validateNamedPropertyParameter(ctxt, beanDesc, candidate, i, name, injectId); } } - properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId); + properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId, hasAnySetter); } creators.addPropertyCreator(candidate.creator(), true, properties); } @@ -630,7 +633,8 @@ private void _reportUnwrappedCreatorProperty(DeserializationContext ctxt, protected SettableBeanProperty constructCreatorProperty(DeserializationContext ctxt, BeanDescription beanDesc, PropertyName name, int index, AnnotatedParameter param, - JacksonInject.Value injectable) + JacksonInject.Value injectable, + Boolean hasAnySetter) throws JsonMappingException { final DeserializationConfig config = ctxt.getConfig(); @@ -668,7 +672,7 @@ protected SettableBeanProperty constructCreatorProperty(DeserializationContext c // so it is not called directly here SettableBeanProperty prop = CreatorProperty.construct(name, type, property.getWrapperName(), typeDeser, beanDesc.getClassAnnotations(), param, index, injectable, - metadata); + metadata, hasAnySetter); JsonDeserializer deser = findDeserializerFromAnnotation(ctxt, param); if (deser == null) { deser = type.getValueHandler(); @@ -681,6 +685,41 @@ protected SettableBeanProperty constructCreatorProperty(DeserializationContext c return prop; } + /** + * @deprecated Since 2.18, use {@link #constructCreatorProperty(DeserializationContext, BeanDescription, + * PropertyName, int, AnnotatedParameter, JacksonInject.Value, Boolean)} instead. + */ + protected SettableBeanProperty constructCreatorProperty(DeserializationContext ctxt, + BeanDescription beanDesc, PropertyName name, int index, + AnnotatedParameter param, + JacksonInject.Value injectable) + throws JsonMappingException + { + return constructCreatorProperty(ctxt, beanDesc, name, index, param, injectable, null); + } + + private PropertyName _findParamName(AnnotatedParameter param, AnnotationIntrospector intr) + { + if (intr != null) { + PropertyName name = intr.findNameForDeserialization(param); + if (name != null) { + // 16-Nov-2020, tatu: One quirk, wrt [databind#2932]; may get "use implicit" + // marker; should not return that + if (!name.isEmpty()) { + return name; + } + } + // 14-Apr-2014, tatu: Need to also consider possible implicit name + // (for JDK8, or via paranamer) + + String str = intr.findImplicitPropertyName(param); + if (str != null && !str.isEmpty()) { + return PropertyName.construct(str); + } + } + return null; + } + /** * Helper method copied from {@code POJOPropertyBuilder} since that won't be * applied to creator parameters 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 f3bd2708fc..159ad15e69 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 @@ -8,6 +8,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.deser.CreatorProperty; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference; @@ -180,17 +181,12 @@ public Object[] getParameters(SettableBeanProperty[] props) } } - // [databind#562]: Respect @JsonAnySetter in @JsonCreator - // check if we have anySetter Param - AnnotationIntrospector ai = _context.getAnnotationIntrospector(); - for (int i = 0; i < props.length; i++) { - SettableBeanProperty prop = props[i]; - AnnotatedMember member = prop.getMember(); - if (member == null) { + // [databind#562] Since 2.18 : Respect @JsonAnySetter in @JsonCreator + for (int i = 0; i < _creatorParameters.length; i++) { + if (!(props[i] instanceof CreatorProperty cp)) { continue; } - Boolean hasAnySetter = ai.hasAnySetter(member); - if (hasAnySetter == null || !hasAnySetter) { + if (!cp.hasAnySetter()) { continue; } // So we have prop with anySetter. Should be Map-like, so let's assign such? @@ -200,13 +196,14 @@ public Object[] getParameters(SettableBeanProperty[] props) try { next.assign(param); } catch (IOException e) { - _context.reportInputMismatch(prop, e.getMessage()); + _context.reportInputMismatch(cp, e.getMessage()); } } // assign it then return _creatorParameters[i] = param; break; } + return _creatorParameters; } From 2ece9f959fd67d0499c7c3f30b91c437c34f268f Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 4 Feb 2024 19:41:16 +0900 Subject: [PATCH 39/60] Remove unncessary anySetter construction --- .../deser/BeanDeserializerFactory.java | 20 ------------------- .../databind/deser/CreatorProperty.java | 18 ++++++++--------- .../deser/impl/PropertyValueBuffer.java | 11 ++++------ 3 files changed, 13 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index c3189970e8..80d431e868 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -544,11 +544,8 @@ protected void addBeanProps(DeserializationContext ctxt, // Also, do we have a fallback "any" setter? AnnotatedMember anySetter = beanDesc.findAnySetterAccessor(); - AnnotatedMember creatorPropWithAnySetter = _findCreatorPropWithAnySetter(ctxt, creatorProps); if (anySetter != null) { builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetter)); - } else if (creatorPropWithAnySetter != null) { - builder.setAnySetter(constructAnySetter(ctxt, beanDesc, creatorPropWithAnySetter)); } else { // 23-Jan-2018, tatu: although [databind#1805] would suggest we should block // properties regardless, for now only consider unless there's any setter... @@ -838,23 +835,6 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, prop = new BeanProperty.Std(PropertyName.construct(mutator.getName()), valueType, null, mutator, PropertyMetadata.STD_OPTIONAL); - - } else if (mutator instanceof AnnotatedParameter){ - AnnotatedParameter af = (AnnotatedParameter) mutator; - // get the type from the content type of the map object - JavaType fieldType = af.getType(); - // 31-Jul-2022, tatu: Not just Maps any more but also JsonNode, so: - if (fieldType.isMapLikeType()) { - fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); - keyType = fieldType.getKeyType(); - valueType = fieldType.getContentType(); - prop = new BeanProperty.Std(PropertyName.construct("stuff"), - fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); - } else { - return ctxt.reportBadDefinition(beanDesc.getType(), String.format( - "Unsupported type for any-setter: %s -- only support `Map`s, `JsonNode` and `ObjectNode` ", - ClassUtil.getTypeDescription(fieldType))); - } } else if (isField) { AnnotatedField af = (AnnotatedField) mutator; // get the type from the content type of the map object diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 1b2d7c399a..14114b75c7 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -80,7 +80,7 @@ public class CreatorProperty * * @since 2.18 */ - protected final Boolean _hasAnySetter; + protected final Boolean _isAnySetterProp; /** * @since 2.18 @@ -89,14 +89,14 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, - PropertyMetadata metadata, Boolean hasAnySetter) + PropertyMetadata metadata, Boolean isAnySetterProp) { super(name, type, wrapperName, typeDeser, contextAnnotations, metadata); _annotated = param; _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; - _hasAnySetter = hasAnySetter; + _isAnySetterProp = isAnySetterProp; } /** @@ -151,10 +151,10 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, PropertyMetadata metadata, - Boolean hasAnySetter) + Boolean isAnySetterProp) { return new CreatorProperty(name, type, wrapperName, typeDeser, contextAnnotations, - param, index, injectable, metadata, hasAnySetter); + param, index, injectable, metadata, isAnySetterProp); } /** @@ -181,7 +181,7 @@ protected CreatorProperty(CreatorProperty src, PropertyName newName) { _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _hasAnySetter = src._hasAnySetter; // [databind#562] Since 2.18 + _isAnySetterProp = src._isAnySetterProp; // [databind#562] Since 2.18 } protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, @@ -192,7 +192,7 @@ protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _hasAnySetter = src._hasAnySetter; // [databind#562] Since 2.18 + _isAnySetterProp = src._isAnySetterProp; // [databind#562] Since 2.18 } @Override @@ -361,8 +361,8 @@ public boolean isInjectionOnly() { /** * @since 2.18 */ - public boolean hasAnySetter() { - return Boolean.TRUE.equals(_hasAnySetter); + public boolean isAnySetterProp() { + return Boolean.TRUE.equals(_isAnySetterProp); } /* 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 159ad15e69..15cde4b634 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 @@ -11,7 +11,6 @@ import com.fasterxml.jackson.databind.deser.CreatorProperty; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; -import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; /** @@ -183,14 +182,12 @@ public Object[] getParameters(SettableBeanProperty[] props) // [databind#562] Since 2.18 : Respect @JsonAnySetter in @JsonCreator for (int i = 0; i < _creatorParameters.length; i++) { - if (!(props[i] instanceof CreatorProperty cp)) { + if (!(props[i] instanceof CreatorProperty cp) + || (!cp.isAnySetterProp()) + || (!cp.getType().isMapLikeType()) + ) { continue; } - if (!cp.hasAnySetter()) { - continue; - } - // So we have prop with anySetter. Should be Map-like, so let's assign such? - // Assign all remaining values to the map Map param = new HashMap<>(); for (PropertyValue next = buffered(); next != null; next = next.next) { try { From d3d8546550baa34b7a691b8e4069d936c8ae8ae3 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 4 Feb 2024 19:41:34 +0900 Subject: [PATCH 40/60] Revert "Remove unncessary anySetter construction" This reverts commit 6a892f870da6bbd39145ce1c0c8f890c921f4b95. --- .../deser/BeanDeserializerFactory.java | 20 +++++++++++++++++++ .../databind/deser/CreatorProperty.java | 18 ++++++++--------- .../deser/impl/PropertyValueBuffer.java | 11 ++++++---- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index 80d431e868..c3189970e8 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -544,8 +544,11 @@ protected void addBeanProps(DeserializationContext ctxt, // Also, do we have a fallback "any" setter? AnnotatedMember anySetter = beanDesc.findAnySetterAccessor(); + AnnotatedMember creatorPropWithAnySetter = _findCreatorPropWithAnySetter(ctxt, creatorProps); if (anySetter != null) { builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetter)); + } else if (creatorPropWithAnySetter != null) { + builder.setAnySetter(constructAnySetter(ctxt, beanDesc, creatorPropWithAnySetter)); } else { // 23-Jan-2018, tatu: although [databind#1805] would suggest we should block // properties regardless, for now only consider unless there's any setter... @@ -835,6 +838,23 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, prop = new BeanProperty.Std(PropertyName.construct(mutator.getName()), valueType, null, mutator, PropertyMetadata.STD_OPTIONAL); + + } else if (mutator instanceof AnnotatedParameter){ + AnnotatedParameter af = (AnnotatedParameter) mutator; + // get the type from the content type of the map object + JavaType fieldType = af.getType(); + // 31-Jul-2022, tatu: Not just Maps any more but also JsonNode, so: + if (fieldType.isMapLikeType()) { + fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); + keyType = fieldType.getKeyType(); + valueType = fieldType.getContentType(); + prop = new BeanProperty.Std(PropertyName.construct("stuff"), + fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); + } else { + return ctxt.reportBadDefinition(beanDesc.getType(), String.format( + "Unsupported type for any-setter: %s -- only support `Map`s, `JsonNode` and `ObjectNode` ", + ClassUtil.getTypeDescription(fieldType))); + } } else if (isField) { AnnotatedField af = (AnnotatedField) mutator; // get the type from the content type of the map object diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 14114b75c7..1b2d7c399a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -80,7 +80,7 @@ public class CreatorProperty * * @since 2.18 */ - protected final Boolean _isAnySetterProp; + protected final Boolean _hasAnySetter; /** * @since 2.18 @@ -89,14 +89,14 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, - PropertyMetadata metadata, Boolean isAnySetterProp) + PropertyMetadata metadata, Boolean hasAnySetter) { super(name, type, wrapperName, typeDeser, contextAnnotations, metadata); _annotated = param; _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; - _isAnySetterProp = isAnySetterProp; + _hasAnySetter = hasAnySetter; } /** @@ -151,10 +151,10 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, PropertyMetadata metadata, - Boolean isAnySetterProp) + Boolean hasAnySetter) { return new CreatorProperty(name, type, wrapperName, typeDeser, contextAnnotations, - param, index, injectable, metadata, isAnySetterProp); + param, index, injectable, metadata, hasAnySetter); } /** @@ -181,7 +181,7 @@ protected CreatorProperty(CreatorProperty src, PropertyName newName) { _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _isAnySetterProp = src._isAnySetterProp; // [databind#562] Since 2.18 + _hasAnySetter = src._hasAnySetter; // [databind#562] Since 2.18 } protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, @@ -192,7 +192,7 @@ protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _isAnySetterProp = src._isAnySetterProp; // [databind#562] Since 2.18 + _hasAnySetter = src._hasAnySetter; // [databind#562] Since 2.18 } @Override @@ -361,8 +361,8 @@ public boolean isInjectionOnly() { /** * @since 2.18 */ - public boolean isAnySetterProp() { - return Boolean.TRUE.equals(_isAnySetterProp); + public boolean hasAnySetter() { + return Boolean.TRUE.equals(_hasAnySetter); } /* 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 15cde4b634..159ad15e69 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 @@ -11,6 +11,7 @@ import com.fasterxml.jackson.databind.deser.CreatorProperty; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; +import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; /** @@ -182,12 +183,14 @@ public Object[] getParameters(SettableBeanProperty[] props) // [databind#562] Since 2.18 : Respect @JsonAnySetter in @JsonCreator for (int i = 0; i < _creatorParameters.length; i++) { - if (!(props[i] instanceof CreatorProperty cp) - || (!cp.isAnySetterProp()) - || (!cp.getType().isMapLikeType()) - ) { + if (!(props[i] instanceof CreatorProperty cp)) { continue; } + if (!cp.hasAnySetter()) { + continue; + } + // So we have prop with anySetter. Should be Map-like, so let's assign such? + // Assign all remaining values to the map Map param = new HashMap<>(); for (PropertyValue next = buffered(); next != null; next = next.next) { try { From 3d632f4de609074c79443ab759ad4a072c4031b6 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 4 Feb 2024 19:44:04 +0900 Subject: [PATCH 41/60] Put back isAnySetterXxx methods and fields --- .../databind/deser/CreatorProperty.java | 18 +++++++++--------- .../deser/impl/PropertyValueBuffer.java | 3 +-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 1b2d7c399a..95e23afaf4 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -80,7 +80,7 @@ public class CreatorProperty * * @since 2.18 */ - protected final Boolean _hasAnySetter; + protected final Boolean _isAnySetterProp; /** * @since 2.18 @@ -89,14 +89,14 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, - PropertyMetadata metadata, Boolean hasAnySetter) + PropertyMetadata metadata, Boolean isAnySetterProp) { super(name, type, wrapperName, typeDeser, contextAnnotations, metadata); _annotated = param; _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; - _hasAnySetter = hasAnySetter; + _isAnySetterProp = isAnySetterProp; } /** @@ -151,10 +151,10 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, PropertyMetadata metadata, - Boolean hasAnySetter) + Boolean isAnySetter) { return new CreatorProperty(name, type, wrapperName, typeDeser, contextAnnotations, - param, index, injectable, metadata, hasAnySetter); + param, index, injectable, metadata, isAnySetter); } /** @@ -181,7 +181,7 @@ protected CreatorProperty(CreatorProperty src, PropertyName newName) { _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _hasAnySetter = src._hasAnySetter; // [databind#562] Since 2.18 + _isAnySetterProp = src._isAnySetterProp; // [databind#562] Since 2.18 } protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, @@ -192,7 +192,7 @@ protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _hasAnySetter = src._hasAnySetter; // [databind#562] Since 2.18 + _isAnySetterProp = src._isAnySetterProp; // [databind#562] Since 2.18 } @Override @@ -361,8 +361,8 @@ public boolean isInjectionOnly() { /** * @since 2.18 */ - public boolean hasAnySetter() { - return Boolean.TRUE.equals(_hasAnySetter); + public boolean isAnySetterProp() { + return Boolean.TRUE.equals(_isAnySetterProp); } /* 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 159ad15e69..62a660ae6e 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 @@ -11,7 +11,6 @@ import com.fasterxml.jackson.databind.deser.CreatorProperty; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; -import com.fasterxml.jackson.databind.deser.UnresolvedForwardReference; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; /** @@ -186,7 +185,7 @@ public Object[] getParameters(SettableBeanProperty[] props) if (!(props[i] instanceof CreatorProperty cp)) { continue; } - if (!cp.hasAnySetter()) { + if (!cp.isAnySetterProp()) { continue; } // So we have prop with anySetter. Should be Map-like, so let's assign such? From ce58b148d30afa15ab47ebce880a6f689bc26dd8 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sun, 4 Feb 2024 21:00:21 +0900 Subject: [PATCH 42/60] Fix JDK 8 error --- .../deser/impl/PropertyValueBuffer.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) 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 62a660ae6e..c829f30620 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 @@ -182,25 +182,25 @@ public Object[] getParameters(SettableBeanProperty[] props) // [databind#562] Since 2.18 : Respect @JsonAnySetter in @JsonCreator for (int i = 0; i < _creatorParameters.length; i++) { - if (!(props[i] instanceof CreatorProperty cp)) { + if (!(props[i] instanceof CreatorProperty)) { continue; } - if (!cp.isAnySetterProp()) { - continue; - } - // So we have prop with anySetter. Should be Map-like, so let's assign such? - // Assign all remaining values to the map - Map param = new HashMap<>(); - for (PropertyValue next = buffered(); next != null; next = next.next) { - try { - next.assign(param); - } catch (IOException e) { - _context.reportInputMismatch(cp, e.getMessage()); + CreatorProperty cp = (CreatorProperty) props[i]; + if (cp.isAnySetterProp() && !cp.getType().isMapLikeType()) { + // So we have prop with anySetter. Should be Map-like, so let's assign such? + // Assign all remaining values to the map + Map param = new HashMap<>(); + for (PropertyValue next = buffered(); next != null; next = next.next) { + try { + next.assign(param); + } catch (IOException e) { + _context.reportInputMismatch(cp, e.getMessage()); + } } + // assign it then return + _creatorParameters[i] = param; + break; } - // assign it then return - _creatorParameters[i] = param; - break; } return _creatorParameters; From 21a0795b5d032a56fc3a8bcc6ba4ba64514de2aa Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 9 Feb 2024 18:58:27 +0900 Subject: [PATCH 43/60] Move test dir --- .../deser/creators}/AnySetterForCreator562Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/com/fasterxml/jackson/{failing => databind/deser/creators}/AnySetterForCreator562Test.java (96%) diff --git a/src/test/java/com/fasterxml/jackson/failing/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java similarity index 96% rename from src/test/java/com/fasterxml/jackson/failing/AnySetterForCreator562Test.java rename to src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java index a9725045b0..4ffe9ffb9f 100644 --- a/src/test/java/com/fasterxml/jackson/failing/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.failing; +package com.fasterxml.jackson.databind.deser.creators; import java.util.HashMap; import java.util.Map; From 09296d10060717280d877c9f32291b353f9b33b0 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 9 Feb 2024 18:58:37 +0900 Subject: [PATCH 44/60] Fix error --- .../jackson/databind/deser/impl/PropertyValueBuffer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c829f30620..dd992933ef 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 @@ -186,7 +186,7 @@ public Object[] getParameters(SettableBeanProperty[] props) continue; } CreatorProperty cp = (CreatorProperty) props[i]; - if (cp.isAnySetterProp() && !cp.getType().isMapLikeType()) { + if (cp.isAnySetterProp() && cp.getType().isMapLikeType()) { // So we have prop with anySetter. Should be Map-like, so let's assign such? // Assign all remaining values to the map Map param = new HashMap<>(); From 3445dddf471bc697115cce6f8bceb7c95c5fb36e Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 9 Feb 2024 21:47:43 +0900 Subject: [PATCH 45/60] Add new SettableAnyProperty impl --- .../databind/deser/BeanDeserializer.java | 2 +- .../deser/BeanDeserializerFactory.java | 6 ++ .../databind/deser/CreatorProperty.java | 8 ++- .../databind/deser/SettableAnyProperty.java | 67 +++++++++++++++++++ .../databind/deser/ValueInstantiator.java | 7 ++ .../deser/impl/PropertyBasedCreator.java | 5 +- .../deser/impl/PropertyValueBuffer.java | 32 ++++----- 7 files changed, 108 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index 4e6a9e9ff1..9cb9d12a64 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -524,7 +524,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri Object bean; try { if (_anySetter != null) { - bean = creator.buildSimple(ctxt, buffer); + bean = creator.buildWithAnySetter(ctxt, buffer, _anySetter); } else { bean = creator.build(ctxt, buffer); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index c3189970e8..c81acb65dd 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -828,6 +828,7 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, JavaType keyType; JavaType valueType; final boolean isField = mutator instanceof AnnotatedField; + boolean isMapParam = false; if (mutator instanceof AnnotatedMethod) { // we know it's a 2-arg method, second arg is the value @@ -850,6 +851,7 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, valueType = fieldType.getContentType(); prop = new BeanProperty.Std(PropertyName.construct("stuff"), fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); + isMapParam = true; } else { return ctxt.reportBadDefinition(beanDesc.getType(), String.format( "Unsupported type for any-setter: %s -- only support `Map`s, `JsonNode` and `ObjectNode` ", @@ -916,6 +918,10 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, return SettableAnyProperty.constructForMapField(ctxt, prop, mutator, valueType, keyDeser, deser, typeDeser); } + if (isMapParam) { + return SettableAnyProperty.constructForMapParameter(ctxt, + prop, mutator, valueType, keyDeser, deser, typeDeser); + } return SettableAnyProperty.constructForMethod(ctxt, prop, mutator, valueType, keyDeser, deser, typeDeser); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 95e23afaf4..2501918d8b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.lang.annotation.Annotation; +import java.util.Map; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.core.JsonParser; @@ -96,7 +97,7 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; - _isAnySetterProp = isAnySetterProp; + _isAnySetterProp = isAnySetterProp; // [databind#562] Since 2.18 } /** @@ -399,4 +400,9 @@ private void _reportMissingSetter(JsonParser p, DeserializationContext ctxt) thr throw InvalidDefinitionException.from(p, msg, getType()); } } + + public Map createAndBuildMap(DeserializationContext context, SettableAnyProperty anySetter) throws IOException { + SettableAnyProperty.MapParameterAnyProperty mapParap = (SettableAnyProperty.MapParameterAnyProperty) anySetter; + return mapParap.initMap(context); + } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java index b7aafa205b..22607ff32f 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java @@ -117,6 +117,28 @@ public static SettableAnyProperty constructForJsonNodeField(DeserializationConte ctxt.getNodeFactory()); } + /** + * @since 2.18 + */ + public static SettableAnyProperty constructForMapParameter(DeserializationContext ctxt, + BeanProperty property, + AnnotatedMember field, JavaType valueType, + KeyDeserializer keyDeser, + JsonDeserializer valueDeser, TypeDeserializer typeDeser) + { + Class mapType = field.getRawType(); + // 02-Aug-2022, tatu: Ideally would be resolved to a concrete type by caller but + // alas doesn't appear to happen. Nor does `BasicDeserializerFactory` expose method + // for finding default or explicit mappings. + if (mapType == Map.class) { + mapType = LinkedHashMap.class; + } + ValueInstantiator vi = JDKValueInstantiators.findStdValueInstantiator(ctxt.getConfig(), mapType); + return new MapParameterAnyProperty(property, field, valueType, + keyDeser, valueDeser, typeDeser, + vi); + } + // Abstract @since 2.14 public abstract SettableAnyProperty withValueDeserializer(JsonDeserializer deser); @@ -437,4 +459,49 @@ public SettableAnyProperty withValueDeserializer(JsonDeserializer deser) return this; } } + + /** + * @since 2.18 + */ + protected static class MapParameterAnyProperty extends SettableAnyProperty + implements java.io.Serializable + { + private static final long serialVersionUID = 1L; + + protected final ValueInstantiator _valueInstantiator; + + public MapParameterAnyProperty(BeanProperty property, + AnnotatedMember field, JavaType valueType, + KeyDeserializer keyDeser, + JsonDeserializer valueDeser, TypeDeserializer typeDeser, + ValueInstantiator inst) { + super(property, field, valueType, + keyDeser, valueDeser, typeDeser); + _valueInstantiator = inst; + } + + @Override + public SettableAnyProperty withValueDeserializer(JsonDeserializer deser) { + return new MapParameterAnyProperty(_property, _setter, _type, + _keyDeserializer, deser, _valueTypeDeserializer, + _valueInstantiator); + } + + @SuppressWarnings("unchecked") + @Override + protected void _set(Object instance, Object propName, Object value) throws Exception { + throw new UnsupportedOperationException("Cannot set any properties for constructor parameter of type `Map`"); + } + + protected Map initMap(DeserializationContext ctxt) + throws IOException + { + if (_valueInstantiator == null) { + throw JsonMappingException.from(ctxt, String.format( + "Cannot create an instance of %s for use as \"any-setter\" '%s'", + ClassUtil.nameOf(_type.getRawClass()), _property.getName())); + } + return (Map) _valueInstantiator.createUsingDefault(ctxt); + } + } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java b/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java index 648bdcc3d3..ffd17a77ee 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java @@ -301,6 +301,13 @@ public Object createFromObjectWith(DeserializationContext ctxt, return createFromObjectWith(ctxt, buffer.getParameters(props)); } + public Object createFromObjectWith(DeserializationContext ctxt, + SettableBeanProperty[] props, PropertyValueBuffer buffer, SettableAnyProperty anySetter) + throws IOException + { + return createFromObjectWith(ctxt, buffer.getParametersWithAnySetter(props, anySetter)); + } + /** * Method to called to create value instance from JSON Object using * an intermediate "delegate" value to pass to createor method diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java index 14488e88c2..544db0f544 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.deser.SettableAnyProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; import com.fasterxml.jackson.databind.deser.ValueInstantiator; @@ -197,10 +198,10 @@ public PropertyValueBuffer startBuilding(JsonParser p, DeserializationContext ct return new PropertyValueBuffer(p, ctxt, _propertyCount, oir); } - public Object buildSimple(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException + public Object buildWithAnySetter(DeserializationContext ctxt, PropertyValueBuffer buffer, SettableAnyProperty anySetter) throws IOException { return _valueInstantiator.createFromObjectWith(ctxt, - _allProperties, buffer); + _allProperties, buffer, anySetter); } public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException 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 dd992933ef..005e13429c 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 @@ -2,7 +2,6 @@ import java.io.IOException; import java.util.BitSet; -import java.util.HashMap; import java.util.Map; import com.fasterxml.jackson.core.JsonParser; @@ -179,30 +178,33 @@ public Object[] getParameters(SettableBeanProperty[] props) } } } + return _creatorParameters; + } + public Object[] getParametersWithAnySetter(SettableBeanProperty[] props, SettableAnyProperty anySetter) + throws JsonMappingException + { + getParameters(props); // [databind#562] Since 2.18 : Respect @JsonAnySetter in @JsonCreator for (int i = 0; i < _creatorParameters.length; i++) { - if (!(props[i] instanceof CreatorProperty)) { + if (!(props[i] instanceof CreatorProperty) || !((CreatorProperty) props[i]).isAnySetterProp()) { continue; } CreatorProperty cp = (CreatorProperty) props[i]; - if (cp.isAnySetterProp() && cp.getType().isMapLikeType()) { - // So we have prop with anySetter. Should be Map-like, so let's assign such? - // Assign all remaining values to the map - Map param = new HashMap<>(); + Object param = null; + try { + // cp.initMap(_context); + Map map = cp.createAndBuildMap(_context, anySetter); for (PropertyValue next = buffered(); next != null; next = next.next) { - try { - next.assign(param); - } catch (IOException e) { - _context.reportInputMismatch(cp, e.getMessage()); - } + next.assign(map); } - // assign it then return - _creatorParameters[i] = param; - break; + param = map; + } catch (IOException e) { + _context.reportInputMismatch(cp, e.getMessage()); } + _creatorParameters[i] = param; + break; } - return _creatorParameters; } From 9e951558b633862d6f9bc223a8ded63bc458ca21 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 9 Feb 2024 22:05:48 +0900 Subject: [PATCH 46/60] Remove hard coded prop name --- .../jackson/databind/deser/BeanDeserializerFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index c81acb65dd..7ea64703b9 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -849,7 +849,7 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); keyType = fieldType.getKeyType(); valueType = fieldType.getContentType(); - prop = new BeanProperty.Std(PropertyName.construct("stuff"), + prop = new BeanProperty.Std(PropertyName.NO_NAME, fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); isMapParam = true; } else { From 190853287f33cad340da45dabd7ad8373f997165 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 9 Feb 2024 22:26:51 +0900 Subject: [PATCH 47/60] Update javadoc and clean comments --- .../jackson/databind/deser/CreatorProperty.java | 15 ++++++++++++--- .../databind/deser/impl/PropertyValueBuffer.java | 3 +-- .../creators/AnySetterForCreator562Test.java | 4 ++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 2501918d8b..171bbafd29 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -144,8 +144,9 @@ public CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperNam * method parameter; used for accessing annotations of the property * @param injectable Information about injectable value, if any * @param index Index of this property within creator invocation - * - * @since 2.11 + * @param isAnySetter Marker flag to indicate that current property is used to handle "any setter" via `@JsonAnySetter`. + * + * @since 2.18 */ public static CreatorProperty construct(PropertyName name, JavaType type, PropertyName wrapperName, TypeDeserializer typeDeser, @@ -401,7 +402,15 @@ private void _reportMissingSetter(JsonParser p, DeserializationContext ctxt) thr } } - public Map createAndBuildMap(DeserializationContext context, SettableAnyProperty anySetter) throws IOException { + /** + * @since 2.18 + */ + public Map initMap(DeserializationContext context, SettableAnyProperty anySetter) + throws IOException + { + if (!isAnySetterProp()) { + throw new IllegalStateException("Cannot create Map for non-AnySetter creator property"); + } SettableAnyProperty.MapParameterAnyProperty mapParap = (SettableAnyProperty.MapParameterAnyProperty) anySetter; return mapParap.initMap(context); } 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 005e13429c..41c99a7eda 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 @@ -193,8 +193,7 @@ public Object[] getParametersWithAnySetter(SettableBeanProperty[] props, Settabl CreatorProperty cp = (CreatorProperty) props[i]; Object param = null; try { - // cp.initMap(_context); - Map map = cp.createAndBuildMap(_context, anySetter); + Map map = cp.initMap(_context, anySetter); for (PropertyValue next = buffered(); next != null; next = next.next) { next.assign(map); } diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java index 4ffe9ffb9f..139a0d0948 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java @@ -24,8 +24,8 @@ static class POJO562 @JsonCreator public POJO562(@JsonProperty("a") String a, - @JsonAnySetter Map - leftovers) { + @JsonAnySetter Map leftovers + ) { this.a = a; stuff = leftovers; } From 659bbf2b9dbf0decb7903fb068ef48663224c477 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 9 Feb 2024 16:52:35 -0800 Subject: [PATCH 48/60] Minor clean up, streamlining --- .../deser/BeanDeserializerFactory.java | 4 +-- .../databind/deser/CreatorProperty.java | 11 ++++---- .../databind/deser/SettableAnyProperty.java | 3 ++- .../deser/impl/PropertyValueBuffer.java | 26 +++++++++++-------- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index 7ea64703b9..dc592987f0 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -670,12 +670,10 @@ private AnnotatedMember _findCreatorPropWithAnySetter(DeserializationContext ctx for (SettableBeanProperty prop : creatorProps) { AnnotatedMember m = prop.getMember(); if (m != null) { - Boolean hasAnySetter = ai.hasAnySetter(m); - if (hasAnySetter != null && hasAnySetter) { + if (Boolean.TRUE.equals(ai.hasAnySetter(m))) { return prop.getMember(); } } - } } return null; diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 171bbafd29..df882c4f92 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -81,7 +81,7 @@ public class CreatorProperty * * @since 2.18 */ - protected final Boolean _isAnySetterProp; + protected final boolean _isAnySetterProp; /** * @since 2.18 @@ -97,13 +97,14 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; - _isAnySetterProp = isAnySetterProp; // [databind#562] Since 2.18 + _isAnySetterProp = Boolean.TRUE.equals(isAnySetterProp); // [databind#562] Since 2.18 } /** * @since 2.11 * @deprecated Since 2.18. use factory later version instead. */ + @Deprecated protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperName, TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, @@ -163,6 +164,7 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper * @since 2.11 * @deprecated Since 2.18. use later version instead. */ + @Deprecated public static CreatorProperty construct(PropertyName name, JavaType type, PropertyName wrapperName, TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, @@ -364,7 +366,7 @@ public boolean isInjectionOnly() { * @since 2.18 */ public boolean isAnySetterProp() { - return Boolean.TRUE.equals(_isAnySetterProp); + return _isAnySetterProp; } /* @@ -411,7 +413,6 @@ public Map initMap(DeserializationContext context, SettableAnyPr if (!isAnySetterProp()) { throw new IllegalStateException("Cannot create Map for non-AnySetter creator property"); } - SettableAnyProperty.MapParameterAnyProperty mapParap = (SettableAnyProperty.MapParameterAnyProperty) anySetter; - return mapParap.initMap(context); + return ((SettableAnyProperty.MapParameterAnyProperty) anySetter).initMap(context); } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java index 22607ff32f..0cb94f5d04 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java @@ -487,15 +487,16 @@ public SettableAnyProperty withValueDeserializer(JsonDeserializer deser) _valueInstantiator); } - @SuppressWarnings("unchecked") @Override protected void _set(Object instance, Object propName, Object value) throws Exception { throw new UnsupportedOperationException("Cannot set any properties for constructor parameter of type `Map`"); } + @SuppressWarnings("unchecked") protected Map initMap(DeserializationContext ctxt) throws IOException { + // This really should have been caught earlier if (_valueInstantiator == null) { throw JsonMappingException.from(ctxt, String.format( "Cannot create an instance of %s for use as \"any-setter\" '%s'", 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 41c99a7eda..247048691f 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 @@ -149,28 +149,29 @@ public Object getParameter(SettableBeanProperty prop) public Object[] getParameters(SettableBeanProperty[] props) throws JsonMappingException { + final Object[] creatorParams = _creatorParameters; // quick check to see if anything else is needed if (_paramsNeeded > 0) { if (_paramsSeenBig == null) { int mask = _paramsSeen; // not optimal, could use `Integer.trailingZeroes()`, but for now should not // really matter for common cases - for (int ix = 0, len = _creatorParameters.length; ix < len; ++ix, mask >>= 1) { + for (int ix = 0, len = creatorParams.length; ix < len; ++ix, mask >>= 1) { if ((mask & 1) == 0) { - _creatorParameters[ix] = _findMissing(props[ix]); + creatorParams[ix] = _findMissing(props[ix]); } } } else { - final int len = _creatorParameters.length; + final int len = creatorParams.length; for (int ix = 0; (ix = _paramsSeenBig.nextClearBit(ix)) < len; ++ix) { - _creatorParameters[ix] = _findMissing(props[ix]); + creatorParams[ix] = _findMissing(props[ix]); } } } if (_context.isEnabled(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES)) { for (int ix = 0; ix < props.length; ++ix) { - if (_creatorParameters[ix] == null) { + if (creatorParams[ix] == null) { SettableBeanProperty prop = props[ix]; _context.reportInputMismatch(prop, "Null value for creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES` enabled", @@ -178,19 +179,22 @@ public Object[] getParameters(SettableBeanProperty[] props) } } } - return _creatorParameters; + return creatorParams; } public Object[] getParametersWithAnySetter(SettableBeanProperty[] props, SettableAnyProperty anySetter) throws JsonMappingException { - getParameters(props); + final Object[] creatorParams = getParameters(props); // [databind#562] Since 2.18 : Respect @JsonAnySetter in @JsonCreator - for (int i = 0; i < _creatorParameters.length; i++) { - if (!(props[i] instanceof CreatorProperty) || !((CreatorProperty) props[i]).isAnySetterProp()) { + for (int i = 0; i < creatorParams.length; i++) { + if (!(props[i] instanceof CreatorProperty)) { continue; } CreatorProperty cp = (CreatorProperty) props[i]; + if (!cp.isAnySetterProp()) { + continue; + } Object param = null; try { Map map = cp.initMap(_context, anySetter); @@ -201,10 +205,10 @@ public Object[] getParametersWithAnySetter(SettableBeanProperty[] props, Settabl } catch (IOException e) { _context.reportInputMismatch(cp, e.getMessage()); } - _creatorParameters[i] = param; + creatorParams[i] = param; break; } - return _creatorParameters; + return creatorParams; } protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingException From fcfac63f6282b25a757a65507ec02118f122bca3 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 9 Feb 2024 17:09:01 -0800 Subject: [PATCH 49/60] Add verification there's only one any-setter --- .../deser/BasicDeserializerFactory.java | 23 +++++++++----- .../databind/deser/CreatorProperty.java | 11 +++---- .../creators/AnySetterForCreator562Test.java | 30 +++++++++++++++++-- 3 files changed, 50 insertions(+), 14 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 708c29fabc..d5d5c49b1c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -513,12 +513,12 @@ private void _addExplicitPropertyCreator(DeserializationContext ctxt, //System.err.println("_addExplicitPropertyCreator(): "+candidate); final int paramCount = candidate.paramCount(); SettableBeanProperty[] properties = new SettableBeanProperty[paramCount]; + int anySetterIndex = -1; for (int i = 0; i < paramCount; ++i) { JacksonInject.Value injectId = candidate.injection(i); AnnotatedParameter param = candidate.parameter(i); PropertyName name = candidate.paramName(i); - Boolean hasAnySetter = null; if (name == null) { // 21-Sep-2017, tatu: Looks like we want to block accidental use of Unwrapped, @@ -530,15 +530,21 @@ private void _addExplicitPropertyCreator(DeserializationContext ctxt, name = candidate.findImplicitParamName(i); // [databind#562] Allow @JsonAnySetter in creators. Introspection is done here only because of // _validateNamedPropertyParameter() check. Otherwise, in constructCreatorProperty seems appropriate.... - hasAnySetter = ctxt.getAnnotationIntrospector().hasAnySetter(param); - if (Boolean.TRUE.equals(hasAnySetter)) { + if (Boolean.TRUE.equals(ctxt.getAnnotationIntrospector().hasAnySetter(param))) { + if (anySetterIndex >= 0) { + ctxt.reportBadTypeDefinition(beanDesc, + "More than one 'any-setter' specified (parameters #%d vs #%d)", + anySetterIndex, i); + } + anySetterIndex = i; // no-op } else { _validateNamedPropertyParameter(ctxt, beanDesc, candidate, i, name, injectId); } } - properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId, hasAnySetter); + properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId, + anySetterIndex >= 0); } creators.addPropertyCreator(candidate.creator(), true, properties); } @@ -629,12 +635,14 @@ private void _reportUnwrappedCreatorProperty(DeserializationContext ctxt, * Method that will construct a property object that represents * a logical property passed via Creator (constructor or static * factory method) + * + * @since 2.18 */ protected SettableBeanProperty constructCreatorProperty(DeserializationContext ctxt, BeanDescription beanDesc, PropertyName name, int index, AnnotatedParameter param, JacksonInject.Value injectable, - Boolean hasAnySetter) + boolean hasAnySetter) throws JsonMappingException { final DeserializationConfig config = ctxt.getConfig(); @@ -687,15 +695,16 @@ protected SettableBeanProperty constructCreatorProperty(DeserializationContext c /** * @deprecated Since 2.18, use {@link #constructCreatorProperty(DeserializationContext, BeanDescription, - * PropertyName, int, AnnotatedParameter, JacksonInject.Value, Boolean)} instead. + * PropertyName, int, AnnotatedParameter, JacksonInject.Value, boolean)} instead. */ + @Deprecated protected SettableBeanProperty constructCreatorProperty(DeserializationContext ctxt, BeanDescription beanDesc, PropertyName name, int index, AnnotatedParameter param, JacksonInject.Value injectable) throws JsonMappingException { - return constructCreatorProperty(ctxt, beanDesc, name, index, param, injectable, null); + return constructCreatorProperty(ctxt, beanDesc, name, index, param, injectable, false); } private PropertyName _findParamName(AnnotatedParameter param, AnnotationIntrospector intr) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index df882c4f92..0b73d6a2fb 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -90,14 +90,14 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, - PropertyMetadata metadata, Boolean isAnySetterProp) + PropertyMetadata metadata, boolean isAnySetterProp) { super(name, type, wrapperName, typeDeser, contextAnnotations, metadata); _annotated = param; _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; - _isAnySetterProp = Boolean.TRUE.equals(isAnySetterProp); // [databind#562] Since 2.18 + _isAnySetterProp = isAnySetterProp; // [databind#562] Since 2.18 } /** @@ -111,7 +111,8 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper int index, JacksonInject.Value injectable, PropertyMetadata metadata) { - this(name, type, wrapperName, typeDeser, contextAnnotations, param, index, injectable, metadata, null); + this(name, type, wrapperName, typeDeser, contextAnnotations, param, index, injectable, + metadata, false); } /** @@ -154,7 +155,7 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, PropertyMetadata metadata, - Boolean isAnySetter) + boolean isAnySetter) { return new CreatorProperty(name, type, wrapperName, typeDeser, contextAnnotations, param, index, injectable, metadata, isAnySetter); @@ -172,7 +173,7 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper PropertyMetadata metadata) { return new CreatorProperty(name, type, wrapperName, typeDeser, contextAnnotations, - param, index, injectable, metadata, null); + param, index, injectable, metadata, false); } /** diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java index 139a0d0948..09794528d3 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java @@ -3,15 +3,17 @@ import java.util.HashMap; import java.util.Map; -import org.junit.jupiter.api.Test; - import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; +import org.junit.jupiter.api.Test; + import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; class AnySetterForCreator562Test extends DatabindTestUtil { @@ -31,6 +33,17 @@ public POJO562(@JsonProperty("a") String a, } } + // [databind#562]: failing cacse + static class MultipleAny562 + { + @JsonCreator + public MultipleAny562(@JsonProperty("a") String a, + @JsonAnySetter Map leftovers, + @JsonAnySetter Map leftovers2) { + throw new Error("Should never get here!"); + } + } + private final ObjectMapper MAPPER = newJsonMapper(); // [databind#562] @@ -48,4 +61,17 @@ void anySetterViaCreator562() throws Exception assertEquals("value", pojo.a); assertEquals(expected, pojo.stuff); } + + // [databind#562] + @Test + public void testAnySetterViaCreator562FailForDup() throws Exception + { + try { + MAPPER.readValue("{}", MultipleAny562.class); + fail("Should not pass"); + } catch (InvalidDefinitionException e) { + verifyException(e, "Invalid type definition"); + verifyException(e, "More than one 'any-setter'"); + } + } } From f6a6c37816cf4cc0330ab7bca85c0caef7193a9c Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 10 Feb 2024 10:32:38 +0900 Subject: [PATCH 50/60] Test case for disabled(@JsonAnySetter) (rain check? --- .../creators/AnySetterForCreator562Test.java | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java index 09794528d3..12455e8b00 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java @@ -33,7 +33,7 @@ public POJO562(@JsonProperty("a") String a, } } - // [databind#562]: failing cacse + // [databind#562]: failing case static class MultipleAny562 { @JsonCreator @@ -44,6 +44,22 @@ public MultipleAny562(@JsonProperty("a") String a, } } + // [databind#562] + static class PojoWithDisabled + { + String a; + + Map stuff; + + @JsonCreator + public PojoWithDisabled(@JsonProperty("a") String a, + @JsonAnySetter(enabled = false) Map leftovers + ) { + this.a = a; + stuff = leftovers; + } + } + private final ObjectMapper MAPPER = newJsonMapper(); // [databind#562] @@ -74,4 +90,18 @@ public void testAnySetterViaCreator562FailForDup() throws Exception verifyException(e, "More than one 'any-setter'"); } } + + // [databind#562] + @Test + public void testAnySetterViaCreator562Disabled() throws Exception + { + try { + MAPPER.readValue(a2q("{'a':'value', 'b':42, 'c': 111}"), + PojoWithDisabled.class); + fail(); + } catch (InvalidDefinitionException e) { + verifyException(e, "Invalid type definition for type"); + verifyException(e, "has no property name (and is not Injectable): can not use as property-based Creator"); + } + } } From 93b85875fe04e51e907f9a4ed94b77490aa773e3 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 10 Feb 2024 10:32:56 +0900 Subject: [PATCH 51/60] Require non-null _valueInstantiator during construction --- .../jackson/databind/deser/SettableAnyProperty.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java index 0cb94f5d04..a103246ee2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.*; @@ -477,7 +478,7 @@ public MapParameterAnyProperty(BeanProperty property, ValueInstantiator inst) { super(property, field, valueType, keyDeser, valueDeser, typeDeser); - _valueInstantiator = inst; + _valueInstantiator = Objects.requireNonNull(inst, "ValueInstantiator for MapParameterAnyProperty cannot be `null`"); } @Override @@ -496,12 +497,6 @@ protected void _set(Object instance, Object propName, Object value) throws Excep protected Map initMap(DeserializationContext ctxt) throws IOException { - // This really should have been caught earlier - if (_valueInstantiator == null) { - throw JsonMappingException.from(ctxt, String.format( - "Cannot create an instance of %s for use as \"any-setter\" '%s'", - ClassUtil.nameOf(_type.getRawClass()), _property.getName())); - } return (Map) _valueInstantiator.createUsingDefault(ctxt); } } From 134d8d1711ab6356f686af5beec99fa229806bf2 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 10 Feb 2024 10:51:39 +0900 Subject: [PATCH 52/60] Add test with DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES --- .../creators/AnySetterForCreator562Test.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java index 12455e8b00..c1d4ec019d 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java @@ -78,6 +78,24 @@ void anySetterViaCreator562() throws Exception assertEquals(expected, pojo.stuff); } + // [databind#562] + @Test + public void testNoAnySetterContentTest562() throws Exception + { + ObjectMapper mapper = jsonMapperBuilder() + .enable(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES) + .build(); + + Map expected = new HashMap<>(); + expected.put("b", Integer.valueOf(42)); + expected.put("c", Integer.valueOf(111)); + + POJO562 pojo = mapper.readValue(a2q("{'a':'value'}"), POJO562.class); + + assertEquals("value", pojo.a); + assertEquals(expected, pojo.stuff); + } + // [databind#562] @Test public void testAnySetterViaCreator562FailForDup() throws Exception From 7c609054ceb127bbaf64dfd7a100c8ac47143aad Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 10 Feb 2024 11:02:03 +0900 Subject: [PATCH 53/60] Fix tests --- .../creators/AnySetterForCreator562Test.java | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java index c1d4ec019d..af44079dbe 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; import org.junit.jupiter.api.Test; @@ -80,20 +81,37 @@ void anySetterViaCreator562() throws Exception // [databind#562] @Test - public void testNoAnySetterContentTest562() throws Exception + public void testWithFailureConfigs562() throws Exception { - ObjectMapper mapper = jsonMapperBuilder() - .enable(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES) - .build(); + ObjectMapper failOnNullMapper = jsonMapperBuilder() + .enable(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES).build(); - Map expected = new HashMap<>(); - expected.put("b", Integer.valueOf(42)); - expected.put("c", Integer.valueOf(111)); + try { + failOnNullMapper.readValue(a2q("{'a':'value'}"), POJO562.class); + fail("Should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, "Null value for creator property"); + } - POJO562 pojo = mapper.readValue(a2q("{'a':'value'}"), POJO562.class); + ObjectMapper failOnMissingMapper = jsonMapperBuilder() + .enable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES).build(); + try { + failOnMissingMapper.readValue(a2q("{'a':'value'}"), POJO562.class); + fail("Should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, "Missing creator property"); + } - assertEquals("value", pojo.a); - assertEquals(expected, pojo.stuff); + ObjectMapper failOnBothMapper = jsonMapperBuilder() + .enable(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES) + .enable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES) + .build(); + try { + failOnBothMapper.readValue(a2q("{'a':'value'}"), POJO562.class); + fail("Should not pass"); + } catch (MismatchedInputException e) { + verifyException(e, "Missing creator property"); + } } // [databind#562] From 3e62a2bb6cf5ed821e22700b9e3c25ee58a89dae Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 10 Feb 2024 11:02:38 +0900 Subject: [PATCH 54/60] Update AnySetterForCreator562Test.java --- .../databind/deser/creators/AnySetterForCreator562Test.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java index af44079dbe..c73a66dfa2 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java @@ -90,7 +90,7 @@ public void testWithFailureConfigs562() throws Exception failOnNullMapper.readValue(a2q("{'a':'value'}"), POJO562.class); fail("Should not pass"); } catch (MismatchedInputException e) { - verifyException(e, "Null value for creator property"); + verifyException(e, "Null value for creator property ''"); } ObjectMapper failOnMissingMapper = jsonMapperBuilder() @@ -99,7 +99,7 @@ public void testWithFailureConfigs562() throws Exception failOnMissingMapper.readValue(a2q("{'a':'value'}"), POJO562.class); fail("Should not pass"); } catch (MismatchedInputException e) { - verifyException(e, "Missing creator property"); + verifyException(e, "Missing creator property ''"); } ObjectMapper failOnBothMapper = jsonMapperBuilder() @@ -110,7 +110,7 @@ public void testWithFailureConfigs562() throws Exception failOnBothMapper.readValue(a2q("{'a':'value'}"), POJO562.class); fail("Should not pass"); } catch (MismatchedInputException e) { - verifyException(e, "Missing creator property"); + verifyException(e, "Missing creator property ''"); } } From 60a2933b3d5d4cc78ea2ff82a9d4b95868375f7d Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 10 Feb 2024 14:41:07 +0900 Subject: [PATCH 55/60] Implement version 2 Add quick check before iteration start --- .../deser/BasicDeserializerFactory.java | 2 +- .../databind/deser/BeanDeserializer.java | 13 +- .../databind/deser/CreatorProperty.java | 10 +- .../databind/deser/SettableAnyProperty.java | 4 +- .../databind/deser/ValueInstantiator.java | 7 -- .../deser/impl/PropertyBasedCreator.java | 31 ++++- .../deser/impl/PropertyValueBuffer.java | 116 ++++++++++-------- 7 files changed, 102 insertions(+), 81 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 d5d5c49b1c..b8c1d0a563 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -636,7 +636,7 @@ private void _reportUnwrappedCreatorProperty(DeserializationContext ctxt, * a logical property passed via Creator (constructor or static * factory method) * - * @since 2.18 + * @since 2.19 */ protected SettableBeanProperty constructCreatorProperty(DeserializationContext ctxt, BeanDescription beanDesc, PropertyName name, int index, diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index 9cb9d12a64..071a9d6069 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -415,7 +415,9 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri throws IOException { final PropertyBasedCreator creator = _propertyBasedCreator; - PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, _objectIdReader); + PropertyValueBuffer buffer = (_anySetter != null) + ? creator.startBuildingWithAnySetter(p, ctxt, _objectIdReader, _anySetter) + : creator.startBuilding(p, ctxt, _objectIdReader); TokenBuffer unknown = null; final Class activeView = _needViewProcesing ? ctxt.getActiveView() : null; @@ -495,9 +497,10 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri continue; } // "any property"? + if (_anySetter != null) { try { - buffer.bufferMapProperty(propName, _anySetter.deserialize(p, ctxt)); + buffer.bufferWithAnySetter(ctxt, p, creator, propName); } catch (Exception e) { wrapAndThrow(e, _beanType.getRawClass(), propName, ctxt); } @@ -523,11 +526,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri // We hit END_OBJECT, so: Object bean; try { - if (_anySetter != null) { - bean = creator.buildWithAnySetter(ctxt, buffer, _anySetter); - } else { - bean = creator.build(ctxt, buffer); - } + bean = creator.build(ctxt, buffer); } catch (Exception e) { return wrapInstantiationProblem(e, ctxt); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 0b73d6a2fb..a7070c6fdc 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -79,12 +79,12 @@ public class CreatorProperty /** * Marker flag to indicate that current property is used to handle "any setter" via `@JsonAnySetter`. * - * @since 2.18 + * @since 2.19 */ protected final boolean _isAnySetterProp; /** - * @since 2.18 + * @since 2.19 */ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperName, TypeDeserializer typeDeser, @@ -148,7 +148,7 @@ public CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperNam * @param index Index of this property within creator invocation * @param isAnySetter Marker flag to indicate that current property is used to handle "any setter" via `@JsonAnySetter`. * - * @since 2.18 + * @since 2.19 */ public static CreatorProperty construct(PropertyName name, JavaType type, PropertyName wrapperName, TypeDeserializer typeDeser, @@ -364,7 +364,7 @@ public boolean isInjectionOnly() { // public boolean isInjectionOnly() { return false; } /** - * @since 2.18 + * @since 2.19 */ public boolean isAnySetterProp() { return _isAnySetterProp; @@ -406,7 +406,7 @@ private void _reportMissingSetter(JsonParser p, DeserializationContext ctxt) thr } /** - * @since 2.18 + * @since 2.19 */ public Map initMap(DeserializationContext context, SettableAnyProperty anySetter) throws IOException diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java index a103246ee2..f639881fd1 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java @@ -119,7 +119,7 @@ public static SettableAnyProperty constructForJsonNodeField(DeserializationConte } /** - * @since 2.18 + * @since 2.19 */ public static SettableAnyProperty constructForMapParameter(DeserializationContext ctxt, BeanProperty property, @@ -462,7 +462,7 @@ public SettableAnyProperty withValueDeserializer(JsonDeserializer deser) } /** - * @since 2.18 + * @since 2.19 */ protected static class MapParameterAnyProperty extends SettableAnyProperty implements java.io.Serializable diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java b/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java index ffd17a77ee..648bdcc3d3 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java @@ -301,13 +301,6 @@ public Object createFromObjectWith(DeserializationContext ctxt, return createFromObjectWith(ctxt, buffer.getParameters(props)); } - public Object createFromObjectWith(DeserializationContext ctxt, - SettableBeanProperty[] props, PropertyValueBuffer buffer, SettableAnyProperty anySetter) - throws IOException - { - return createFromObjectWith(ctxt, buffer.getParametersWithAnySetter(props, anySetter)); - } - /** * Method to called to create value instance from JSON Object using * an intermediate "delegate" value to pass to createor method diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java index 544db0f544..132958d387 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.deser.CreatorProperty; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; import com.fasterxml.jackson.databind.deser.ValueInstantiator; @@ -188,6 +189,16 @@ public SettableBeanProperty findCreatorProperty(int propertyIndex) { /********************************************************** */ + /** + * Method called when starting to build a bean instance. + * + * @since 2.19 (added SettableAnyProperty parameter) + */ + public PropertyValueBuffer startBuildingWithAnySetter(JsonParser p, DeserializationContext ctxt, + ObjectIdReader oir, SettableAnyProperty anySetter) { + return new PropertyValueBuffer(p, ctxt, _propertyCount, oir, anySetter); + } + /** * Method called when starting to build a bean instance. * @@ -198,12 +209,6 @@ public PropertyValueBuffer startBuilding(JsonParser p, DeserializationContext ct return new PropertyValueBuffer(p, ctxt, _propertyCount, oir); } - public Object buildWithAnySetter(DeserializationContext ctxt, PropertyValueBuffer buffer, SettableAnyProperty anySetter) throws IOException - { - return _valueInstantiator.createFromObjectWith(ctxt, - _allProperties, buffer, anySetter); - } - public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) throws IOException { Object bean = _valueInstantiator.createFromObjectWith(ctxt, @@ -221,6 +226,20 @@ public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) thr return bean; } + public CreatorProperty findAnySetterProp() { + for (SettableBeanProperty prop : _allProperties) { + if (!(prop instanceof CreatorProperty)) { + continue; + } + CreatorProperty cp = (CreatorProperty) prop; + if (!cp.isAnySetterProp()) { + continue; + } + return cp; + } + return null; + } + /* /********************************************************** /* Helper classes 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 247048691f..3a30b07329 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 @@ -5,7 +5,6 @@ import java.util.Map; import com.fasterxml.jackson.core.JsonParser; - import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.CreatorProperty; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; @@ -19,8 +18,7 @@ * and hence need buffering before instance (that will have properties * to assign values to) is constructed. */ -public class PropertyValueBuffer -{ +public class PropertyValueBuffer { /* /********************************************************** /* Configuration @@ -77,6 +75,20 @@ public class PropertyValueBuffer */ protected Object _idValue; + /** + * [databind#562]: Allow use of `@JsonAnySetter` in `@JsonCreator` + * + * @since 2.19 + */ + protected SettableAnyProperty _anySetter; + + /** + * [databind#562]: Allow use of `@JsonAnySetter` in `@JsonCreator` + * + * @since 2.19 + */ + private Map _anySetterMap; + /* /********************************************************** /* Life-cycle @@ -84,8 +96,7 @@ public class PropertyValueBuffer */ public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramCount, - ObjectIdReader oir) - { + ObjectIdReader oir, SettableAnyProperty anySetter) { _parser = p; _context = ctxt; _paramsNeeded = paramCount; @@ -96,6 +107,12 @@ public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramC } else { _paramsSeenBig = new BitSet(); } + _anySetter = anySetter; + } + + public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramCount, + ObjectIdReader oir) { + this(p, ctxt, paramCount, oir, null); } /** @@ -104,8 +121,7 @@ public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramC * * @since 2.8 */ - public final boolean hasParameter(SettableBeanProperty prop) - { + public final boolean hasParameter(SettableBeanProperty prop) { if (_paramsSeenBig == null) { return ((_paramsSeen >> prop.getCreatorIndex()) & 1) == 1; } @@ -123,8 +139,7 @@ public final boolean hasParameter(SettableBeanProperty prop) * @since 2.8 */ public Object getParameter(SettableBeanProperty prop) - throws JsonMappingException - { + throws JsonMappingException { Object value; if (hasParameter(prop)) { value = _creatorParameters[prop.getCreatorIndex()]; @@ -147,8 +162,7 @@ public Object getParameter(SettableBeanProperty prop) * then whole JSON Object has been processed, */ public Object[] getParameters(SettableBeanProperty[] props) - throws JsonMappingException - { + throws JsonMappingException { final Object[] creatorParams = _creatorParameters; // quick check to see if anything else is needed if (_paramsNeeded > 0) { @@ -174,60 +188,39 @@ public Object[] getParameters(SettableBeanProperty[] props) if (creatorParams[ix] == null) { SettableBeanProperty prop = props[ix]; _context.reportInputMismatch(prop, - "Null value for creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES` enabled", - prop.getName(), props[ix].getCreatorIndex()); + "Null value for creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES` enabled", + prop.getName(), props[ix].getCreatorIndex()); } } } - return creatorParams; - } - - public Object[] getParametersWithAnySetter(SettableBeanProperty[] props, SettableAnyProperty anySetter) - throws JsonMappingException - { - final Object[] creatorParams = getParameters(props); // [databind#562] Since 2.18 : Respect @JsonAnySetter in @JsonCreator - for (int i = 0; i < creatorParams.length; i++) { - if (!(props[i] instanceof CreatorProperty)) { - continue; - } - CreatorProperty cp = (CreatorProperty) props[i]; - if (!cp.isAnySetterProp()) { - continue; - } - Object param = null; - try { - Map map = cp.initMap(_context, anySetter); - for (PropertyValue next = buffered(); next != null; next = next.next) { - next.assign(map); + if (_anySetter != null) { + for (int i = 0; i < creatorParams.length; i++) { + if (props[i].getMember() == _anySetter.getProperty().getMember()) { + creatorParams[i] = _anySetterMap; + break; } - param = map; - } catch (IOException e) { - _context.reportInputMismatch(cp, e.getMessage()); } - creatorParams[i] = param; - break; } return creatorParams; } - protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingException - { + protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingException { // First: do we have injectable value? Object injectableValueId = prop.getInjectableValueId(); if (injectableValueId != null) { return _context.findInjectableValue(prop.getInjectableValueId(), - prop, null); + prop, null); } // Second: required? if (prop.isRequired()) { _context.reportInputMismatch(prop, "Missing required creator property '%s' (index %d)", - prop.getName(), prop.getCreatorIndex()); + prop.getName(), prop.getCreatorIndex()); } if (_context.isEnabled(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES)) { _context.reportInputMismatch(prop, - "Missing creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES` enabled", - prop.getName(), prop.getCreatorIndex()); + "Missing creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES` enabled", + prop.getName(), prop.getCreatorIndex()); } try { // Third: NullValueProvider? (22-Sep-2019, [databind#2458]) @@ -262,8 +255,7 @@ protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingExcep * * @since 2.1 */ - public boolean readIdProperty(String propName) throws IOException - { + public boolean readIdProperty(String propName) throws IOException { if ((_objectIdReader != null) && propName.equals(_objectIdReader.propertyName.getSimpleName())) { _idValue = _objectIdReader.readObjectReference(_parser, _context); return true; @@ -274,8 +266,7 @@ public boolean readIdProperty(String propName) throws IOException /** * Helper method called to handle Object Id value collected earlier, if any */ - public Object handleIdValue(final DeserializationContext ctxt, Object bean) throws IOException - { + public Object handleIdValue(final DeserializationContext ctxt, Object bean) throws IOException { if (_objectIdReader != null) { if (_idValue != null) { ReadableObjectId roid = ctxt.findObjectId(_idValue, _objectIdReader.generator, _objectIdReader.resolver); @@ -293,20 +284,22 @@ public Object handleIdValue(final DeserializationContext ctxt, Object bean) thro return bean; } - protected PropertyValue buffered() { return _buffered; } + protected PropertyValue buffered() { + return _buffered; + } - public boolean isComplete() { return _paramsNeeded <= 0; } + public boolean isComplete() { + return _paramsNeeded <= 0; + } /** * Method called to buffer value for given property, as well as check whether * we now have values for all (creator) properties that we expect to get values for. * * @return True if we have received all creator parameters - * * @since 2.6 */ - public boolean assignParameter(SettableBeanProperty prop, Object value) - { + public boolean assignParameter(SettableBeanProperty prop, Object value) { final int ix = prop.getCreatorIndex(); _creatorParameters[ix] = value; if (_paramsSeenBig == null) { @@ -341,4 +334,21 @@ public void bufferAnyProperty(SettableAnyProperty prop, String propName, Object public void bufferMapProperty(Object key, Object value) { _buffered = new PropertyValue.Map(_buffered, value, key); } + + /** + * @since 2.19 + */ + public void bufferWithAnySetter(DeserializationContext ctxt, JsonParser p, PropertyBasedCreator creator, String propName) + throws IOException { + // Only called once, to initialize map + if (_anySetterMap == null) { + CreatorProperty cp = creator.findAnySetterProp(); + try { + _anySetterMap = cp.initMap(_context, _anySetter); + } catch (IOException e) { + _context.reportInputMismatch(cp, e.getMessage()); + } + } + _anySetterMap.put(propName, _anySetter.deserialize(p, ctxt)); + } } From 40061f9c439338df4cdba5df029e470a763eb102 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 10 Feb 2024 15:13:53 +0900 Subject: [PATCH 56/60] Clean up changes --- .../deser/BasicDeserializerFactory.java | 5 +- .../databind/deser/BeanDeserializer.java | 3 +- .../databind/deser/CreatorProperty.java | 21 +++--- .../deser/impl/PropertyBasedCreator.java | 4 +- .../deser/impl/PropertyValueBuffer.java | 68 ++++++++++++------- 5 files changed, 56 insertions(+), 45 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 b8c1d0a563..b8c14e7736 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -528,8 +528,7 @@ private void _addExplicitPropertyCreator(DeserializationContext ctxt, _reportUnwrappedCreatorProperty(ctxt, beanDesc, param); } name = candidate.findImplicitParamName(i); - // [databind#562] Allow @JsonAnySetter in creators. Introspection is done here only because of - // _validateNamedPropertyParameter() check. Otherwise, in constructCreatorProperty seems appropriate.... + // [databind#562] Allow @JsonAnySetter in creators if (Boolean.TRUE.equals(ctxt.getAnnotationIntrospector().hasAnySetter(param))) { if (anySetterIndex >= 0) { ctxt.reportBadTypeDefinition(beanDesc, @@ -694,7 +693,7 @@ protected SettableBeanProperty constructCreatorProperty(DeserializationContext c } /** - * @deprecated Since 2.18, use {@link #constructCreatorProperty(DeserializationContext, BeanDescription, + * @deprecated since 2.19, use {@link #constructCreatorProperty(DeserializationContext, BeanDescription, * PropertyName, int, AnnotatedParameter, JacksonInject.Value, boolean)} instead. */ @Deprecated diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index 071a9d6069..b449bc19b6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -500,7 +500,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri if (_anySetter != null) { try { - buffer.bufferWithAnySetter(ctxt, p, creator, propName); + buffer.bufferAnySetter(ctxt, p, creator, propName); } catch (Exception e) { wrapAndThrow(e, _beanType.getRawClass(), propName, ctxt); } @@ -530,7 +530,6 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri } catch (Exception e) { return wrapInstantiationProblem(e, ctxt); } - // 13-Apr-2020, tatu: [databind#2678] need to handle injection here if (_injectables != null) { injectValues(ctxt, bean); diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index a7070c6fdc..79a8e2dc2b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -81,7 +81,7 @@ public class CreatorProperty * * @since 2.19 */ - protected final boolean _isAnySetterProp; + protected final boolean _isAnySetter; /** * @since 2.19 @@ -90,19 +90,19 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper TypeDeserializer typeDeser, Annotations contextAnnotations, AnnotatedParameter param, int index, JacksonInject.Value injectable, - PropertyMetadata metadata, boolean isAnySetterProp) + PropertyMetadata metadata, boolean isAnySetter) { super(name, type, wrapperName, typeDeser, contextAnnotations, metadata); _annotated = param; _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; - _isAnySetterProp = isAnySetterProp; // [databind#562] Since 2.18 + _isAnySetter = isAnySetter; // [databind#562] since 2.19 } /** * @since 2.11 - * @deprecated Since 2.18. use factory later version instead. + * @deprecated since 2.19. use factory later version instead. */ @Deprecated protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperName, @@ -163,7 +163,7 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper /** * @since 2.11 - * @deprecated Since 2.18. use later version instead. + * @deprecated since 2.19. use later version instead. */ @Deprecated public static CreatorProperty construct(PropertyName name, JavaType type, PropertyName wrapperName, @@ -186,7 +186,7 @@ protected CreatorProperty(CreatorProperty src, PropertyName newName) { _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _isAnySetterProp = src._isAnySetterProp; // [databind#562] Since 2.18 + _isAnySetter = src._isAnySetter; // [databind#562] since 2.19 } protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, @@ -197,7 +197,7 @@ protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _isAnySetterProp = src._isAnySetterProp; // [databind#562] Since 2.18 + _isAnySetter = src._isAnySetter; // [databind#562] since 2.19 } @Override @@ -366,8 +366,8 @@ public boolean isInjectionOnly() { /** * @since 2.19 */ - public boolean isAnySetterProp() { - return _isAnySetterProp; + public boolean isAnySetter() { + return _isAnySetter; } /* @@ -411,9 +411,6 @@ private void _reportMissingSetter(JsonParser p, DeserializationContext ctxt) thr public Map initMap(DeserializationContext context, SettableAnyProperty anySetter) throws IOException { - if (!isAnySetterProp()) { - throw new IllegalStateException("Cannot create Map for non-AnySetter creator property"); - } return ((SettableAnyProperty.MapParameterAnyProperty) anySetter).initMap(context); } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java index 132958d387..cd6121c9dc 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java @@ -226,13 +226,13 @@ public Object build(DeserializationContext ctxt, PropertyValueBuffer buffer) thr return bean; } - public CreatorProperty findAnySetterProp() { + public CreatorProperty findAnySetterProperty() { for (SettableBeanProperty prop : _allProperties) { if (!(prop instanceof CreatorProperty)) { continue; } CreatorProperty cp = (CreatorProperty) prop; - if (!cp.isAnySetterProp()) { + if (!cp.isAnySetter()) { continue; } return cp; 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 3a30b07329..5e99a02b62 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 @@ -5,6 +5,7 @@ import java.util.Map; import com.fasterxml.jackson.core.JsonParser; + import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.CreatorProperty; import com.fasterxml.jackson.databind.deser.SettableAnyProperty; @@ -18,7 +19,8 @@ * and hence need buffering before instance (that will have properties * to assign values to) is constructed. */ -public class PropertyValueBuffer { +public class PropertyValueBuffer +{ /* /********************************************************** /* Configuration @@ -80,7 +82,7 @@ public class PropertyValueBuffer { * * @since 2.19 */ - protected SettableAnyProperty _anySetter; + protected final SettableAnyProperty _anySetter; /** * [databind#562]: Allow use of `@JsonAnySetter` in `@JsonCreator` @@ -96,7 +98,8 @@ public class PropertyValueBuffer { */ public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramCount, - ObjectIdReader oir, SettableAnyProperty anySetter) { + ObjectIdReader oir, SettableAnyProperty anySetter) + { _parser = p; _context = ctxt; _paramsNeeded = paramCount; @@ -110,8 +113,13 @@ public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramC _anySetter = anySetter; } + /** + * + * @deprecated Since 2.19, use later version instead. + */ public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramCount, - ObjectIdReader oir) { + ObjectIdReader oir) + { this(p, ctxt, paramCount, oir, null); } @@ -121,7 +129,8 @@ public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramC * * @since 2.8 */ - public final boolean hasParameter(SettableBeanProperty prop) { + public final boolean hasParameter(SettableBeanProperty prop) + { if (_paramsSeenBig == null) { return ((_paramsSeen >> prop.getCreatorIndex()) & 1) == 1; } @@ -139,7 +148,8 @@ public final boolean hasParameter(SettableBeanProperty prop) { * @since 2.8 */ public Object getParameter(SettableBeanProperty prop) - throws JsonMappingException { + throws JsonMappingException + { Object value; if (hasParameter(prop)) { value = _creatorParameters[prop.getCreatorIndex()]; @@ -162,7 +172,8 @@ public Object getParameter(SettableBeanProperty prop) * then whole JSON Object has been processed, */ public Object[] getParameters(SettableBeanProperty[] props) - throws JsonMappingException { + throws JsonMappingException + { final Object[] creatorParams = _creatorParameters; // quick check to see if anything else is needed if (_paramsNeeded > 0) { @@ -188,12 +199,12 @@ public Object[] getParameters(SettableBeanProperty[] props) if (creatorParams[ix] == null) { SettableBeanProperty prop = props[ix]; _context.reportInputMismatch(prop, - "Null value for creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES` enabled", - prop.getName(), props[ix].getCreatorIndex()); + "Null value for creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES` enabled", + prop.getName(), props[ix].getCreatorIndex()); } } } - // [databind#562] Since 2.18 : Respect @JsonAnySetter in @JsonCreator + // [databind#562] since 2.19 : Respect @JsonAnySetter in @JsonCreator if (_anySetter != null) { for (int i = 0; i < creatorParams.length; i++) { if (props[i].getMember() == _anySetter.getProperty().getMember()) { @@ -205,22 +216,23 @@ public Object[] getParameters(SettableBeanProperty[] props) return creatorParams; } - protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingException { + protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingException + { // First: do we have injectable value? Object injectableValueId = prop.getInjectableValueId(); if (injectableValueId != null) { return _context.findInjectableValue(prop.getInjectableValueId(), - prop, null); + prop, null); } // Second: required? if (prop.isRequired()) { _context.reportInputMismatch(prop, "Missing required creator property '%s' (index %d)", - prop.getName(), prop.getCreatorIndex()); + prop.getName(), prop.getCreatorIndex()); } if (_context.isEnabled(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES)) { _context.reportInputMismatch(prop, - "Missing creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES` enabled", - prop.getName(), prop.getCreatorIndex()); + "Missing creator property '%s' (index %d); `DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES` enabled", + prop.getName(), prop.getCreatorIndex()); } try { // Third: NullValueProvider? (22-Sep-2019, [databind#2458]) @@ -255,7 +267,8 @@ protected Object _findMissing(SettableBeanProperty prop) throws JsonMappingExcep * * @since 2.1 */ - public boolean readIdProperty(String propName) throws IOException { + public boolean readIdProperty(String propName) throws IOException + { if ((_objectIdReader != null) && propName.equals(_objectIdReader.propertyName.getSimpleName())) { _idValue = _objectIdReader.readObjectReference(_parser, _context); return true; @@ -266,7 +279,8 @@ public boolean readIdProperty(String propName) throws IOException { /** * Helper method called to handle Object Id value collected earlier, if any */ - public Object handleIdValue(final DeserializationContext ctxt, Object bean) throws IOException { + public Object handleIdValue(final DeserializationContext ctxt, Object bean) throws IOException + { if (_objectIdReader != null) { if (_idValue != null) { ReadableObjectId roid = ctxt.findObjectId(_idValue, _objectIdReader.generator, _objectIdReader.resolver); @@ -284,22 +298,20 @@ public Object handleIdValue(final DeserializationContext ctxt, Object bean) thro return bean; } - protected PropertyValue buffered() { - return _buffered; - } + protected PropertyValue buffered() { return _buffered; } - public boolean isComplete() { - return _paramsNeeded <= 0; - } + public boolean isComplete() { return _paramsNeeded <= 0; } /** * Method called to buffer value for given property, as well as check whether * we now have values for all (creator) properties that we expect to get values for. * * @return True if we have received all creator parameters + * * @since 2.6 */ - public boolean assignParameter(SettableBeanProperty prop, Object value) { + public boolean assignParameter(SettableBeanProperty prop, Object value) + { final int ix = prop.getCreatorIndex(); _creatorParameters[ix] = value; if (_paramsSeenBig == null) { @@ -338,11 +350,15 @@ public void bufferMapProperty(Object key, Object value) { /** * @since 2.19 */ - public void bufferWithAnySetter(DeserializationContext ctxt, JsonParser p, PropertyBasedCreator creator, String propName) + public void bufferAnySetter(DeserializationContext ctxt, JsonParser p, PropertyBasedCreator creator, String propName) throws IOException { // Only called once, to initialize map if (_anySetterMap == null) { - CreatorProperty cp = creator.findAnySetterProp(); + CreatorProperty cp = creator.findAnySetterProperty(); + if (cp == null) { + ctxt.reportBadDefinition(creator.getClass(), + "Invalid configuration: no creator property with 'any-setter' annotation found"); + } try { _anySetterMap = cp.initMap(_context, _anySetter); } catch (IOException e) { From cd86a749288765f44000b538a56d760637d437c8 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 31 May 2024 12:58:17 +0900 Subject: [PATCH 57/60] Add missing imports --- .../databind/deser/creators/AnySetterForCreator562Test.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java index c73a66dfa2..81ee42950c 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; import com.fasterxml.jackson.databind.exc.MismatchedInputException; From 2de9bebf420149e385851a1793dcfce9e7558226 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Fri, 31 May 2024 21:30:53 +0900 Subject: [PATCH 58/60] Clean up code, version, etc... --- .../deser/BasicDeserializerFactory.java | 33 +++---------------- .../databind/deser/BeanDeserializer.java | 1 - .../deser/BeanDeserializerFactory.java | 19 ++++++----- .../databind/deser/CreatorProperty.java | 22 ++++++------- .../databind/deser/SettableAnyProperty.java | 31 +++++++++-------- .../deser/impl/PropertyBasedCreator.java | 2 +- .../deser/impl/PropertyValueBuffer.java | 14 ++++---- 7 files changed, 51 insertions(+), 71 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 24f1811409..5474c0ebe0 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -401,7 +401,7 @@ private void _addImplicitDelegatingConstructors(DeserializationContext ctxt, if (injectable != null) { ++injectCount; - properties[i] = constructCreatorProperty(ctxt, beanDesc, null, i, param, injectable); + properties[i] = constructCreatorProperty(ctxt, beanDesc, null, i, param, injectable, false); continue; } NameTransformer unwrapper = intr.findUnwrappingNameTransformer(param); @@ -411,7 +411,6 @@ private void _addImplicitDelegatingConstructors(DeserializationContext ctxt, properties[i] = constructCreatorProperty(ctxt, beanDesc, UNWRAPPED_CREATOR_PARAM_NAME, i, param, null); ++explicitNameCount; */ - continue; } } @@ -473,7 +472,7 @@ private boolean _addExplicitDelegatingCreator(DeserializationContext ctxt, AnnotatedParameter param = candidate.parameter(i); JacksonInject.Value injectId = candidate.injection(i); if (injectId != null) { - properties[i] = constructCreatorProperty(ctxt, beanDesc, null, i, param, injectId); + properties[i] = constructCreatorProperty(ctxt, beanDesc, null, i, param, injectId, false); continue; } if (ix < 0) { @@ -518,7 +517,6 @@ private void _addExplicitPropertyCreator(DeserializationContext ctxt, JacksonInject.Value injectId = candidate.injection(i); AnnotatedParameter param = candidate.parameter(i); PropertyName name = candidate.paramName(i); - if (name == null) { // 21-Sep-2017, tatu: Looks like we want to block accidental use of Unwrapped, // as that will not work with Creators well at all @@ -534,7 +532,6 @@ private void _addExplicitPropertyCreator(DeserializationContext ctxt, anySetterIndex, i); } anySetterIndex = i; - // no-op } else { // Must be injectable or have name; without either won't work if ((name == null) && (injectId == null)) { @@ -619,7 +616,7 @@ private void _reportUnwrappedCreatorProperty(DeserializationContext ctxt, * a logical property passed via Creator (constructor or static * factory method) * - * @since 2.19 + * @since 2.18 */ protected SettableBeanProperty constructCreatorProperty(DeserializationContext ctxt, BeanDescription beanDesc, PropertyName name, int index, @@ -677,7 +674,7 @@ protected SettableBeanProperty constructCreatorProperty(DeserializationContext c } /** - * @deprecated since 2.19, use {@link #constructCreatorProperty(DeserializationContext, BeanDescription, + * @deprecated since 2.18, use {@link #constructCreatorProperty(DeserializationContext, BeanDescription, * PropertyName, int, AnnotatedParameter, JacksonInject.Value, boolean)} instead. */ @Deprecated @@ -690,28 +687,6 @@ protected SettableBeanProperty constructCreatorProperty(DeserializationContext c return constructCreatorProperty(ctxt, beanDesc, name, index, param, injectable, false); } - private PropertyName _findParamName(AnnotatedParameter param, AnnotationIntrospector intr) - { - if (intr != null) { - PropertyName name = intr.findNameForDeserialization(param); - if (name != null) { - // 16-Nov-2020, tatu: One quirk, wrt [databind#2932]; may get "use implicit" - // marker; should not return that - if (!name.isEmpty()) { - return name; - } - } - // 14-Apr-2014, tatu: Need to also consider possible implicit name - // (for JDK8, or via paranamer) - - String str = intr.findImplicitPropertyName(param); - if (str != null && !str.isEmpty()) { - return PropertyName.construct(str); - } - } - return null; - } - /** * Helper method copied from {@code POJOPropertyBuilder} since that won't be * applied to creator parameters diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index b449bc19b6..0f87d767fa 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -497,7 +497,6 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri continue; } // "any property"? - if (_anySetter != null) { try { buffer.bufferAnySetter(ctxt, p, creator, propName); diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index dc592987f0..b7d825f6eb 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -664,6 +664,7 @@ protected void addBeanProps(DeserializationContext ctxt, } } + // since 2.18 private AnnotatedMember _findCreatorPropWithAnySetter(DeserializationContext ctxt, SettableBeanProperty[] creatorProps) { if (creatorProps != null) { AnnotationIntrospector ai = ctxt.getAnnotationIntrospector(); @@ -838,23 +839,22 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, valueType, null, mutator, PropertyMetadata.STD_OPTIONAL); + // [databind#562] Allow @JsonAnySetter in creators } else if (mutator instanceof AnnotatedParameter){ AnnotatedParameter af = (AnnotatedParameter) mutator; // get the type from the content type of the map object JavaType fieldType = af.getType(); - // 31-Jul-2022, tatu: Not just Maps any more but also JsonNode, so: - if (fieldType.isMapLikeType()) { - fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); - keyType = fieldType.getKeyType(); - valueType = fieldType.getContentType(); - prop = new BeanProperty.Std(PropertyName.NO_NAME, - fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); - isMapParam = true; - } else { + if (!fieldType.isMapLikeType()) { return ctxt.reportBadDefinition(beanDesc.getType(), String.format( "Unsupported type for any-setter: %s -- only support `Map`s, `JsonNode` and `ObjectNode` ", ClassUtil.getTypeDescription(fieldType))); } + fieldType = resolveMemberAndTypeAnnotations(ctxt, mutator, fieldType); + keyType = fieldType.getKeyType(); + valueType = fieldType.getContentType(); + prop = new BeanProperty.Std(PropertyName.NO_NAME, + fieldType, null, mutator, PropertyMetadata.STD_OPTIONAL); + isMapParam = true; } else if (isField) { AnnotatedField af = (AnnotatedField) mutator; // get the type from the content type of the map object @@ -916,6 +916,7 @@ protected SettableAnyProperty constructAnySetter(DeserializationContext ctxt, return SettableAnyProperty.constructForMapField(ctxt, prop, mutator, valueType, keyDeser, deser, typeDeser); } + // [databind#562] Allow @JsonAnySetter in creators if (isMapParam) { return SettableAnyProperty.constructForMapParameter(ctxt, prop, mutator, valueType, keyDeser, deser, typeDeser); diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 79a8e2dc2b..8db4d51b72 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -79,12 +79,12 @@ public class CreatorProperty /** * Marker flag to indicate that current property is used to handle "any setter" via `@JsonAnySetter`. * - * @since 2.19 + * @since 2.18 */ protected final boolean _isAnySetter; /** - * @since 2.19 + * @since 2.18 */ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperName, TypeDeserializer typeDeser, @@ -97,12 +97,12 @@ protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapper _creatorIndex = index; _injectableValue = injectable; _fallbackSetter = null; - _isAnySetter = isAnySetter; // [databind#562] since 2.19 + _isAnySetter = isAnySetter; // [databind#562] since 2.18 } /** * @since 2.11 - * @deprecated since 2.19. use factory later version instead. + * @deprecated since 2.18. use later version instead. */ @Deprecated protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperName, @@ -148,7 +148,7 @@ public CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperNam * @param index Index of this property within creator invocation * @param isAnySetter Marker flag to indicate that current property is used to handle "any setter" via `@JsonAnySetter`. * - * @since 2.19 + * @since 2.18 */ public static CreatorProperty construct(PropertyName name, JavaType type, PropertyName wrapperName, TypeDeserializer typeDeser, @@ -163,7 +163,7 @@ public static CreatorProperty construct(PropertyName name, JavaType type, Proper /** * @since 2.11 - * @deprecated since 2.19. use later version instead. + * @deprecated since 2.18. use later version instead. */ @Deprecated public static CreatorProperty construct(PropertyName name, JavaType type, PropertyName wrapperName, @@ -186,7 +186,7 @@ protected CreatorProperty(CreatorProperty src, PropertyName newName) { _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _isAnySetter = src._isAnySetter; // [databind#562] since 2.19 + _isAnySetter = src._isAnySetter; // [databind#562] since 2.18 } protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, @@ -197,7 +197,7 @@ protected CreatorProperty(CreatorProperty src, JsonDeserializer deser, _fallbackSetter = src._fallbackSetter; _creatorIndex = src._creatorIndex; _ignorable = src._ignorable; - _isAnySetter = src._isAnySetter; // [databind#562] since 2.19 + _isAnySetter = src._isAnySetter; // [databind#562] since 2.18 } @Override @@ -364,7 +364,7 @@ public boolean isInjectionOnly() { // public boolean isInjectionOnly() { return false; } /** - * @since 2.19 + * @since 2.18 */ public boolean isAnySetter() { return _isAnySetter; @@ -406,11 +406,11 @@ private void _reportMissingSetter(JsonParser p, DeserializationContext ctxt) thr } /** - * @since 2.19 + * @since 2.18 */ public Map initMap(DeserializationContext context, SettableAnyProperty anySetter) throws IOException { - return ((SettableAnyProperty.MapParameterAnyProperty) anySetter).initMap(context); + return ((SettableAnyProperty.MapParamAnyProperty) anySetter).initMap(context); } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java index f639881fd1..0779450c88 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java @@ -119,7 +119,7 @@ public static SettableAnyProperty constructForJsonNodeField(DeserializationConte } /** - * @since 2.19 + * @since 2.18 */ public static SettableAnyProperty constructForMapParameter(DeserializationContext ctxt, BeanProperty property, @@ -135,7 +135,7 @@ public static SettableAnyProperty constructForMapParameter(DeserializationContex mapType = LinkedHashMap.class; } ValueInstantiator vi = JDKValueInstantiators.findStdValueInstantiator(ctxt.getConfig(), mapType); - return new MapParameterAnyProperty(property, field, valueType, + return new MapParamAnyProperty(property, field, valueType, keyDeser, valueDeser, typeDeser, vi); } @@ -462,34 +462,39 @@ public SettableAnyProperty withValueDeserializer(JsonDeserializer deser) } /** - * @since 2.19 + * [databind#562] Allow @JsonAnySetter on Creator constructor + * + * @since 2.18 */ - protected static class MapParameterAnyProperty extends SettableAnyProperty + protected static class MapParamAnyProperty extends SettableAnyProperty implements java.io.Serializable { private static final long serialVersionUID = 1L; protected final ValueInstantiator _valueInstantiator; - public MapParameterAnyProperty(BeanProperty property, - AnnotatedMember field, JavaType valueType, - KeyDeserializer keyDeser, - JsonDeserializer valueDeser, TypeDeserializer typeDeser, - ValueInstantiator inst) { + public MapParamAnyProperty(BeanProperty property, + AnnotatedMember field, JavaType valueType, + KeyDeserializer keyDeser, + JsonDeserializer valueDeser, TypeDeserializer typeDeser, + ValueInstantiator inst) + { super(property, field, valueType, - keyDeser, valueDeser, typeDeser); + keyDeser, valueDeser, typeDeser); _valueInstantiator = Objects.requireNonNull(inst, "ValueInstantiator for MapParameterAnyProperty cannot be `null`"); } @Override - public SettableAnyProperty withValueDeserializer(JsonDeserializer deser) { - return new MapParameterAnyProperty(_property, _setter, _type, + public SettableAnyProperty withValueDeserializer(JsonDeserializer deser) + { + return new MapParamAnyProperty(_property, _setter, _type, _keyDeserializer, deser, _valueTypeDeserializer, _valueInstantiator); } @Override - protected void _set(Object instance, Object propName, Object value) throws Exception { + protected void _set(Object instance, Object propName, Object value) throws Exception + { throw new UnsupportedOperationException("Cannot set any properties for constructor parameter of type `Map`"); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java index cd6121c9dc..ffcef5553c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/PropertyBasedCreator.java @@ -192,7 +192,7 @@ public SettableBeanProperty findCreatorProperty(int propertyIndex) { /** * Method called when starting to build a bean instance. * - * @since 2.19 (added SettableAnyProperty parameter) + * @since 2.18 (added SettableAnyProperty parameter) */ public PropertyValueBuffer startBuildingWithAnySetter(JsonParser p, DeserializationContext ctxt, ObjectIdReader oir, SettableAnyProperty anySetter) { 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 5e99a02b62..d6122b2810 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 @@ -80,14 +80,14 @@ public class PropertyValueBuffer /** * [databind#562]: Allow use of `@JsonAnySetter` in `@JsonCreator` * - * @since 2.19 + * @since 2.18 */ protected final SettableAnyProperty _anySetter; /** * [databind#562]: Allow use of `@JsonAnySetter` in `@JsonCreator` * - * @since 2.19 + * @since 2.18 */ private Map _anySetterMap; @@ -115,7 +115,7 @@ public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramC /** * - * @deprecated Since 2.19, use later version instead. + * @deprecated since 2.18, use later version instead. */ public PropertyValueBuffer(JsonParser p, DeserializationContext ctxt, int paramCount, ObjectIdReader oir) @@ -204,12 +204,12 @@ public Object[] getParameters(SettableBeanProperty[] props) } } } - // [databind#562] since 2.19 : Respect @JsonAnySetter in @JsonCreator + // [databind#562] since 2.18 : Respect @JsonAnySetter in @JsonCreator if (_anySetter != null) { for (int i = 0; i < creatorParams.length; i++) { if (props[i].getMember() == _anySetter.getProperty().getMember()) { creatorParams[i] = _anySetterMap; - break; + break; } } } @@ -348,7 +348,7 @@ public void bufferMapProperty(Object key, Object value) { } /** - * @since 2.19 + * @since 2.18 */ public void bufferAnySetter(DeserializationContext ctxt, JsonParser p, PropertyBasedCreator creator, String propName) throws IOException { @@ -357,7 +357,7 @@ public void bufferAnySetter(DeserializationContext ctxt, JsonParser p, PropertyB CreatorProperty cp = creator.findAnySetterProperty(); if (cp == null) { ctxt.reportBadDefinition(creator.getClass(), - "Invalid configuration: no creator property with 'any-setter' annotation found"); + "Invalid configuration: no creator property with 'any-setter' annotation found"); } try { _anySetterMap = cp.initMap(_context, _anySetter); From 2cd2b9db50674eba5571bcead3b1fad3c33bf182 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 1 Jun 2024 10:18:28 +0900 Subject: [PATCH 59/60] Apply review --- .../databind/deser/BasicDeserializerFactory.java | 2 +- .../databind/deser/BeanDeserializerFactory.java | 13 +++++-------- .../jackson/databind/deser/CreatorProperty.java | 2 +- .../jackson/databind/deser/SettableAnyProperty.java | 2 +- 4 files changed, 8 insertions(+), 11 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 eaa0dd317c..bac17ff3d3 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BasicDeserializerFactory.java @@ -539,7 +539,7 @@ private void _addSelectedPropertiesBasedCreator(DeserializationContext ctxt, } } properties[i] = constructCreatorProperty(ctxt, beanDesc, name, i, param, injectId, - anySetterIndex >= 0); + anySetterIndex == i); } creators.addPropertyCreator(candidate.creator(), true, properties); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index b7d825f6eb..6c89fdaa47 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -544,7 +544,7 @@ protected void addBeanProps(DeserializationContext ctxt, // Also, do we have a fallback "any" setter? AnnotatedMember anySetter = beanDesc.findAnySetterAccessor(); - AnnotatedMember creatorPropWithAnySetter = _findCreatorPropWithAnySetter(ctxt, creatorProps); + AnnotatedMember creatorPropWithAnySetter = _findCreatorPropWithAnySetter(creatorProps); if (anySetter != null) { builder.setAnySetter(constructAnySetter(ctxt, beanDesc, anySetter)); } else if (creatorPropWithAnySetter != null) { @@ -561,6 +561,7 @@ protected void addBeanProps(DeserializationContext ctxt, } } } + final boolean useGettersAsSetters = ctxt.isEnabled(MapperFeature.USE_GETTERS_AS_SETTERS) && ctxt.isEnabled(MapperFeature.AUTO_DETECT_GETTERS); @@ -665,15 +666,11 @@ protected void addBeanProps(DeserializationContext ctxt, } // since 2.18 - private AnnotatedMember _findCreatorPropWithAnySetter(DeserializationContext ctxt, SettableBeanProperty[] creatorProps) { + private AnnotatedMember _findCreatorPropWithAnySetter(SettableBeanProperty[] creatorProps) { if (creatorProps != null) { - AnnotationIntrospector ai = ctxt.getAnnotationIntrospector(); for (SettableBeanProperty prop : creatorProps) { - AnnotatedMember m = prop.getMember(); - if (m != null) { - if (Boolean.TRUE.equals(ai.hasAnySetter(m))) { - return prop.getMember(); - } + if (((CreatorProperty) prop).isAnySetter()) { + return prop.getMember(); } } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 8db4d51b72..9c609f6480 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -411,6 +411,6 @@ private void _reportMissingSetter(JsonParser p, DeserializationContext ctxt) thr public Map initMap(DeserializationContext context, SettableAnyProperty anySetter) throws IOException { - return ((SettableAnyProperty.MapParamAnyProperty) anySetter).initMap(context); + return ((SettableAnyProperty.MapParamAnyProperty) anySetter).createAnyPropertyMap(context); } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java index 0779450c88..1c257e759f 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/SettableAnyProperty.java @@ -499,7 +499,7 @@ protected void _set(Object instance, Object propName, Object value) throws Excep } @SuppressWarnings("unchecked") - protected Map initMap(DeserializationContext ctxt) + protected Map createAnyPropertyMap(DeserializationContext ctxt) throws IOException { return (Map) _valueInstantiator.createUsingDefault(ctxt); From b616cb605bcc4e8c5262294eae25a16f97d1c6ce Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 1 Jun 2024 10:28:49 +0900 Subject: [PATCH 60/60] Change initMap() method to createAnyPropertyMap() --- .../jackson/databind/deser/CreatorProperty.java | 2 +- .../databind/deser/impl/PropertyValueBuffer.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java index 9c609f6480..ea79d4caa5 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/CreatorProperty.java @@ -408,7 +408,7 @@ private void _reportMissingSetter(JsonParser p, DeserializationContext ctxt) thr /** * @since 2.18 */ - public Map initMap(DeserializationContext context, SettableAnyProperty anySetter) + public Map createAnyPropertyMap(DeserializationContext context, SettableAnyProperty anySetter) throws IOException { return ((SettableAnyProperty.MapParamAnyProperty) anySetter).createAnyPropertyMap(context); 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 d6122b2810..908eabcea9 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 @@ -89,7 +89,7 @@ public class PropertyValueBuffer * * @since 2.18 */ - private Map _anySetterMap; + private Map _anyPropertyMap; /* /********************************************************** @@ -208,7 +208,7 @@ public Object[] getParameters(SettableBeanProperty[] props) if (_anySetter != null) { for (int i = 0; i < creatorParams.length; i++) { if (props[i].getMember() == _anySetter.getProperty().getMember()) { - creatorParams[i] = _anySetterMap; + creatorParams[i] = _anyPropertyMap; break; } } @@ -353,18 +353,18 @@ public void bufferMapProperty(Object key, Object value) { public void bufferAnySetter(DeserializationContext ctxt, JsonParser p, PropertyBasedCreator creator, String propName) throws IOException { // Only called once, to initialize map - if (_anySetterMap == null) { + if (_anyPropertyMap == null) { CreatorProperty cp = creator.findAnySetterProperty(); if (cp == null) { ctxt.reportBadDefinition(creator.getClass(), "Invalid configuration: no creator property with 'any-setter' annotation found"); } try { - _anySetterMap = cp.initMap(_context, _anySetter); + _anyPropertyMap = cp.createAnyPropertyMap(_context, _anySetter); } catch (IOException e) { _context.reportInputMismatch(cp, e.getMessage()); } } - _anySetterMap.put(propName, _anySetter.deserialize(p, ctxt)); + _anyPropertyMap.put(propName, _anySetter.deserialize(p, ctxt)); } }