Added support for collections, maps, and arbitrary depth of type adapters for Intercept annotation.

Added more tests for the features.
This commit is contained in:
Inderjeet Singh 2012-10-18 02:37:43 +00:00
parent 714ac8e643
commit fd4fbe4132
6 changed files with 132 additions and 12 deletions

View File

@ -797,7 +797,7 @@ public final class Gson {
TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
TypeAdapter<T> typeAdapter = (TypeAdapter<T>) getAdapter(typeToken);
T object = typeAdapter.read(reader);
invokeInterceptorIfNeeded(object, typeToken);
invokeInterceptorIfNeeded(object, typeToken.getRawType());
return object;
} catch (EOFException e) {
/*
@ -890,9 +890,15 @@ public final class Gson {
}
}
private <T> void invokeInterceptorIfNeeded(T object, Type type) {
@SuppressWarnings("unchecked")
TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(type);
Class<? super T> clazz = typeToken.getRawType();
invokeInterceptorIfNeeded(object, clazz);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private <T> void invokeInterceptorIfNeeded(T object, TypeToken<T> type) {
Class<? super T> clazz = type.getRawType();
private <T> void invokeInterceptorIfNeeded(T object, Class<T> clazz) {
Intercept interceptor = clazz.getAnnotation(Intercept.class);
if (interceptor == null) return;
// TODO: We don't need to construct an instance of postDeserializer every time. we can
@ -913,4 +919,16 @@ public final class Gson {
.append("}");
return sb.toString();
}
/**
* Not part of the Gson API. Do not use.
*/
public static final class $Internal$Access {
public static <T> void invokeInterceptor(Gson gson, T instance, Type type) {
gson.invokeInterceptorIfNeeded(instance, type);
}
public static <T> void invokeInterceptor(Gson gson, T instance, Class<T> clazz) {
gson.invokeInterceptorIfNeeded(instance, clazz);
}
}
}

View File

@ -16,9 +16,6 @@
package com.google.gson.internal.bind;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
@ -26,6 +23,9 @@ import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
@ -51,10 +51,12 @@ public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
}
};
private final Gson context;
private final Class<E> componentType;
private final TypeAdapter<E> componentTypeAdapter;
public ArrayTypeAdapter(Gson context, TypeAdapter<E> componentTypeAdapter, Class<E> componentType) {
this.context = context;
this.componentTypeAdapter =
new TypeAdapterRuntimeTypeWrapper<E>(context, componentTypeAdapter, componentType);
this.componentType = componentType;
@ -70,6 +72,7 @@ public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
in.beginArray();
while (in.hasNext()) {
E instance = componentTypeAdapter.read(in);
Gson.$Internal$Access.invokeInterceptor(context, instance, componentType);
list.add(instance);
}
in.endArray();

View File

@ -58,12 +58,16 @@ public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
}
private final class Adapter<E> extends TypeAdapter<Collection<E>> {
private final Gson context;
private final Type elementType;
private final TypeAdapter<E> elementTypeAdapter;
private final ObjectConstructor<? extends Collection<E>> constructor;
public Adapter(Gson context, Type elementType,
TypeAdapter<E> elementTypeAdapter,
ObjectConstructor<? extends Collection<E>> constructor) {
this.context = context;
this.elementType = elementType;
this.elementTypeAdapter =
new TypeAdapterRuntimeTypeWrapper<E>(context, elementTypeAdapter, elementType);
this.constructor = constructor;
@ -79,6 +83,7 @@ public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
in.beginArray();
while (in.hasNext()) {
E instance = elementTypeAdapter.read(in);
Gson.$Internal$Access.invokeInterceptor(context, instance, elementType);
collection.add(instance);
}
in.endArray();

View File

@ -144,6 +144,9 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
}
private final class Adapter<K, V> extends TypeAdapter<Map<K, V>> {
private final Gson context;
private final Type keyType;
private final Type valueType;
private final TypeAdapter<K> keyTypeAdapter;
private final TypeAdapter<V> valueTypeAdapter;
private final ObjectConstructor<? extends Map<K, V>> constructor;
@ -151,6 +154,9 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
public Adapter(Gson context, Type keyType, TypeAdapter<K> keyTypeAdapter,
Type valueType, TypeAdapter<V> valueTypeAdapter,
ObjectConstructor<? extends Map<K, V>> constructor) {
this.context = context;
this.keyType = keyType;
this.valueType = valueType;
this.keyTypeAdapter =
new TypeAdapterRuntimeTypeWrapper<K>(context, keyTypeAdapter, keyType);
this.valueTypeAdapter =
@ -172,7 +178,10 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
while (in.hasNext()) {
in.beginArray(); // entry array
K key = keyTypeAdapter.read(in);
Gson.$Internal$Access.invokeInterceptor(context, key, keyType);
V value = valueTypeAdapter.read(in);
Gson.$Internal$Access.invokeInterceptor(context, value, valueType);
V replaced = map.put(key, value);
if (replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);
@ -185,7 +194,9 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
while (in.hasNext()) {
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
K key = keyTypeAdapter.read(in);
Gson.$Internal$Access.invokeInterceptor(context, key, keyType);
V value = valueTypeAdapter.read(in);
Gson.$Internal$Access.invokeInterceptor(context, value, valueType);
V replaced = map.put(key, value);
if (replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);

View File

@ -91,6 +91,7 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
@Override void read(JsonReader reader, Object value)
throws IOException, IllegalAccessException {
Object fieldValue = typeAdapter.read(reader);
Gson.$Internal$Access.invokeInterceptor(context, fieldValue, fieldType.getRawType());
if (fieldValue != null || !isPrimitive) {
field.set(value, fieldValue);
}

View File

@ -15,15 +15,25 @@
*/
package com.google.gson.functional;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import junit.framework.TestCase;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.internal.alpha.Intercept;
import com.google.gson.internal.alpha.JsonPostDeserializer;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
/**
* Unit tests for {@link Intercept} and {@link JsonPostDeserializer}.
@ -37,7 +47,7 @@ public final class InterceptorTest extends TestCase {
@Override
public void setUp() throws Exception {
super.setUp();
this.gson = new Gson();
this.gson = new GsonBuilder().enableComplexMapKeySerialization().create();
}
public void testExceptionsPropagated() {
@ -47,41 +57,113 @@ public final class InterceptorTest extends TestCase {
} catch (JsonParseException expected) {}
}
public void testPostDeserializeTopLevelClass() {
public void testTopLevelClass() {
User user = gson.fromJson("{name:'bob',password:'pwd'}", User.class);
assertEquals(User.DEFAULT_EMAIL, user.email);
}
public void testPostDeserializeList() {
public void testList() {
List<User> list = gson.fromJson("[{name:'bob',password:'pwd'}]", new TypeToken<List<User>>(){}.getType());
User user = list.get(0);
assertEquals(User.DEFAULT_EMAIL, user.email);
}
public void testPostDeserializeField() {
public void testCollection() {
Collection<User> list = gson.fromJson("[{name:'bob',password:'pwd'}]", new TypeToken<Collection<User>>(){}.getType());
User user = list.iterator().next();
assertEquals(User.DEFAULT_EMAIL, user.email);
}
public void testMapKeyAndValues() {
Type mapType = new TypeToken<Map<User, Address>>(){}.getType();
try {
gson.fromJson("[[{name:'bob',password:'pwd'},{}]]", mapType);
fail();
} catch (JsonSyntaxException expected) {}
Map<User, Address> map = gson.fromJson("[[{name:'bob',password:'pwd'},{city:'Mountain View',state:'CA',zip:'94043'}]]",
mapType);
Entry<User, Address> entry = map.entrySet().iterator().next();
assertEquals(User.DEFAULT_EMAIL, entry.getKey().email);
assertEquals(Address.DEFAULT_FIRST_LINE, entry.getValue().firstLine);
}
public void testField() {
UserGroup userGroup = gson.fromJson("{user:{name:'bob',password:'pwd'}}", UserGroup.class);
assertEquals(User.DEFAULT_EMAIL, userGroup.user.email);
}
public void testCustomTypeAdapter() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(User.class, new TypeAdapter<User>() {
@Override public void write(JsonWriter out, User value) throws IOException {
throw new UnsupportedOperationException();
}
@Override public User read(JsonReader in) throws IOException {
in.beginObject();
in.nextName();
String name = in.nextString();
in.nextName();
String password = in.nextString();
in.endObject();
return new User(name, password);
}})
.create();
UserGroup userGroup = gson.fromJson("{user:{name:'bob',password:'pwd'}}", UserGroup.class);
assertEquals(User.DEFAULT_EMAIL, userGroup.user.email);
}
public void testDirectInvocationOfTypeAdapter() throws Exception {
TypeAdapter<UserGroup> adapter = gson.getAdapter(UserGroup.class);
UserGroup userGroup = adapter.fromJson("{\"user\":{\"name\":\"bob\",\"password\":\"pwd\"}}");
assertEquals(User.DEFAULT_EMAIL, userGroup.user.email);
}
@SuppressWarnings("unused")
private static final class UserGroup {
User user;
String city;
}
@Intercept(postDeserialize = UserValidator.class)
@SuppressWarnings("unused")
private static final class User {
static final String DEFAULT_EMAIL = "invalid@invalid.com";
String name;
String password;
String email;
Address address;
public User(String name, String password) {
this.name = name;
this.password = password;
}
}
private static final class UserValidator implements JsonPostDeserializer<User> {
public void postDeserialize(User user) {
if (user.name == null || user.password == null) {
throw new JsonParseException("name and password are required fields.");
throw new JsonSyntaxException("name and password are required fields.");
}
if (user.email == null) user.email = User.DEFAULT_EMAIL;
}
}
}
@Intercept(postDeserialize = AddressValidator.class)
@SuppressWarnings("unused")
private static final class Address {
static final String DEFAULT_FIRST_LINE = "unknown";
String firstLine;
String secondLine;
String city;
String state;
String zip;
}
private static final class AddressValidator implements JsonPostDeserializer<Address> {
public void postDeserialize(Address address) {
if (address.city == null || address.state == null || address.zip == null) {
throw new JsonSyntaxException("Address city, state and zip are required fields.");
}
if (address.firstLine == null) address.firstLine = Address.DEFAULT_FIRST_LINE;
}
}
}