Skip to content

Commit 2088a49

Browse files
committed
Fix #2555 (use @JsonProperty.index for serialization ordering)
1 parent 7c7b5c9 commit 2088a49

File tree

3 files changed

+91
-42
lines changed

3 files changed

+91
-42
lines changed

release-notes/VERSION-2.x

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Project: jackson-databind
2222
#2522: `DeserializationContext.handleMissingInstantiator()` throws `MismatchedInputException`
2323
for non-static inner classes
2424
#2525: Incorrect `JsonStreamContext` for `TokenBuffer` and `TreeTraversingParser`
25+
#2555: Use `@JsonProperty(index)` for sorting properties on serialization
2526
- Add `SerializerProvider.findContentValueSerializer()` methods
2627
2728
2.10.2 (not yet released)

src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -336,17 +336,14 @@ protected void collectAll()
336336
_renameUsing(props, naming);
337337
}
338338

339-
/* Sort by visibility (explicit over implicit); drop all but first
340-
* of member type (getter, setter etc) if there is visibility
341-
* difference
342-
*/
339+
// Sort by visibility (explicit over implicit); drop all but first of member
340+
// type (getter, setter etc) if there is visibility difference
343341
for (POJOPropertyBuilder property : props.values()) {
344342
property.trimByVisibility();
345343
}
346344

347-
/* and, if required, apply wrapper name: note, MUST be done after
348-
* annotations are merged.
349-
*/
345+
// and, if required, apply wrapper name: note, MUST be done after
346+
// annotations are merged.
350347
if (_config.isEnabled(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME)) {
351348
_renameWithWrappers(props);
352349
}
@@ -933,25 +930,24 @@ protected void _renameWithWrappers(Map<String, POJOPropertyBuilder> props)
933930
/**********************************************************
934931
*/
935932

936-
/* First, order by [JACKSON-90] (explicit ordering and/or alphabetic)
937-
* and then for [JACKSON-170] (implicitly order creator properties before others)
938-
*/
933+
// First, order by(explicit ordering and/or alphabetic),
934+
// then by (optional) index (if any)
935+
// and then implicitly order creator properties before others)
936+
939937
protected void _sortProperties(Map<String, POJOPropertyBuilder> props)
940938
{
941939
// Then how about explicit ordering?
942-
AnnotationIntrospector intr = _annotationIntrospector;
940+
final AnnotationIntrospector intr = _annotationIntrospector;
943941
Boolean alpha = intr.findSerializationSortAlphabetically((Annotated) _classDef);
944-
boolean sort;
945-
946-
if (alpha == null) {
947-
sort = _config.shouldSortPropertiesAlphabetically();
948-
} else {
949-
sort = alpha.booleanValue();
950-
}
942+
final boolean sort = (alpha == null)
943+
? _config.shouldSortPropertiesAlphabetically()
944+
: alpha.booleanValue();
945+
final boolean indexed = _anyIndexed(props.values());
946+
951947
String[] propertyOrder = intr.findSerializationPropertyOrder(_classDef);
952948

953949
// no sorting? no need to shuffle, then
954-
if (!sort && (_creatorProperties == null) && (propertyOrder == null)) {
950+
if (!sort && !indexed && (_creatorProperties == null) && (propertyOrder == null)) {
955951
return;
956952
}
957953
int size = props.size();
@@ -966,11 +962,11 @@ protected void _sortProperties(Map<String, POJOPropertyBuilder> props)
966962
for (POJOPropertyBuilder prop : props.values()) {
967963
all.put(prop.getName(), prop);
968964
}
969-
Map<String,POJOPropertyBuilder> ordered = new LinkedHashMap<String,POJOPropertyBuilder>(size+size);
965+
Map<String,POJOPropertyBuilder> ordered = new LinkedHashMap<>(size+size);
970966
// Ok: primarily by explicit order
971967
if (propertyOrder != null) {
972968
for (String name : propertyOrder) {
973-
POJOPropertyBuilder w = all.get(name);
969+
POJOPropertyBuilder w = all.remove(name);
974970
if (w == null) { // will also allow use of "implicit" names for sorting
975971
for (POJOPropertyBuilder prop : props.values()) {
976972
if (name.equals(prop.getInternalName())) {
@@ -986,7 +982,26 @@ protected void _sortProperties(Map<String, POJOPropertyBuilder> props)
986982
}
987983
}
988984
}
989-
// And secondly by sorting Creator properties before other unordered properties
985+
986+
// Second (starting with 2.11): index, if any:
987+
if (indexed) {
988+
Map<Integer,POJOPropertyBuilder> byIndex = new TreeMap<>();
989+
Iterator<Map.Entry<String,POJOPropertyBuilder>> it = all.entrySet().iterator();
990+
while (it.hasNext()) {
991+
Map.Entry<String,POJOPropertyBuilder> entry = it.next();
992+
POJOPropertyBuilder prop = entry.getValue();
993+
Integer index = prop.getMetadata().getIndex();
994+
if (index != null) {
995+
byIndex.put(index, prop);
996+
it.remove();
997+
}
998+
}
999+
for (POJOPropertyBuilder prop : byIndex.values()) {
1000+
ordered.put(prop.getName(), prop);
1001+
}
1002+
}
1003+
1004+
// Third by sorting Creator properties before other unordered properties
9901005
if (_creatorProperties != null) {
9911006
/* As per [databind#311], this is bit delicate; but if alphabetic ordering
9921007
* is mandated, at least ensure creator properties are in alphabetic
@@ -1008,6 +1023,8 @@ protected void _sortProperties(Map<String, POJOPropertyBuilder> props)
10081023
// 16-Jan-2016, tatu: Related to [databind#1317], make sure not to accidentally
10091024
// add back pruned creator properties!
10101025
String name = prop.getName();
1026+
// 27-Nov-2019, tatu: Not sure why, but we should NOT remove it from `all` tho:
1027+
// if (all.remove(name) != null) {
10111028
if (all.containsKey(name)) {
10121029
ordered.put(name, prop);
10131030
}
@@ -1017,7 +1034,16 @@ protected void _sortProperties(Map<String, POJOPropertyBuilder> props)
10171034
ordered.putAll(all);
10181035
props.clear();
10191036
props.putAll(ordered);
1020-
}
1037+
}
1038+
1039+
private boolean _anyIndexed(Collection<POJOPropertyBuilder> props) {
1040+
for (POJOPropertyBuilder prop : props) {
1041+
if (prop.getMetadata().hasIndex()) {
1042+
return true;
1043+
}
1044+
}
1045+
return false;
1046+
}
10211047

10221048
/*
10231049
/**********************************************************

src/test/java/com/fasterxml/jackson/databind/ser/TestSerializationOrder.java renamed to src/test/java/com/fasterxml/jackson/databind/ser/SerializationOrderTest.java

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* Unit tests for verifying that constraints on ordering of serialized
99
* properties are held.
1010
*/
11-
public class TestSerializationOrder
11+
public class SerializationOrderTest
1212
extends BaseMapTest
1313
{
1414
static class BeanWithCreator
@@ -68,7 +68,7 @@ static class BeanFor459 {
6868

6969
// For [databind#311]
7070
@JsonPropertyOrder(alphabetic = true)
71-
public class BeanForGH311 {
71+
static class BeanForGH311 {
7272
private final int a;
7373
private final int b;
7474

@@ -82,6 +82,21 @@ public BeanForGH311(@JsonProperty("b") int b, @JsonProperty("a") int a) { //b an
8282
public int getB() { return b; }
8383
}
8484

85+
// We'll expect ordering of "FUBAR"
86+
@JsonPropertyOrder({ "f" })
87+
static class OrderingByIndexBean {
88+
public int r;
89+
public int a;
90+
91+
@JsonProperty(index = 1)
92+
public int b;
93+
94+
@JsonProperty(index = 0)
95+
public int u;
96+
97+
public int f;
98+
}
99+
85100
/*
86101
/*********************************************
87102
/* Unit tests
@@ -90,19 +105,23 @@ public BeanForGH311(@JsonProperty("b") int b, @JsonProperty("a") int a) { //b an
90105

91106
private final ObjectMapper MAPPER = newJsonMapper();
92107

93-
public void testImplicitOrderByCreator() throws Exception
94-
{
95-
assertEquals("{\"c\":1,\"a\":2,\"b\":0}", MAPPER.writeValueAsString(new BeanWithCreator(1, 2)));
108+
private final ObjectMapper ALPHA_MAPPER = jsonMapperBuilder()
109+
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
110+
.build();
111+
112+
public void testImplicitOrderByCreator() throws Exception {
113+
assertEquals("{\"c\":1,\"a\":2,\"b\":0}",
114+
MAPPER.writeValueAsString(new BeanWithCreator(1, 2)));
96115
}
97116

98-
public void testExplicitOrder() throws Exception
99-
{
100-
assertEquals("{\"c\":3,\"a\":1,\"b\":2,\"d\":4}", MAPPER.writeValueAsString(new BeanWithOrder(1, 2, 3, 4)));
117+
public void testExplicitOrder() throws Exception {
118+
assertEquals("{\"c\":3,\"a\":1,\"b\":2,\"d\":4}",
119+
MAPPER.writeValueAsString(new BeanWithOrder(1, 2, 3, 4)));
101120
}
102121

103-
public void testAlphabeticOrder() throws Exception
104-
{
105-
assertEquals("{\"d\":4,\"a\":1,\"b\":2,\"c\":3}", MAPPER.writeValueAsString(new SubBeanWithOrder(1, 2, 3, 4)));
122+
public void testAlphabeticOrder() throws Exception {
123+
assertEquals("{\"d\":4,\"a\":1,\"b\":2,\"c\":3}",
124+
MAPPER.writeValueAsString(new SubBeanWithOrder(1, 2, 3, 4)));
106125
}
107126

108127
public void testOrderWithMixins() throws Exception
@@ -122,20 +141,23 @@ public void testOrderWrt268() throws Exception
122141

123142
public void testOrderWithFeature() throws Exception
124143
{
125-
ObjectMapper m = jsonMapperBuilder()
126-
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
127-
.build();
128144
assertEquals("{\"a\":1,\"b\":2,\"c\":3,\"d\":4}",
129-
m.writeValueAsString(new BeanFor459()));
145+
ALPHA_MAPPER.writeValueAsString(new BeanFor459()));
130146
}
131147

132148
// [databind#311]
133149
public void testAlphaAndCreatorOrdering() throws Exception
134150
{
135-
ObjectMapper m = jsonMapperBuilder()
136-
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
137-
.build();
138-
String json = m.writeValueAsString(new BeanForGH311(2, 1));
151+
String json = ALPHA_MAPPER.writeValueAsString(new BeanForGH311(2, 1));
139152
assertEquals("{\"a\":1,\"b\":2}", json);
140153
}
154+
155+
// [databind#2555]
156+
public void testOrderByIndexEtc() throws Exception
157+
{
158+
// since "default" order can actually vary with later JDKs, only verify
159+
// case of alphabetic-as-default
160+
assertEquals(aposToQuotes("{'f':0,'u':0,'b':0,'a':0,'r':0}"),
161+
ALPHA_MAPPER.writeValueAsString(new OrderingByIndexBean()));
162+
}
141163
}

0 commit comments

Comments
 (0)