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.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()) {
|
||||||
Object value = context.deserialize(childElement, childType);
|
if (childElement == null || childElement.isJsonNull()) {
|
||||||
list.add(value);
|
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) {
|
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();
|
||||||
|
@ -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()) {
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
@ -78,6 +80,43 @@ public class CollectionTest extends TestCase {
|
|||||||
MoreAsserts.assertEquals(expected[i], toIntArray(target.get(i)));
|
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() {
|
public void testNullsInListSerialization() {
|
||||||
List<String> list = new ArrayList<String>();
|
List<String> list = new ArrayList<String>();
|
||||||
|
@ -75,11 +75,38 @@ public class MapTest extends TestCase {
|
|||||||
String json = gson.toJson(map, typeOfMap);
|
String json = gson.toJson(map, typeOfMap);
|
||||||
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\""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>();
|
||||||
|
Loading…
Reference in New Issue
Block a user