Added code to ensure that circular reference situations with custom handlers are detected.
Split CircularReferenceTest out of ObjectTest.
This commit is contained in:
parent
933a3e5150
commit
907082102d
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Exception class to indicate a circular reference error.
|
||||
* This class is not part of the public API and hence is not public.
|
||||
*
|
||||
* @author Inderjeet Singh
|
||||
* @author Joel Leitch
|
||||
*/
|
||||
final class CircularReferenceException extends RuntimeException {
|
||||
private static final long serialVersionUID = 7444343294106513081L;
|
||||
private final Object offendingNode;
|
||||
|
||||
CircularReferenceException(Object offendingNode) {
|
||||
super("circular reference error");
|
||||
this.offendingNode = offendingNode;
|
||||
}
|
||||
|
||||
public IllegalStateException createDetailedException(Field offendingField) {
|
||||
StringBuilder msg = new StringBuilder(getMessage());
|
||||
if (offendingField != null) {
|
||||
msg.append("\n ").append("Offending field: ").append(offendingField.getName() + "\n");
|
||||
}
|
||||
if (offendingNode != null) {
|
||||
msg.append("\n ").append("Offending object: ").append(offendingNode);
|
||||
}
|
||||
return new IllegalStateException(msg.toString(), this);
|
||||
}
|
||||
}
|
@ -17,8 +17,6 @@
|
||||
package com.google.gson;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
|
@ -54,7 +54,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
|
||||
return;
|
||||
}
|
||||
if (ancestors.contains(node)) {
|
||||
throw new IllegalStateException("Circular reference found: " + node);
|
||||
throw new CircularReferenceException(node);
|
||||
}
|
||||
ancestors.push(node);
|
||||
}
|
||||
@ -85,6 +85,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
|
||||
}
|
||||
|
||||
public void visitArrayField(Field f, Type typeOfF, Object obj) {
|
||||
try {
|
||||
if (isFieldNull(f, obj)) {
|
||||
if (serializeNulls) {
|
||||
addChildAsElement(f, JsonNull.createJsonNull());
|
||||
@ -93,9 +94,13 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
|
||||
Object array = getFieldValue(f, obj);
|
||||
addAsChildOfObject(f, typeOfF, array);
|
||||
}
|
||||
} catch (CircularReferenceException e) {
|
||||
throw e.createDetailedException(f);
|
||||
}
|
||||
}
|
||||
|
||||
public void visitObjectField(Field f, Type typeOfF, Object obj) {
|
||||
try {
|
||||
if (isFieldNull(f, obj)) {
|
||||
if (serializeNulls) {
|
||||
addChildAsElement(f, JsonNull.createJsonNull());
|
||||
@ -107,6 +112,9 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
|
||||
}
|
||||
addAsChildOfObject(f, typeOfF, fieldValue);
|
||||
}
|
||||
} catch (CircularReferenceException e) {
|
||||
throw e.createDetailedException(f);
|
||||
}
|
||||
}
|
||||
|
||||
// This takes care of situations where the field was declared as an Object, but the
|
||||
@ -160,6 +168,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean visitUsingCustomHandler(Object obj, Type objType) {
|
||||
try {
|
||||
JsonSerializer serializer = serializers.getHandlerFor(objType);
|
||||
if (serializer == null && obj != null) {
|
||||
serializer = serializers.getHandlerFor(obj.getClass());
|
||||
@ -169,11 +178,24 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
|
||||
if (obj == null) {
|
||||
assignToRoot(JsonNull.createJsonNull());
|
||||
} else {
|
||||
assignToRoot(serializer.serialize(obj, objType, context));
|
||||
assignToRoot(invokeCustomHandler(obj, objType, serializer));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (CircularReferenceException e) {
|
||||
throw e.createDetailedException(null);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private JsonElement invokeCustomHandler(Object obj, Type objType, JsonSerializer serializer) {
|
||||
start(obj);
|
||||
try {
|
||||
return serializer.serialize(obj, objType, context);
|
||||
} finally {
|
||||
end(obj);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -189,13 +211,15 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
|
||||
}
|
||||
JsonSerializer serializer = serializers.getHandlerFor(actualTypeOfField);
|
||||
if (serializer != null) {
|
||||
JsonElement child = serializer.serialize(obj, actualTypeOfField, context);
|
||||
JsonElement child = invokeCustomHandler(obj, actualTypeOfField, serializer);
|
||||
addChildAsElement(f, child);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException();
|
||||
} catch (CircularReferenceException e) {
|
||||
throw e.createDetailedException(f);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.common.TestTypes.ClassOverridingEquals;
|
||||
|
||||
/**
|
||||
* Functional tests related to circular reference detection and error reporting.
|
||||
*
|
||||
* @author Inderjeet Singh
|
||||
* @author Joel Leitch
|
||||
*/
|
||||
public class CircularReferenceTest extends TestCase {
|
||||
private Gson gson;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
gson = new Gson();
|
||||
}
|
||||
|
||||
public void testCircularSerialization() throws Exception {
|
||||
ContainsReferenceToSelfType a = new ContainsReferenceToSelfType();
|
||||
ContainsReferenceToSelfType b = new ContainsReferenceToSelfType();
|
||||
a.children.add(b);
|
||||
b.children.add(a);
|
||||
try {
|
||||
gson.toJson(a);
|
||||
fail("Circular types should not get printed!");
|
||||
} catch (IllegalStateException expected) {
|
||||
assertTrue(expected.getMessage().contains("children"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testSelfReferenceSerialization() throws Exception {
|
||||
ClassOverridingEquals objA = new ClassOverridingEquals();
|
||||
objA.ref = objA;
|
||||
|
||||
try {
|
||||
gson.toJson(objA);
|
||||
fail("Circular reference to self can not be serialized!");
|
||||
} catch (IllegalStateException expected) { }
|
||||
}
|
||||
|
||||
public void testSelfReferenceArrayFieldSerialization() throws Exception {
|
||||
ClassWithSelfReferenceArray objA = new ClassWithSelfReferenceArray();
|
||||
objA.children = new ClassWithSelfReferenceArray[]{objA};
|
||||
|
||||
try {
|
||||
gson.toJson(objA);
|
||||
fail("Circular reference to self can not be serialized!");
|
||||
} catch (IllegalStateException expected) {
|
||||
assertTrue(expected.getMessage().contains("children"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testSelfReferenceCustomHandlerSerialization() throws Exception {
|
||||
ClassWithSelfReference obj = new ClassWithSelfReference();
|
||||
obj.child = obj;
|
||||
Gson gson = new GsonBuilder().registerTypeAdapter(ClassWithSelfReference.class, new JsonSerializer<ClassWithSelfReference>() {
|
||||
public JsonElement serialize(ClassWithSelfReference src, Type typeOfSrc,
|
||||
JsonSerializationContext context) {
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.addProperty("property", "value");
|
||||
obj.add("child", context.serialize(src.child));
|
||||
return obj;
|
||||
}
|
||||
}).create();
|
||||
try {
|
||||
gson.toJson(obj);
|
||||
fail("Circular reference to self can not be serialized!");
|
||||
} catch (IllegalStateException expected) {
|
||||
assertTrue(expected.getMessage().contains("Offending"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testDirectedAcyclicGraphSerialization() throws Exception {
|
||||
ContainsReferenceToSelfType a = new ContainsReferenceToSelfType();
|
||||
ContainsReferenceToSelfType b = new ContainsReferenceToSelfType();
|
||||
ContainsReferenceToSelfType c = new ContainsReferenceToSelfType();
|
||||
a.children.add(b);
|
||||
a.children.add(c);
|
||||
b.children.add(c);
|
||||
assertNotNull(gson.toJson(a));
|
||||
}
|
||||
|
||||
public void testDirectedAcyclicGraphDeserialization() throws Exception {
|
||||
String json = "{\"children\":[{\"children\":[{\"children\":[]}]},{\"children\":[]}]}";
|
||||
ContainsReferenceToSelfType target = gson.fromJson(json, ContainsReferenceToSelfType.class);
|
||||
assertNotNull(target);
|
||||
assertEquals(2, target.children.size());
|
||||
}
|
||||
|
||||
private static class ContainsReferenceToSelfType {
|
||||
Collection<ContainsReferenceToSelfType> children = new ArrayList<ContainsReferenceToSelfType>();
|
||||
}
|
||||
|
||||
private static class ClassWithSelfReference {
|
||||
ClassWithSelfReference child;
|
||||
}
|
||||
|
||||
private static class ClassWithSelfReferenceArray {
|
||||
@SuppressWarnings("unused")
|
||||
ClassWithSelfReferenceArray[] children;
|
||||
}
|
||||
}
|
@ -28,7 +28,6 @@ import com.google.gson.InstanceCreator;
|
||||
import com.google.gson.common.TestTypes.ArrayOfObjects;
|
||||
import com.google.gson.common.TestTypes.BagOfPrimitiveWrappers;
|
||||
import com.google.gson.common.TestTypes.BagOfPrimitives;
|
||||
import com.google.gson.common.TestTypes.ClassOverridingEquals;
|
||||
import com.google.gson.common.TestTypes.ClassWithArray;
|
||||
import com.google.gson.common.TestTypes.ClassWithNoFields;
|
||||
import com.google.gson.common.TestTypes.ClassWithObjects;
|
||||
@ -91,23 +90,6 @@ public class ObjectTest extends TestCase {
|
||||
assertEquals(jsonString, target.getExpectedJson());
|
||||
}
|
||||
|
||||
public void testDirectedAcyclicGraphSerialization() throws Exception {
|
||||
ContainsReferenceToSelfType a = new ContainsReferenceToSelfType();
|
||||
ContainsReferenceToSelfType b = new ContainsReferenceToSelfType();
|
||||
ContainsReferenceToSelfType c = new ContainsReferenceToSelfType();
|
||||
a.children.add(b);
|
||||
a.children.add(c);
|
||||
b.children.add(c);
|
||||
assertNotNull(gson.toJson(a));
|
||||
}
|
||||
|
||||
public void testDirectedAcyclicGraphDeserialization() throws Exception {
|
||||
String json = "{\"children\":[{\"children\":[{\"children\":[]}]},{\"children\":[]}]}";
|
||||
ContainsReferenceToSelfType target = gson.fromJson(json, ContainsReferenceToSelfType.class);
|
||||
assertNotNull(target);
|
||||
assertEquals(2, target.children.size());
|
||||
}
|
||||
|
||||
public void testClassWithTransientFieldsSerialization() throws Exception {
|
||||
ClassWithTransientFields<Long> target = new ClassWithTransientFields<Long>(1L);
|
||||
assertEquals(target.getExpectedJson(), gson.toJson(target));
|
||||
@ -231,11 +213,15 @@ public class ObjectTest extends TestCase {
|
||||
|
||||
public void testEmptyCollectionInAnObjectDeserialization() throws Exception {
|
||||
String json = "{\"children\":[]}";
|
||||
ContainsReferenceToSelfType target = gson.fromJson(json, ContainsReferenceToSelfType.class);
|
||||
ClassWithCollectionField target = gson.fromJson(json, ClassWithCollectionField.class);
|
||||
assertNotNull(target);
|
||||
assertTrue(target.children.isEmpty());
|
||||
}
|
||||
|
||||
private static class ClassWithCollectionField {
|
||||
Collection<String> children = new ArrayList<String>();
|
||||
}
|
||||
|
||||
public void testPrimitiveArrayInAnObjectDeserialization() throws Exception {
|
||||
String json = "{\"longArray\":[0,1,2,3,4,5,6,7,8,9]}";
|
||||
PrimitiveArray target = gson.fromJson(json, PrimitiveArray.class);
|
||||
@ -252,31 +238,10 @@ public class ObjectTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testEmptyCollectionInAnObjectSerialization() throws Exception {
|
||||
ContainsReferenceToSelfType target = new ContainsReferenceToSelfType();
|
||||
ClassWithCollectionField target = new ClassWithCollectionField();
|
||||
assertEquals("{\"children\":[]}", gson.toJson(target));
|
||||
}
|
||||
|
||||
public void testCircularSerialization() throws Exception {
|
||||
ContainsReferenceToSelfType a = new ContainsReferenceToSelfType();
|
||||
ContainsReferenceToSelfType b = new ContainsReferenceToSelfType();
|
||||
a.children.add(b);
|
||||
b.children.add(a);
|
||||
try {
|
||||
gson.toJson(a);
|
||||
fail("Circular types should not get printed!");
|
||||
} catch (IllegalStateException expected) { }
|
||||
}
|
||||
|
||||
public void testSelfReferenceSerialization() throws Exception {
|
||||
ClassOverridingEquals objA = new ClassOverridingEquals();
|
||||
objA.ref = objA;
|
||||
|
||||
try {
|
||||
gson.toJson(objA);
|
||||
fail("Circular reference to self can not be serialized!");
|
||||
} catch (IllegalStateException expected) { }
|
||||
}
|
||||
|
||||
public void testPrivateNoArgConstructorDeserialization() throws Exception {
|
||||
ClassWithPrivateNoArgsConstructor target =
|
||||
gson.fromJson("{\"a\":20}", ClassWithPrivateNoArgsConstructor.class);
|
||||
@ -339,11 +304,6 @@ public class ObjectTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
private static class ContainsReferenceToSelfType {
|
||||
public Collection<ContainsReferenceToSelfType> children =
|
||||
new ArrayList<ContainsReferenceToSelfType>();
|
||||
}
|
||||
|
||||
private static class ArrayOfArrays {
|
||||
private final BagOfPrimitives[][] elements;
|
||||
public ArrayOfArrays() {
|
||||
|
Loading…
Reference in New Issue
Block a user