Fix type hierarchy adapters to do a runtime check.

Otherwise if we have a type hierarchy adapter for Vehicle, and we
attempt to decode a JSON string as a Car, we get the right exception
if the JSON string is actually decoded as a Truck.
This commit is contained in:
jwilson 2015-10-21 11:40:24 -04:00
parent 93605e7145
commit 7d1973e6c5
2 changed files with 68 additions and 38 deletions

View File

@ -16,6 +16,22 @@
package com.google.gson.internal.bind;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonIOException;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.SerializedName;
import com.google.gson.internal.LazilyParsedNumber;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
@ -34,23 +50,6 @@ import java.util.Map;
import java.util.StringTokenizer;
import java.util.UUID;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonIOException;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.SerializedName;
import com.google.gson.internal.LazilyParsedNumber;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
/**
* Type adapters for basic types.
*/
@ -815,12 +814,33 @@ public final class TypeAdapters {
};
}
public static <TT> TypeAdapterFactory newTypeHierarchyFactory(
final Class<TT> clazz, final TypeAdapter<TT> typeAdapter) {
/**
* Returns a factory for all subtypes of {@code typeAdapter}. We do a runtime check to confirm
* that the deserialized type matches the type requested.
*/
public static <T1> TypeAdapterFactory newTypeHierarchyFactory(
final Class<T1> clazz, final TypeAdapter<T1> typeAdapter) {
return new TypeAdapterFactory() {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
return clazz.isAssignableFrom(typeToken.getRawType()) ? (TypeAdapter<T>) typeAdapter : null;
public <T2> TypeAdapter<T2> create(Gson gson, TypeToken<T2> typeToken) {
final Class<? super T2> requestedType = typeToken.getRawType();
if (!clazz.isAssignableFrom(requestedType)) {
return null;
}
return (TypeAdapter<T2>) new TypeAdapter<T1>() {
@Override public void write(JsonWriter out, T1 value) throws IOException {
typeAdapter.write(out, value);
}
@Override public T1 read(JsonReader in) throws IOException {
T1 result = typeAdapter.read(in);
if (result != null && !requestedType.isInstance(result)) {
throw new JsonSyntaxException("Expected a " + requestedType.getName()
+ " but was " + result.getClass().getName());
}
return result;
}
};
}
@Override public String toString() {
return "Factory[typeHierarchy=" + clazz.getName() + ",adapter=" + typeAdapter + "]";

View File

@ -15,6 +15,21 @@
*/
package com.google.gson.functional;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
@ -40,24 +55,8 @@ import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.UUID;
import junit.framework.TestCase;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
/**
* Functional test for Json serialization and deserialization for common classes for which default
* support is provided in Gson. The tests for Map types are available in {@link MapTest}.
@ -479,7 +478,8 @@ public class DefaultTypeAdaptersTest extends TestCase {
Gson gson = new GsonBuilder()
.setDateFormat(pattern)
.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
public Date deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException {
return new Date(1315806903103L);
}
@ -618,6 +618,16 @@ public class DefaultTypeAdaptersTest extends TestCase {
assertEquals(JsonNull.INSTANCE, gson.fromJson("null", JsonNull.class));
}
public void testJsonElementTypeMismatch() {
try {
gson.fromJson("\"abc\"", JsonObject.class);
fail();
} catch (JsonSyntaxException expected) {
assertEquals("Expected a com.google.gson.JsonObject but was com.google.gson.JsonPrimitive",
expected.getMessage());
}
}
private static class ClassWithBigDecimal {
BigDecimal value;
ClassWithBigDecimal(String value) {