Skip to content

Commit 0a4b3a1

Browse files
authored
Add JsonNodeFeature.WRITE_PROPERTIES_SORTED for sorting ObjectNode properties on serialization (#3998)
1 parent 778198c commit 0a4b3a1

File tree

4 files changed

+59
-4
lines changed

4 files changed

+59
-4
lines changed

release-notes/VERSION-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Project: jackson-databind
2323
#3950: Create new `JavaType` subtype `IterationType` (extending `SimpleType`)
2424
#3953: Use `JsonTypeInfo.Value` for annotation handling
2525
(contributed by Joo-Hyuk K)
26+
#3965: Add `JsonNodeFeature.WRITE_PROPERTIES_SORTED` for sorting `ObjectNode` properties
27+
on serialization
2628

2729
2.15.3 (not yet released)
2830

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ public enum JsonNodeFeature implements DatatypeFeature
2727
*/
2828
WRITE_NULL_PROPERTIES(true),
2929

30+
/**
31+
* When writing {@code com.fasterxml.jackson.databind.JsonNode}s are Object properties
32+
* (for {@code ObjectNode}s) sorted alphabetically (using natural order of
33+
* {@link java.lang.String}) or not?
34+
* If not sorted, order is the insertion order; when reading this also means retaining
35+
* order from the input document.
36+
*<p>
37+
* Default value: {@code false}
38+
*
39+
* @since 2.16
40+
*/
41+
WRITE_PROPERTIES_SORTED(false),
42+
3043
// // // Merge configuration settings
3144

3245
// // // 03-Aug-2022, tatu: Possible other additions:

src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ public void serialize(JsonGenerator g, SerializerProvider provider)
430430
}
431431
}
432432
g.writeStartObject(this);
433-
for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
433+
for (Map.Entry<String, JsonNode> en : _contentsToSerialize(provider).entrySet()) {
434434
JsonNode value = en.getValue();
435435
g.writeFieldName(en.getKey());
436436
value.serialize(g, provider);
@@ -457,7 +457,7 @@ public void serializeWithType(JsonGenerator g, SerializerProvider provider,
457457
if (trimEmptyArray || skipNulls) {
458458
serializeFilteredContents(g, provider, trimEmptyArray, skipNulls);
459459
} else {
460-
for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
460+
for (Map.Entry<String, JsonNode> en : _contentsToSerialize(provider).entrySet()) {
461461
JsonNode value = en.getValue();
462462
g.writeFieldName(en.getKey());
463463
value.serialize(g, provider);
@@ -476,7 +476,7 @@ protected void serializeFilteredContents(final JsonGenerator g, final Serializer
476476
final boolean trimEmptyArray, final boolean skipNulls)
477477
throws IOException
478478
{
479-
for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
479+
for (Map.Entry<String, JsonNode> en : _contentsToSerialize(provider).entrySet()) {
480480
// 17-Feb-2009, tatu: Can we trust that all nodes will always
481481
// extend BaseJsonNode? Or if not, at least implement
482482
// JsonSerializable? Let's start with former, change if
@@ -497,6 +497,21 @@ protected void serializeFilteredContents(final JsonGenerator g, final Serializer
497497
}
498498
}
499499

500+
/**
501+
* Helper method for encapsulating details of accessing child node entries
502+
* to serialize.
503+
*
504+
* @since 2.16
505+
*/
506+
protected Map<String, JsonNode> _contentsToSerialize(SerializerProvider ctxt) {
507+
if (ctxt.isEnabled(JsonNodeFeature.WRITE_PROPERTIES_SORTED)) {
508+
if (!_children.isEmpty()) {
509+
return new TreeMap<>(_children);
510+
}
511+
}
512+
return _children;
513+
}
514+
500515
/*
501516
/**********************************************************
502517
/* Extended ObjectNode API, mutators, since 2.1

src/test/java/com/fasterxml/jackson/databind/node/NodeFeaturesTest.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public void testImplicitVsExplicit()
5656

5757
/*
5858
/**********************************************************************
59-
/* ObjectNode property handling
59+
/* ObjectNode property handling: null-handling
6060
/**********************************************************************
6161
*/
6262

@@ -109,6 +109,31 @@ public void testWriteNulls() throws Exception
109109
assertEquals(a2q("{'a':1,'c':true}"), w.writeValueAsString(doc));
110110
}
111111

112+
/*
113+
/**********************************************************************
114+
/* ObjectNode property handling: sorting on write
115+
/**********************************************************************
116+
*/
117+
118+
// [databind#3476]
119+
public void testWriteSortedProperties() throws Exception
120+
{
121+
assertFalse(WRITER.isEnabled(JsonNodeFeature.WRITE_PROPERTIES_SORTED));
122+
123+
ObjectNode doc = MAPPER.createObjectNode();
124+
doc.put("b", 2);
125+
doc.put("c", 3);
126+
doc.put("a", 1);
127+
128+
// by default, retain insertion order:
129+
assertEquals(a2q("{'b':2,'c':3,'a':1}"), WRITER.writeValueAsString(doc));
130+
131+
// but if forcing sorting, changes
132+
ObjectWriter w2 = WRITER.with(JsonNodeFeature.WRITE_PROPERTIES_SORTED);
133+
assertTrue(w2.isEnabled(JsonNodeFeature.WRITE_PROPERTIES_SORTED));
134+
assertEquals(a2q("{'a':1,'b':2,'c':3}"), w2.writeValueAsString(doc));
135+
}
136+
112137
/*
113138
/**********************************************************************
114139
/* Other features

0 commit comments

Comments
 (0)