Skip to content

Commit 7862283

Browse files
committed
Merge branch '2.19' into 2.x
2 parents 5b4f16d + dd27a21 commit 7862283

File tree

6 files changed

+139
-8
lines changed

6 files changed

+139
-8
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/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,40 @@ protected void collectAll()
501501
_collected = true;
502502
}
503503

504+
/**
505+
* [databind#5215] JsonAnyGetter Serializer behavior change from 2.18.4 to 2.19.0
506+
* Put anyGetter in the end, before actual sorting further down {@link POJOPropertiesCollector#_sortProperties(Map)}
507+
*/
508+
private Map<String, POJOPropertyBuilder> _putAnyGettersInTheEnd(
509+
Map<String, POJOPropertyBuilder> sortedProps)
510+
{
511+
AnnotatedMember anyAccessor;
512+
513+
if (_anyGetters != null) {
514+
anyAccessor = _anyGetters.getFirst();
515+
} else if (_anyGetterField != null) {
516+
anyAccessor = _anyGetterField.getFirst();
517+
} else {
518+
return sortedProps;
519+
}
520+
521+
// Here we'll use insertion-order preserving map, since possible alphabetic
522+
// sorting already done earlier
523+
Map<String, POJOPropertyBuilder> newAll = new LinkedHashMap<>(sortedProps.size() * 2);
524+
POJOPropertyBuilder anyGetterProp = null;
525+
for (POJOPropertyBuilder prop : sortedProps.values()) {
526+
if (prop.hasFieldOrGetter(anyAccessor)) {
527+
anyGetterProp = prop;
528+
} else {
529+
newAll.put(prop.getName(), prop);
530+
}
531+
}
532+
if (anyGetterProp != null) {
533+
newAll.put(anyGetterProp.getName(), anyGetterProp);
534+
}
535+
return newAll;
536+
}
537+
504538
/*
505539
/**********************************************************************
506540
/* Property introspection: Fields
@@ -1588,14 +1622,16 @@ protected void _sortProperties(Map<String, POJOPropertyBuilder> props)
15881622
Map<String, POJOPropertyBuilder> all;
15891623
// Need to (re)sort alphabetically?
15901624
if (sortAlpha) {
1591-
all = new TreeMap<String,POJOPropertyBuilder>();
1625+
all = new TreeMap<>();
15921626
} else {
1593-
all = new LinkedHashMap<String,POJOPropertyBuilder>(size+size);
1627+
all = new LinkedHashMap<>(size+size);
15941628
}
1595-
1629+
// First, handle sorting caller expects:
15961630
for (POJOPropertyBuilder prop : props.values()) {
15971631
all.put(prop.getName(), prop);
15981632
}
1633+
all = _putAnyGettersInTheEnd(all);
1634+
15991635
Map<String,POJOPropertyBuilder> ordered = new LinkedHashMap<>(size+size);
16001636
// Ok: primarily by explicit order
16011637
if (propertyOrder != null) {

src/main/java/com/fasterxml/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 com.fasterxml.jackson.databind.introspect;
22

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

@@ -764,6 +765,24 @@ protected int _setterPriority(AnnotatedMethod m)
764765
return 2;
765766
}
766767

768+
// @since 2.19.2
769+
public boolean hasFieldOrGetter(AnnotatedMember member) {
770+
return _hasAccessor(_fields, member) || _hasAccessor(_getters, member);
771+
}
772+
773+
private boolean _hasAccessor(Linked<? extends AnnotatedMember> node,
774+
AnnotatedMember memberToMatch)
775+
{
776+
// AnnotatedXxx are not canonical, but underlying JDK Members are:
777+
final Member rawMemberToMatch = memberToMatch.getMember();
778+
for (; node != null; node = node.next) {
779+
if (node.value.getMember() == rawMemberToMatch) {
780+
return true;
781+
}
782+
}
783+
return false;
784+
}
785+
767786
/*
768787
/**********************************************************
769788
/* Implementations of refinement accessors
@@ -877,19 +896,19 @@ public JsonProperty.Access withMember(AnnotatedMember member) {
877896
*/
878897

879898
public void addField(AnnotatedField a, PropertyName name, boolean explName, boolean visible, boolean ignored) {
880-
_fields = new Linked<AnnotatedField>(a, _fields, name, explName, visible, ignored);
899+
_fields = new Linked<>(a, _fields, name, explName, visible, ignored);
881900
}
882901

883902
public void addCtor(AnnotatedParameter a, PropertyName name, boolean explName, boolean visible, boolean ignored) {
884-
_ctorParameters = new Linked<AnnotatedParameter>(a, _ctorParameters, name, explName, visible, ignored);
903+
_ctorParameters = new Linked<>(a, _ctorParameters, name, explName, visible, ignored);
885904
}
886905

887906
public void addGetter(AnnotatedMethod a, PropertyName name, boolean explName, boolean visible, boolean ignored) {
888-
_getters = new Linked<AnnotatedMethod>(a, _getters, name, explName, visible, ignored);
907+
_getters = new Linked<>(a, _getters, name, explName, visible, ignored);
889908
}
890909

891910
public void addSetter(AnnotatedMethod a, PropertyName name, boolean explName, boolean visible, boolean ignored) {
892-
_setters = new Linked<AnnotatedMethod>(a, _setters, name, explName, visible, ignored);
911+
_setters = new Linked<>(a, _setters, name, explName, visible, ignored);
893912
}
894913

895914
/**

src/test-jdk17/java/com/fasterxml/jackson/databind/records/RecordJsonSerDeser188Test.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ public void serialize(String value, JsonGenerator jgen, SerializerProvider provi
4848
}
4949
}
5050

51-
@SuppressWarnings("serial")
5251
static class PrefixStringDeserializer extends StdScalarDeserializer<String>
5352
{
5453
private static final long serialVersionUID = 1L;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.fasterxml.jackson.databind.ser;
2+
3+
import com.fasterxml.jackson.annotation.JsonAnyGetter;
4+
import com.fasterxml.jackson.annotation.JsonAnySetter;
5+
import com.fasterxml.jackson.databind.MapperFeature;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import com.fasterxml.jackson.databind.SerializationFeature;
8+
import com.fasterxml.jackson.databind.json.JsonMapper;
9+
import com.fasterxml.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)