Skip to content Skip to sidebar Skip to footer

Gson Deserialization With Generic Types And Generic Field Names

Let's say we have structure like this: JSON: { 'body': { 'cats': [ { 'cat': { 'id': 1, 'title': 'cat1' } }, { 'c

Solution 1:

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:

classBody<T> {
    private List<T> list;

    publicBody(List<T> list) {
        this.list = list;
    }
}

classCat {
    privateint id;
    private String title;

    ...
}

classTruck {
    privateint id;
    private String engine;
    privateint 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.

classCustomJsonDeserializer<T> implementsJsonDeserializer<Body<T>> {

    privatefinal Class<T> clazz;

    publicCustomJsonDeserializer(Class<T> clazz) {
        this.clazz = clazz;
    }

    @Overridepublic Body<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)throws JsonParseException {
        JsonObjectbody= json.getAsJsonObject().getAsJsonObject("body");
        JsonArrayarr= body.entrySet().iterator().next().getValue().getAsJsonArray();
        List<T> list = newArrayList<>();
        for(JsonElement element : arr) {
            JsonElementinnerElement= element.getAsJsonObject().entrySet().iterator().next().getValue();
            list.add(context.deserialize(innerElement, clazz));
        }
        returnnewBody<>(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:

TypetruckType=newTypeToken<Body<Truck>>(){}.getType();
Gsongson=newGsonBuilder()
    .registerTypeAdapter(truckType, newCustomJsonDeserializer<>(Truck.class))
    .create();
Body<Truck> body = gson.fromJson(newFileReader("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.

Solution 2:

I would be using this approach:

publicclassBody {
    private List<Animal> animals;
   }
}

publicclassAnimal {}

publicclassDogextendsAnimal {}

publicclassCatextendsAnimal {}

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 = newGsonBuilder()
                    .registerTypeAdapter(Animal.class, newAnimalSerializer())
                    .create();

Where TypeAdapter should look smth like:

publicclassAnimalSerializerimplementsJsonSerializer<Animal>, JsonDeserializer<Animal> {

    private static final String CLASS_META_KEY="clz";

    @Overridepublic 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);
        elseif(src instanceof Dog)
            element = context.serialize(src, Dog.class);
        elsethrow new IllegalArgumentException("Unspecifiad class serializer for "+src.getClass().getName());
        element.getAsJsonObject().addProperty(CLASS_META_KEY, src.getClass().getCanonicalName());
        return element;
    }

    @Overridepublic 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;
    }
}

Solution 3:

publicclassbody {

    private List<cats> cats;
    private List<dogs> dogs;

    publicclasscats {
        private list<cat> cat;
   }
    publicclassdogs {
        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.

Post a Comment for "Gson Deserialization With Generic Types And Generic Field Names"