From 9245bebdba9a3227674f3c0b08d86165891f02cc Mon Sep 17 00:00:00 2001 From: Inderjeet Singh Date: Fri, 14 Nov 2008 02:11:46 +0000 Subject: [PATCH] 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. --- .../com/google/gson/DefaultTypeAdapters.java | 45 ++++++++++++++----- .../JsonDeserializationContextDefault.java | 4 ++ .../gson/JsonDeserializationVisitor.java | 9 +--- .../google/gson/JsonSerializationVisitor.java | 14 +++--- .../com/google/gson/JsonTreeNavigator.java | 16 +++++-- .../gson/ParameterizedTypeHandlerMap.java | 29 ++++++++++-- .../gson/functional/CollectionTest.java | 39 ++++++++++++++++ .../com/google/gson/functional/MapTest.java | 29 +++++++++++- .../functional/ParameterizedTypesTest.java | 14 ++++++ 9 files changed, 167 insertions(+), 32 deletions(-) diff --git a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java index 5cb1db09..6997968c 100644 --- a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java +++ b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java @@ -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, JsonDeserializer, InstanceCreator { + private static class CollectionTypeAdapter implements JsonSerializer, + JsonDeserializer, InstanceCreator { 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 list = new LinkedList(); + // 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, JsonDeserializer, InstanceCreator { + 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 map = new LinkedHashMap(); + // 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 map = constructMapType(typeOfT, context); Type childType = new TypeInfoMap(typeOfT).getValueType(); for (Map.Entry 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(); diff --git a/gson/src/main/java/com/google/gson/JsonDeserializationContextDefault.java b/gson/src/main/java/com/google/gson/JsonDeserializationContextDefault.java index f622402d..4e0d9668 100644 --- a/gson/src/main/java/com/google/gson/JsonDeserializationContextDefault.java +++ b/gson/src/main/java/com/google/gson/JsonDeserializationContextDefault.java @@ -39,6 +39,10 @@ final class JsonDeserializationContextDefault implements JsonDeserializationCont this.typeAdapter = typeAdapter; } + ObjectConstructor getObjectConstructor() { + return objectConstructor; + } + @SuppressWarnings("unchecked") public T deserialize(JsonElement json, Type typeOfT) throws JsonParseException { if (json.isJsonArray()) { diff --git a/gson/src/main/java/com/google/gson/JsonDeserializationVisitor.java b/gson/src/main/java/com/google/gson/JsonDeserializationVisitor.java index bd4c12f1..5ca7e8ea 100644 --- a/gson/src/main/java/com/google/gson/JsonDeserializationVisitor.java +++ b/gson/src/main/java/com/google/gson/JsonDeserializationVisitor.java @@ -80,14 +80,7 @@ abstract class JsonDeserializationVisitor 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; diff --git a/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java b/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java index 3fd785a2..cb5db5f1 100644 --- a/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java +++ b/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java @@ -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); diff --git a/gson/src/main/java/com/google/gson/JsonTreeNavigator.java b/gson/src/main/java/com/google/gson/JsonTreeNavigator.java index 61080a65..945f2a30 100644 --- a/gson/src/main/java/com/google/gson/JsonTreeNavigator.java +++ b/gson/src/main/java/com/google/gson/JsonTreeNavigator.java @@ -51,8 +51,8 @@ final class JsonTreeNavigator { visitor.startObject(object); boolean isFirst = true; for (Map.Entry 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); diff --git a/gson/src/main/java/com/google/gson/ParameterizedTypeHandlerMap.java b/gson/src/main/java/com/google/gson/ParameterizedTypeHandlerMap.java index 88507188..9e76456a 100644 --- a/gson/src/main/java/com/google/gson/ParameterizedTypeHandlerMap.java +++ b/gson/src/main/java/com/google/gson/ParameterizedTypeHandlerMap.java @@ -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 { } 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; } diff --git a/gson/src/test/java/com/google/gson/functional/CollectionTest.java b/gson/src/test/java/com/google/gson/functional/CollectionTest.java index c3375c95..c2573bb1 100644 --- a/gson/src/test/java/com/google/gson/functional/CollectionTest.java +++ b/gson/src/test/java/com/google/gson/functional/CollectionTest.java @@ -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 list = new LinkedList(); + list.add("a1"); + list.add("a2"); + Type linkedListType = new TypeToken>() {}.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>() {}.getType(); + List list = gson.fromJson(json, linkedListType); + assertEquals("a1", list.get(0)); + assertEquals("a2", list.get(1)); + } + + public void testQueueSerialization() { + Queue queue = new LinkedList(); + queue.add("a1"); + queue.add("a2"); + Type queueType = new TypeToken>() {}.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>() {}.getType(); + Queue queue = gson.fromJson(json, queueType); + assertEquals("a1", queue.element()); + queue.remove(); + assertEquals("a2", queue.element()); + } public void testNullsInListSerialization() { List list = new ArrayList(); diff --git a/gson/src/test/java/com/google/gson/functional/MapTest.java b/gson/src/test/java/com/google/gson/functional/MapTest.java index 3581544d..ac4654e2 100755 --- a/gson/src/test/java/com/google/gson/functional/MapTest.java +++ b/gson/src/test/java/com/google/gson/functional/MapTest.java @@ -75,11 +75,38 @@ public class MapTest extends TestCase { String json = gson.toJson(map, typeOfMap); assertEquals("{}", json); } + + public void testParameterizedMapSubclassSerialization() { + MyParameterizedMap map = new MyParameterizedMap(); + map.put("a", "b"); + Type type = new TypeToken>() {}.getType(); + String json = gson.toJson(map, type); + assertTrue(json.contains("\"a\":\"b\"")); + } + + @SuppressWarnings("unchecked") + public void testParameterizedMapSubclassDeserialization() { + Type type = new TypeToken>() {}.getType(); + Gson gson = new GsonBuilder().registerTypeAdapter(type, + new InstanceCreator() { + public MyParameterizedMap createInstance(Type type) { + return new MyParameterizedMap(); + } + }).create(); + String json = "{\"a\":1,\"b\":2}"; + MyParameterizedMap map = gson.fromJson(json, type); + assertEquals(1, ((Integer) map.get("a")).intValue()); + assertEquals(2, ((Integer) map.get("b")).intValue()); + } + + private static class MyParameterizedMap extends LinkedHashMap { + 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\"")); } diff --git a/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java b/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java index 10eea778..8823c638 100644 --- a/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java +++ b/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java @@ -227,6 +227,20 @@ public class ParameterizedTypesTest extends TestCase { assertEquals(objAfterDeserialization.getExpectedJson(), json); } + @SuppressWarnings("unchecked") + public void testParameterizedTypeGenericArraysSerialization() throws Exception { + List list = new ArrayList(); + list.add(1); + list.add(2); + List[] arrayOfLists = new List[] { list, list }; + + Type typeOfSrc = new TypeToken>() {}.getType(); + ObjectWithTypeVariables objToSerialize = + new ObjectWithTypeVariables(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 list = new ArrayList();