For the Collection and Map types, using ObjectConstructor to create instance instead of hard-coding a specific type.

This helps is handling cases where the user is using their own subclass of Collection or Map.

Updated ParameterizedTypeHandlerMap to return the handler corresponding to Map and Collection for subclasses if user has not specified a specific handler.

Fixed the logic in JsonTreeNavigator to not output a comma if the first field of an object was null.
This commit is contained in:
Inderjeet Singh 2008-11-14 02:11:46 +00:00
parent eaa43b76e4
commit 9245bebdba
9 changed files with 167 additions and 32 deletions

View File

@ -29,6 +29,7 @@ import java.net.URL;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
@ -328,7 +329,8 @@ final class DefaultTypeAdapters {
}
@SuppressWarnings({ "unchecked" })
private static class CollectionTypeAdapter implements JsonSerializer<Collection>, JsonDeserializer<Collection>, InstanceCreator<Collection> {
private static class CollectionTypeAdapter implements JsonSerializer<Collection>,
JsonDeserializer<Collection>, InstanceCreator<Collection> {
public JsonElement serialize(Collection src, Type typeOfSrc, JsonSerializationContext context) {
if (src == null) {
@ -348,19 +350,31 @@ final class DefaultTypeAdapters {
return array;
}
public Collection deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
public Collection deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonNull()) {
return null;
}
// Using list to preserve order in which elements are entered
List<Object> list = new LinkedList<Object>();
// Use ObjectConstructor to create instance instead of hard-coding a specific type.
// This handles cases where users are using their own subclass of Collection.
Collection collection = constructCollectionType(typeOfT, context);
Type childType = new TypeInfoCollection(typeOfT).getElementType();
for (JsonElement childElement : json.getAsJsonArray()) {
Object value = context.deserialize(childElement, childType);
list.add(value);
if (childElement == null || childElement.isJsonNull()) {
collection.add(null);
} else {
Object value = context.deserialize(childElement, childType);
collection.add(value);
}
}
return list;
return collection;
}
private Collection constructCollectionType(Type collectionType,
JsonDeserializationContext context) {
JsonDeserializationContextDefault contextImpl = (JsonDeserializationContextDefault) context;
ObjectConstructor objectConstructor = contextImpl.getObjectConstructor();
return (Collection) objectConstructor.construct(collectionType);
}
public Collection createInstance(Type type) {
@ -371,6 +385,7 @@ final class DefaultTypeAdapters {
@SuppressWarnings("unchecked")
static class MapTypeAdapter implements JsonSerializer<Map>, JsonDeserializer<Map>,
InstanceCreator<Map> {
public JsonElement serialize(Map src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject map = new JsonObject();
Type childGenericType = null;
@ -387,10 +402,12 @@ final class DefaultTypeAdapters {
}
return map;
}
public Map deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
// Using linked hash map to preserve order in which elements are entered
Map<String, Object> map = new LinkedHashMap<String, Object>();
// Use ObjectConstructor to create instance instead of hard-coding a specific type.
// This handles cases where users are using their own subclass of Map.
Map<String, Object> map = constructMapType(typeOfT, context);
Type childType = new TypeInfoMap(typeOfT).getValueType();
for (Map.Entry<String, JsonElement> entry : json.getAsJsonObject().entrySet()) {
Object value = context.deserialize(entry.getValue(), childType);
@ -398,9 +415,17 @@ final class DefaultTypeAdapters {
}
return map;
}
private Map constructMapType(Type mapType, JsonDeserializationContext context) {
JsonDeserializationContextDefault contextImpl = (JsonDeserializationContextDefault) context;
ObjectConstructor objectConstructor = contextImpl.getObjectConstructor();
return (Map) objectConstructor.construct(mapType);
}
public Map createInstance(Type type) {
return new LinkedHashMap();
}
@Override
public String toString() {
return MapTypeAdapter.class.getSimpleName();

View File

@ -39,6 +39,10 @@ final class JsonDeserializationContextDefault implements JsonDeserializationCont
this.typeAdapter = typeAdapter;
}
ObjectConstructor getObjectConstructor() {
return objectConstructor;
}
@SuppressWarnings("unchecked")
public <T> T deserialize(JsonElement json, Type typeOfT) throws JsonParseException {
if (json.isJsonArray()) {

View File

@ -80,14 +80,7 @@ abstract class JsonDeserializationVisitor<T> implements ObjectNavigator.Visitor
@SuppressWarnings("unchecked")
public final boolean visitUsingCustomHandler(Object obj, Type objType) {
JsonDeserializer deserializer = (JsonDeserializer) deserializers.getHandlerFor(objType);
if (deserializer == null) {
if (objType instanceof Map) {
deserializer = deserializers.getHandlerFor(Map.class);
} else if (objType instanceof Collection) {
deserializer = deserializers.getHandlerFor(Collection.class);
}
}
JsonDeserializer deserializer = deserializers.getHandlerFor(objType);
if (deserializer != null) {
target = (T) deserializer.deserialize(json, objType, context);
return true;

View File

@ -187,13 +187,13 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
@SuppressWarnings("unchecked")
public boolean visitUsingCustomHandler(Object obj, Type objType) {
JsonSerializer serializer = serializers.getHandlerFor(objType);
if (serializer == null) {
if (obj instanceof Map) {
serializer = serializers.getHandlerFor(Map.class);
} else if (obj instanceof Collection) {
serializer = serializers.getHandlerFor(Collection.class);
}
}
// if (serializer == null) {
// if (obj instanceof Map) {
// serializer = serializers.getHandlerFor(Map.class);
// } else if (obj instanceof Collection) {
// serializer = serializers.getHandlerFor(Collection.class);
// }
// }
if (serializer != null) {
if (obj == null) {
assignToRoot(JsonNull.INSTANCE);

View File

@ -51,8 +51,8 @@ final class JsonTreeNavigator {
visitor.startObject(object);
boolean isFirst = true;
for (Map.Entry<String, JsonElement> member : object.entrySet()) {
visitChild(object, member.getKey(), member.getValue(), isFirst);
if (isFirst) {
boolean visited = visitChild(object, member.getKey(), member.getValue(), isFirst);
if (visited && isFirst) {
isFirst = false;
}
}
@ -62,12 +62,18 @@ final class JsonTreeNavigator {
}
}
private void visitChild(JsonObject parent, String childName, JsonElement child, boolean isFirst) {
/**
* Returns true if the child was visited, false if it was skipped.
*/
private boolean visitChild(JsonObject parent, String childName, JsonElement child,
boolean isFirst) {
if (child != null) {
if (child.isJsonNull()) {
if (visitNulls) {
visitor.visitNullObjectMember(parent, childName, isFirst);
navigate(child.getAsJsonNull());
} else { // Null value is being skipped.
return false;
}
} else if (child.isJsonArray()) {
JsonArray childAsArray = child.getAsJsonArray();
@ -81,8 +87,12 @@ final class JsonTreeNavigator {
visitor.visitObjectMember(parent, childName, child.getAsJsonPrimitive(), isFirst);
}
}
return true;
}
/**
* Returns true if the child was visited, false if it was skipped.
*/
private void visitChild(JsonArray parent, JsonElement child, boolean isFirst) {
if (child == null || child.isJsonNull()) {
visitor.visitNullArrayMember(parent, isFirst);

View File

@ -18,6 +18,7 @@ package com.google.gson;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@ -62,12 +63,34 @@ final class ParameterizedTypeHandlerMap<T> {
}
public synchronized T getHandlerFor(Type type) {
T handler = map.get(type);
T handler = getRawHandlerFor(type);
Type rawType = type;
if (handler == null && type instanceof ParameterizedType) {
// a handler for a non-generic version is registered, so use that
Type rawType = ((ParameterizedType)type).getRawType();
// a handler for a non-generic version may be registered, so use that
rawType = ((ParameterizedType)type).getRawType();
handler = map.get(rawType);
}
// Check for map or collection
if (handler == null) {
if (rawType instanceof Class) {
Class<?> rawClass = (Class<?>) rawType;
if (Map.class.isAssignableFrom(rawClass)) {
handler = map.get(Map.class);
} else if (Collection.class.isAssignableFrom(rawClass)) {
handler = map.get(Collection.class);
}
}
}
return handler;
}
private synchronized T getRawHandlerFor(Type type) {
T handler = map.get(type);
if (type instanceof Map) {
handler = map.get(Map.class);
} else if (type instanceof Collection) {
handler = map.get(Collection.class);
}
return handler;
}

View File

@ -30,7 +30,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
/**
* Functional tests for Json serialization and deserialization of collections.
@ -78,6 +80,43 @@ public class CollectionTest extends TestCase {
MoreAsserts.assertEquals(expected[i], toIntArray(target.get(i)));
}
}
public void testLinkedListSerialization() {
List<String> list = new LinkedList<String>();
list.add("a1");
list.add("a2");
Type linkedListType = new TypeToken<LinkedList<String>>() {}.getType();
String json = gson.toJson(list, linkedListType);
assertTrue(json.contains("a1"));
assertTrue(json.contains("a2"));
}
public void testLinkedListDeserialization() {
String json = "['a1','a2']";
Type linkedListType = new TypeToken<LinkedList<String>>() {}.getType();
List<String> list = gson.fromJson(json, linkedListType);
assertEquals("a1", list.get(0));
assertEquals("a2", list.get(1));
}
public void testQueueSerialization() {
Queue<String> queue = new LinkedList<String>();
queue.add("a1");
queue.add("a2");
Type queueType = new TypeToken<Queue<String>>() {}.getType();
String json = gson.toJson(queue, queueType);
assertTrue(json.contains("a1"));
assertTrue(json.contains("a2"));
}
public void testQueueDeserialization() {
String json = "['a1','a2']";
Type queueType = new TypeToken<Queue<String>>() {}.getType();
Queue<String> queue = gson.fromJson(json, queueType);
assertEquals("a1", queue.element());
queue.remove();
assertEquals("a2", queue.element());
}
public void testNullsInListSerialization() {
List<String> list = new ArrayList<String>();

View File

@ -75,11 +75,38 @@ public class MapTest extends TestCase {
String json = gson.toJson(map, typeOfMap);
assertEquals("{}", json);
}
public void testParameterizedMapSubclassSerialization() {
MyParameterizedMap<String, String> map = new MyParameterizedMap<String, String>();
map.put("a", "b");
Type type = new TypeToken<MyParameterizedMap<String, String>>() {}.getType();
String json = gson.toJson(map, type);
assertTrue(json.contains("\"a\":\"b\""));
}
@SuppressWarnings("unchecked")
public void testParameterizedMapSubclassDeserialization() {
Type type = new TypeToken<MyParameterizedMap<String, Integer>>() {}.getType();
Gson gson = new GsonBuilder().registerTypeAdapter(type,
new InstanceCreator<MyParameterizedMap>() {
public MyParameterizedMap createInstance(Type type) {
return new MyParameterizedMap();
}
}).create();
String json = "{\"a\":1,\"b\":2}";
MyParameterizedMap<String, Integer> map = gson.fromJson(json, type);
assertEquals(1, ((Integer) map.get("a")).intValue());
assertEquals(2, ((Integer) map.get("b")).intValue());
}
private static class MyParameterizedMap<K, V> extends LinkedHashMap<K, V> {
int foo = 10;
}
public void testMapSubclassSerialization() {
MyMap map = new MyMap();
map.put("a", "b");
String json = gson.toJson(map);
String json = gson.toJson(map, MyMap.class);
assertTrue(json.contains("\"a\":\"b\""));
}

View File

@ -227,6 +227,20 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(objAfterDeserialization.getExpectedJson(), json);
}
@SuppressWarnings("unchecked")
public void testParameterizedTypeGenericArraysSerialization() throws Exception {
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
List<Integer>[] arrayOfLists = new List[] { list, list };
Type typeOfSrc = new TypeToken<ObjectWithTypeVariables<Integer>>() {}.getType();
ObjectWithTypeVariables<Integer> objToSerialize =
new ObjectWithTypeVariables<Integer>(null, null, null, arrayOfLists, null, null);
String json = gson.toJson(objToSerialize, typeOfSrc);
assertEquals("{\"arrayOfListOfTypeParameters\":[[1,2],[1,2]]}", json);
}
@SuppressWarnings("unchecked")
public void testParameterizedTypeGenericArraysDeserialization() throws Exception {
List<Integer> list = new ArrayList<Integer>();