Ensured that a base class custom serializer is run when the type is specified explicitly during serialization. For all other situations, ensured that the actual type of the object is taken into consideration while serializing. First a custom handler corresponding to the actual type is looked up, then a custom handler for the specified type.

Created some serialization specific tests regarding custom serializers. Revised some tests to use toJsonTree for better asserts instead of string matching.
This commit is contained in:
Inderjeet Singh 2009-10-08 19:28:53 +00:00
parent 839b0c2f94
commit b634804533
13 changed files with 356 additions and 124 deletions

View File

@ -202,7 +202,7 @@ public final class Gson {
if (src == null) { if (src == null) {
return JsonNull.createJsonNull(); return JsonNull.createJsonNull();
} }
return toJsonTree(src, src.getClass()); return toJsonTree(src, src.getClass(), false);
} }
/** /**
@ -222,12 +222,16 @@ public final class Gson {
* @since 1.4 * @since 1.4
*/ */
public JsonElement toJsonTree(Object src, Type typeOfSrc) { public JsonElement toJsonTree(Object src, Type typeOfSrc) {
return toJsonTree(src, typeOfSrc, true);
}
private JsonElement toJsonTree(Object src, Type typeOfSrc, boolean preserveType) {
if (src == null) { if (src == null) {
return JsonNull.createJsonNull(); return JsonNull.createJsonNull();
} }
JsonSerializationContext context = new JsonSerializationContextDefault( JsonSerializationContextDefault context = new JsonSerializationContextDefault(
createDefaultObjectNavigatorFactory(serializationStrategy), serializeNulls, serializers); createDefaultObjectNavigatorFactory(serializationStrategy), serializeNulls, serializers);
return context.serialize(src, typeOfSrc); return context.serialize(src, typeOfSrc, preserveType);
} }
/** /**
@ -247,7 +251,7 @@ public final class Gson {
if (src == null) { if (src == null) {
return serializeNulls ? NULL_STRING : ""; return serializeNulls ? NULL_STRING : "";
} }
return toJson(src, src.getClass()); return toJson(src, src.getClass(), false);
} }
/** /**
@ -266,8 +270,12 @@ public final class Gson {
* @return Json representation of {@code src} * @return Json representation of {@code src}
*/ */
public String toJson(Object src, Type typeOfSrc) { public String toJson(Object src, Type typeOfSrc) {
return toJson(src, typeOfSrc, true);
}
private String toJson(Object src, Type typeOfSrc, boolean preserveType) {
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();
toJson(src, typeOfSrc, writer); toJson(src, typeOfSrc, writer, preserveType);
return writer.toString(); return writer.toString();
} }
@ -287,7 +295,7 @@ public final class Gson {
public void toJson(Object src, Appendable writer) { public void toJson(Object src, Appendable writer) {
try { try {
if (src != null) { if (src != null) {
toJson(src, src.getClass(), writer); toJson(src, src.getClass(), writer, false);
} else if (serializeNulls) { } else if (serializeNulls) {
writeOutNullString(writer); writeOutNullString(writer);
} }
@ -312,7 +320,11 @@ public final class Gson {
* @since 1.2 * @since 1.2
*/ */
public void toJson(Object src, Type typeOfSrc, Appendable writer) { public void toJson(Object src, Type typeOfSrc, Appendable writer) {
JsonElement jsonElement = toJsonTree(src, typeOfSrc); toJson(src, typeOfSrc, writer, true);
}
private void toJson(Object src, Type typeOfSrc, Appendable writer, boolean preserveType) {
JsonElement jsonElement = toJsonTree(src, typeOfSrc, preserveType);
toJson(jsonElement, writer); toJson(jsonElement, writer);
} }

View File

@ -60,7 +60,7 @@ final class JsonDeserializationContextDefault implements JsonDeserializationCont
JsonDeserializationContext context) throws JsonParseException { JsonDeserializationContext context) throws JsonParseException {
JsonArrayDeserializationVisitor<T> visitor = new JsonArrayDeserializationVisitor<T>( JsonArrayDeserializationVisitor<T> visitor = new JsonArrayDeserializationVisitor<T>(
jsonArray, arrayType, navigatorFactory, objectConstructor, deserializers, context); jsonArray, arrayType, navigatorFactory, objectConstructor, deserializers, context);
ObjectNavigator on = navigatorFactory.create(new ObjectTypePair(null, arrayType)); ObjectNavigator on = navigatorFactory.create(new ObjectTypePair(null, arrayType, true));
on.accept(visitor); on.accept(visitor);
return visitor.getTarget(); return visitor.getTarget();
} }
@ -69,7 +69,7 @@ final class JsonDeserializationContextDefault implements JsonDeserializationCont
JsonDeserializationContext context) throws JsonParseException { JsonDeserializationContext context) throws JsonParseException {
JsonObjectDeserializationVisitor<T> visitor = new JsonObjectDeserializationVisitor<T>( JsonObjectDeserializationVisitor<T> visitor = new JsonObjectDeserializationVisitor<T>(
jsonObject, typeOfT, navigatorFactory, objectConstructor, deserializers, context); jsonObject, typeOfT, navigatorFactory, objectConstructor, deserializers, context);
ObjectNavigator on = navigatorFactory.create(new ObjectTypePair(null, typeOfT)); ObjectNavigator on = navigatorFactory.create(new ObjectTypePair(null, typeOfT, true));
on.accept(visitor); on.accept(visitor);
return visitor.getTarget(); return visitor.getTarget();
} }
@ -79,7 +79,7 @@ final class JsonDeserializationContextDefault implements JsonDeserializationCont
JsonDeserializationContext context) throws JsonParseException { JsonDeserializationContext context) throws JsonParseException {
JsonObjectDeserializationVisitor<T> visitor = new JsonObjectDeserializationVisitor<T>( JsonObjectDeserializationVisitor<T> visitor = new JsonObjectDeserializationVisitor<T>(
json, typeOfT, navigatorFactory, objectConstructor, deserializers, context); json, typeOfT, navigatorFactory, objectConstructor, deserializers, context);
ObjectNavigator on = navigatorFactory.create(new ObjectTypePair(json.getAsObject(), typeOfT)); ObjectNavigator on = navigatorFactory.create(new ObjectTypePair(json.getAsObject(), typeOfT, true));
on.accept(visitor); on.accept(visitor);
Object target = visitor.getTarget(); Object target = visitor.getTarget();
return (T) target; return (T) target;

View File

@ -92,10 +92,14 @@ abstract class JsonDeserializationVisitor<T> implements ObjectNavigator.Visitor
} }
private Object visitChild(Type type, JsonDeserializationVisitor<?> childVisitor) { private Object visitChild(Type type, JsonDeserializationVisitor<?> childVisitor) {
ObjectNavigator on = factory.create(new ObjectTypePair(null, type)); ObjectNavigator on = factory.create(new ObjectTypePair(null, type, true));
on.accept(childVisitor); on.accept(childVisitor);
// the underlying object may have changed during the construction phase // the underlying object may have changed during the construction phase
// This happens primarily because of custom deserializers // This happens primarily because of custom deserializers
return childVisitor.getTarget(); return childVisitor.getTarget();
} }
public ObjectTypePair getActualTypeIfMoreSpecific(ObjectTypePair objTypePair) {
return objTypePair;
}
} }

View File

@ -42,11 +42,15 @@ final class JsonSerializationContextDefault implements JsonSerializationContext
if (src == null) { if (src == null) {
return JsonNull.createJsonNull(); return JsonNull.createJsonNull();
} }
return serialize(src, src.getClass()); return serialize(src, src.getClass(), false);
} }
public JsonElement serialize(Object src, Type typeOfSrc) { public JsonElement serialize(Object src, Type typeOfSrc) {
ObjectNavigator on = factory.create(new ObjectTypePair(src, typeOfSrc)); return serialize(src, typeOfSrc, true);
}
public JsonElement serialize(Object src, Type typeOfSrc, boolean preserveType) {
ObjectNavigator on = factory.create(new ObjectTypePair(src, typeOfSrc, preserveType));
JsonSerializationVisitor visitor = JsonSerializationVisitor visitor =
new JsonSerializationVisitor(factory, serializeNulls, serializers, this, ancestors); new JsonSerializationVisitor(factory, serializeNulls, serializers, this, ancestors);
on.accept(visitor); on.accept(visitor);

View File

@ -22,7 +22,7 @@ import java.lang.reflect.Type;
/** /**
* A visitor that adds JSON elements corresponding to each field of an object * A visitor that adds JSON elements corresponding to each field of an object
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
*/ */
@ -36,15 +36,15 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
private JsonElement root; private JsonElement root;
JsonSerializationVisitor(ObjectNavigatorFactory factory, boolean serializeNulls, JsonSerializationVisitor(ObjectNavigatorFactory factory, boolean serializeNulls,
ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers, ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers, JsonSerializationContext context,
JsonSerializationContext context, MemoryRefStack ancestors) { MemoryRefStack ancestors) {
this.factory = factory; this.factory = factory;
this.serializeNulls = serializeNulls; this.serializeNulls = serializeNulls;
this.serializers = serializers; this.serializers = serializers;
this.context = context; this.context = context;
this.ancestors = ancestors; this.ancestors = ancestors;
} }
public Object getTarget() { public Object getTarget() {
return null; return null;
} }
@ -77,10 +77,10 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
for (int i = 0; i < length; ++i) { for (int i = 0; i < length; ++i) {
Object child = Array.get(array, i); Object child = Array.get(array, i);
Type childType = componentType; Type childType = componentType;
if (child != null) { // we should not get more specific component type yet since it is possible
childType = getActualTypeIfMoreSpecific(childType, child.getClass()); // that a custom
} // serializer is registered for the componentType
addAsArrayElement(new ObjectTypePair(child, childType)); addAsArrayElement(new ObjectTypePair(child, childType, false));
} }
} }
@ -92,7 +92,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
} }
} else { } else {
Object array = getFieldValue(f, obj); Object array = getFieldValue(f, obj);
addAsChildOfObject(f, new ObjectTypePair(array, typeOfF)); addAsChildOfObject(f, new ObjectTypePair(array, typeOfF, false));
} }
} catch (CircularReferenceException e) { } catch (CircularReferenceException e) {
throw e.createDetailedException(f); throw e.createDetailedException(f);
@ -107,33 +107,16 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
} }
} else { } else {
Object fieldValue = getFieldValue(f, obj); Object fieldValue = getFieldValue(f, obj);
if (fieldValue != null) { // we should not get more specific component type yet since it is
typeOfF = getActualTypeIfMoreSpecific(typeOfF, fieldValue.getClass()); // possible that a custom
} // serializer is registered for the componentType
addAsChildOfObject(f, new ObjectTypePair(fieldValue, typeOfF)); addAsChildOfObject(f, new ObjectTypePair(fieldValue, typeOfF, false));
} }
} catch (CircularReferenceException e) { } catch (CircularReferenceException e) {
throw e.createDetailedException(f); throw e.createDetailedException(f);
} }
} }
// This takes care of situations where the field was declared as an Object, but the
// actual value contains something more specific. See Issue 54.
// TODO (inder): This solution will not work if the field is of a generic type, but
// the actual object is of a raw type (which is a sub-class of the generic type).
private Type getActualTypeIfMoreSpecific(Type type, Class<?> actualClass) {
if (type instanceof Class<?>) {
Class<?> typeAsClass = (Class<?>) type;
if (typeAsClass.isAssignableFrom(actualClass)) {
type = actualClass;
}
if (type == Object.class) {
type = actualClass;
}
}
return type;
}
public void visitPrimitive(Object obj) { public void visitPrimitive(Object obj) {
JsonElement json = obj == null ? JsonNull.createJsonNull() : new JsonPrimitive(obj); JsonElement json = obj == null ? JsonNull.createJsonNull() : new JsonPrimitive(obj);
assignToRoot(json); assignToRoot(json);
@ -170,28 +153,35 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
public boolean visitUsingCustomHandler(ObjectTypePair objTypePair) { public boolean visitUsingCustomHandler(ObjectTypePair objTypePair) {
try { try {
Object obj = objTypePair.getObject(); Object obj = objTypePair.getObject();
Type objType = objTypePair.getType(); if (obj == null) {
JsonSerializer serializer = serializers.getHandlerFor(objType); if (serializeNulls) {
if (serializer == null && obj != null) {
serializer = serializers.getHandlerFor(obj.getClass());
}
if (serializer != null) {
if (obj == null) {
assignToRoot(JsonNull.createJsonNull()); assignToRoot(JsonNull.createJsonNull());
} else {
assignToRoot(invokeCustomHandler(objTypePair, serializer));
} }
return true; return true;
} }
return false; JsonElement element = findAndInvokeCustomSerializer(objTypePair);
if (element != null) {
assignToRoot(element);
return true;
} else {
return false;
}
} catch (CircularReferenceException e) { } catch (CircularReferenceException e) {
throw e.createDetailedException(null); throw e.createDetailedException(null);
} }
} }
/**
* objTypePair.getObject() must not be null
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private JsonElement invokeCustomHandler(ObjectTypePair objTypePair, JsonSerializer serializer) { private JsonElement findAndInvokeCustomSerializer(ObjectTypePair objTypePair) {
Pair<JsonSerializer, ObjectTypePair> pair = objTypePair.getMatchingSerializer(serializers);
if (pair == null) {
return null;
}
JsonSerializer serializer = pair.getFirst();
objTypePair = pair.getSecond();
start(objTypePair); start(objTypePair);
try { try {
return serializer.serialize(objTypePair.getObject(), objTypePair.getType(), context); return serializer.serialize(objTypePair.getObject(), objTypePair.getType(), context);
@ -201,7 +191,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public boolean visitFieldUsingCustomHandler(Field f, Type actualTypeOfField, Object parent) { public boolean visitFieldUsingCustomHandler(Field f, Type declaredTypeOfField, Object parent) {
try { try {
Preconditions.checkState(root.isJsonObject()); Preconditions.checkState(root.isJsonObject());
Object obj = f.get(parent); Object obj = f.get(parent);
@ -211,14 +201,14 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
} }
return true; return true;
} }
JsonSerializer serializer = serializers.getHandlerFor(actualTypeOfField); ObjectTypePair objTypePair = new ObjectTypePair(obj, declaredTypeOfField, false);
if (serializer != null) { JsonElement child = findAndInvokeCustomSerializer(objTypePair);
ObjectTypePair objTypePair = new ObjectTypePair(obj, actualTypeOfField); if (child != null) {
JsonElement child = invokeCustomHandler(objTypePair, serializer);
addChildAsElement(f, child); addChildAsElement(f, child);
return true; return true;
} else {
return false;
} }
return false;
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
throw new RuntimeException(); throw new RuntimeException();
} catch (CircularReferenceException e) { } catch (CircularReferenceException e) {
@ -246,4 +236,8 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
public JsonElement getJsonElement() { public JsonElement getJsonElement() {
return root; return root;
} }
public ObjectTypePair getActualTypeIfMoreSpecific(ObjectTypePair objTypePair) {
return objTypePair.toMoreSpecificType();
}
} }

View File

@ -63,6 +63,8 @@ final class ObjectNavigator {
*/ */
public boolean visitFieldUsingCustomHandler(Field f, Type actualTypeOfField, Object parent); public boolean visitFieldUsingCustomHandler(Field f, Type actualTypeOfField, Object parent);
public ObjectTypePair getActualTypeIfMoreSpecific(ObjectTypePair objTypePair);
/** /**
* Retrieve the current target * Retrieve the current target
*/ */
@ -114,10 +116,8 @@ final class ObjectNavigator {
objectToVisit = visitor.getTarget(); objectToVisit = visitor.getTarget();
} else { } else {
visitor.startVisitingObject(objectToVisit); visitor.startVisitingObject(objectToVisit);
// For all classes in the inheritance hierarchy (including the current class), ObjectTypePair currObjTypePair = visitor.getActualTypeIfMoreSpecific(objTypePair);
// visit all fields Class<?> topLevelClass = new TypeInfo(currObjTypePair.getType()).getRawClass();
Class<?> topLevelClass = (objTypeInfo.getRawClass() == Object.class)
? objectToVisit.getClass() : objTypeInfo.getRawClass();
for (Class<?> curr = topLevelClass; curr != null && !curr.equals(Object.class); for (Class<?> curr = topLevelClass; curr != null && !curr.equals(Object.class);
curr = curr.getSuperclass()) { curr = curr.getSuperclass()) {
if (!curr.isSynthetic()) { if (!curr.isSynthetic()) {
@ -145,14 +145,14 @@ final class ObjectNavigator {
continue; // skip continue; // skip
} else { } else {
TypeInfo fieldTypeInfo = TypeInfoFactory.getTypeInfoForField(f, objTypePair.getType()); TypeInfo fieldTypeInfo = TypeInfoFactory.getTypeInfoForField(f, objTypePair.getType());
Type actualTypeOfField = fieldTypeInfo.getActualType(); Type declaredTypeOfField = fieldTypeInfo.getActualType();
boolean visitedWithCustomHandler = boolean visitedWithCustomHandler =
visitor.visitFieldUsingCustomHandler(f, actualTypeOfField, obj); visitor.visitFieldUsingCustomHandler(f, declaredTypeOfField, obj);
if (!visitedWithCustomHandler) { if (!visitedWithCustomHandler) {
if (fieldTypeInfo.isArray()) { if (fieldTypeInfo.isArray()) {
visitor.visitArrayField(f, actualTypeOfField, obj); visitor.visitArrayField(f, declaredTypeOfField, obj);
} else { } else {
visitor.visitObjectField(f, actualTypeOfField, obj); visitor.visitObjectField(f, declaredTypeOfField, obj);
} }
} }
} }

View File

@ -27,17 +27,67 @@ final class ObjectTypePair {
private final Object obj; private final Object obj;
private final Type type; private final Type type;
private final boolean preserveType;
public ObjectTypePair(Object obj, Type type) { ObjectTypePair(Object obj, Type type, boolean preserveType) {
this.obj = obj; this.obj = obj;
this.type = type; this.type = type;
this.preserveType = preserveType;
} }
public Object getObject() { Object getObject() {
return obj; return obj;
} }
public Type getType() { Type getType() {
return type;
}
@SuppressWarnings("unchecked")
Pair<JsonSerializer, ObjectTypePair> getMatchingSerializer(
ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers) {
if (obj == null) {
return null;
}
JsonSerializer serializer = null;
if (!preserveType) {
// First try looking up the serializer for the actual type
ObjectTypePair moreSpecificType = toMoreSpecificType();
serializer = serializers.getHandlerFor(moreSpecificType.type);
if (serializer != null) {
return new Pair<JsonSerializer, ObjectTypePair>(serializer, moreSpecificType);
}
}
// Try the specified type
serializer = serializers.getHandlerFor(type);
return serializer == null ? null : new Pair<JsonSerializer, ObjectTypePair>(serializer, this);
}
ObjectTypePair toMoreSpecificType() {
if (preserveType || obj == null) {
return this;
}
Type actualType = getActualTypeIfMoreSpecific(type, obj.getClass());
if (actualType == type) {
return this;
}
return new ObjectTypePair(obj, actualType, preserveType);
}
// This takes care of situations where the field was declared as an Object, but the
// actual value contains something more specific. See Issue 54.
// TODO (inder): This solution will not work if the field is of a generic type, but
// the actual object is of a raw type (which is a sub-class of the generic type).
static Type getActualTypeIfMoreSpecific(Type type, Class<?> actualClass) {
if (type instanceof Class<?>) {
Class<?> typeAsClass = (Class<?>) type;
if (typeAsClass.isAssignableFrom(actualClass)) {
type = actualClass;
}
if (type == Object.class) {
type = actualClass;
}
}
return type; return type;
} }
@ -74,6 +124,6 @@ final class ObjectTypePair {
} else if (!type.equals(other.type)) { } else if (!type.equals(other.type)) {
return false; return false;
} }
return true; return preserveType == other.preserveType;
} }
} }

View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2009 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;
final class Pair<FIRST, SECOND> {
private final FIRST first;
private final SECOND second;
Pair(FIRST first, SECOND second) {
this.first = first;
this.second = second;
}
public FIRST getFirst() {
return first;
}
public SECOND getSecond() {
return second;
}
}

View File

@ -43,7 +43,7 @@ public class MemoryRefStackTest extends TestCase {
} }
public void testPushPeekAndPop() throws Exception { public void testPushPeekAndPop() throws Exception {
ObjectTypePair obj = new ObjectTypePair(this, getClass()); ObjectTypePair obj = new ObjectTypePair(this, getClass(), true);
assertEquals(obj, stack.push(obj)); assertEquals(obj, stack.push(obj));
assertEquals(obj, stack.peek()); assertEquals(obj, stack.peek());
@ -51,7 +51,7 @@ public class MemoryRefStackTest extends TestCase {
} }
public void testPopTooMany() throws Exception { public void testPopTooMany() throws Exception {
ObjectTypePair obj = new ObjectTypePair(this, getClass()); ObjectTypePair obj = new ObjectTypePair(this, getClass(), true);
stack.push(obj); stack.push(obj);
assertEquals(obj, stack.pop()); assertEquals(obj, stack.pop());
@ -64,9 +64,9 @@ public class MemoryRefStackTest extends TestCase {
MockObject objA = new MockObject(); MockObject objA = new MockObject();
MockObject objB = new MockObject(); MockObject objB = new MockObject();
assertEquals(objA, objB); assertEquals(objA, objB);
stack.push(new ObjectTypePair(objA, MockObject.class)); stack.push(new ObjectTypePair(objA, MockObject.class, true));
assertTrue(stack.contains(new ObjectTypePair(objA, MockObject.class))); assertTrue(stack.contains(new ObjectTypePair(objA, MockObject.class, true)));
assertFalse(stack.contains(new ObjectTypePair(objB, MockObject.class))); assertFalse(stack.contains(new ObjectTypePair(objB, MockObject.class, true)));
} }
private static class MockObject { private static class MockObject {

View File

@ -16,17 +16,18 @@
package com.google.gson.common; package com.google.gson.common;
import java.lang.reflect.Type;
import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer; import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer; import com.google.gson.JsonSerializer;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import java.lang.reflect.Type;
/** /**
* Types used for testing JSON serialization and deserialization * Types used for testing JSON serialization and deserialization
* *
@ -34,34 +35,62 @@ import com.google.gson.annotations.SerializedName;
* @author Joel Leitch * @author Joel Leitch
*/ */
public class TestTypes { public class TestTypes {
public static class Base {
public static final String BASE_NAME = Base.class.getSimpleName();
public static final String BASE_FIELD_KEY = "baseName";
public static final String SERIALIZER_KEY = "serializerName";
public String baseName = BASE_NAME;
public String serializerName;
}
public static class BaseClass { public static class Sub extends Base {
final String baseField; public static final String SUB_NAME = Sub.class.getSimpleName();
public BaseClass() { public static final String SUB_FIELD_KEY = "subName";
this("baseFieldValue"); public final String subName = SUB_NAME;
}
public static class ClassWithBaseField {
public static final String FIELD_KEY = "base";
public final Base base;
@SuppressWarnings("unused")
private ClassWithBaseField() {
this(null);
} }
public BaseClass(String value) { public ClassWithBaseField(Base base) {
this.baseField = value; this.base = base;
}
public String getExpectedJson() {
return String.format("{\"baseField\":\"%s\"}", baseField);
} }
} }
public static class SubClass extends BaseClass { public static class ClassWithBaseArrayField {
final String subField; public static final String FIELD_KEY = "base";
public SubClass() { public final Base[] base;
this("subFieldValue"); @SuppressWarnings("unused")
private ClassWithBaseArrayField() {
this(null);
} }
public SubClass(String subFieldValue) { public ClassWithBaseArrayField(Base[] base) {
this.subField = subFieldValue; this.base = base;
}
@Override
public String getExpectedJson() {
return String.format("{\"subField\":\"%s\",\"baseField\":\"%s\"}", subField, baseField);
} }
} }
public static class BaseSerializer implements JsonSerializer<Base> {
public static final String NAME = BaseSerializer.class.getSimpleName();
public JsonElement serialize(Base src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject obj = new JsonObject();
obj.addProperty(Base.SERIALIZER_KEY, NAME);
return obj;
}
}
public static class SubSerializer implements JsonSerializer<Sub> {
public static final String NAME = SubSerializer.class.getSimpleName();
public JsonElement serialize(Sub src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject obj = new JsonObject();
obj.addProperty(Base.SERIALIZER_KEY, NAME);
return obj;
}
}
public static class StringWrapper { public static class StringWrapper {
public final String someConstantStringInstanceField; public final String someConstantStringInstanceField;

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2009 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.functional;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.common.TestTypes.Base;
import com.google.gson.common.TestTypes.BaseSerializer;
import com.google.gson.common.TestTypes.ClassWithBaseArrayField;
import com.google.gson.common.TestTypes.ClassWithBaseField;
import com.google.gson.common.TestTypes.Sub;
import com.google.gson.common.TestTypes.SubSerializer;
import junit.framework.TestCase;
/**
* Functional Test exercising custom serialization only. When test applies to both
* serialization and deserialization then add it to CustomTypeAdapterTest.
*
* @author Inderjeet Singh
*/
public class CustomSerializerTest extends TestCase {
public void testBaseClassSerializerInvokedForBaseClassFields() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Base.class, new BaseSerializer())
.registerTypeAdapter(Sub.class, new SubSerializer())
.create();
ClassWithBaseField target = new ClassWithBaseField(new Base());
JsonObject json = (JsonObject) gson.toJsonTree(target);
JsonObject base = json.get("base").getAsJsonObject();
assertEquals(BaseSerializer.NAME, base.get(Base.SERIALIZER_KEY).getAsString());
}
public void testSubClassSerializerInvokedForBaseClassFieldsHoldingSubClassInstances() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Base.class, new BaseSerializer())
.registerTypeAdapter(Sub.class, new SubSerializer())
.create();
ClassWithBaseField target = new ClassWithBaseField(new Sub());
JsonObject json = (JsonObject) gson.toJsonTree(target);
JsonObject base = json.get("base").getAsJsonObject();
assertEquals(SubSerializer.NAME, base.get(Base.SERIALIZER_KEY).getAsString());
}
public void testSubClassSerializerInvokedForBaseClassFieldsHoldingArrayOfSubClassInstances() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Base.class, new BaseSerializer())
.registerTypeAdapter(Sub.class, new SubSerializer())
.create();
ClassWithBaseArrayField target = new ClassWithBaseArrayField(new Base[] {new Sub(), new Sub()});
JsonObject json = (JsonObject) gson.toJsonTree(target);
JsonArray array = json.get("base").getAsJsonArray();
for (JsonElement element : array) {
JsonElement serializerKey = element.getAsJsonObject().get(Base.SERIALIZER_KEY);
assertEquals(SubSerializer.NAME, serializerKey.getAsString());
}
}
public void testBaseClassSerializerInvokedForBaseClassFieldsHoldingSubClassInstances() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Base.class, new BaseSerializer())
.create();
ClassWithBaseField target = new ClassWithBaseField(new Sub());
JsonObject json = (JsonObject) gson.toJsonTree(target);
JsonObject base = json.get("base").getAsJsonObject();
assertEquals(BaseSerializer.NAME, base.get(Base.SERIALIZER_KEY).getAsString());
}
}

View File

@ -473,5 +473,4 @@ public class CustomTypeAdaptersTest extends TestCase {
return new DataHolder(jsonElement.getAsString()); return new DataHolder(jsonElement.getAsString());
} }
} }
} }

View File

@ -15,6 +15,19 @@
*/ */
package com.google.gson.functional; package com.google.gson.functional;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.common.TestTypes.Base;
import com.google.gson.common.TestTypes.ClassWithBaseArrayField;
import com.google.gson.common.TestTypes.ClassWithBaseField;
import com.google.gson.common.TestTypes.Nested;
import com.google.gson.common.TestTypes.Sub;
import junit.framework.TestCase;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -23,14 +36,6 @@ import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import junit.framework.TestCase;
import com.google.gson.Gson;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.common.TestTypes.BaseClass;
import com.google.gson.common.TestTypes.Nested;
import com.google.gson.common.TestTypes.SubClass;
/** /**
* Functional tests for Json serialization and deserialization of classes with * Functional tests for Json serialization and deserialization of classes with
* inheritance hierarchies. * inheritance hierarchies.
@ -62,26 +67,40 @@ public class InheritanceTest extends TestCase {
assertEquals(json, target.getExpectedJson()); assertEquals(json, target.getExpectedJson());
} }
public void testClassWithBaseFieldSerialization() throws Exception { public void testClassWithBaseFieldSerialization() {
ClassWithBaseFields sub = new ClassWithBaseFields(); ClassWithBaseField sub = new ClassWithBaseField(new Sub());
sub.field = new SubClass(); JsonObject json = (JsonObject) gson.toJsonTree(sub);
String json = gson.toJson(sub); JsonElement base = json.getAsJsonObject().get(ClassWithBaseField.FIELD_KEY);
String expectedJson = sub.field.getExpectedJson(); assertEquals(Sub.SUB_NAME, base.getAsJsonObject().get(Sub.SUB_FIELD_KEY).getAsString());
assertTrue(json.contains(expectedJson));
} }
public void testClassWithBaseArrayFieldSerialization() throws Exception { public void testClassWithBaseArrayFieldSerialization() {
ClassWithBaseFields sub = new ClassWithBaseFields(); Base[] baseClasses = new Base[]{ new Sub(), new Sub()};
sub.array = new BaseClass[]{ new SubClass("sub1"), new SubClass("sub2")}; ClassWithBaseArrayField sub = new ClassWithBaseArrayField(baseClasses);
String json = gson.toJson(sub); JsonObject json = gson.toJsonTree(sub).getAsJsonObject();
assertTrue(json.contains("sub1")); JsonArray bases = json.get(ClassWithBaseArrayField.FIELD_KEY).getAsJsonArray();
assertTrue(json.contains("sub2")); for (JsonElement element : bases) {
assertEquals(Sub.SUB_NAME, element.getAsJsonObject().get(Sub.SUB_FIELD_KEY).getAsString());
}
} }
private static class ClassWithBaseFields { public void testBaseSerializedAsSub() {
BaseClass field; Base base = new Sub();
@SuppressWarnings("unused") JsonObject json = gson.toJsonTree(base).getAsJsonObject();
BaseClass[] array; assertEquals(Sub.SUB_NAME, json.get(Sub.SUB_FIELD_KEY).getAsString());
}
public void testBaseSerializedAsBaseWhenSpecifiedWithExplicitType() {
Base base = new Sub();
JsonObject json = gson.toJsonTree(base, Base.class).getAsJsonObject();
assertEquals(Base.BASE_NAME, json.get(Base.BASE_FIELD_KEY).getAsString());
assertNull(json.get(Sub.SUB_FIELD_KEY));
}
public void testBaseSerializedAsSubWhenSpecifiedWithExplicitType() {
Base base = new Sub();
JsonObject json = gson.toJsonTree(base, Sub.class).getAsJsonObject();
assertEquals(Sub.SUB_NAME, json.get(Sub.SUB_FIELD_KEY).getAsString());
} }
private static class SubTypeOfNested extends Nested { private static class SubTypeOfNested extends Nested {