Home » Android » java – GSON deserialization with generic types and generic field names

java – GSON deserialization with generic types and generic field names

Posted by: admin June 15, 2020 Leave a comment

Questions:

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

How to&Answers:

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.

Answer:

I would be using 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’ll have serialization w/o any boilerplates, except fact that you have to use Gson TypeAdapter for Animal class, like:

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

Where TypeAdapter should look smth 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;
    }
}

Answer:

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 doesn’t really reduce boilerplate code, but it should prevent you from having a separate body class with lists of classes that are just lists of your actual animals. It should make for you to just have your body class, and then a class for each animal with it’s stats.