Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion gson/src/main/java/com/google/gson/internal/Excluder.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.Since;
import com.google.gson.annotations.Until;
import com.google.gson.internal.bind.SerializationDelegatingTypeAdapter;
import com.google.gson.internal.reflect.ReflectionHelper;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
Expand Down Expand Up @@ -118,7 +119,7 @@ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return null;
}

return new TypeAdapter<T>() {
return new SerializationDelegatingTypeAdapter<T>() {
/**
* The delegate is lazily created because it may not be needed, and creating it may fail.
* Field has to be {@code volatile} because {@link Gson} guarantees to be thread-safe.
Expand Down Expand Up @@ -152,6 +153,13 @@ private TypeAdapter<T> delegate() {
}
return d;
}

@Override
public TypeAdapter<T> getSerializationDelegate() {
// When skipSerialize is true this adapter handles serialization itself (writes null),
// so there is no delegation. Otherwise it delegates to the next adapter in the chain.
return skipSerialize ? this : delegate();
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,7 @@ public void write(JsonWriter out, T value) throws IOException {
@SuppressWarnings("unchecked")
TypeAdapter<T> runtimeTypeAdapter =
(TypeAdapter<T>) context.getAdapter(TypeToken.get(runtimeType));
// For backward compatibility only check ReflectiveTypeAdapterFactory.Adapter here but not any
// other wrapping adapters, see
// https://github.com/google/gson/pull/1787#issuecomment-1222175189
if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
if (!isReflective(runtimeTypeAdapter)) {
// The user registered a type adapter for the runtime type, so we will use that
chosen = runtimeTypeAdapter;
} else if (!isReflective(delegate)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import static com.google.common.truth.Truth.assertThat;

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
Expand Down Expand Up @@ -151,14 +153,13 @@ public void testJsonDeserializer_JsonSerializerDelegate() {
}

/**
* When a {@link JsonDeserializer} is registered for Subclass, and a custom {@link JsonSerializer}
* is registered for Base, then Gson should prefer the reflective adapter for Subclass for
* backward compatibility (see https://github.com/google/gson/pull/1787#issuecomment-1222175189)
* even though normally TypeAdapterRuntimeTypeWrapper should prefer the custom serializer for
* Base.
* When a {@link JsonDeserializer} is registered for Subclass (which only affects
* deserialization), and a custom {@link JsonSerializer} is registered for Base, then Gson should
* prefer the custom serializer for Base because the Subclass deserializer has no effect on
* serialization.
*/
@Test
public void testJsonDeserializer_SubclassBackwardCompatibility() {
public void testJsonDeserializer_SubclassWithBaseSerializer() {
Gson gson =
new GsonBuilder()
.registerTypeAdapter(
Expand All @@ -173,7 +174,47 @@ public void testJsonDeserializer_SubclassBackwardCompatibility() {
.create();

String json = gson.toJson(new Container());
assertThat(json).isEqualTo("{\"b\":{\"f\":\"test\"}}");
assertThat(json).isEqualTo("{\"b\":\"base\"}");
}

/**
* Regression test for https://github.com/google/gson/issues/2190: a deserialization-only
* ExclusionStrategy that excludes the runtime type must not interfere with serialization. The
* custom TypeAdapter registered for the declared type should still be used.
*/
@Test
public void testDeserializationExclusionStrategy_CustomBaseAdapter() {
Gson gson =
new GsonBuilder()
.addDeserializationExclusionStrategy(
new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return false;
}

@Override
public boolean shouldSkipClass(Class<?> clazz) {
return clazz == Subclass.class;
}
})
.registerTypeAdapter(
Base.class,
new TypeAdapter<Base>() {
@Override
public Base read(JsonReader in) throws IOException {
throw new UnsupportedOperationException();
}

@Override
public void write(JsonWriter out, Base value) throws IOException {
out.value("custom-adapter");
}
})
.create();

String json = gson.toJson(new Container());
assertThat(json).isEqualTo("{\"b\":\"custom-adapter\"}");
}

private static class CyclicBase {
Expand Down
Loading