Let's say we have structure like this:
JSON:
{
"body": {
"cats": [
{
"cat": {
"id": 1,
"title": "cat1"
}
},
{
"cat": {
"id": 2,
"title": "cat2"
}
}
]
}
}
And corresponding POJO:
Response.class
private final Body body;
Body.class
private final Collection<CatWrapper> cats
CatWrapper.class
private final Cat cat
Cat.class
private final int id;
private final String title;
But let say now we have the same structure, but instead of Cat
we receive truck
{
"body": {
"trucks": [
{
"truck": {
"id": 1,
"engine": "big",
"wheels": 12
}
},
{
"truck": {
"id": 2,
"engine": "super big",
"wheels": 128
}
}
]
}
}
I'm using GSON (default Retrofit
json parser) on Android
, consider this while giving solutions.
We could use generics here so response would look like:
private final Body<ListResponse<ItemWrapper<T>>
but we can't since the field names are specific to a class.
QUESTION:
What Can I do to serialize it automaticaly without creating so many boilerplate classes? I don't really need separate classes like BodyCat
, BodyTruck
, BodyAnyOtherClassInThisStructure
and I'm looking for a way to avoid having them.
@EDIT:
I've change classes (cat, dog -> cat,truck) due to inheritance confusion, classes used here as example DO NOT extends one another
One idea would be to define a custom generic deserializer. Its generic type will represent the concrete class of the list's elements wrapped in a Body
instance.
Assuming the following classes:
class Body<T> {
private List<T> list;
public Body(List<T> list) {
this.list = list;
}
}
class Cat {
private int id;
private String title;
...
}
class Truck {
private int id;
private String engine;
private int wheels;
...
}
The deserializer assumes that the structure of the json is always the same, in the sense that you have an object that contains an object named "body". Also it assumes that the value in the first key-value pair of this body is a list.
Now for each element in the json array, we need again to fetch the inner object associated with each key. We deserialize this value and put it in the list.
class CustomJsonDeserializer<T> implements JsonDeserializer<Body<T>> {
private final Class<T> clazz;
public CustomJsonDeserializer(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public Body<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject body = json.getAsJsonObject().getAsJsonObject("body");
JsonArray arr = body.entrySet().iterator().next().getValue().getAsJsonArray();
List<T> list = new ArrayList<>();
for(JsonElement element : arr) {
JsonElement innerElement = element.getAsJsonObject().entrySet().iterator().next().getValue();
list.add(context.deserialize(innerElement, clazz));
}
return new Body<>(list);
}
}
For the final step, you just need to create the corresponding type, instantiate and register the adapter in the parser. For instance for trucks:
Type truckType = new TypeToken<Body<Truck>>(){}.getType();
Gson gson = new GsonBuilder()
.registerTypeAdapter(truckType, new CustomJsonDeserializer<>(Truck.class))
.create();
Body<Truck> body = gson.fromJson(new FileReader("file.json"), truckType);
You can even return the list directly from the adapter if you want to get rid of the Body
class.
With the trucks you'll get [1_big_12, 2_super big_128]
as output and [1_cat1, 2_cat2]
with the cats.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With