Skip to content

Commit 01a108b

Browse files
committed
Merge branch '2.x' into 3.x
2 parents d06d45f + 7862283 commit 01a108b

File tree

5 files changed

+139
-7
lines changed

5 files changed

+139
-7
lines changed

release-notes/CREDITS-2.x

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1944,6 +1944,10 @@ Ryan Schmitt (@rschmitt)
19441944
* Contributed #5099: Fix regression in `ObjectNode.with()`
19451945
(2.19.0)
19461946
1947+
Eddú Meléndez Gonzales (@eddumelendez)
1948+
* Reported #5215: `@JsonAnyGetter` serialization order change from 2.18.4 to 2.19.0
1949+
(2.19.2)
1950+
19471951
Giulio Longfils (@giulong)
19481952
* Contributed #3072: Allow specifying `@JacksonInject` does not fail when there's no
19491953
corresponding value

release-notes/VERSION-2.x

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ Project: jackson-databind
2626
(fix by Joo-Hyuk K)
2727
- Generate SBOMs [JSTEP-14]
2828
29+
2.19.2 (not yet released)
30+
31+
#5215: `@JsonAnyGetter` serialization order change from 2.18.4 to 2.19.0
32+
(reported by Eddú M)
33+
(fix by Joo-Hyuk K)
34+
2935
2.19.1 (13-Jun-2025)
3036
3137
#5139: In `CollectionDeserializer`, `JsonSetter.contentNulls` is sometimes ignored

src/main/java/tools/jackson/databind/introspect/POJOPropertiesCollector.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,40 @@ protected void collectAll()
450450
_collected = true;
451451
}
452452

453+
/**
454+
* [databind#5215] JsonAnyGetter Serializer behavior change from 2.18.4 to 2.19.0
455+
* Put anyGetter in the end, before actual sorting further down {@link POJOPropertiesCollector#_sortProperties(Map)}
456+
*/
457+
private Map<String, POJOPropertyBuilder> _putAnyGettersInTheEnd(
458+
Map<String, POJOPropertyBuilder> sortedProps)
459+
{
460+
AnnotatedMember anyAccessor;
461+
462+
if (_anyGetters != null) {
463+
anyAccessor = _anyGetters.getFirst();
464+
} else if (_anyGetterField != null) {
465+
anyAccessor = _anyGetterField.getFirst();
466+
} else {
467+
return sortedProps;
468+
}
469+
470+
// Here we'll use insertion-order preserving map, since possible alphabetic
471+
// sorting already done earlier
472+
Map<String, POJOPropertyBuilder> newAll = new LinkedHashMap<>(sortedProps.size() * 2);
473+
POJOPropertyBuilder anyGetterProp = null;
474+
for (POJOPropertyBuilder prop : sortedProps.values()) {
475+
if (prop.hasFieldOrGetter(anyAccessor)) {
476+
anyGetterProp = prop;
477+
} else {
478+
newAll.put(prop.getName(), prop);
479+
}
480+
}
481+
if (anyGetterProp != null) {
482+
newAll.put(anyGetterProp.getName(), anyGetterProp);
483+
}
484+
return newAll;
485+
}
486+
453487
/*
454488
/**********************************************************************
455489
/* Property introspection: Fields
@@ -1558,14 +1592,16 @@ protected void _sortProperties(Map<String, POJOPropertyBuilder> props)
15581592
Map<String, POJOPropertyBuilder> all;
15591593
// Need to (re)sort alphabetically?
15601594
if (sortAlpha) {
1561-
all = new TreeMap<String,POJOPropertyBuilder>();
1595+
all = new TreeMap<>();
15621596
} else {
1563-
all = new LinkedHashMap<String,POJOPropertyBuilder>(size+size);
1597+
all = new LinkedHashMap<>(size+size);
15641598
}
1565-
1599+
// First, handle sorting caller expects:
15661600
for (POJOPropertyBuilder prop : props.values()) {
15671601
all.put(prop.getName(), prop);
15681602
}
1603+
all = _putAnyGettersInTheEnd(all);
1604+
15691605
Map<String,POJOPropertyBuilder> ordered = new LinkedHashMap<>(size+size);
15701606
// Ok: primarily by explicit order
15711607
if (propertyOrder != null) {

src/main/java/tools/jackson/databind/introspect/POJOPropertyBuilder.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package tools.jackson.databind.introspect;
22

3+
import java.lang.reflect.Member;
34
import java.util.*;
45
import java.util.stream.Collectors;
56

@@ -769,6 +770,24 @@ protected int _setterPriority(AnnotatedMethod m)
769770
return 2;
770771
}
771772

773+
// @since 2.19.2
774+
public boolean hasFieldOrGetter(AnnotatedMember member) {
775+
return _hasAccessor(_fields, member) || _hasAccessor(_getters, member);
776+
}
777+
778+
private boolean _hasAccessor(Linked<? extends AnnotatedMember> node,
779+
AnnotatedMember memberToMatch)
780+
{
781+
// AnnotatedXxx are not canonical, but underlying JDK Members are:
782+
final Member rawMemberToMatch = memberToMatch.getMember();
783+
for (; node != null; node = node.next) {
784+
if (node.value.getMember() == rawMemberToMatch) {
785+
return true;
786+
}
787+
}
788+
return false;
789+
}
790+
772791
/*
773792
/**********************************************************
774793
/* Implementations of refinement accessors
@@ -874,19 +893,19 @@ public JsonProperty.Access withMember(AnnotatedMember member) {
874893
*/
875894

876895
public void addField(AnnotatedField a, PropertyName name, boolean explName, boolean visible, boolean ignored) {
877-
_fields = new Linked<AnnotatedField>(a, _fields, name, explName, visible, ignored);
896+
_fields = new Linked<>(a, _fields, name, explName, visible, ignored);
878897
}
879898

880899
public void addCtor(AnnotatedParameter a, PropertyName name, boolean explName, boolean visible, boolean ignored) {
881-
_ctorParameters = new Linked<AnnotatedParameter>(a, _ctorParameters, name, explName, visible, ignored);
900+
_ctorParameters = new Linked<>(a, _ctorParameters, name, explName, visible, ignored);
882901
}
883902

884903
public void addGetter(AnnotatedMethod a, PropertyName name, boolean explName, boolean visible, boolean ignored) {
885-
_getters = new Linked<AnnotatedMethod>(a, _getters, name, explName, visible, ignored);
904+
_getters = new Linked<>(a, _getters, name, explName, visible, ignored);
886905
}
887906

888907
public void addSetter(AnnotatedMethod a, PropertyName name, boolean explName, boolean visible, boolean ignored) {
889-
_setters = new Linked<AnnotatedMethod>(a, _setters, name, explName, visible, ignored);
908+
_setters = new Linked<>(a, _setters, name, explName, visible, ignored);
890909
}
891910

892911
/**
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package tools.jackson.databind.ser;
2+
3+
import com.fasterxml.jackson.annotation.JsonAnyGetter;
4+
import com.fasterxml.jackson.annotation.JsonAnySetter;
5+
import tools.jackson.databind.MapperFeature;
6+
import tools.jackson.databind.ObjectMapper;
7+
import tools.jackson.databind.SerializationFeature;
8+
import tools.jackson.databind.json.JsonMapper;
9+
import tools.jackson.databind.testutil.DatabindTestUtil;
10+
11+
import org.junit.jupiter.api.Test;
12+
13+
import java.util.LinkedHashMap;
14+
import java.util.Map;
15+
16+
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
18+
// For [databind#5215]: Any-getter should be sorted last, by default
19+
public class AnyGetterOrdering5215Test
20+
extends DatabindTestUtil
21+
{
22+
static class DynaBean {
23+
public String l;
24+
public String j;
25+
public String a;
26+
27+
protected Map<String, Object> extensions = new LinkedHashMap<>();
28+
29+
@JsonAnyGetter
30+
public Map<String, Object> getExtensions() {
31+
return extensions;
32+
}
33+
34+
@JsonAnySetter
35+
public void addExtension(String name, Object value) {
36+
extensions.put(name, value);
37+
}
38+
}
39+
40+
/*
41+
/**********************************************************************
42+
/* Test methods
43+
/**********************************************************************
44+
*/
45+
46+
private final ObjectMapper MAPPER = JsonMapper.builder()
47+
.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)
48+
.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)
49+
.build();
50+
51+
@Test
52+
public void testDynaBean() throws Exception
53+
{
54+
DynaBean b = new DynaBean();
55+
b.a = "1";
56+
b.j = "2";
57+
b.l = "3";
58+
b.addExtension("z", "5");
59+
b.addExtension("b", "4");
60+
assertEquals(a2q("{" +
61+
"'a':'1'," +
62+
"'j':'2'," +
63+
"'l':'3'," +
64+
"'b':'4'," +
65+
"'z':'5'}"), MAPPER.writeValueAsString(b));
66+
}
67+
}

0 commit comments

Comments
 (0)