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:
parent
714ac8e643
commit
fd4fbe4132
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user