Skip to content

Commit

Permalink
Fix #171: allow keeping Record field declaration order for serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Nov 5, 2024
1 parent 0ccaac1 commit 78d0d56
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 9 deletions.
11 changes: 11 additions & 0 deletions jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/JSON.java
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,17 @@ public enum Feature
*/
WRITE_READONLY_BEAN_PROPERTIES(true, true),

/**
* Feature that determines whether record fields are serialized in declaration
* order (enabled) or not (disabled). If disabled, record fields are serialized
* same way as POJO properties, that is, alphabetically sorted.
*<p>
* Feature is disabled by default for backwards compatibility reasons.
*
* @since 2.19
*/
WRITE_RECORD_FIELDS_IN_DECLARATION_ORDER(false, true),

/**
* Feature that determines whether access to {@link java.lang.reflect.Method}s and
* {@link java.lang.reflect.Constructor}s that are used with dynamically
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,32 @@ private POJODefinition _introspectDefinition(Class<?> beanType,
// For Serialization OTOH we need sorting (although would probably
// be better to sort after the fact, maybe in future)

Map<String,PropBuilder> propsByName = forSerialization ?
new TreeMap<>() : new LinkedHashMap<>();

Map<String,PropBuilder> propsByName;
// 04-Nov-2024, tatu [jackson-jr#171] May need to retain order
// for Record serialization too
final boolean recordSerInDeclOrder = isRecord && forSerialization
&& JSON.Feature.WRITE_RECORD_FIELDS_IN_DECLARATION_ORDER.isEnabled(features);

// Alphabetic ordering unnecessary for Deserialization (and some serialization too)
if (forSerialization && !recordSerInDeclOrder) {
propsByName = new TreeMap<>();
} else {
propsByName = new LinkedHashMap<>();
}

final BeanConstructors constructors;
if (forSerialization) {
if (recordSerInDeclOrder) {
Constructor<?> canonical = _getCanonicalRecordConstructor(beanType);
for (Parameter ctorParam : canonical.getParameters()) {
_propFrom(propsByName, ctorParam.getName());
}
}
constructors = null;
} else {
constructors = new BeanConstructors(beanType);
if (isRecord) {
Constructor<?> canonical = RecordsHelpers.findCanonicalConstructor(beanType);
if (canonical == null) { // should never happen
throw new IllegalArgumentException(
"Unable to find canonical constructor of Record type `"+beanType.getClass().getName()+"`");
}
Constructor<?> canonical = _getCanonicalRecordConstructor(beanType);
constructors.addRecordConstructor(canonical);
// And then let's "seed" properties to ensure correct ordering
// of Properties wrt Canonical constructor parameters:
Expand Down Expand Up @@ -105,6 +117,15 @@ private POJODefinition _introspectDefinition(Class<?> beanType,
return new POJODefinition(beanType, props, constructors);
}

private Constructor<?> _getCanonicalRecordConstructor(Class<?> beanType) {
Constructor<?> canonical = RecordsHelpers.findCanonicalConstructor(beanType);
if (canonical == null) { // should never happen
throw new IllegalArgumentException(
"Unable to find canonical constructor of Record type `"+beanType.getClass().getName()+"`");
}
return canonical;
}

private static void _introspect(Class<?> currType, Map<String, PropBuilder> props,
int features, boolean isRecord)
{
Expand Down
19 changes: 19 additions & 0 deletions jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public record WrapperRecord(Cow cow, String hello) {
public record RecordWithWrapper(Cow cow, Wrapper nested, int someInt) {
}

// [jackson-jr#171]: Whether to serialize Records in declaration or alphabetical order
public record RecordNonAlphabetic171(int c, int b, int a) {
}

record SingleIntRecord(int value) { }
record SingleLongRecord(long value) { }
record SingleStringRecord(String value) { }
Expand Down Expand Up @@ -162,4 +166,19 @@ public void testSingleFieldRecords() throws Exception {
assertEquals("{\"value\":\"abc\"}", json);
assertEquals(inputStr, jsonHandler.beanFrom(SingleStringRecord.class, json));
}

// [jackson-jr#171]: Whether to serialize Records in declaration or alphabetical order
public void testRecordFieldWriteOrder() throws Exception
{
RecordNonAlphabetic171 input = new RecordNonAlphabetic171(1, 2, 3);

// Alphabetical order:
assertEquals("{\"a\":3,\"b\":2,\"c\":1}",
jsonHandler.without(JSON.Feature.WRITE_RECORD_FIELDS_IN_DECLARATION_ORDER).asString(input));

// Declaration order:
assertEquals("{\"c\":1,\"b\":2,\"a\":3}",
jsonHandler.with(JSON.Feature.WRITE_RECORD_FIELDS_IN_DECLARATION_ORDER).asString(input));
}
}

3 changes: 2 additions & 1 deletion release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ Modules:

2.19.0 (not yet released)

-
#171: Add a `JSON.Feature.WRITE_RECORD_FIELDS_IN_DECLARATION_ORDER` for
retaining Serialization order of Java Records (instead of alphabetic)

2.18.1 (28-Oct-2024)

Expand Down

0 comments on commit 78d0d56

Please sign in to comment.