Retrofit 2、GSON和自定义反序列化器

5
我是一个有用的助手,可以翻译文本。

我已经使用Retrofit 2和一些POJO对象有一段时间了。这是一个很棒的库,功能非常好,但它要求我写一些可怕的、混乱的模型,我想摆脱它们。

让我给你看看……我有以下JSON:

    {
    "things": [
        {
            "thing": {
                "id": 823,
                "created_at": "2016-02-09T22:55:07.153Z",
                "published_at": "2016-02-10T19:23:42.666Z",
                "downloads": 16073,
                "size": 10716291
            }
        },
    ],
    "count": 4,
    "links": {}
    }

使用POJO Schema生成器会创建不必要的类,使得代码维护变得困难。
这将会创建以下内容:
Things.java
    @SerializedName("things")
    @Expose
    public List<Things_> things = new ArrayList<>();
Things_.java
    @SerializedName("thing")
    @Expose
    private Thing__ thing;
Things__.java
    // Insert normal variables and getter/setters here

我已经简化了一下,因为这只是为了表达想法。在我的使用中,我当然将这些类重命名,使它们更易于管理。但我认为有一种方法可以简单地跳过Thing和Thing_,让我返回实际模型数据的列表(Thing__),并且可以删除其中两个类,“Thing__”可以简称为“Thing”。
我是对的。 Gson允许自定义反序列化,让我实现了这个目标。我快速地编写了一个Deserializer并使用了一个适当的TypeToken。
    Gson gson = new GsonBuilder()
            .registerTypeAdapter(new TypeToken<ArrayList<Thing>>(){}.getType(), new AddonDeserializer())
            .create();

    List<Thing> model = gson.fromJson(jsonString, new TypeToken<ArrayList<Thing>>(){}.getType());

果然,传递上面的Json确实给了我一个可用的物品列表。

引入Retrofit 2!在我的Retrofit 2实例中添加registerTypeAdapter()(通过我的gson实例),现在我得到了一个错误消息:

 Expected BEGIN_ARRAY but was BEGIN_OBJECT

这可能是因为我的调用方式不正确:
@Get("end/of/url/here/{slug}.json") 
Call<List<Thing>> getThings(@Path("slug") String slug);

我的 Json 以一个对象 ("things") 开始,其中包含一个 "thing" 对象的数组。我的反序列化程序对此没有任何问题:

public class ThingDeserializer implements JsonDeserializer<List<Thing>> {
    @Override
    public List<Thing> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        JsonArray array = json.getAsJsonObject().getAsJsonArray("things");

        ArrayList<Thing> list = new ArrayList<>();

        for (JsonElement anArray : array) {
            list.add((Thing) context.deserialize(anArray.getAsJsonObject().get("thing").getAsJsonObject(), Thing.class));
        }

        return list;
    }
}

无论如何,感谢您阅读完这个很长的问题!

我需要做些什么不同的事情或者如何操作Retrofit才能像我编写的Gson Deserializer一样工作?我现在的方法可以运行,但是为了学习新东西并编写更好维护的代码,我想想找出更好的方法。我可以使用ResponseBody回调和通过我的Deserializer将Json传递,但肯定有更好的方法。


1
我不明白为什么你没有一个类,我们称之为ThingsResponse,其中包含一个Things列表和另外两个属性count和links。并使用该POJO从API调用中获取Retrofit响应。 - Jonathan Aste
@JonathanAste 我可以这样做。基本上这就是我现在拥有的。但是对于一个模型,有三个类。最终,我将会解析一个XML文件,其中布局略有不同,但是需要转换为相同的模型。如果能够将它们简化一些,那将非常有用。我只是想知道是否有一种方法可以让Retrofit与我的反序列化程序配合使用,而无需手动获取原始响应并进行解析,因为RF应该为我完成这项工作。 - MattBoothDev
嗯,看起来你可以创建一个'TypeAdapterFactory'而不是一个反序列化器。检查一下这个链接是否能帮到你:https://dev59.com/TYjca4cB1Zd3GeqPqQL3#28736167 - Jonathan Aste
@JonathanAste 那个方法不起作用。结果还是一样的 :( 不过还是谢谢你的帮助。 - MattBoothDev
没事了,我想我搞定了! - MattBoothDev
1个回答

8

多亏了@JonathanAste,我终于弄明白了。

我需要一个TypeAdapterFactory实现,而不是一个Deserializer。

public class ThingTypeAdapterFactory implements TypeAdapterFactory {

    public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {

        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
        final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);

       return new TypeAdapter<T>() {

            public void write(JsonWriter out, T value) throws IOException {
                delegate.write(out, value);
            }

            public T read(JsonReader in) throws IOException {

                JsonElement jsonElement = elementAdapter.read(in);
                if (jsonElement.isJsonObject()) {
                    JsonObject jsonObject = jsonElement.getAsJsonObject();
                    if (jsonObject.has("things") && jsonObject.get("things").isJsonArray())
                    {
                        jsonElement = jsonObject.get("things");
                    }
                }

                if (jsonElement.isJsonObject()) {
                    JsonObject jsonObject = jsonElement.getAsJsonObject();
                    if (jsonObject.has("thing") && jsonObject.get("thing").isJsonObject())
                    {
                        jsonElement = jsonObject.get("thing");
                    }
                }

                return delegate.fromJsonTree(jsonElement);
            }
        }.nullSafe();
    }
}

并且这使得你可以使用

@GET("end/of/url/here/{slug}.json") 
Call<List<Thing>> getThings(@Path("slug") String slug);

没有问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接