From eaa43b76e44d2491dfbabb69e9d20beceffd9822 Mon Sep 17 00:00:00 2001 From: Inderjeet Singh Date: Thu, 13 Nov 2008 23:40:10 +0000 Subject: [PATCH] Added a custom type adapter for Collection class. We will migrate the code to use it instead of special cases for collections all over. This type adapter is called at a few places already. Also added tests for verifying that Gson can handle serialization and deserialization of sub types of Maps. The deserialization test fails currently. --- .../com/google/gson/DefaultTypeAdapters.java | 45 ++++++++++++++++++ .../gson/JsonDeserializationVisitor.java | 13 +++++- .../google/gson/JsonSerializationVisitor.java | 11 ++++- .../com/google/gson/TypeInfoCollection.java | 46 +++++++++++++++++++ .../com/google/gson/functional/MapTest.java | 25 ++++++++++ 5 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 gson/src/main/java/com/google/gson/TypeInfoCollection.java diff --git a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java index ef6361ce..5cb1db09 100644 --- a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java +++ b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java @@ -59,6 +59,7 @@ final class DefaultTypeAdapters { private static final UrlTypeAdapter URL_TYPE_ADAPTER = new UrlTypeAdapter(); private static final UriTypeAdapter URI_TYPE_ADAPTER = new UriTypeAdapter(); private static final LocaleTypeAdapter LOCALE_TYPE_ADAPTER = new LocaleTypeAdapter(); + private static final CollectionTypeAdapter COLLECTION_TYPE_ADAPTER = new CollectionTypeAdapter(); private static final MapTypeAdapter MAP_TYPE_ADAPTER = new MapTypeAdapter(); private static final BigDecimalTypeAdapter BIG_DECIMAL_TYPE_ADAPTER = new BigDecimalTypeAdapter(); private static final BigIntegerTypeAdapter BIG_INTEGER_TYPE_ADAPTER = new BigIntegerTypeAdapter(); @@ -92,6 +93,7 @@ final class DefaultTypeAdapters { map.register(URL.class, wrapSerializer(URL_TYPE_ADAPTER)); map.register(URI.class, wrapSerializer(URI_TYPE_ADAPTER)); map.register(Locale.class, wrapSerializer(LOCALE_TYPE_ADAPTER)); + map.register(Collection.class, COLLECTION_TYPE_ADAPTER); map.register(Map.class, wrapSerializer(MAP_TYPE_ADAPTER)); map.register(Date.class, wrapSerializer(DATE_TYPE_ADAPTER)); map.register(BigDecimal.class, wrapSerializer(BIG_DECIMAL_TYPE_ADAPTER)); @@ -107,6 +109,7 @@ final class DefaultTypeAdapters { map.register(URL.class, wrapDeserializer(URL_TYPE_ADAPTER)); map.register(URI.class, wrapDeserializer(URI_TYPE_ADAPTER)); map.register(Locale.class, wrapDeserializer(LOCALE_TYPE_ADAPTER)); + map.register(Collection.class, wrapDeserializer(COLLECTION_TYPE_ADAPTER)); map.register(Map.class, wrapDeserializer(MAP_TYPE_ADAPTER)); map.register(Date.class, wrapDeserializer(DATE_TYPE_ADAPTER)); map.register(BigDecimal.class, wrapDeserializer(BIG_DECIMAL_TYPE_ADAPTER)); @@ -121,6 +124,7 @@ final class DefaultTypeAdapters { map.register(Enum.class, ENUM_TYPE_ADAPTER); map.register(URL.class, URL_TYPE_ADAPTER); map.register(Locale.class, LOCALE_TYPE_ADAPTER); + map.register(Collection.class, COLLECTION_TYPE_ADAPTER); map.register(Map.class, MAP_TYPE_ADAPTER); map.register(BigDecimal.class, BIG_DECIMAL_TYPE_ADAPTER); map.register(BigInteger.class, BIG_INTEGER_TYPE_ADAPTER); @@ -323,6 +327,47 @@ final class DefaultTypeAdapters { } } + @SuppressWarnings({ "unchecked" }) + private static class CollectionTypeAdapter implements JsonSerializer, JsonDeserializer, InstanceCreator { + + public JsonElement serialize(Collection src, Type typeOfSrc, JsonSerializationContext context) { + if (src == null) { + return JsonNull.INSTANCE; + } + JsonArray array = new JsonArray(); + Type childGenericType = null; + if (typeOfSrc instanceof ParameterizedType) { + childGenericType = new TypeInfoCollection(typeOfSrc).getElementType(); + } + for (Object child : src) { + Type childType = (childGenericType == null) ? + childType = child.getClass() : childGenericType; + JsonElement element = context.serialize(child, childType); + array.add(element); + } + return array; + } + + 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(); + Type childType = new TypeInfoCollection(typeOfT).getElementType(); + for (JsonElement childElement : json.getAsJsonArray()) { + Object value = context.deserialize(childElement, childType); + list.add(value); + } + return list; + } + + public Collection createInstance(Type type) { + return new LinkedList(); + } + } + @SuppressWarnings("unchecked") static class MapTypeAdapter implements JsonSerializer, JsonDeserializer, InstanceCreator { diff --git a/gson/src/main/java/com/google/gson/JsonDeserializationVisitor.java b/gson/src/main/java/com/google/gson/JsonDeserializationVisitor.java index 49648965..bd4c12f1 100644 --- a/gson/src/main/java/com/google/gson/JsonDeserializationVisitor.java +++ b/gson/src/main/java/com/google/gson/JsonDeserializationVisitor.java @@ -17,6 +17,8 @@ package com.google.gson; import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Map; import java.util.logging.Logger; /** @@ -78,9 +80,16 @@ abstract class JsonDeserializationVisitor implements ObjectNavigator.Visitor @SuppressWarnings("unchecked") public final boolean visitUsingCustomHandler(Object obj, Type objType) { - JsonDeserializer deserializer = (JsonDeserializer) deserializers.getHandlerFor(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); + } + } if (deserializer != null) { - target = deserializer.deserialize(json, objType, context); + target = (T) deserializer.deserialize(json, objType, context); return true; } return false; diff --git a/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java b/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java index 578e8a42..3fd785a2 100644 --- a/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java +++ b/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java @@ -187,8 +187,12 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor { @SuppressWarnings("unchecked") public boolean visitUsingCustomHandler(Object obj, Type objType) { JsonSerializer serializer = serializers.getHandlerFor(objType); - if (serializer == null && obj instanceof Map) { - serializer = serializers.getHandlerFor(Map.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) { @@ -210,6 +214,9 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor { if (serializer == null && obj instanceof Map) { serializer = serializers.getHandlerFor(Map.class); } + if (serializer == null && obj instanceof Collection) { + serializer = serializers.getHandlerFor(Collection.class); + } if (serializer != null) { JsonElement child = serializer.serialize(obj, actualTypeOfField, context); addChildAsElement(f, child); diff --git a/gson/src/main/java/com/google/gson/TypeInfoCollection.java b/gson/src/main/java/com/google/gson/TypeInfoCollection.java new file mode 100644 index 00000000..50fe0350 --- /dev/null +++ b/gson/src/main/java/com/google/gson/TypeInfoCollection.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; + +/** + * A convenience object for retrieving the map type information. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +final class TypeInfoCollection { + private final ParameterizedType collectionType; + + public TypeInfoCollection(Type collectionType) { + if (!(collectionType instanceof ParameterizedType)) { + throw new IllegalArgumentException( + "Collection objects need to be parameterized unless you use a custom serializer. " + + "Use the com.google.gson.reflect.TypeToken to extract the ParameterizedType."); + } + TypeInfo rawType = new TypeInfo(collectionType); + Preconditions.checkArgument(Collection.class.isAssignableFrom(rawType.getRawClass())); + this.collectionType = (ParameterizedType) collectionType; + } + + public Type getElementType() { + return collectionType.getActualTypeArguments()[0]; + } +} 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 4152e035..3581544d 100755 --- a/gson/src/test/java/com/google/gson/functional/MapTest.java +++ b/gson/src/test/java/com/google/gson/functional/MapTest.java @@ -22,6 +22,8 @@ import java.util.Map; import junit.framework.TestCase; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.InstanceCreator; import com.google.gson.reflect.TypeToken; /** @@ -73,4 +75,27 @@ public class MapTest extends TestCase { String json = gson.toJson(map, typeOfMap); assertEquals("{}", json); } + + public void testMapSubclassSerialization() { + MyMap map = new MyMap(); + map.put("a", "b"); + String json = gson.toJson(map); + assertTrue(json.contains("\"a\":\"b\"")); + } + + public void testMapSubclassDeserialization() { + Gson gson = new GsonBuilder().registerTypeAdapter(MyMap.class, new InstanceCreator(){ + public MyMap createInstance(Type type) { + return new MyMap(); + } + }).create(); + String json = "{\"a\":1,\"b\":2}"; + MyMap map = gson.fromJson(json, MyMap.class); + assertEquals("1", map.get("a")); + assertEquals("2", map.get("b")); + } + + private static class MyMap extends LinkedHashMap { + int foo = 10; + } }