Ignore unregistered subtypes in RuntimeTypeAdapterFactory

AdamMc331 :

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.

pirho :

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 RuntimeTypeAdapterFactorys 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.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=94656&siteId=1