首页 > 解决方案 > 忽略 RuntimeTypeAdapterFactory 中未注册的子类型

问题描述

我们有一个使用 GSON 作为转换器的 Retrofit API,它会调用要显示给用户的卡片列表。卡片遵循以下格式:

[
    {
        "cardType": "user",
        "data": {}
    },
    {
        "cardType": "content",
        "data": {}
    }
]

属性因卡而data异,因此我们使用 GSON 来解决此问题RuntimeTypeAdapterFactory

final RuntimeTypeAdapterFactory<Card> factory = RuntimeTypeAdapterFactory
        .of(Card.class, "cardType")
        .registerSubtype(UserCard.class, "user")
        ...
        .registerSubtype(ContentCard.class, "content");

但是,我们发现如果更新 API 以包含我们不期望的新cardType,这会默默地反序列化失败。我的意思是默默地response.isSuccessful()返回true,但response.body()为null。我们能够确定新卡类型的唯一方法是问题是通过反复试验。

有没有办法让 GSON 忽略我们尚未注册的任何 cardTypes?如果我们尝试添加这张新卡但应用程序不支持它,我想忽略它。

标签: javaandroidjsongsonretrofit

解决方案


我认为问题在于尝试为未注册的类型名称创建适配器时会引发异常(参见s 源代码的RuntimeTypeAdapterFactory摘录):RuntimeTypeAdapterFactory

public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
    ...

    // typeFieldName is the type name that is given when registering the sub type
    if (jsonObject.has(typeFieldName)) {
        throw new JsonParseException("cannot serialize " + srcType.getName()
            + " because it already defines a field named " + typeFieldName);
    }

    ...
}

这可能会导致Retrofit将整个响应截断为null.

然后看声明TypeAdapterFactory

public interface TypeAdapterFactory {
    /**
    * Returns a type adapter for {@code type}, or null if this factory doesn't
    * support {@code type}.
    */
    <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type);
}

如果您能够返回null而不是抛出该异常,我想Retrofit可以返回它识别的所有子类。

这很容易通过扩展RuntimeTypeAdapterFactory和覆盖有问题的方法来处理,但不幸的是,类被声明为final.

因此,您可以创建一个包装类型适配器,在其中调用原始 RuntimeTypeAdapter,或者像我一样将类的源代码复制RuntimeTypeAdapterFactory到您自己的某个包中,然后根据您的需要对其进行编辑。它是开源的。所以有问题的部分就像:

if (jsonObject.has(typeFieldName)) {
    log.warn("cannot serialize " + srcType.getName()
            + " because it already defines a field named " + typeFieldName);
    return null;
}

澄清一下,因为您现在返回null而不是抛出,所以您的列表将包含此 null 元素。要真正忽略它,请确保您调用list.filterNotNull()实现。


推荐阅读