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();