忽略 RuntimeTypeAdapterFactory 中未注册的子类型

Ada*_*331 4 java android json gson retrofit

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

[
    {
        "cardType": "user",
        "data": {}
    },
    {
        "cardType": "content",
        "data": {}
    }
]
Run Code Online (Sandbox Code Playgroud)

卡之间的属性data有所不同,因此为了解决这个问题,我们使用 GSON 的RuntimeTypeAdapterFactory

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

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

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

pir*_*rho 5

我认为问题是由于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);
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)

这可能会导致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);
}
Run Code Online (Sandbox Code Playgroud)

如果您能够返回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;
}
Run Code Online (Sandbox Code Playgroud)

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