GSON deserialization with typical common field types and names

Let's say we have a structure like this:

JSON:

{ "body": { "cats": [ { "cat": { "id": 1, "title": "cat1" } }, { "cat": { "id": 2, "title": "cat2" } } ] } } 

And the 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 suppose now we have the same structure, but instead of Cat we get truck

 { "body": { "trucks": [ { "truck": { "id": 1, "engine": "big", "wheels": 12 } }, { "truck": { "id": 2, "engine": "super big", "wheels": 128 } } ] } } 

I use GSON (default Retrofit json parser) on Android , keep this in mind when offering solutions.

We could use generics here to make the answer look like this:

private final Body<ListResponse<ItemWrapper<T>> but we cannot, as field names are class specific.

QUESTION:

What can I do to serialize it automatically without creating as many template classes? I don't need separate classes like BodyCat , BodyTruck , BodyAnyOtherClassInThisStructure and I'm looking for a way to avoid them.

@EDIT:

I changed the classes (cat, dog → cat, truck) due to the confusion of inheritance, the classes used here as an example DO NOT extend each other

+7
java json android generics gson
source share
3 answers

One idea would be to define a custom universal deserializer. Its generic type will be a specific class of list items wrapped in an instance of Body .

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; ... } 

Deserializer assumes that the json structure is always the same, in the sense that you have an object containing an object named "body". It is also assumed 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 to get the internal object associated with each key again. We deserialize this value and put it on 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 last step, you just need to create the appropriate type, instantiate and register the adapter in the parser. For example, 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 trucks you will get [1_big_12, 2_super big_128] as output and [1_cat1, 2_cat2] with cats.

+7
source share

I would use this approach:

 public class Body { private List<Animal> animals; } } public class Animal {} public class Dog extends Animal {} public class Cat extends Animal {} 

In this case, you will have serialization without any templates, except for the fact that you must use the Gson TypeAdapter for the Animal class, for example:

 Gson gson = new GsonBuilder() .registerTypeAdapter(Animal.class, new AnimalSerializer()) .create(); 

Where the TypeAdapter should look something like:

 public class AnimalSerializer implements JsonSerializer<Animal>, JsonDeserializer<Animal> { private static final String CLASS_META_KEY="clz"; @Override public JsonElement serialize(Animal src, Type typeOfSrc, JsonSerializationContext context) { JsonElement element=null; if (src == null) { return element; } if(src instanceof Cat) element = context.serialize(src, Cat.class); else if(src instanceof Dog) element = context.serialize(src, Dog.class); else throw new IllegalArgumentException("Unspecifiad class serializer for "+src.getClass().getName()); element.getAsJsonObject().addProperty(CLASS_META_KEY, src.getClass().getCanonicalName()); return element; } @Override public Field deserialize(JsonElement jsonElement, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { Class<?> clz; Animal animal; JsonObject object = jsonElement.getAsJsonObject(); if (object.has(CLASS_META_KEY)) { String className = object.get(CLASS_META_KEY).getAsString(); try { clz = Class.forName(className); } catch (Exception e) { Log.e(TAG, "Can't deserialize class="+className,e); clz = Animal.class; } animal = context.deserialize(jsonElement, clz); } else { animal = context.deserialize(jsonElement, typeOfT); } return animal; } } 
+2
source share
 public class body { private List<cats> cats; private List<dogs> dogs; public class cats { private list<cat> cat; } public class dogs { private list<dog> dog; } } 

This does not really reduce the template code, but it should prevent you from having a separate body class with lists of classes that are only lists of your real animals. He should make for you just his body class, and then a class for each animal with its statistics.

-one
source share

All Articles