We have a Retrofit API using GSON as the converter, and it makes a call for a list of cards to display to the user. A card follows this format:
[
{
"cardType": "user",
"data": {}
},
{
"cardType": "content",
"data": {}
}
]
The data
property varies between cards, so to resolve this we use GSON's RuntimeTypeAdapterFactory
:
final RuntimeTypeAdapterFactory<Card> factory = RuntimeTypeAdapterFactory
.of(Card.class, "cardType")
.registerSubtype(UserCard.class, "user")
...
.registerSubtype(ContentCard.class, "content");
However, we've found that if the API is updated to include a new cardType that we aren't expecting, this silently fails to deserialize. By silently I mean, response.isSuccessful()
still returns true, but the response.body()
is null. The only way we were able to determine the new card type was the problem was through trial and error.
Is there any way to have GSON ignore any cardTypes that we haven't registered? If we try to add this new card but the app can't support it I would like to just ignore it.
I think that the problem is due the fact that RuntimeTypeAdapterFactory
will throw an exception when trying to create adapter for unregistered type name (see excerpt from RuntimeTypeAdapterFactory
s source code):
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);
}
...
}
This might lead Retrofit
to truncate the whole response to null
.
Then see the declaration of 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);
}
If you were able to return null
instead of throwing that exception I guess Retrofit
could return all the sub classes it recognises.
This would be easy to handle by extending RuntimeTypeAdapterFactory
and overriding the problematic method but unfortunately class is declared as final
.
So you can either create a wrapping type adapter that calls the original RuntimeTypeAdapter inside it or as I would do it just copy the source for the class RuntimeTypeAdapterFactory
in to some package of your own and edit it correspoding your needs. It is Open Source. So the problematic part would be like:
if (jsonObject.has(typeFieldName)) {
log.warn("cannot serialize " + srcType.getName()
+ " because it already defines a field named " + typeFieldName);
return null;
}
To clarify, because you are now returning null
instead of throwing, your list will contain this null element. To truly ignore it, make sure you call list.filterNotNull()
at the implementation.