diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index 73c0705958..49aea28a95 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -642,33 +642,28 @@ public TypeAdapter getAdapter(TypeToken type) { * types, our stats factory will not count the number of String or primitives that will be * read or written. * - *

If {@code skipPast} is {@code null} or a factory which has neither been registered - * on the {@link GsonBuilder} nor specified with the {@link JsonAdapter @JsonAdapter} annotation - * on a class, then this method behaves as if {@link #getAdapter(TypeToken)} had been called. - * This also means that for fields with {@code @JsonAdapter} annotation this method behaves - * normally like {@code getAdapter} (except for corner cases where a custom {@link InstanceCreator} - * is used to create an instance of the factory). + *

If {@code skipPast} is a factory which has neither been registered on the {@link GsonBuilder} + * nor specified with the {@link JsonAdapter @JsonAdapter} annotation on a class, then this + * method behaves as if {@link #getAdapter(TypeToken)} had been called. This also means that + * for fields with {@code @JsonAdapter} annotation this method behaves normally like {@code getAdapter} + * (except for corner cases where a custom {@link InstanceCreator} is used to create an + * instance of the factory). * * @param skipPast The type adapter factory that needs to be skipped while searching for * a matching type adapter. In most cases, you should just pass this (the type adapter - * factory from where {@code getDelegateAdapter} method is being invoked). May be {@code null}. + * factory from where {@code getDelegateAdapter} method is being invoked). * @param type Type for which the delegate adapter is being searched for. * * @since 2.2 */ public TypeAdapter getDelegateAdapter(TypeAdapterFactory skipPast, TypeToken type) { + Objects.requireNonNull(skipPast, "skipPast must not be null"); Objects.requireNonNull(type, "type must not be null"); - if (skipPast != null) { - if (jsonAdapterFactory.isClassJsonAdapterFactory(type.getRawType(), skipPast)) { - skipPast = jsonAdapterFactory; - } else if (!factories.contains(skipPast)) { - // Probably a factory from @JsonAdapter on a field - skipPast = null; - } - } - - if (skipPast == null) { + if (jsonAdapterFactory.isClassJsonAdapterFactory(type.getRawType(), skipPast)) { + skipPast = jsonAdapterFactory; + } else if (!factories.contains(skipPast)) { + // Probably a factory from @JsonAdapter on a field return getAdapter(type); } diff --git a/gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java b/gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java index 334105834a..573de3324d 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java +++ b/gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java @@ -45,7 +45,13 @@ private static class DummyTypeAdapterFactory implements TypeAdapterFactory { * Factory used for {@link TreeTypeAdapter}s created for {@code @JsonAdapter} * on a class. */ - private static final TypeAdapterFactory TREE_TYPE_DUMMY_FACTORY = new DummyTypeAdapterFactory(); + private static final TypeAdapterFactory TREE_TYPE_CLASS_DUMMY_FACTORY = new DummyTypeAdapterFactory(); + + /** + * Factory used for {@link TreeTypeAdapter}s created for {@code @JsonAdapter} + * on a field. + */ + private static final TypeAdapterFactory TREE_TYPE_FIELD_DUMMY_FACTORY = new DummyTypeAdapterFactory(); private final ConstructorConstructor constructorConstructor; /** @@ -115,12 +121,17 @@ TypeAdapter getTypeAdapter(ConstructorConstructor constructorConstructor, Gso ? (JsonDeserializer) instance : null; - TypeAdapterFactory skipPast = null; + TypeAdapterFactory skipPast; if (isClassAnnotation) { - // Use dummy `skipPast` value; otherwise TreeTypeAdapter's call to `Gson.getDelegateAdapter` would - // cause infinite recursion because it would keep returning adapter specified by @JsonAdapter - skipPast = TREE_TYPE_DUMMY_FACTORY; + // Use dummy `skipPast` value and put it into factory map; otherwise TreeTypeAdapter's call to + // `Gson.getDelegateAdapter` would cause infinite recursion because it would keep returning the + // adapter specified by @JsonAdapter + skipPast = TREE_TYPE_CLASS_DUMMY_FACTORY; adapterFactoryMap.put(type.getRawType(), skipPast); + } else { + // Use dummy `skipPast` value, but don't put it into factory map; this way `Gson.getDelegateAdapter` + // will return regular adapter for field type without actually skipping past any factory + skipPast = TREE_TYPE_FIELD_DUMMY_FACTORY; } @SuppressWarnings({ "unchecked", "rawtypes" }) TypeAdapter tempAdapter = new TreeTypeAdapter(serializer, deserializer, gson, type, skipPast, nullSafe); diff --git a/gson/src/test/java/com/google/gson/GsonTest.java b/gson/src/test/java/com/google/gson/GsonTest.java index 806132f51e..d8598ec953 100644 --- a/gson/src/test/java/com/google/gson/GsonTest.java +++ b/gson/src/test/java/com/google/gson/GsonTest.java @@ -325,12 +325,12 @@ public TypeAdapter create(Gson gson, TypeToken type) { TypeToken type = TypeToken.get(Number.class); + assertThrows(NullPointerException.class, () -> gson.getDelegateAdapter(null, type)); + assertThrows(NullPointerException.class, () -> gson.getDelegateAdapter(factory1, null)); + // For unknown factory the first adapter for that type should be returned assertThat(gson.getDelegateAdapter(new DummyFactory(new DummyAdapter(0)), type)).isEqualTo(adapter2); - // For null as 'skipPast' the first adapter for that type should be returned - assertThat(gson.getDelegateAdapter(null, type)).isEqualTo(adapter2); - assertThat(gson.getDelegateAdapter(factory2, type)).isEqualTo(adapter1); // Default Gson adapter should be returned assertThat(gson.getDelegateAdapter(factory1, type)).isNotInstanceOf(DummyAdapter.class);