google / google/gson

Serialize bean fields in class inheritance order

Open
#2,309 3 comments 1 reaction 0 assignees View on GitHub
enhancement
Dominant language
Java
Stars
24.2k
Forks
4.5k
Avg merge
6d 4h
Merged PRs (30d)
12

Description

# Problem solved by the feature

Consider the following skeleton resource model to be exposed through a REST/JSON API:

```java
public abstract static class BaseResource {
public String id;
}

public static final class SpecificResource extends BaseResource {
public String data;
}
```

With respect to output readability, especially when models grow large, it would be very convenient to have fields serialized in definition order, as:

```json
{
"id": "/resources/specific/123",
"data": "resource payload"
}
```

However, Gson currently serializes them backward, as:

```json
{
"data": "resource payload",
"id": "/resources/specific/123"
}
```

Quick test with:

```java
public static void main(final String... args) {

final SpecificResource resource=new SpecificResource();

resource.id="/resources/specific/123";
resource.data="resource payload";

System.out.println(new GsonBuilder()
.setPrettyPrinting()
.create()
.toJson(resource)
);

}
```

# Feature description

Reverse class scanning order in:

https://github.com/google/gson/blob/6c27553c8375515e9522facf5ea82b8161d5ffb2/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java#L228

The current code scans classes upwards, accumulating fields in a ordered `LinkedHashMap` along the lines of:

```java
private Map getBoundFields(
Gson context,
TypeToken type, Class raw,
boolean blockInaccessible, boolean isRecord
) {

Map result = new LinkedHashMap<>();

while (raw != Object.class) {

// …

result.put(name, boundField);

// …

type = TypeToken.get(
$Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
raw = type.getRawType();

}

return result;

}
```

Downward class scanning could be easily performed with the assistance of a recursive accumulator method, like for instance:

```java
private Map getBoundFields(
Gson context,
TypeToken type, Class raw,
boolean blockInaccessible, boolean isRecord
) {

return getBoundFields(context, type, raw, blockInaccessible, isRecord, new LinkedHashMap<>());

}

private Map getBoundFields(
Gson context,
TypeToken type, Class raw,
boolean blockInaccessible, boolean isRecord,
Map result // ‹‹‹‹ accumulator
) {

if (raw != Object.class) {

// scan superclass

TypeToken superType = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
Class superRaw = superType.getRawType();

getBoundFields(context, superType, superRaw, blockInaccessible, isRecord, result);


// scan current class

// …

result.put(name, boundField);

// …

}

return result;

}
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.