Skip to content

Commit 75bd749

Browse files
authored
Add support for strict property ordering (#2879)
1 parent 207b7f6 commit 75bd749

File tree

5 files changed

+81
-5
lines changed

5 files changed

+81
-5
lines changed

src/main/java/com/fasterxml/jackson/databind/MapperFeature.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,20 @@ public enum MapperFeature implements ConfigFeature
375375
*/
376376
SORT_PROPERTIES_ALPHABETICALLY(false),
377377

378+
/**
379+
* Feature that enforces strict ordering as requested by other configuration methods
380+
* for POJO fields (note: does <b>not</b> apply to {@link java.util.Map}
381+
* serialization!):
382+
* if enabled, ordering is preserved even if {@link com.fasterxml.jackson.annotation.JsonCreator}
383+
* is present. Without this feature properties referenced by {@link com.fasterxml.jackson.annotation.JsonCreator}
384+
* taking precedence over other properties even if sorting is requested.
385+
*<p>
386+
* Note that if ordering is not enabled using other ways, this feature has no effect.
387+
*<p>
388+
* Feature is disabled by default.
389+
*/
390+
STRICT_PROPERTIES_ORDERING(false),
391+
378392
/*
379393
/******************************************************
380394
/* Name-related features

src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,14 @@ public final boolean shouldSortPropertiesAlphabetically() {
188188
return isEnabled(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
189189
}
190190

191+
/**
192+
* Accessor for checking whether default settings for forcing property
193+
* ordering is enabled.
194+
*/
195+
public final boolean shouldPreservePropertiesOrdering() {
196+
return isEnabled(MapperFeature.STRICT_PROPERTIES_ORDERING);
197+
}
198+
191199
/**
192200
* Accessor for checking whether configuration indicates that
193201
* "root wrapping" (use of an extra property/name pair at root level)

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1115,7 +1115,8 @@ protected void _sortProperties(Map<String, POJOPropertyBuilder> props)
11151115
}
11161116

11171117
// Third by sorting Creator properties before other unordered properties
1118-
if (_creatorProperties != null) {
1118+
// (unless strict ordering is requested)
1119+
if (_creatorProperties != null && !_config.shouldPreservePropertiesOrdering()) {
11191120
/* As per [databind#311], this is bit delicate; but if alphabetic ordering
11201121
* is mandated, at least ensure creator properties are in alphabetic
11211122
* order. Related question of creator vs non-creator is punted for now,

src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public void testCopy() throws Exception
126126
assertTrue(m.isEnabled(JsonParser.Feature.IGNORE_UNDEFINED));
127127

128128
// // First: verify that handling of features is decoupled:
129-
129+
130130
ObjectMapper m2 = m.copy();
131131
assertFalse(m2.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
132132
m2.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
@@ -215,7 +215,7 @@ public void testFailedCopy() throws Exception
215215
}
216216
}
217217

218-
public void testAnnotationIntrospectorCopyin()
218+
public void testAnnotationIntrospectorCopying()
219219
{
220220
ObjectMapper m = new ObjectMapper();
221221
m.setAnnotationIntrospector(new MyAnnotationIntrospector());
@@ -272,6 +272,32 @@ public void testConfigForPropertySorting() throws Exception
272272
assertTrue(dc.shouldSortPropertiesAlphabetically());
273273
}
274274

275+
// Test to ensure that we can check forced property ordering defaults...
276+
public void testConfigForForcedPropertySorting() throws Exception
277+
{
278+
ObjectMapper m = new ObjectMapper();
279+
280+
// sort-alphabetically is disabled by default:
281+
assertFalse(m.isEnabled(MapperFeature.STRICT_PROPERTIES_ORDERING));
282+
SerializationConfig sc = m.getSerializationConfig();
283+
assertFalse(sc.isEnabled(MapperFeature.STRICT_PROPERTIES_ORDERING));
284+
assertFalse(sc.shouldPreservePropertiesOrdering());
285+
DeserializationConfig dc = m.getDeserializationConfig();
286+
assertFalse(dc.shouldPreservePropertiesOrdering());
287+
288+
// but when enabled, should be visible:
289+
m = jsonMapperBuilder()
290+
.enable(MapperFeature.STRICT_PROPERTIES_ORDERING)
291+
.build();
292+
sc = m.getSerializationConfig();
293+
assertTrue(sc.isEnabled(MapperFeature.STRICT_PROPERTIES_ORDERING));
294+
assertTrue(sc.shouldPreservePropertiesOrdering());
295+
dc = m.getDeserializationConfig();
296+
// and not just via SerializationConfig, but also via DeserializationConfig
297+
assertTrue(dc.isEnabled(MapperFeature.STRICT_PROPERTIES_ORDERING));
298+
assertTrue(dc.shouldPreservePropertiesOrdering());
299+
}
300+
275301
public void testJsonFactoryLinkage()
276302
{
277303
// first, implicit factory, giving implicit linkage
@@ -284,7 +310,7 @@ public void testJsonFactoryLinkage()
284310
assertSame(m, f.getCodec());
285311
}
286312

287-
public void testProviderConfig() throws Exception
313+
public void testProviderConfig() throws Exception
288314
{
289315
ObjectMapper m = new ObjectMapper();
290316
final String JSON = "{ \"x\" : 3 }";
@@ -332,7 +358,7 @@ public void testCustomDefaultPrettyPrinter() throws Exception
332358
assertEquals("[1,2]", m.writer().without(SerializationFeature.INDENT_OUTPUT)
333359
.writeValueAsString(input));
334360
}
335-
361+
336362
// For [databind#703], [databind#978]
337363
public void testNonSerializabilityOfObject()
338364
{

src/test/java/com/fasterxml/jackson/databind/ser/SerializationOrderTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,22 @@ public BeanForGH311(@JsonProperty("b") int b, @JsonProperty("a") int a) { //b an
8282
public int getB() { return b; }
8383
}
8484

85+
static class BeanForStrictOrdering {
86+
private final int a;
87+
private int b;
88+
private final int c;
89+
90+
@JsonCreator
91+
public BeanForStrictOrdering(@JsonProperty("c") int c, @JsonProperty("a") int a) { //b and a are out of order, although alphabetic = true
92+
this.a = a;
93+
this.c = c;
94+
}
95+
96+
public int getA() { return a; }
97+
public int getB() { return b; }
98+
public int getC() { return c; }
99+
}
100+
85101
// For [databind#2879]
86102
@JsonPropertyOrder({ "a", "c" })
87103
static class BeanFor2879 {
@@ -126,6 +142,11 @@ static class OrderingByIndexBean {
126142
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
127143
.build();
128144

145+
private final ObjectMapper STRICT_ALPHA_MAPPER = jsonMapperBuilder()
146+
.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)
147+
.enable(MapperFeature.STRICT_PROPERTIES_ORDERING)
148+
.build();
149+
129150
public void testImplicitOrderByCreator() throws Exception {
130151
assertEquals("{\"c\":1,\"a\":2,\"b\":0}",
131152
MAPPER.writeValueAsString(new BeanWithCreator(1, 2)));
@@ -187,4 +208,10 @@ public void testOrderByIndexEtc() throws Exception
187208
assertEquals(aposToQuotes("{'f':0,'u':0,'b':0,'a':0,'r':0}"),
188209
ALPHA_MAPPER.writeValueAsString(new OrderingByIndexBean()));
189210
}
211+
212+
public void testStrictAlphaAndCreatorOrdering() throws Exception
213+
{
214+
String json = STRICT_ALPHA_MAPPER.writeValueAsString(new BeanForStrictOrdering(1, 2));
215+
assertEquals("{\"a\":2,\"b\":0,\"c\":1}", json);
216+
}
190217
}

0 commit comments

Comments
 (0)