Serialize bean fields in class inheritance order
- 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
Assessment
This issue has not been assessed yet.