-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Parse nested json into flat model class objects #2555
Comments
For the example you provided, you could solve this with a custom Flattening type adapter proof-of-concept (click to expand)class FlatteningAdapterFactory implements TypeAdapterFactory {
// Called by Gson
public FlatteningAdapterFactory() {}
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
TypeAdapter<JsonObject> jsonObjectAdapter = gson.getAdapter(JsonObject.class);
TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, type);
return new TypeAdapter<T>() {
private final char separator = '.';
private void flattenInto(String name, JsonObject toFlatten, JsonObject destination) {
for (Map.Entry<String, JsonElement> entry : toFlatten.entrySet()) {
String flattenedName = name + separator + entry.getKey();
if (destination.has(flattenedName)) {
throw new IllegalArgumentException("Duplicate name: " + flattenedName);
}
destination.add(flattenedName, entry.getValue());
}
}
@Override
public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.skipValue();
return null;
}
/*
* Flattens nested JsonObject values, e.g.:
* {
* "a": 1,
* "b": {
* "x": true,
* "y": 2
* }
* }
* Becomes
* {
* "a": 1,
* "b.x": true,
* "b.y": 2
* }
*/
JsonObject jsonObject = jsonObjectAdapter.read(in);
JsonObject flattened = new JsonObject();
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
String name = entry.getKey();
JsonElement value = entry.getValue();
// Flatten the value
if (value instanceof JsonObject) {
flattenInto(name, (JsonObject) value, flattened);
}
// But also add the non-flattened value in case this entry should not actually be flattened
// The delegate adapter will then ignore either the flattened or the non-flattened entries
if (flattened.has(name)) {
throw new IllegalArgumentException("Duplicate name: " + name);
}
flattened.add(name, value);
}
// Now read the flattened JsonObject using the delegate adapter
return delegateAdapter.fromJsonTree(flattened);
}
@Override
public void write(JsonWriter out, T value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
/*
* Expands the flattened JsonObject, e.g.:
* {
* "a": 1,
* "b.x": true,
* "b.y": 2
* }
* Becomes
* {
* "a": 1,
* "b": {
* "x": true,
* "y": 2
* }
* }
*/
JsonObject flattened = (JsonObject) delegateAdapter.toJsonTree(value);
JsonObject expanded = new JsonObject();
Map<String, JsonElement> expandedAsMap = expanded.asMap();
for (Map.Entry<String, JsonElement> entry : flattened.entrySet()) {
String name = entry.getKey();
JsonElement entryValue = entry.getValue();
// Expand the flattened entry
int separatorIndex = name.indexOf(separator);
if (separatorIndex != -1) {
String namePrefix = name.substring(0, separatorIndex);
String nameSuffix = name.substring(separatorIndex + 1);
JsonObject nestedObject =
(JsonObject) expandedAsMap.computeIfAbsent(namePrefix, k -> new JsonObject());
if (nestedObject.has(nameSuffix)) {
throw new IllegalArgumentException("Duplicate name: " + nameSuffix);
}
nestedObject.add(nameSuffix, entryValue);
} else {
if (expanded.has(name)) {
throw new IllegalArgumentException("Duplicate name: " + name);
}
expanded.add(name, entryValue);
}
}
// Finally write the expanded JsonObject to the actual writer
jsonObjectAdapter.write(out, expanded);
}
};
}
} Usage: @JsonAdapter(FlatteningAdapterFactory.class)
class MyClass {
int id;
String name;
@SerializedName("address.street")
String street;
@SerializedName("address.city")
String city;
@Override
public String toString() {
return "MyClass [id=" + id + ", name=" + name + ", street=" + street + ", city=" + city + "]";
}
} Gson gson = new Gson();
MyClass deserialized = gson.fromJson(
"{\"id\":1,\"name\":\"my name\",\"address\":{\"street\":\"my lane\",\"city\":\"my city\"}}",
MyClass.class
);
System.out.println(deserialized);
String json = gson.toJson(deserialized);
System.out.println(json); I haven't tested this extensively though, it might not be very performant, and it might not work well for more complex cases. There are also multiple Stack Overflow questions and answers regarding how to flatten model classes, which could maybe be used as solution here as well. |
Problem solved by the feature
No need of creating nested classes according to the json file.
Feature description
if i have a nested json like :
{ id : 1 ,
name : "myname" ,
address : {
street : "my lane" ,
city : "mycity" }
}
I want to parse this json into target model class but cannot parse without creating nested class 'address' inside my target class.
There should be a method to write like @serialized("address.street") in model class field which automatically get the nested street value from address so that i do not need to create a nested class in order to parse this json into object .
Target model class will be Model> id, name, street, city
Alternatives / workarounds
alternative is simply create nested class Model> id , name, Address> street, city
The text was updated successfully, but these errors were encountered: