使用泛型类型和通用字段名称进行GSON反序列化

时间:2016-01-07 16:31:22

标签: java android json generics gson

我们说我们有这样的结构:

JSON:

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

和相应的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;

但是现在我们说我们有相同的结构,但我们收到Cat而不是truck

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

我在Retrofit上使用GSON(默认Android json解析器),在给出解决方案的同时考虑这一点。

我们可以在这里使用泛型,所以响应看起来像:

private final Body<ListResponse<ItemWrapper<T>>但我们不能,因为字段名称是特定于类的。

问题:

如果不创建如此多的样板类,我可以自动序列化它吗?我并不需要单独的课程,例如BodyCatBodyTruckBodyAnyOtherClassInThisStructure,而我正在寻找避免使用它们的方法。

@EDIT:

由于继承混淆,我改变了类(猫,狗 - >猫,卡车),这里使用的类例如不要相互延伸

3 个答案:

答案 0 :(得分:7)

一个想法是定义一个自定义通用反序列化器。它的泛型类型将表示包含在Body实例中的列表元素的具体类。

假设以下课程:

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;

    ...
}

反序列化器假定json的结构总是相同的,因为你有一个包含名为&#34; body&#34;的对象的对象。此外,它假定此正文的第一个键值对中的值是一个列表。

现在,对于json数组中的每个元素,我们需要再次获取与每个键关联的内部对象。我们反序列化该值并将其放在列表中。

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

对于最后一步,您只需要创建相应的类型,在解析器中实例化并注册适配器。例如卡车:

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);

如果你想摆脱Body类,你甚至可以直接从适配器返回列表。

使用卡车,您将获得[1_big_12, 2_super big_128]作为输出,[1_cat1, 2_cat2]与猫一起。

答案 1 :(得分:2)

我会用这种方法:

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

public class Animal {}

public class Dog extends Animal {}

public class Cat extends Animal {}

在这种情况下,除了必须对TypeAdapter类使用Gson Animal这样的事实外,您将对所有样板进行序列化,例如:

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

TypeAdapter应该看起来像:

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 :(得分:-1)

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

}

这并没有真正减少样板代码,但它应该会阻止你有一个单独的body类,其中的类列表只是你真实动物的列表。它应该让你只需要你的身体类,然后用每个动物的类来获得它的统计数据。