-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Open
Labels
Description
Problem solved by the feature
Consider the following skeleton resource model to be exposed through a REST/JSON API:
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:
{
"id": "/resources/specific/123",
"data": "resource payload"
}However, Gson currently serializes them backward, as:
{
"data": "resource payload",
"id": "/resources/specific/123"
}Quick test with:
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:
gson/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java
Line 228 in 6c27553
| private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw, |
The current code scans classes upwards, accumulating fields in a ordered LinkedHashMap along the lines of:
private Map<String, ReflectiveTypeAdapterFactory.BoundField> getBoundFields(
Gson context,
TypeToken<?> type, Class<?> raw,
boolean blockInaccessible, boolean isRecord
) {
Map<String, ReflectiveTypeAdapterFactory.BoundField> 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:
private Map<String, ReflectiveTypeAdapterFactory.BoundField> getBoundFields(
Gson context,
TypeToken<?> type, Class<?> raw,
boolean blockInaccessible, boolean isRecord
) {
return getBoundFields(context, type, raw, blockInaccessible, isRecord, new LinkedHashMap<>());
}
private Map<String, ReflectiveTypeAdapterFactory.BoundField> getBoundFields(
Gson context,
TypeToken<?> type, Class<?> raw,
boolean blockInaccessible, boolean isRecord,
Map<String, ReflectiveTypeAdapterFactory.BoundField> 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;
}Philzen