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;
|
package com.google.gson;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,7 +54,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ancestors.contains(node)) {
|
if (ancestors.contains(node)) {
|
||||||
throw new IllegalStateException("Circular reference found: " + node);
|
throw new CircularReferenceException(node);
|
||||||
}
|
}
|
||||||
ancestors.push(node);
|
ancestors.push(node);
|
||||||
}
|
}
|
||||||
@ -85,6 +85,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void visitArrayField(Field f, Type typeOfF, Object obj) {
|
public void visitArrayField(Field f, Type typeOfF, Object obj) {
|
||||||
|
try {
|
||||||
if (isFieldNull(f, obj)) {
|
if (isFieldNull(f, obj)) {
|
||||||
if (serializeNulls) {
|
if (serializeNulls) {
|
||||||
addChildAsElement(f, JsonNull.createJsonNull());
|
addChildAsElement(f, JsonNull.createJsonNull());
|
||||||
@ -93,9 +94,13 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
|
|||||||
Object array = getFieldValue(f, obj);
|
Object array = getFieldValue(f, obj);
|
||||||
addAsChildOfObject(f, typeOfF, array);
|
addAsChildOfObject(f, typeOfF, array);
|
||||||
}
|
}
|
||||||
|
} catch (CircularReferenceException e) {
|
||||||
|
throw e.createDetailedException(f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void visitObjectField(Field f, Type typeOfF, Object obj) {
|
public void visitObjectField(Field f, Type typeOfF, Object obj) {
|
||||||
|
try {
|
||||||
if (isFieldNull(f, obj)) {
|
if (isFieldNull(f, obj)) {
|
||||||
if (serializeNulls) {
|
if (serializeNulls) {
|
||||||
addChildAsElement(f, JsonNull.createJsonNull());
|
addChildAsElement(f, JsonNull.createJsonNull());
|
||||||
@ -107,6 +112,9 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
|
|||||||
}
|
}
|
||||||
addAsChildOfObject(f, typeOfF, fieldValue);
|
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
|
// 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")
|
@SuppressWarnings("unchecked")
|
||||||
public boolean visitUsingCustomHandler(Object obj, Type objType) {
|
public boolean visitUsingCustomHandler(Object obj, Type objType) {
|
||||||
|
try {
|
||||||
JsonSerializer serializer = serializers.getHandlerFor(objType);
|
JsonSerializer serializer = serializers.getHandlerFor(objType);
|
||||||
if (serializer == null && obj != null) {
|
if (serializer == null && obj != null) {
|
||||||
serializer = serializers.getHandlerFor(obj.getClass());
|
serializer = serializers.getHandlerFor(obj.getClass());
|
||||||
@ -169,11 +178,24 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
|
|||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
assignToRoot(JsonNull.createJsonNull());
|
assignToRoot(JsonNull.createJsonNull());
|
||||||
} else {
|
} else {
|
||||||
assignToRoot(serializer.serialize(obj, objType, context));
|
assignToRoot(invokeCustomHandler(obj, objType, serializer));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
@ -189,13 +211,15 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
|
|||||||
}
|
}
|
||||||
JsonSerializer serializer = serializers.getHandlerFor(actualTypeOfField);
|
JsonSerializer serializer = serializers.getHandlerFor(actualTypeOfField);
|
||||||
if (serializer != null) {
|
if (serializer != null) {
|
||||||
JsonElement child = serializer.serialize(obj, actualTypeOfField, context);
|
JsonElement child = invokeCustomHandler(obj, actualTypeOfField, serializer);
|
||||||
addChildAsElement(f, child);
|
addChildAsElement(f, child);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
throw new RuntimeException();
|
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.ArrayOfObjects;
|
||||||
import com.google.gson.common.TestTypes.BagOfPrimitiveWrappers;
|
import com.google.gson.common.TestTypes.BagOfPrimitiveWrappers;
|
||||||
import com.google.gson.common.TestTypes.BagOfPrimitives;
|
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.ClassWithArray;
|
||||||
import com.google.gson.common.TestTypes.ClassWithNoFields;
|
import com.google.gson.common.TestTypes.ClassWithNoFields;
|
||||||
import com.google.gson.common.TestTypes.ClassWithObjects;
|
import com.google.gson.common.TestTypes.ClassWithObjects;
|
||||||
@ -91,23 +90,6 @@ public class ObjectTest extends TestCase {
|
|||||||
assertEquals(jsonString, target.getExpectedJson());
|
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 {
|
public void testClassWithTransientFieldsSerialization() throws Exception {
|
||||||
ClassWithTransientFields<Long> target = new ClassWithTransientFields<Long>(1L);
|
ClassWithTransientFields<Long> target = new ClassWithTransientFields<Long>(1L);
|
||||||
assertEquals(target.getExpectedJson(), gson.toJson(target));
|
assertEquals(target.getExpectedJson(), gson.toJson(target));
|
||||||
@ -231,11 +213,15 @@ public class ObjectTest extends TestCase {
|
|||||||
|
|
||||||
public void testEmptyCollectionInAnObjectDeserialization() throws Exception {
|
public void testEmptyCollectionInAnObjectDeserialization() throws Exception {
|
||||||
String json = "{\"children\":[]}";
|
String json = "{\"children\":[]}";
|
||||||
ContainsReferenceToSelfType target = gson.fromJson(json, ContainsReferenceToSelfType.class);
|
ClassWithCollectionField target = gson.fromJson(json, ClassWithCollectionField.class);
|
||||||
assertNotNull(target);
|
assertNotNull(target);
|
||||||
assertTrue(target.children.isEmpty());
|
assertTrue(target.children.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class ClassWithCollectionField {
|
||||||
|
Collection<String> children = new ArrayList<String>();
|
||||||
|
}
|
||||||
|
|
||||||
public void testPrimitiveArrayInAnObjectDeserialization() throws Exception {
|
public void testPrimitiveArrayInAnObjectDeserialization() throws Exception {
|
||||||
String json = "{\"longArray\":[0,1,2,3,4,5,6,7,8,9]}";
|
String json = "{\"longArray\":[0,1,2,3,4,5,6,7,8,9]}";
|
||||||
PrimitiveArray target = gson.fromJson(json, PrimitiveArray.class);
|
PrimitiveArray target = gson.fromJson(json, PrimitiveArray.class);
|
||||||
@ -252,31 +238,10 @@ public class ObjectTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testEmptyCollectionInAnObjectSerialization() throws Exception {
|
public void testEmptyCollectionInAnObjectSerialization() throws Exception {
|
||||||
ContainsReferenceToSelfType target = new ContainsReferenceToSelfType();
|
ClassWithCollectionField target = new ClassWithCollectionField();
|
||||||
assertEquals("{\"children\":[]}", gson.toJson(target));
|
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 {
|
public void testPrivateNoArgConstructorDeserialization() throws Exception {
|
||||||
ClassWithPrivateNoArgsConstructor target =
|
ClassWithPrivateNoArgsConstructor target =
|
||||||
gson.fromJson("{\"a\":20}", ClassWithPrivateNoArgsConstructor.class);
|
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 static class ArrayOfArrays {
|
||||||
private final BagOfPrimitives[][] elements;
|
private final BagOfPrimitives[][] elements;
|
||||||
public ArrayOfArrays() {
|
public ArrayOfArrays() {
|
||||||
|
Loading…
Reference in New Issue
Block a user