Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix TypeAdapterRuntimeTypeWrapper not detecting reflective TreeTypeAdapter and FutureTypeAdapter #1787

Expand Up @@ -89,6 +89,18 @@ private TypeAdapter<T> delegate() {
: (delegate = gson.getDelegateAdapter(skipPast, typeToken));
}

/**
* Returns the type adapter which is used for serialization. Returns {@code this}
* if this {@code TreeTypeAdapter} has a {@link #serializer}; otherwise returns
* the delegate.
*
* @return the type adapter which is used for serialization.
Marcono1234 marked this conversation as resolved.
Show resolved Hide resolved
*/
// Package-private for TypeAdapterRuntimeTypeWrapper
TypeAdapter<T> getSerializingTypeAdapter() {
return serializer != null ? this : delegate();
}

/**
* Returns a new factory that will match each type against {@code exactType}.
*/
Expand Down
Expand Up @@ -54,10 +54,10 @@ public void write(JsonWriter out, T value) throws IOException {
Type runtimeType = getRuntimeTypeIfMoreSpecific(type, value);
if (runtimeType != type) {
TypeAdapter runtimeTypeAdapter = context.getAdapter(TypeToken.get(runtimeType));
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 (!(delegate instanceof ReflectiveTypeAdapterFactory.Adapter)) {
} else if (!isReflective(delegate)) {
// The user registered a type adapter for Base class, so we prefer it over the
// reflective type adapter for the runtime type
chosen = delegate;
Expand All @@ -69,12 +69,34 @@ public void write(JsonWriter out, T value) throws IOException {
chosen.write(out, value);
}

/**
* @param typeAdapter the type adapter to check.
Marcono1234 marked this conversation as resolved.
Show resolved Hide resolved
* @return whether the type adapter uses reflection.
*/
private static boolean isReflective(TypeAdapter<?> typeAdapter) {
if (typeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter) {
return true;
} else if (typeAdapter instanceof TreeTypeAdapter) {
// Have to get actual serializing adapter for TreeTypeAdapter because
// TreeTypeAdapter without `serializer` might fall back to reflective
// type adapter
TreeTypeAdapter<?> treeTypeAdapter = (TreeTypeAdapter<?>) typeAdapter;
TypeAdapter<?> serializingTypeAdapter = treeTypeAdapter.getSerializingTypeAdapter();
if (serializingTypeAdapter == treeTypeAdapter) {
return false;
} else {
// Check TreeTypeAdapter delegate
return isReflective(serializingTypeAdapter);
}
}
return false;
}

/**
* Finds a compatible runtime type if it is more specific
*/
private Type getRuntimeTypeIfMoreSpecific(Type type, Object value) {
if (value != null
&& (type == Object.class || type instanceof TypeVariable<?> || type instanceof Class<?>)) {
private static Type getRuntimeTypeIfMoreSpecific(Type type, Object value) {
if (value != null && (type instanceof Class<?> || type instanceof TypeVariable<?>)) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: Simplified this because type instanceof Class<?> already includes the check for type == Object.class.

type = value.getClass();
}
return type;
Expand Down
@@ -0,0 +1,126 @@
package com.google.gson.functional;

import java.io.IOException;
import java.lang.reflect.Type;

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 junit.framework.TestCase;

public class TypeAdapterRuntimeTypeWrapperTest extends TestCase {
Marcono1234 marked this conversation as resolved.
Show resolved Hide resolved
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 UnsupportedOperationException();
}
}

/**
* When custom {@link JsonSerializer} is registered for Base should
* prefer that over reflective adapter for Subclass for serialization.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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);
}
}