Skip to content

getDelegateAdapter() does not work properly in TypeAdapterFactory used from JsonAdapter annotation #1028

@zman0900

Description

@zman0900

GSON's getDelegateAdapter method does not appear to work properly inside of a TypeAdapterFactory that is used through the @JsonAdapter annotation. This was found using GSON 2.8.0 and Java 7.

For example, given a class like:

public final class Thing {
    @JsonAdapter(NonEmptyMapAdapterFactory.class)
    private final Map<Integer, String> data;

    public Thing() {
        data = new HashMap<>();
    }
}

And a TypeAdapterFactory like:

public class NonEmptyMapAdapterFactory implements TypeAdapterFactory {

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
        if (!Map.class.isAssignableFrom(type.getRawType())) {
            return null;
        }

        //final TypeAdapter<T> delegate = gson.getAdapter(type);
        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);

        return new TypeAdapter<T>() {
            @Override
            public void write(final JsonWriter writer, T value) throws IOException {
                final Map map = (Map) value;
                if (map == null || map.isEmpty()) {
                    writer.nullValue();
                } else {
                    delegate.write(writer, value);
                }
            }

            @SuppressWarnings("unchecked")
            @Override
            public T read(final JsonReader reader) throws IOException {
                final T map = delegate.read(reader);
                if (map == null) {
                    return (T) new HashMap();
                } else {
                    return map;
                }
            }
        };
    }

}

The call to gson.getDelegateAdapter(this, type); returns an adapter of type "ReflectiveTypeAdapterFactory$Adapter". Calling gson.getAdapter(type); instead does return the expected "MapTypeAdapterFactory$Adapter", but this obviously won't work correctly if this adapter factory is instead used without the JsonAdapter annotation.

If the annotation is removed from the field in the Thing class and gson is instead set up like:

new GsonBuilder()
        .registerTypeAdapterFactory(new NonEmptyMapAdapterFactory())
        .create();

Then the custom TypeAdapterFactory shown above works exactly as expected: gson.getDelegateAdapter(this, type); returns a "MapTypeAdapterFactory$Adapter", but calling gson.getAdapter(type); returns the the same class, resulting in a stack overflow in the read and write methods.

The problem seems to happen because of this code at the beginning of getDelegateAdapter():

// Hack. If the skipPast factory isn't registered, assume the factory is being requested via
// our @JsonAdapter annotation.
if (!factories.contains(skipPast)) {
  skipPast = jsonAdapterFactory;
}

In the list of factories, jsonAdapterFactory comes after the entry for the MapTypeAdapterFactory, causing both to be skipped.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions