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:
parent
eaa43b76e4
commit
9245bebdba
@ -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();
|
||||
|
@ -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()) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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>();
|
||||
|
@ -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\""));
|
||||
}
|
||||
|
||||
|
@ -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>();
|
||||
|
Loading…
Reference in New Issue
Block a user