From 3e7ebf85568e7c9ef5b1373907c89a215e27e2b9 Mon Sep 17 00:00:00 2001 From: Inderjeet Singh Date: Fri, 25 Sep 2009 19:54:25 +0000 Subject: [PATCH] Fixed issue 156. Added support for serializing an object field (or array elements) as per its actual type. Refactored inheritance related tests into its own test class. Added regression tests for issue 156. --- .../google/gson/JsonSerializationVisitor.java | 27 +- .../com/google/gson/common/TestTypes.java | 27 ++ .../gson/functional/InheritanceTest.java | 236 ++++++++++++++++++ .../google/gson/functional/ObjectTest.java | 172 ------------- 4 files changed, 285 insertions(+), 177 deletions(-) create mode 100644 gson/src/test/java/com/google/gson/functional/InheritanceTest.java 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() {