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
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.io.IOException;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;

final class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
private final Gson context;
Expand Down Expand Up @@ -100,8 +101,21 @@ private static boolean isReflective(TypeAdapter<?> typeAdapter) {

/** Finds a compatible runtime type if it is more specific */
private static Type getRuntimeTypeIfMoreSpecific(Type type, Object value) {
if (value != null && (type instanceof Class<?> || type instanceof TypeVariable<?>)) {
type = value.getClass();
if (value == null) {
return type;
}

Type declaredType = type;
// A wildcard says nothing more than its upper bound, so `? extends Base` is decided the same
// way as `Base`. Note that the bound is only used to make this decision; when it is a
// parameterized type the declared type is kept, the same as for a plain parameterized type,
// because the runtime class would lose the type arguments.
if (declaredType instanceof WildcardType) {
declaredType = ((WildcardType) declaredType).getUpperBounds()[0];
}

if (declaredType instanceof Class<?> || declaredType instanceof TypeVariable<?>) {
return value.getClass();
}
return type;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,55 @@ public void testMapOfSubclassFields() {
assertThat(sub.get("s").getAsInt()).isEqualTo(3);
}

@Test
public void testWildcardListOfSubclassFields() {
List<Sub> list = new ArrayList<>();
list.add(new Sub(2, 3));
ClassWithWildcardContainersOfBaseFields target =
new ClassWithWildcardContainersOfBaseFields(list, null, null);
String json = gson.toJson(target);
assertThat(json).contains("{\"s\":3,\"b\":2}");
}

@Test
public void testWildcardMapOfSubclassFields() {
Map<String, Sub> map = new HashMap<>();
map.put("sub", new Sub(2, 3));
ClassWithWildcardContainersOfBaseFields target =
new ClassWithWildcardContainersOfBaseFields(null, map, null);
JsonObject json = gson.toJsonTree(target).getAsJsonObject().get("map").getAsJsonObject();
JsonObject sub = json.get("sub").getAsJsonObject();
assertThat(sub.get("b").getAsInt()).isEqualTo(2);
assertThat(sub.get("s").getAsInt()).isEqualTo(3);
}

/** A wildcard can also end up as the type of a field, through a resolved type variable. */
@Test
public void testWildcardTypeVariableSubclassFields() {
Container<Sub> container = new Container<>(new Sub(2, 3));
ClassWithWildcardContainersOfBaseFields target =
new ClassWithWildcardContainersOfBaseFields(null, null, container);
JsonObject json = gson.toJsonTree(target).getAsJsonObject().get("container").getAsJsonObject();
JsonObject sub = json.get("t").getAsJsonObject();
assertThat(sub.get("b").getAsInt()).isEqualTo(2);
assertThat(sub.get("s").getAsInt()).isEqualTo(3);
}

/**
* For a wildcard whose bound is a parameterized type, Gson has to stick to the declared type, the
* same way it does for a plain parameterized type.
*/
@Test
public void testWildcardListOfParameterizedSubclassFields() {
List<ParameterizedSub<String>> list = new ArrayList<>();
list.add(new ParameterizedSub<>("two", "three"));
ClassWithWildcardContainerOfParameterizedBaseFields target =
new ClassWithWildcardContainerOfParameterizedBaseFields(list);
String json = gson.toJson(target);
assertThat(json).contains("{\"t\":\"two\"}");
assertThat(json).doesNotContain("\"s\":");
}

/** For parameterized type, Gson ignores the more-specific type and sticks to the declared type */
@Test
public void testParameterizedSubclassFields() {
Expand Down Expand Up @@ -153,6 +202,38 @@ private static class ClassWithContainersOfBaseFields {
}
}

private static class Container<T> {
T t;

Container(T t) {
this.t = t;
}
}

private static class ClassWithWildcardContainersOfBaseFields {
Collection<? extends Base> collection;
Map<String, ? extends Base> map;
Container<? extends Base> container;

ClassWithWildcardContainersOfBaseFields(
Collection<? extends Base> collection,
Map<String, ? extends Base> map,
Container<? extends Base> container) {
this.collection = collection;
this.map = map;
this.container = container;
}
}

private static class ClassWithWildcardContainerOfParameterizedBaseFields {
Collection<? extends ParameterizedBase<String>> collection;

ClassWithWildcardContainerOfParameterizedBaseFields(
Collection<? extends ParameterizedBase<String>> collection) {
this.collection = collection;
}
}

private static class ParameterizedBase<T> {
T t;

Expand Down