-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix TypeAdapterRuntimeTypeWrapper not detecting reflective TreeTypeAd…
…apter and FutureTypeAdapter (#1787) * Fix TypeAdapterRuntimeTypeWrapper not detecting reflective TreeTypeAdapter Previously on serialization TypeAdapterRuntimeTypeWrapper preferred a TreeTypeAdapter without `serializer` which falls back to the reflective adapter. This behavior was incorrect because it caused the reflective adapter for a Base class to be used for serialization (indirectly as TreeTypeAdapter delegate) instead of using the reflective adapter for a Subclass extending Base. * Address review feedback * Convert TypeAdapterRuntimeTypeWrapperTest to JUnit 4 test * Prefer wrapped reflective adapter for serialization of subclass * Detect reflective adapter used as delegate for Gson.FutureTypeAdapter * Tiny style tweak. Co-authored-by: Éamonn McManus <[email protected]>
- Loading branch information
1 parent
5269701
commit 8451c1f
Showing
5 changed files
with
256 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
gson/src/main/java/com/google/gson/internal/bind/SerializationDelegatingTypeAdapter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.google.gson.internal.bind; | ||
|
||
import com.google.gson.TypeAdapter; | ||
|
||
/** | ||
* Type adapter which might delegate serialization to another adapter. | ||
*/ | ||
public abstract class SerializationDelegatingTypeAdapter<T> extends TypeAdapter<T> { | ||
/** | ||
* Returns the adapter used for serialization, might be {@code this} or another adapter. | ||
* That other adapter might itself also be a {@code SerializationDelegatingTypeAdapter}. | ||
*/ | ||
public abstract TypeAdapter<T> getSerializationDelegate(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
193 changes: 193 additions & 0 deletions
193
gson/src/test/java/com/google/gson/functional/TypeAdapterRuntimeTypeWrapperTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
package com.google.gson.functional; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
|
||
import com.google.gson.Gson; | ||
import com.google.gson.GsonBuilder; | ||
import com.google.gson.JsonDeserializationContext; | ||
import com.google.gson.JsonDeserializer; | ||
import com.google.gson.JsonElement; | ||
import com.google.gson.JsonPrimitive; | ||
import com.google.gson.JsonSerializationContext; | ||
import com.google.gson.JsonSerializer; | ||
import com.google.gson.TypeAdapter; | ||
import com.google.gson.stream.JsonReader; | ||
import com.google.gson.stream.JsonWriter; | ||
import java.io.IOException; | ||
import java.lang.reflect.Type; | ||
import org.junit.Test; | ||
|
||
public class TypeAdapterRuntimeTypeWrapperTest { | ||
private static class Base { | ||
} | ||
private static class Subclass extends Base { | ||
@SuppressWarnings("unused") | ||
String f = "test"; | ||
} | ||
private static class Container { | ||
@SuppressWarnings("unused") | ||
Base b = new Subclass(); | ||
} | ||
private static class Deserializer implements JsonDeserializer<Base> { | ||
@Override | ||
public Base deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { | ||
throw new AssertionError("not needed for this test"); | ||
} | ||
} | ||
|
||
/** | ||
* When custom {@link JsonSerializer} is registered for Base should | ||
* prefer that over reflective adapter for Subclass for serialization. | ||
*/ | ||
@Test | ||
public void testJsonSerializer() { | ||
Gson gson = new GsonBuilder() | ||
.registerTypeAdapter(Base.class, new JsonSerializer<Base>() { | ||
@Override | ||
public JsonElement serialize(Base src, Type typeOfSrc, JsonSerializationContext context) { | ||
return new JsonPrimitive("serializer"); | ||
} | ||
}) | ||
.create(); | ||
|
||
String json = gson.toJson(new Container()); | ||
assertEquals("{\"b\":\"serializer\"}", json); | ||
} | ||
|
||
/** | ||
* When only {@link JsonDeserializer} is registered for Base, then on | ||
* serialization should prefer reflective adapter for Subclass since | ||
* Base would use reflective adapter as delegate. | ||
*/ | ||
@Test | ||
public void testJsonDeserializer_ReflectiveSerializerDelegate() { | ||
Gson gson = new GsonBuilder() | ||
.registerTypeAdapter(Base.class, new Deserializer()) | ||
.create(); | ||
|
||
String json = gson.toJson(new Container()); | ||
assertEquals("{\"b\":{\"f\":\"test\"}}", json); | ||
} | ||
|
||
/** | ||
* When {@link JsonDeserializer} with custom adapter as delegate is | ||
* registered for Base, then on serialization should prefer custom adapter | ||
* delegate for Base over reflective adapter for Subclass. | ||
*/ | ||
@Test | ||
public void testJsonDeserializer_CustomSerializerDelegate() { | ||
Gson gson = new GsonBuilder() | ||
// Register custom delegate | ||
.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 delegate"); | ||
} | ||
}) | ||
.registerTypeAdapter(Base.class, new Deserializer()) | ||
.create(); | ||
|
||
String json = gson.toJson(new Container()); | ||
assertEquals("{\"b\":\"custom delegate\"}", json); | ||
} | ||
|
||
/** | ||
* When two (or more) {@link JsonDeserializer}s are registered for Base | ||
* which eventually fall back to reflective adapter as delegate, then on | ||
* serialization should prefer reflective adapter for Subclass. | ||
*/ | ||
@Test | ||
public void testJsonDeserializer_ReflectiveTreeSerializerDelegate() { | ||
Gson gson = new GsonBuilder() | ||
// Register delegate which itself falls back to reflective serialization | ||
.registerTypeAdapter(Base.class, new Deserializer()) | ||
.registerTypeAdapter(Base.class, new Deserializer()) | ||
.create(); | ||
|
||
String json = gson.toJson(new Container()); | ||
assertEquals("{\"b\":{\"f\":\"test\"}}", json); | ||
} | ||
|
||
/** | ||
* When {@link JsonDeserializer} with {@link JsonSerializer} as delegate | ||
* is registered for Base, then on serialization should prefer | ||
* {@code JsonSerializer} over reflective adapter for Subclass. | ||
*/ | ||
@Test | ||
public void testJsonDeserializer_JsonSerializerDelegate() { | ||
Gson gson = new GsonBuilder() | ||
// Register JsonSerializer as delegate | ||
.registerTypeAdapter(Base.class, new JsonSerializer<Base>() { | ||
@Override | ||
public JsonElement serialize(Base src, Type typeOfSrc, JsonSerializationContext context) { | ||
return new JsonPrimitive("custom delegate"); | ||
} | ||
}) | ||
.registerTypeAdapter(Base.class, new Deserializer()) | ||
.create(); | ||
|
||
String json = gson.toJson(new Container()); | ||
assertEquals("{\"b\":\"custom delegate\"}", json); | ||
} | ||
|
||
/** | ||
* 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. | ||
*/ | ||
@Test | ||
public void testJsonDeserializer_SubclassBackwardCompatibility() { | ||
Gson gson = new GsonBuilder() | ||
.registerTypeAdapter(Subclass.class, new JsonDeserializer<Subclass>() { | ||
@Override | ||
public Subclass deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { | ||
throw new AssertionError("not needed for this test"); | ||
} | ||
}) | ||
.registerTypeAdapter(Base.class, new JsonSerializer<Base>() { | ||
@Override | ||
public JsonElement serialize(Base src, Type typeOfSrc, JsonSerializationContext context) { | ||
return new JsonPrimitive("base"); | ||
} | ||
}) | ||
.create(); | ||
|
||
String json = gson.toJson(new Container()); | ||
assertEquals("{\"b\":{\"f\":\"test\"}}", json); | ||
} | ||
|
||
private static class CyclicBase { | ||
@SuppressWarnings("unused") | ||
CyclicBase f; | ||
} | ||
|
||
private static class CyclicSub extends CyclicBase { | ||
@SuppressWarnings("unused") | ||
int i; | ||
|
||
public CyclicSub(int i) { | ||
this.i = i; | ||
} | ||
} | ||
|
||
/** | ||
* Tests behavior when the type of a field refers to a type whose adapter is | ||
* currently in the process of being created. For these cases {@link Gson} | ||
* uses a future adapter for the type. That adapter later uses the actual | ||
* adapter as delegate. | ||
*/ | ||
@Test | ||
public void testGsonFutureAdapter() { | ||
CyclicBase b = new CyclicBase(); | ||
b.f = new CyclicSub(2); | ||
String json = new Gson().toJson(b); | ||
assertEquals("{\"f\":{\"i\":2}}", json); | ||
} | ||
} |