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);
|
TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
|
||||||
TypeAdapter<T> typeAdapter = (TypeAdapter<T>) getAdapter(typeToken);
|
TypeAdapter<T> typeAdapter = (TypeAdapter<T>) getAdapter(typeToken);
|
||||||
T object = typeAdapter.read(reader);
|
T object = typeAdapter.read(reader);
|
||||||
invokeInterceptorIfNeeded(object, typeToken);
|
invokeInterceptorIfNeeded(object, typeToken.getRawType());
|
||||||
return object;
|
return object;
|
||||||
} catch (EOFException e) {
|
} 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" })
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
private <T> void invokeInterceptorIfNeeded(T object, TypeToken<T> type) {
|
private <T> void invokeInterceptorIfNeeded(T object, Class<T> clazz) {
|
||||||
Class<? super T> clazz = type.getRawType();
|
|
||||||
Intercept interceptor = clazz.getAnnotation(Intercept.class);
|
Intercept interceptor = clazz.getAnnotation(Intercept.class);
|
||||||
if (interceptor == null) return;
|
if (interceptor == null) return;
|
||||||
// TODO: We don't need to construct an instance of postDeserializer every time. we can
|
// TODO: We don't need to construct an instance of postDeserializer every time. we can
|
||||||
|
@ -913,4 +919,16 @@ public final class Gson {
|
||||||
.append("}");
|
.append("}");
|
||||||
return sb.toString();
|
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;
|
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.io.IOException;
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.lang.reflect.GenericArrayType;
|
import java.lang.reflect.GenericArrayType;
|
||||||
|
@ -26,6 +23,9 @@ import java.lang.reflect.Type;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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.internal.$Gson$Types;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.google.gson.stream.JsonReader;
|
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 Class<E> componentType;
|
||||||
private final TypeAdapter<E> componentTypeAdapter;
|
private final TypeAdapter<E> componentTypeAdapter;
|
||||||
|
|
||||||
public ArrayTypeAdapter(Gson context, TypeAdapter<E> componentTypeAdapter, Class<E> componentType) {
|
public ArrayTypeAdapter(Gson context, TypeAdapter<E> componentTypeAdapter, Class<E> componentType) {
|
||||||
|
this.context = context;
|
||||||
this.componentTypeAdapter =
|
this.componentTypeAdapter =
|
||||||
new TypeAdapterRuntimeTypeWrapper<E>(context, componentTypeAdapter, componentType);
|
new TypeAdapterRuntimeTypeWrapper<E>(context, componentTypeAdapter, componentType);
|
||||||
this.componentType = componentType;
|
this.componentType = componentType;
|
||||||
|
@ -70,6 +72,7 @@ public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
|
||||||
in.beginArray();
|
in.beginArray();
|
||||||
while (in.hasNext()) {
|
while (in.hasNext()) {
|
||||||
E instance = componentTypeAdapter.read(in);
|
E instance = componentTypeAdapter.read(in);
|
||||||
|
Gson.$Internal$Access.invokeInterceptor(context, instance, componentType);
|
||||||
list.add(instance);
|
list.add(instance);
|
||||||
}
|
}
|
||||||
in.endArray();
|
in.endArray();
|
||||||
|
|
|
@ -58,12 +58,16 @@ public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class Adapter<E> extends TypeAdapter<Collection<E>> {
|
private final class Adapter<E> extends TypeAdapter<Collection<E>> {
|
||||||
|
private final Gson context;
|
||||||
|
private final Type elementType;
|
||||||
private final TypeAdapter<E> elementTypeAdapter;
|
private final TypeAdapter<E> elementTypeAdapter;
|
||||||
private final ObjectConstructor<? extends Collection<E>> constructor;
|
private final ObjectConstructor<? extends Collection<E>> constructor;
|
||||||
|
|
||||||
public Adapter(Gson context, Type elementType,
|
public Adapter(Gson context, Type elementType,
|
||||||
TypeAdapter<E> elementTypeAdapter,
|
TypeAdapter<E> elementTypeAdapter,
|
||||||
ObjectConstructor<? extends Collection<E>> constructor) {
|
ObjectConstructor<? extends Collection<E>> constructor) {
|
||||||
|
this.context = context;
|
||||||
|
this.elementType = elementType;
|
||||||
this.elementTypeAdapter =
|
this.elementTypeAdapter =
|
||||||
new TypeAdapterRuntimeTypeWrapper<E>(context, elementTypeAdapter, elementType);
|
new TypeAdapterRuntimeTypeWrapper<E>(context, elementTypeAdapter, elementType);
|
||||||
this.constructor = constructor;
|
this.constructor = constructor;
|
||||||
|
@ -79,6 +83,7 @@ public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
in.beginArray();
|
in.beginArray();
|
||||||
while (in.hasNext()) {
|
while (in.hasNext()) {
|
||||||
E instance = elementTypeAdapter.read(in);
|
E instance = elementTypeAdapter.read(in);
|
||||||
|
Gson.$Internal$Access.invokeInterceptor(context, instance, elementType);
|
||||||
collection.add(instance);
|
collection.add(instance);
|
||||||
}
|
}
|
||||||
in.endArray();
|
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 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<K> keyTypeAdapter;
|
||||||
private final TypeAdapter<V> valueTypeAdapter;
|
private final TypeAdapter<V> valueTypeAdapter;
|
||||||
private final ObjectConstructor<? extends Map<K, V>> constructor;
|
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,
|
public Adapter(Gson context, Type keyType, TypeAdapter<K> keyTypeAdapter,
|
||||||
Type valueType, TypeAdapter<V> valueTypeAdapter,
|
Type valueType, TypeAdapter<V> valueTypeAdapter,
|
||||||
ObjectConstructor<? extends Map<K, V>> constructor) {
|
ObjectConstructor<? extends Map<K, V>> constructor) {
|
||||||
|
this.context = context;
|
||||||
|
this.keyType = keyType;
|
||||||
|
this.valueType = valueType;
|
||||||
this.keyTypeAdapter =
|
this.keyTypeAdapter =
|
||||||
new TypeAdapterRuntimeTypeWrapper<K>(context, keyTypeAdapter, keyType);
|
new TypeAdapterRuntimeTypeWrapper<K>(context, keyTypeAdapter, keyType);
|
||||||
this.valueTypeAdapter =
|
this.valueTypeAdapter =
|
||||||
|
@ -172,7 +178,10 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
while (in.hasNext()) {
|
while (in.hasNext()) {
|
||||||
in.beginArray(); // entry array
|
in.beginArray(); // entry array
|
||||||
K key = keyTypeAdapter.read(in);
|
K key = keyTypeAdapter.read(in);
|
||||||
|
Gson.$Internal$Access.invokeInterceptor(context, key, keyType);
|
||||||
|
|
||||||
V value = valueTypeAdapter.read(in);
|
V value = valueTypeAdapter.read(in);
|
||||||
|
Gson.$Internal$Access.invokeInterceptor(context, value, valueType);
|
||||||
V replaced = map.put(key, value);
|
V replaced = map.put(key, value);
|
||||||
if (replaced != null) {
|
if (replaced != null) {
|
||||||
throw new JsonSyntaxException("duplicate key: " + key);
|
throw new JsonSyntaxException("duplicate key: " + key);
|
||||||
|
@ -185,7 +194,9 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
while (in.hasNext()) {
|
while (in.hasNext()) {
|
||||||
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
|
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
|
||||||
K key = keyTypeAdapter.read(in);
|
K key = keyTypeAdapter.read(in);
|
||||||
|
Gson.$Internal$Access.invokeInterceptor(context, key, keyType);
|
||||||
V value = valueTypeAdapter.read(in);
|
V value = valueTypeAdapter.read(in);
|
||||||
|
Gson.$Internal$Access.invokeInterceptor(context, value, valueType);
|
||||||
V replaced = map.put(key, value);
|
V replaced = map.put(key, value);
|
||||||
if (replaced != null) {
|
if (replaced != null) {
|
||||||
throw new JsonSyntaxException("duplicate key: " + key);
|
throw new JsonSyntaxException("duplicate key: " + key);
|
||||||
|
|
|
@ -91,6 +91,7 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
@Override void read(JsonReader reader, Object value)
|
@Override void read(JsonReader reader, Object value)
|
||||||
throws IOException, IllegalAccessException {
|
throws IOException, IllegalAccessException {
|
||||||
Object fieldValue = typeAdapter.read(reader);
|
Object fieldValue = typeAdapter.read(reader);
|
||||||
|
Gson.$Internal$Access.invokeInterceptor(context, fieldValue, fieldType.getRawType());
|
||||||
if (fieldValue != null || !isPrimitive) {
|
if (fieldValue != null || !isPrimitive) {
|
||||||
field.set(value, fieldValue);
|
field.set(value, fieldValue);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,15 +15,25 @@
|
||||||
*/
|
*/
|
||||||
package com.google.gson.functional;
|
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.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.JsonParseException;
|
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.Intercept;
|
||||||
import com.google.gson.internal.alpha.JsonPostDeserializer;
|
import com.google.gson.internal.alpha.JsonPostDeserializer;
|
||||||
import com.google.gson.reflect.TypeToken;
|
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}.
|
* Unit tests for {@link Intercept} and {@link JsonPostDeserializer}.
|
||||||
|
@ -37,7 +47,7 @@ public final class InterceptorTest extends TestCase {
|
||||||
@Override
|
@Override
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
super.setUp();
|
super.setUp();
|
||||||
this.gson = new Gson();
|
this.gson = new GsonBuilder().enableComplexMapKeySerialization().create();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testExceptionsPropagated() {
|
public void testExceptionsPropagated() {
|
||||||
|
@ -47,41 +57,113 @@ public final class InterceptorTest extends TestCase {
|
||||||
} catch (JsonParseException expected) {}
|
} catch (JsonParseException expected) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPostDeserializeTopLevelClass() {
|
public void testTopLevelClass() {
|
||||||
User user = gson.fromJson("{name:'bob',password:'pwd'}", User.class);
|
User user = gson.fromJson("{name:'bob',password:'pwd'}", User.class);
|
||||||
assertEquals(User.DEFAULT_EMAIL, user.email);
|
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());
|
List<User> list = gson.fromJson("[{name:'bob',password:'pwd'}]", new TypeToken<List<User>>(){}.getType());
|
||||||
User user = list.get(0);
|
User user = list.get(0);
|
||||||
assertEquals(User.DEFAULT_EMAIL, user.email);
|
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);
|
UserGroup userGroup = gson.fromJson("{user:{name:'bob',password:'pwd'}}", UserGroup.class);
|
||||||
assertEquals(User.DEFAULT_EMAIL, userGroup.user.email);
|
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 {
|
private static final class UserGroup {
|
||||||
User user;
|
User user;
|
||||||
String city;
|
String city;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Intercept(postDeserialize = UserValidator.class)
|
@Intercept(postDeserialize = UserValidator.class)
|
||||||
|
@SuppressWarnings("unused")
|
||||||
private static final class User {
|
private static final class User {
|
||||||
static final String DEFAULT_EMAIL = "invalid@invalid.com";
|
static final String DEFAULT_EMAIL = "invalid@invalid.com";
|
||||||
String name;
|
String name;
|
||||||
String password;
|
String password;
|
||||||
String email;
|
String email;
|
||||||
|
Address address;
|
||||||
|
public User(String name, String password) {
|
||||||
|
this.name = name;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class UserValidator implements JsonPostDeserializer<User> {
|
private static final class UserValidator implements JsonPostDeserializer<User> {
|
||||||
public void postDeserialize(User user) {
|
public void postDeserialize(User user) {
|
||||||
if (user.name == null || user.password == null) {
|
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;
|
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