首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >忽略RuntimeTypeAdapterFactory中未注册的子类型

忽略RuntimeTypeAdapterFactory中未注册的子类型
EN

Stack Overflow用户
提问于 2018-09-14 13:39:21
回答 2查看 966关注 0票数 3

我们有一个使用GSON作为转换器的Retrofit,它调用一个向用户显示的卡片列表。一张卡片采用这种格式:

代码语言:javascript
复制
[
    {
        "cardType": "user",
        "data": {}
    },
    {
        "cardType": "content",
        "data": {}
    }
]

data属性因卡而异,因此为了解决这个问题,我们使用GSON的RuntimeTypeAdapterFactory

代码语言:javascript
复制
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?如果我们试图添加这个新卡,但应用程序不能支持它,我想忽略它。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-09-14 23:22:49

我认为这个问题是因为RuntimeTypeAdapterFactory在试图为未注册的类型名创建适配器时会抛出一个异常(请参阅RuntimeTypeAdapterFactory源代码中的摘录):

代码语言:javascript
复制
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的声明

代码语言:javascript
复制
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的源代码复制到您自己的某个包中,并根据您的需要对其进行编辑。它是开源的。所以有问题的部分应该是:

代码语言:javascript
复制
if (jsonObject.has(typeFieldName)) {
    log.warn("cannot serialize " + srcType.getName()
            + " because it already defines a field named " + typeFieldName);
    return null;
}

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

票数 4
EN

Stack Overflow用户

发布于 2019-02-22 22:32:53

我们也遇到了类似的问题,但是葛森并没有那么沉默。我们撞错了:

不能反序列化名为Y的X子类型;您忘记注册子类型了吗?

为了实现@pirho的解决方案,我们将RuntimeTypeAdapterFactory复制到我们的项目中。

1.)添加一个新字段

代码语言:javascript
复制
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>();
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>();
private final List<String> labelsToIgnore = new ArrayList<>(); // ADD THIS LINE

2.)添加一个新方法:

代码语言:javascript
复制
public RuntimeTypeAdapterFactory<T> ignoreSubtype(String label) {
    labelsToIgnore.add(label);
    return this;
}

3.)更新read方法在TypeAdapter

代码语言:javascript
复制
public R read(final JsonReader in) {
    JsonElement jsonElement = Streams.parse(in);
    JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
    if (labelJsonElement == null) {
        throw new JsonParseException("cannot deserialize " + baseType
                + " because it does not define a field named " + typeFieldName);
    }
    String label = labelJsonElement.getAsString();
    @SuppressWarnings("unchecked") // registration requires that subtype extends T
            TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
    if (delegate == null) {
        // ------------- ADD THIS:
        if (labelsToIgnore.contains(label)) {
            return null;
        } else {
            throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
                    + label + "; did you forget to register a subtype?");
        }
        // -------------
    }
    return delegate.fromJsonTree(jsonElement);
}

最后,可以这样使用:

代码语言:javascript
复制
RuntimeTypeAdapterFactory<BaseClass> myTypeAdapterFactory 
= RuntimeTypeAdapterFactory
                .of(BaseClass.class, "type")
                .ignoreSubtype("type to ignore") // ADD THIS LINE
                .registerSubtype(SubtypeA.class, "type_a")
                .registerSubtype(SubtypeB.class, "type_b");          
                ...
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52333216

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档