Skip to content

Commit 7ae72a5

Browse files
authored
Prioritize constructor parameter over field if both are annotated with @JsonAnySetter, to fix #4634. (#4641)
1 parent 3ed7f45 commit 7ae72a5

File tree

2 files changed

+45
-8
lines changed

2 files changed

+45
-8
lines changed

src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -666,12 +666,7 @@ private SettableAnyProperty _resolveAnySetter(DeserializationContext ctxt,
666666
BeanDescription beanDesc, SettableBeanProperty[] creatorProps)
667667
throws JsonMappingException
668668
{
669-
// Find the regular method/field level any-setter
670-
AnnotatedMember anySetter = beanDesc.findAnySetterAccessor();
671-
if (anySetter != null) {
672-
return constructAnySetter(ctxt, beanDesc, anySetter);
673-
}
674-
// else look for any-setter via @JsonCreator
669+
// Look for any-setter via @JsonCreator
675670
if (creatorProps != null) {
676671
for (SettableBeanProperty prop : creatorProps) {
677672
AnnotatedMember member = prop.getMember();
@@ -680,6 +675,11 @@ private SettableAnyProperty _resolveAnySetter(DeserializationContext ctxt,
680675
}
681676
}
682677
}
678+
// else find the regular method/field level any-setter
679+
AnnotatedMember anySetter = beanDesc.findAnySetterAccessor();
680+
if (anySetter != null) {
681+
return constructAnySetter(ctxt, beanDesc, anySetter);
682+
}
683683
// not found, that's fine, too
684684
return null;
685685
}

src/test/java/com/fasterxml/jackson/databind/deser/creators/AnySetterForCreator562Test.java

+39-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.junit.jupiter.api.Test;
1818

1919
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertNull;
2021
import static org.junit.jupiter.api.Assertions.fail;
2122

2223
// [databind#562] Allow @JsonAnySetter on Creator constructors
@@ -36,13 +37,29 @@ public POJO562(@JsonProperty("a") String a,
3637
}
3738
}
3839

40+
static class POJO562WithAnnotationOnBothCtorParamAndField
41+
{
42+
String a;
43+
@JsonAnySetter
44+
Map<String,Object> stuffFromField;
45+
Map<String,Object> stuffFromConstructor;
46+
47+
@JsonCreator
48+
public POJO562WithAnnotationOnBothCtorParamAndField(@JsonProperty("a") String a,
49+
@JsonAnySetter Map<String, Object> leftovers
50+
) {
51+
this.a = a;
52+
stuffFromConstructor = leftovers;
53+
}
54+
}
55+
3956
static class POJO562WithField
4057
{
4158
String a;
4259
Map<String,Object> stuff;
4360

4461
public String b;
45-
62+
4663
@JsonCreator
4764
public POJO562WithField(@JsonProperty("a") String a,
4865
@JsonAnySetter Map<String, Object> leftovers
@@ -115,12 +132,32 @@ public void mapAnySetterViaCreator562() throws Exception
115132

116133
assertEquals("value", pojo.a);
117134
assertEquals(expected, pojo.stuff);
118-
135+
119136
// Should also initialize any-setter-Map even if no contents
120137
pojo = MAPPER.readValue(a2q("{'a':'value2'}"), POJO562.class);
121138
assertEquals("value2", pojo.a);
122139
assertEquals(new HashMap<>(), pojo.stuff);
140+
}
123141

142+
// [databind#4634]
143+
@Test
144+
public void mapAnySetterViaCreatorWhenBothCreatorAndFieldAreAnnotated() throws Exception
145+
{
146+
Map<String, Object> expected = new HashMap<>();
147+
expected.put("b", Integer.valueOf(42));
148+
expected.put("c", Integer.valueOf(111));
149+
150+
POJO562WithAnnotationOnBothCtorParamAndField pojo = MAPPER.readValue(a2q(
151+
"{'a':'value', 'b':42, 'c': 111}"
152+
),
153+
POJO562WithAnnotationOnBothCtorParamAndField.class);
154+
155+
assertEquals("value", pojo.a);
156+
assertEquals(expected, pojo.stuffFromConstructor);
157+
// In an ideal world, maybe exception should be thrown for annotating both field + constructor parameter,
158+
// but that scenario is possible in this imperfect world e.g. annotating `@JsonAnySetter` on a Record component
159+
// will cause that annotation to be (auto)propagated to both the field & constructor parameter (& accessor method)
160+
assertNull(pojo.stuffFromField);
124161
}
125162

126163
// Creator and non-Creator props AND any-setter ought to be fine too

0 commit comments

Comments
 (0)