Skip to content

Commit bc623e3

Browse files
authored
Merge pull request #645 from Zetmas/feature/issue#632
Detect and handle circular references when parsing Java beans
2 parents d6227c8 + fca7e17 commit bc623e3

File tree

3 files changed

+165
-3
lines changed

3 files changed

+165
-3
lines changed

src/main/java/org/json/JSONObject.java

+48-3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ of this software and associated documentation files (the "Software"), to deal
3939
import java.util.Collection;
4040
import java.util.Enumeration;
4141
import java.util.HashMap;
42+
import java.util.HashSet;
4243
import java.util.Iterator;
4344
import java.util.Locale;
4445
import java.util.Map;
@@ -365,6 +366,11 @@ public JSONObject(Object bean) {
365366
this.populateMap(bean);
366367
}
367368

369+
private JSONObject(Object bean, Set<Object> objectsRecord) {
370+
this();
371+
this.populateMap(bean, objectsRecord);
372+
}
373+
368374
/**
369375
* Construct a JSONObject from an Object, using reflection to find the
370376
* public members. The resulting JSONObject's keys will be the strings from
@@ -1520,6 +1526,10 @@ public String optString(String key, String defaultValue) {
15201526
* the bean
15211527
*/
15221528
private void populateMap(Object bean) {
1529+
populateMap(bean, new HashSet<Object>());
1530+
}
1531+
1532+
private void populateMap(Object bean, Set<Object> objectsRecord) {
15231533
Class<?> klass = bean.getClass();
15241534

15251535
// If klass is a System class then set includeSuperClass to false.
@@ -1540,10 +1550,22 @@ && isValidMethodName(method.getName())) {
15401550
try {
15411551
final Object result = method.invoke(bean);
15421552
if (result != null) {
1543-
this.map.put(key, wrap(result));
1553+
// check cyclic dependency and throw error if needed
1554+
// the wrap and populateMap combination method is
1555+
// itself DFS recursive
1556+
if (objectsRecord.contains(result)) {
1557+
throw recursivelyDefinedObjectException(key);
1558+
}
1559+
1560+
objectsRecord.add(result);
1561+
1562+
this.map.put(key, wrap(result, objectsRecord));
1563+
1564+
objectsRecord.remove(result);
1565+
15441566
// we don't use the result anywhere outside of wrap
15451567
// if it's a resource we should be sure to close it
1546-
// after calling toString
1568+
// after calling toString
15471569
if (result instanceof Closeable) {
15481570
try {
15491571
((Closeable) result).close();
@@ -2431,6 +2453,10 @@ public static String valueToString(Object value) throws JSONException {
24312453
* @return The wrapped value
24322454
*/
24332455
public static Object wrap(Object object) {
2456+
return wrap(object, null);
2457+
}
2458+
2459+
private static Object wrap(Object object, Set<Object> objectsRecord) {
24342460
try {
24352461
if (NULL.equals(object)) {
24362462
return NULL;
@@ -2465,7 +2491,15 @@ public static Object wrap(Object object) {
24652491
|| object.getClass().getClassLoader() == null) {
24662492
return object.toString();
24672493
}
2468-
return new JSONObject(object);
2494+
if (objectsRecord != null) {
2495+
return new JSONObject(object, objectsRecord);
2496+
}
2497+
else {
2498+
return new JSONObject(object);
2499+
}
2500+
}
2501+
catch (JSONException exception) {
2502+
throw exception;
24692503
} catch (Exception exception) {
24702504
return null;
24712505
}
@@ -2676,4 +2710,15 @@ private static JSONException wrongValueFormatException(
26762710
"JSONObject[" + quote(key) + "] is not a " + valueType + " (" + value + ")."
26772711
, cause);
26782712
}
2713+
2714+
/**
2715+
* Create a new JSONException in a common format for recursive object definition.
2716+
* @param key name of the key
2717+
* @return JSONException that can be thrown.
2718+
*/
2719+
private static JSONException recursivelyDefinedObjectException(String key) {
2720+
return new JSONException(
2721+
"JavaBean object contains recursively defined member variable of key " + quote(key)
2722+
);
2723+
}
26792724
}

src/test/java/org/json/junit/JSONObjectTest.java

+94
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ of this software and associated documentation files (the "Software"), to deal
7373
import org.json.junit.data.MyNumber;
7474
import org.json.junit.data.MyNumberContainer;
7575
import org.json.junit.data.MyPublicClass;
76+
import org.json.junit.data.RecursiveBean;
7677
import org.json.junit.data.Singleton;
7778
import org.json.junit.data.SingletonEnum;
7879
import org.json.junit.data.WeirdList;
@@ -3218,6 +3219,99 @@ public void testPutNullObject() {
32183219
jsonObject.put(null, new Object());
32193220
fail("Expected an exception");
32203221
}
3222+
@Test(expected=JSONException.class)
3223+
public void testSelfRecursiveObject() {
3224+
// A -> A ...
3225+
RecursiveBean ObjA = new RecursiveBean("ObjA");
3226+
ObjA.setRef(ObjA);
3227+
new JSONObject(ObjA);
3228+
fail("Expected an exception");
3229+
}
3230+
@Test(expected=JSONException.class)
3231+
public void testLongSelfRecursiveObject() {
3232+
// B -> A -> A ...
3233+
RecursiveBean ObjA = new RecursiveBean("ObjA");
3234+
RecursiveBean ObjB = new RecursiveBean("ObjB");
3235+
ObjB.setRef(ObjA);
3236+
ObjA.setRef(ObjA);
3237+
new JSONObject(ObjB);
3238+
fail("Expected an exception");
3239+
}
3240+
@Test(expected=JSONException.class)
3241+
public void testSimpleRecursiveObject() {
3242+
// B -> A -> B ...
3243+
RecursiveBean ObjA = new RecursiveBean("ObjA");
3244+
RecursiveBean ObjB = new RecursiveBean("ObjB");
3245+
ObjB.setRef(ObjA);
3246+
ObjA.setRef(ObjB);
3247+
new JSONObject(ObjA);
3248+
fail("Expected an exception");
3249+
}
3250+
@Test(expected=JSONException.class)
3251+
public void testLongRecursiveObject() {
3252+
// D -> C -> B -> A -> D ...
3253+
RecursiveBean ObjA = new RecursiveBean("ObjA");
3254+
RecursiveBean ObjB = new RecursiveBean("ObjB");
3255+
RecursiveBean ObjC = new RecursiveBean("ObjC");
3256+
RecursiveBean ObjD = new RecursiveBean("ObjD");
3257+
ObjC.setRef(ObjB);
3258+
ObjB.setRef(ObjA);
3259+
ObjD.setRef(ObjC);
3260+
ObjA.setRef(ObjD);
3261+
new JSONObject(ObjB);
3262+
fail("Expected an exception");
3263+
}
3264+
@Test(expected=JSONException.class)
3265+
public void testRepeatObjectRecursive() {
3266+
// C -> B -> A -> D -> C ...
3267+
// -> D -> C ...
3268+
RecursiveBean ObjA = new RecursiveBean("ObjA");
3269+
RecursiveBean ObjB = new RecursiveBean("ObjB");
3270+
RecursiveBean ObjC = new RecursiveBean("ObjC");
3271+
RecursiveBean ObjD = new RecursiveBean("ObjD");
3272+
ObjC.setRef(ObjB);
3273+
ObjB.setRef(ObjA);
3274+
ObjB.setRef2(ObjD);
3275+
ObjA.setRef(ObjD);
3276+
ObjD.setRef(ObjC);
3277+
new JSONObject(ObjC);
3278+
fail("Expected an exception");
3279+
}
3280+
@Test
3281+
public void testRepeatObjectNotRecursive() {
3282+
// C -> B -> A
3283+
// -> A
3284+
RecursiveBean ObjA = new RecursiveBean("ObjA");
3285+
RecursiveBean ObjB = new RecursiveBean("ObjB");
3286+
RecursiveBean ObjC = new RecursiveBean("ObjC");
3287+
ObjC.setRef(ObjA);
3288+
ObjB.setRef(ObjA);
3289+
ObjB.setRef2(ObjA);
3290+
new JSONObject(ObjC);
3291+
new JSONObject(ObjB);
3292+
new JSONObject(ObjA);
3293+
}
3294+
@Test
3295+
public void testLongRepeatObjectNotRecursive() {
3296+
// C -> B -> A -> D -> E
3297+
// -> D -> E
3298+
RecursiveBean ObjA = new RecursiveBean("ObjA");
3299+
RecursiveBean ObjB = new RecursiveBean("ObjB");
3300+
RecursiveBean ObjC = new RecursiveBean("ObjC");
3301+
RecursiveBean ObjD = new RecursiveBean("ObjD");
3302+
RecursiveBean ObjE = new RecursiveBean("ObjE");
3303+
ObjC.setRef(ObjB);
3304+
ObjB.setRef(ObjA);
3305+
ObjB.setRef2(ObjD);
3306+
ObjA.setRef(ObjD);
3307+
ObjD.setRef(ObjE);
3308+
new JSONObject(ObjC);
3309+
new JSONObject(ObjB);
3310+
new JSONObject(ObjA);
3311+
new JSONObject(ObjD);
3312+
new JSONObject(ObjE);
3313+
}
3314+
32213315

32223316
@Test
32233317
public void testIssue548ObjectWithEmptyJsonArray() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.json.junit.data;
2+
3+
/**
4+
* test class for verifying if recursively defined bean can be correctly identified
5+
* @author Zetmas
6+
*
7+
*/
8+
public class RecursiveBean {
9+
private String name;
10+
private Object reference;
11+
private Object reference2;
12+
public String getName() { return name; }
13+
public Object getRef() {return reference;}
14+
public Object getRef2() {return reference2;}
15+
public void setRef(Object refObj) {reference = refObj;}
16+
public void setRef2(Object refObj) {reference2 = refObj;}
17+
18+
public RecursiveBean(String name) {
19+
this.name = name;
20+
reference = null;
21+
reference2 = null;
22+
}
23+
}

0 commit comments

Comments
 (0)