我们有一个使用GSON作为转换器的Retrofit,它调用一个向用户显示的卡片列表。一张卡片采用这种格式:
[
{
"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?如果我们试图添加这个新卡,但应用程序不能支持它,我想忽略它。
发布于 2018-09-14 23:22:49
我认为这个问题是因为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,而不是抛出的,您的列表将包含这个空元素。要真正忽略它,请确保在实现时调用list.filterNotNull()。
发布于 2019-02-22 22:32:53
我们也遇到了类似的问题,但是葛森并没有那么沉默。我们撞错了:
不能反序列化名为Y的X子类型;您忘记注册子类型了吗?
为了实现@pirho的解决方案,我们将RuntimeTypeAdapterFactory复制到我们的项目中。
1.)添加一个新字段
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 LINE2.)添加一个新方法:
public RuntimeTypeAdapterFactory<T> ignoreSubtype(String label) {
labelsToIgnore.add(label);
return this;
}3.)更新read方法在TypeAdapter中
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);
}最后,可以这样使用:
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");
...https://stackoverflow.com/questions/52333216
复制相似问题