From 7d1973e6c5e270c0c94eaf6a3e81f4f2c5b2a699 Mon Sep 17 00:00:00 2001 From: jwilson Date: Wed, 21 Oct 2015 11:40:24 -0400 Subject: [PATCH] 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. --- .../gson/internal/bind/TypeAdapters.java | 62 ++++++++++++------- .../functional/DefaultTypeAdaptersTest.java | 44 ++++++++----- 2 files changed, 68 insertions(+), 38 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index a8465fcb..6d60973b 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -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 TypeAdapterFactory newTypeHierarchyFactory( - final Class clazz, final TypeAdapter 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 TypeAdapterFactory newTypeHierarchyFactory( + final Class clazz, final TypeAdapter typeAdapter) { return new TypeAdapterFactory() { @SuppressWarnings("unchecked") - public TypeAdapter create(Gson gson, TypeToken typeToken) { - return clazz.isAssignableFrom(typeToken.getRawType()) ? (TypeAdapter) typeAdapter : null; + public TypeAdapter create(Gson gson, TypeToken typeToken) { + final Class requestedType = typeToken.getRawType(); + if (!clazz.isAssignableFrom(requestedType)) { + return null; + } + return (TypeAdapter) new TypeAdapter() { + @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 + "]"; diff --git a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java index 2b4db893..19866716 100644 --- a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java @@ -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() { - 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) {