diff --git a/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java b/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java index 9d1e5bc9..2d3183c0 100644 --- a/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java +++ b/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java @@ -76,7 +76,11 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor { Type componentType = fieldTypeInfo.getSecondLevelType(); for (int i = 0; i < length; ++i) { Object child = Array.get(array, i); - addAsArrayElement(componentType, child); + Type childType = componentType; + if (child != null) { + childType = getActualTypeIfMoreSpecific(childType, child.getClass()); + } + addAsArrayElement(childType, child); } } @@ -98,15 +102,28 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor { } } else { Object fieldValue = getFieldValue(f, obj); - // This takes care of situations where the field was declared as an Object, but the - // actual value contains something more specific. See Issue 54. - if (fieldValue != null && typeOfF == Object.class) { - typeOfF = fieldValue.getClass(); + if (fieldValue != null) { + typeOfF = getActualTypeIfMoreSpecific(typeOfF, fieldValue.getClass()); } addAsChildOfObject(f, typeOfF, fieldValue); } } + // This takes care of situations where the field was declared as an Object, but the + // actual value contains something more specific. See Issue 54. + 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) { if (obj != null) { JsonElement json = new JsonPrimitive(obj); diff --git a/gson/src/test/java/com/google/gson/common/TestTypes.java b/gson/src/test/java/com/google/gson/common/TestTypes.java index 808f3e0e..3154b93e 100644 --- a/gson/src/test/java/com/google/gson/common/TestTypes.java +++ b/gson/src/test/java/com/google/gson/common/TestTypes.java @@ -35,6 +35,33 @@ import com.google.gson.annotations.SerializedName; */ public class TestTypes { + public static class BaseClass { + final String baseField; + public BaseClass() { + this("baseFieldValue"); + } + public BaseClass(String value) { + this.baseField = value; + } + public String getExpectedJson() { + return String.format("{\"baseField\":\"%s\"}", baseField); + } + } + + public static class SubClass extends BaseClass { + final String subField; + public SubClass() { + this("subFieldValue"); + } + public SubClass(String subFieldValue) { + this.subField = subFieldValue; + } + @Override + public String getExpectedJson() { + return String.format("{\"subField\":\"%s\",\"baseField\":\"%s\"}", subField, baseField); + } + } + public static class StringWrapper { public final String someConstantStringInstanceField; diff --git a/gson/src/test/java/com/google/gson/functional/InheritanceTest.java b/gson/src/test/java/com/google/gson/functional/InheritanceTest.java new file mode 100644 index 00000000..224c265a --- /dev/null +++ b/gson/src/test/java/com/google/gson/functional/InheritanceTest.java @@ -0,0 +1,236 @@ +/* + * 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 java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.SortedSet; +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 + * inheritance hierarchies. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public class InheritanceTest extends TestCase { + private Gson gson; + + @Override + protected void setUp() throws Exception { + super.setUp(); + gson = new Gson(); + } + + public void testSubClassSerialization() throws Exception { + SubTypeOfNested target = new SubTypeOfNested(new BagOfPrimitives(10, 20, false, "stringValue"), + new BagOfPrimitives(30, 40, true, "stringValue")); + assertEquals(target.getExpectedJson(), gson.toJson(target)); + } + + public void testSubClassDeserialization() throws Exception { + String json = "{\"value\":5,\"primitive1\":{\"longValue\":10,\"intValue\":20," + + "\"booleanValue\":false,\"stringValue\":\"stringValue\"},\"primitive2\":" + + "{\"longValue\":30,\"intValue\":40,\"booleanValue\":true," + + "\"stringValue\":\"stringValue\"}}"; + SubTypeOfNested target = gson.fromJson(json, SubTypeOfNested.class); + assertEquals(json, target.getExpectedJson()); + } + + public void testClassWithBaseFieldSerialization() throws Exception { + ClassWithBaseFields sub = new ClassWithBaseFields(); + sub.field = new SubClass(); + String json = gson.toJson(sub); + String expectedJson = sub.field.getExpectedJson(); + assertTrue(json.contains(expectedJson)); + } + + public void testClassWithBaseArrayFieldSerialization() throws Exception { + ClassWithBaseFields sub = new ClassWithBaseFields(); + sub.array = new BaseClass[]{ new SubClass("sub1"), new SubClass("sub2")}; + String json = gson.toJson(sub); + assertTrue(json.contains("sub1")); + assertTrue(json.contains("sub2")); + } + + private static class ClassWithBaseFields { + BaseClass field; + @SuppressWarnings("unused") + BaseClass[] array; + } + + private static class SubTypeOfNested extends Nested { + private final long value = 5; + + // Used by Gson + @SuppressWarnings("unused") + private SubTypeOfNested() { + this(null, null); + } + + public SubTypeOfNested(BagOfPrimitives primitive1, BagOfPrimitives primitive2) { + super(primitive1, primitive2); + } + + @Override + public void appendFields(StringBuilder sb) { + sb.append("\"value\":").append(value).append(","); + super.appendFields(sb); + } + } + + public void testSubInterfacesOfCollectionSerialization() throws Exception { + List list = new LinkedList(); + list.add(0); + list.add(1); + list.add(2); + list.add(3); + Queue queue = new LinkedList(); + queue.add(0L); + queue.add(1L); + queue.add(2L); + queue.add(3L); + Set set = new TreeSet(); + set.add(0.1F); + set.add(0.2F); + set.add(0.3F); + set.add(0.4F); + SortedSet sortedSet = new TreeSet(); + sortedSet.add('a'); + sortedSet.add('b'); + sortedSet.add('c'); + sortedSet.add('d'); + ClassWithSubInterfacesOfCollection target = + new ClassWithSubInterfacesOfCollection(list, queue, set, sortedSet); + assertEquals(target.getExpectedJson(), gson.toJson(target)); + } + + public void testSubInterfacesOfCollectionDeserialization() throws Exception { + String json = "{\"list\":[0,1,2,3],\"queue\":[0,1,2,3],\"set\":[0.1,0.2,0.3,0.4]," + + "\"sortedSet\":[\"a\",\"b\",\"c\",\"d\"]" + + "}"; + ClassWithSubInterfacesOfCollection target = + gson.fromJson(json, ClassWithSubInterfacesOfCollection.class); + assertTrue(target.listContains(0, 1, 2, 3)); + assertTrue(target.queueContains(0, 1, 2, 3)); + assertTrue(target.setContains(0.1F, 0.2F, 0.3F, 0.4F)); + assertTrue(target.sortedSetContains('a', 'b', 'c', 'd')); + } + + private static class ClassWithSubInterfacesOfCollection { + private List list; + private Queue queue; + private Set set; + private SortedSet sortedSet; + + // For use by Gson + @SuppressWarnings("unused") + private ClassWithSubInterfacesOfCollection() { + } + + public ClassWithSubInterfacesOfCollection(List list, Queue queue, Set set, + SortedSet sortedSet) { + this.list = list; + this.queue = queue; + this.set = set; + this.sortedSet = sortedSet; + } + + boolean listContains(int... values) { + for (int value : values) { + if (!list.contains(value)) { + return false; + } + } + return true; + } + + boolean queueContains(long... values) { + for (long value : values) { + if (!queue.contains(value)) { + return false; + } + } + return true; + } + + boolean setContains(float... values) { + for (float value : values) { + if (!set.contains(value)) { + return false; + } + } + return true; + } + + boolean sortedSetContains(char... values) { + for (char value : values) { + if (!sortedSet.contains(value)) { + return false; + } + } + return true; + } + + public String getExpectedJson() { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + sb.append("\"list\":"); + append(sb, list).append(","); + sb.append("\"queue\":"); + append(sb, queue).append(","); + sb.append("\"set\":"); + append(sb, set).append(","); + sb.append("\"sortedSet\":"); + append(sb, sortedSet); + sb.append("}"); + return sb.toString(); + } + + private StringBuilder append(StringBuilder sb, Collection c) { + sb.append("["); + boolean first = true; + for (Object o : c) { + if (!first) { + sb.append(","); + } else { + first = false; + } + if (o instanceof String || o instanceof Character) { + sb.append('\"'); + } + sb.append(o.toString()); + if (o instanceof String || o instanceof Character) { + sb.append('\"'); + } + } + sb.append("]"); + return sb; + } + } +} diff --git a/gson/src/test/java/com/google/gson/functional/ObjectTest.java b/gson/src/test/java/com/google/gson/functional/ObjectTest.java index 78ddfee9..c97ed94d 100644 --- a/gson/src/test/java/com/google/gson/functional/ObjectTest.java +++ b/gson/src/test/java/com/google/gson/functional/ObjectTest.java @@ -19,12 +19,6 @@ package com.google.gson.functional; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; import junit.framework.TestCase; @@ -158,22 +152,6 @@ public class ObjectTest extends TestCase { Nested target = gson.fromJson(json, Nested.class); assertEquals(json, target.getExpectedJson()); } - - public void testInheritenceSerialization() throws Exception { - SubTypeOfNested target = new SubTypeOfNested(new BagOfPrimitives(10, 20, false, "stringValue"), - new BagOfPrimitives(30, 40, true, "stringValue")); - assertEquals(target.getExpectedJson(), gson.toJson(target)); - } - - public void testInheritenceDeserialization() throws Exception { - String json = "{\"value\":5,\"primitive1\":{\"longValue\":10,\"intValue\":20," - + "\"booleanValue\":false,\"stringValue\":\"stringValue\"},\"primitive2\":" - + "{\"longValue\":30,\"intValue\":40,\"booleanValue\":true," - + "\"stringValue\":\"stringValue\"}}"; - SubTypeOfNested target = gson.fromJson(json, SubTypeOfNested.class); - assertEquals(json, target.getExpectedJson()); - } - public void testNullSerialization() throws Exception { assertEquals("", gson.toJson(null)); } @@ -217,44 +195,6 @@ public class ObjectTest extends TestCase { assertEquals(json, target.getExpectedJson()); } - public void testSubInterfacesOfCollectionSerialization() throws Exception { - List list = new LinkedList(); - list.add(0); - list.add(1); - list.add(2); - list.add(3); - Queue queue = new LinkedList(); - queue.add(0L); - queue.add(1L); - queue.add(2L); - queue.add(3L); - Set set = new TreeSet(); - set.add(0.1F); - set.add(0.2F); - set.add(0.3F); - set.add(0.4F); - SortedSet sortedSet = new TreeSet(); - sortedSet.add('a'); - sortedSet.add('b'); - sortedSet.add('c'); - sortedSet.add('d'); - ClassWithSubInterfacesOfCollection target = - new ClassWithSubInterfacesOfCollection(list, queue, set, sortedSet); - assertEquals(target.getExpectedJson(), gson.toJson(target)); - } - - public void testSubInterfacesOfCollectionDeserialization() throws Exception { - String json = "{\"list\":[0,1,2,3],\"queue\":[0,1,2,3],\"set\":[0.1,0.2,0.3,0.4]," - + "\"sortedSet\":[\"a\",\"b\",\"c\",\"d\"]" - + "}"; - ClassWithSubInterfacesOfCollection target = - gson.fromJson(json, ClassWithSubInterfacesOfCollection.class); - assertTrue(target.listContains(0, 1, 2, 3)); - assertTrue(target.queueContains(0, 1, 2, 3)); - assertTrue(target.setContains(0.1F, 0.2F, 0.3F, 0.4F)); - assertTrue(target.sortedSetContains('a', 'b', 'c', 'd')); - } - public void testArrayOfObjectsAsFields() throws Exception { ClassWithObjects classWithObjects = new ClassWithObjects(); BagOfPrimitives bagOfPrimitives = new BagOfPrimitives(); @@ -398,124 +338,12 @@ public class ObjectTest extends TestCase { int value2 = 2; } } - - private static class ClassWithSubInterfacesOfCollection { - private List list; - private Queue queue; - private Set set; - private SortedSet sortedSet; - - // For use by Gson - @SuppressWarnings("unused") - private ClassWithSubInterfacesOfCollection() { - } - - public ClassWithSubInterfacesOfCollection(List list, Queue queue, Set set, - SortedSet sortedSet) { - this.list = list; - this.queue = queue; - this.set = set; - this.sortedSet = sortedSet; - } - - boolean listContains(int... values) { - for (int value : values) { - if (!list.contains(value)) { - return false; - } - } - return true; - } - - boolean queueContains(long... values) { - for (long value : values) { - if (!queue.contains(value)) { - return false; - } - } - return true; - } - - boolean setContains(float... values) { - for (float value : values) { - if (!set.contains(value)) { - return false; - } - } - return true; - } - - boolean sortedSetContains(char... values) { - for (char value : values) { - if (!sortedSet.contains(value)) { - return false; - } - } - return true; - } - - public String getExpectedJson() { - StringBuilder sb = new StringBuilder(); - sb.append("{"); - sb.append("\"list\":"); - append(sb, list).append(","); - sb.append("\"queue\":"); - append(sb, queue).append(","); - sb.append("\"set\":"); - append(sb, set).append(","); - sb.append("\"sortedSet\":"); - append(sb, sortedSet); - sb.append("}"); - return sb.toString(); - } - - private StringBuilder append(StringBuilder sb, Collection c) { - sb.append("["); - boolean first = true; - for (Object o : c) { - if (!first) { - sb.append(","); - } else { - first = false; - } - if (o instanceof String || o instanceof Character) { - sb.append('\"'); - } - sb.append(o.toString()); - if (o instanceof String || o instanceof Character) { - sb.append('\"'); - } - } - sb.append("]"); - return sb; - } - } private static class ContainsReferenceToSelfType { public Collection children = new ArrayList(); } - private static class SubTypeOfNested extends Nested { - private final long value = 5; - - // Used by Gson - @SuppressWarnings("unused") - private SubTypeOfNested() { - this(null, null); - } - - public SubTypeOfNested(BagOfPrimitives primitive1, BagOfPrimitives primitive2) { - super(primitive1, primitive2); - } - - @Override - public void appendFields(StringBuilder sb) { - sb.append("\"value\":").append(value).append(","); - super.appendFields(sb); - } - } - private static class ArrayOfArrays { private final BagOfPrimitives[][] elements; public ArrayOfArrays() {