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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,7 +30,9 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Queue;
/** /**
* Functional tests for Json serialization and deserialization of collections. * Functional tests for Json serialization and deserialization of collections.
@ -79,6 +81,43 @@ public class CollectionTest extends TestCase {
} }
} }
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() { public void testNullsInListSerialization() {
List<String> list = new ArrayList<String>(); List<String> list = new ArrayList<String>();
list.add("foo"); list.add("foo");

View File

@ -76,10 +76,37 @@ public class MapTest extends TestCase {
assertEquals("{}", json); 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() { public void testMapSubclassSerialization() {
MyMap map = new MyMap(); MyMap map = new MyMap();
map.put("a", "b"); map.put("a", "b");
String json = gson.toJson(map); String json = gson.toJson(map, MyMap.class);
assertTrue(json.contains("\"a\":\"b\"")); assertTrue(json.contains("\"a\":\"b\""));
} }

View File

@ -227,6 +227,20 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(objAfterDeserialization.getExpectedJson(), json); 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") @SuppressWarnings("unchecked")
public void testParameterizedTypeGenericArraysDeserialization() throws Exception { public void testParameterizedTypeGenericArraysDeserialization() throws Exception {
List<Integer> list = new ArrayList<Integer>(); List<Integer> list = new ArrayList<Integer>();