From 800f8441509beadb39cb592ff39bbdf875a8f2a4 Mon Sep 17 00:00:00 2001 From: Yih Tsern Date: Wed, 24 Jul 2024 21:59:24 +0800 Subject: [PATCH] Prioritize constructor parameter over field if both are annotated with `@JsonAnySetter`, to fix #4634. --- .../deser/BeanDeserializerFactory.java | 12 +++--- .../creators/AnySetterForCreator562Test.java | 41 ++++++++++++++++++- 2 files changed, 45 insertions(+), 8 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 80d9d492c2..ab02dfee97 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -666,12 +666,7 @@ private SettableAnyProperty _resolveAnySetter(DeserializationContext ctxt, BeanDescription beanDesc, SettableBeanProperty[] creatorProps) throws JsonMappingException { - // Find the regular method/field level any-setter - AnnotatedMember anySetter = beanDesc.findAnySetterAccessor(); - if (anySetter != null) { - return constructAnySetter(ctxt, beanDesc, anySetter); - } - // else look for any-setter via @JsonCreator + // Look for any-setter via @JsonCreator if (creatorProps != null) { for (SettableBeanProperty prop : creatorProps) { AnnotatedMember member = prop.getMember(); @@ -680,6 +675,11 @@ private SettableAnyProperty _resolveAnySetter(DeserializationContext ctxt, } } } + // else find the regular method/field level any-setter + AnnotatedMember anySetter = beanDesc.findAnySetterAccessor(); + if (anySetter != null) { + return constructAnySetter(ctxt, beanDesc, anySetter); + } // not found, that's fine, too return null; } 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 e0ec28ac24..7f13eb9466 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 @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.fail; // [databind#562] Allow @JsonAnySetter on Creator constructors @@ -36,13 +37,29 @@ public POJO562(@JsonProperty("a") String a, } } + static class POJO562WithAnnotationOnBothCtorParamAndField + { + String a; + @JsonAnySetter + Map stuffFromField; + Map stuffFromConstructor; + + @JsonCreator + public POJO562WithAnnotationOnBothCtorParamAndField(@JsonProperty("a") String a, + @JsonAnySetter Map leftovers + ) { + this.a = a; + stuffFromConstructor = leftovers; + } + } + static class POJO562WithField { String a; Map stuff; public String b; - + @JsonCreator public POJO562WithField(@JsonProperty("a") String a, @JsonAnySetter Map leftovers @@ -115,12 +132,32 @@ public void mapAnySetterViaCreator562() throws Exception assertEquals("value", pojo.a); assertEquals(expected, pojo.stuff); - + // Should also initialize any-setter-Map even if no contents pojo = MAPPER.readValue(a2q("{'a':'value2'}"), POJO562.class); assertEquals("value2", pojo.a); assertEquals(new HashMap<>(), pojo.stuff); + } + // [databind#4634] + @Test + public void mapAnySetterViaCreatorWhenBothCreatorAndFieldAreAnnotated() throws Exception + { + Map expected = new HashMap<>(); + expected.put("b", Integer.valueOf(42)); + expected.put("c", Integer.valueOf(111)); + + POJO562WithAnnotationOnBothCtorParamAndField pojo = MAPPER.readValue(a2q( + "{'a':'value', 'b':42, 'c': 111}" + ), + POJO562WithAnnotationOnBothCtorParamAndField.class); + + assertEquals("value", pojo.a); + assertEquals(expected, pojo.stuffFromConstructor); + // In an ideal world, maybe exception should be thrown for annotating both field + constructor parameter, + // but that scenario is possible in this imperfect world e.g. annotating `@JsonAnySetter` on a Record component + // will cause that annotation to be (auto)propagated to both the field & constructor parameter (& accessor method) + assertNull(pojo.stuffFromField); } // Creator and non-Creator props AND any-setter ought to be fine too