Use more specific type for map serialization if possible.

This commit is contained in:
Joel Leitch 2011-04-04 23:13:31 +00:00
parent 50b4e3f4b9
commit 2fb8c92812
6 changed files with 139 additions and 20 deletions

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) 2011 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.Type;
import java.util.Map;
/**
* Captures all the common/shared logic between the old, ({@link MapTypeAdapter}, and
* the new, {@link MapAsArrayTypeAdapter}, map type adapters.
*
* @author Joel Leitch
*/
abstract class BaseMapTypeAdapter
implements JsonSerializer<Map<?, ?>>, JsonDeserializer<Map<?, ?>> {
protected static final JsonElement serialize(JsonSerializationContext context,
Object src, Type srcType) {
JsonSerializationContextDefault contextImpl = (JsonSerializationContextDefault) context;
return contextImpl.serialize(src, srcType, false);
}
@SuppressWarnings("unchecked")
protected static final Map<Object, Object> constructMapType(
Type mapType, JsonDeserializationContext context) {
JsonDeserializationContextDefault contextImpl = (JsonDeserializationContextDefault) context;
ObjectConstructor objectConstructor = contextImpl.getObjectConstructor();
return (Map<Object, Object>) objectConstructor.construct(mapType);
}
}

View File

@ -46,16 +46,20 @@ final class JsonSerializationContextDefault implements JsonSerializationContext
if (src == null) {
return JsonNull.createJsonNull();
}
return serialize(src, src.getClass());
return serialize(src, src.getClass(), false);
}
public JsonElement serialize(Object src, Type typeOfSrc) {
return serialize(src, typeOfSrc, true);
}
JsonElement serialize(Object src, Type typeOfSrc, boolean preserveType) {
if (src == null) {
return JsonNull.createJsonNull();
}
JsonSerializationVisitor visitor = new JsonSerializationVisitor(
objectNavigator, fieldNamingPolicy, serializeNulls, serializers, this, ancestors);
objectNavigator.accept(new ObjectTypePair(src, typeOfSrc, true), visitor);
objectNavigator.accept(new ObjectTypePair(src, typeOfSrc, preserveType), visitor);
return visitor.getJsonElement();
}
}

View File

@ -90,6 +90,7 @@ import java.util.Map;
* object.
*/
final class MapAsArrayTypeAdapter
extends BaseMapTypeAdapter
implements JsonSerializer<Map<?, ?>>, JsonDeserializer<Map<?, ?>> {
public Map<?, ?> deserialize(JsonElement json, Type typeOfT,
@ -122,10 +123,10 @@ final class MapAsArrayTypeAdapter
boolean serializeAsArray = false;
List<JsonElement> keysAndValues = new ArrayList<JsonElement>();
for (Map.Entry<?, ?> entry : src.entrySet()) {
JsonElement key = context.serialize(entry.getKey(), keyAndValueType[0]);
JsonElement key = serialize(context, entry.getKey(), keyAndValueType[0]);
serializeAsArray |= key.isJsonObject() || key.isJsonArray();
keysAndValues.add(key);
keysAndValues.add(context.serialize(entry.getValue(), keyAndValueType[1]));
keysAndValues.add(serialize(context, entry.getValue(), keyAndValueType[1]));
}
if (serializeAsArray) {
@ -146,13 +147,6 @@ final class MapAsArrayTypeAdapter
return result;
}
}
@SuppressWarnings("unchecked")
private Map<Object, Object> constructMapType(Type mapType, JsonDeserializationContext context) {
JsonDeserializationContextDefault contextImpl = (JsonDeserializationContextDefault) context;
ObjectConstructor objectConstructor = contextImpl.getObjectConstructor();
return (Map<Object, Object>) objectConstructor.construct(mapType);
}
private Type[] typeToTypeArguments(Type typeOfT) {
if (typeOfT instanceof ParameterizedType) {

View File

@ -31,8 +31,8 @@ import java.util.Set;
* @author Joel Leitch
*/
@SuppressWarnings("unchecked")
final class MapTypeAdapter implements JsonSerializer<Map<?, ?>>,
JsonDeserializer<Map<?, ?>> {
final class MapTypeAdapter extends BaseMapTypeAdapter {
public JsonElement serialize(Map src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject map = new JsonObject();
Type childGenericType = null;
@ -50,7 +50,7 @@ final class MapTypeAdapter implements JsonSerializer<Map<?, ?>>,
} else {
Type childType = (childGenericType == null)
? value.getClass() : childGenericType;
valueElement = context.serialize(value, childType);
valueElement = serialize(context, value, childType);
}
map.add(String.valueOf(entry.getKey()), valueElement);
}
@ -71,12 +71,6 @@ final class MapTypeAdapter implements JsonSerializer<Map<?, ?>>,
return map;
}
private Map constructMapType(Type mapType, JsonDeserializationContext context) {
JsonDeserializationContextDefault contextImpl = (JsonDeserializationContextDefault) context;
ObjectConstructor objectConstructor = contextImpl.getObjectConstructor();
return (Map) objectConstructor.construct(mapType);
}
@Override
public String toString() {
return MapTypeAdapter.class.getSimpleName();

View File

@ -19,6 +19,10 @@ package com.google.gson.functional;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.JsonElement;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.common.TestTypes;
import com.google.gson.reflect.TypeToken;
import junit.framework.TestCase;
@ -325,4 +329,71 @@ public class MapTest extends TestCase {
assertEquals("{\"a\":12,\"c\":{}}",
new GsonBuilder().create().toJson(map));
}
public final void testInterfaceTypeMap() {
MapClass element = new MapClass();
TestTypes.Sub subType = new TestTypes.Sub();
element.addBase("Test", subType);
element.addSub("Test", subType);
String subTypeJson = new Gson().toJson(subType);
String expected = "{\"bases\":{\"Test\":" + subTypeJson + "},"
+ "\"subs\":{\"Test\":" + subTypeJson + "}}";
Gson gsonWithComplexKeys = new GsonBuilder()
.enableComplexMapKeySerialization()
.create();
String json = gsonWithComplexKeys.toJson(element);
assertEquals(expected, json);
Gson gson = new Gson();
json = gson.toJson(element);
assertEquals(expected, json);
}
public final void testInterfaceTypeMapWithSerializer() {
MapClass element = new MapClass();
TestTypes.Sub subType = new TestTypes.Sub();
element.addBase("Test", subType);
element.addSub("Test", subType);
Gson tempGson = new Gson();
String subTypeJson = tempGson.toJson(subType);
final JsonElement baseTypeJson = tempGson.toJsonTree(subType, TestTypes.Base.class);
String expected = "{\"bases\":{\"Test\":" + baseTypeJson.toString() + "},"
+ "\"subs\":{\"Test\":" + subTypeJson + "}}";
JsonSerializer<TestTypes.Base> baseTypeAdapter = new JsonSerializer<TestTypes.Base>() {
public JsonElement serialize(TestTypes.Base src, Type typeOfSrc,
JsonSerializationContext context) {
return baseTypeJson;
}
};
Gson gson = new GsonBuilder()
.enableComplexMapKeySerialization()
.registerTypeAdapter(TestTypes.Base.class, baseTypeAdapter)
.create();
String json = gson.toJson(element);
assertEquals(expected, json);
gson = new GsonBuilder()
.registerTypeAdapter(TestTypes.Base.class, baseTypeAdapter)
.create();
json = gson.toJson(element);
assertEquals(expected, json);
}
static final class MapClass {
private final Map<String, TestTypes.Base> bases = new HashMap<String, TestTypes.Base>();
private final Map<String, TestTypes.Sub> subs = new HashMap<String, TestTypes.Sub>();
public final void addBase(final String name, final TestTypes.Base value) {
bases.put(name, value);
}
public final void addSub(final String name, final TestTypes.Sub value) {
subs.put(name, value);
}
}
}

View File

@ -26,6 +26,7 @@ import com.google.gson.common.TestTypes.CrazyLongTypeAdapter;
import junit.framework.TestCase;
import java.io.Serializable;
import java.io.StringReader;
import java.math.BigDecimal;
import java.math.BigInteger;
@ -345,6 +346,16 @@ public class PrimitiveTest extends TestCase {
} catch (JsonParseException expected) { }
}
public void testMoreSpecificSerialization() {
Gson gson = new Gson();
String expected = "This is a string";
String expectedJson = gson.toJson(expected);
Serializable serializableString = expected;
String actualJson = gson.toJson(serializableString, Serializable.class);
assertFalse(expectedJson.equals(actualJson));
}
public void testOverridingDefaultPrimitiveSerialization() {
CrazyLongTypeAdapter typeAdapter = new CrazyLongTypeAdapter();
gson = new GsonBuilder()