Skip to content

Commit d7819a4

Browse files
authored
Merge pull request stleary#823 from sk02241994/issue_743
JSON parsing self reference object and array
2 parents 6dba722 + 7701f21 commit d7819a4

File tree

5 files changed

+306
-14
lines changed

5 files changed

+306
-14
lines changed

src/main/java/org/json/JSONArray.java

Lines changed: 97 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,40 @@ public JSONArray(String source) throws JSONException {
149149
* A Collection.
150150
*/
151151
public JSONArray(Collection<?> collection) {
152+
this(collection, 0, new JSONParserConfiguration());
153+
}
154+
155+
/**
156+
* Construct a JSONArray from a Collection.
157+
*
158+
* @param collection
159+
* A Collection.
160+
* @param jsonParserConfiguration
161+
* Configuration object for the JSON parser
162+
*/
163+
public JSONArray(Collection<?> collection, JSONParserConfiguration jsonParserConfiguration) {
164+
this(collection, 0, jsonParserConfiguration);
165+
}
166+
167+
/**
168+
* Construct a JSONArray from a collection with recursion depth.
169+
*
170+
* @param collection
171+
* A Collection.
172+
* @param recursionDepth
173+
* Variable for tracking the count of nested object creations.
174+
* @param jsonParserConfiguration
175+
* Configuration object for the JSON parser
176+
*/
177+
JSONArray(Collection<?> collection, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
178+
if (recursionDepth > jsonParserConfiguration.getMaxNestingDepth()) {
179+
throw new JSONException("JSONArray has reached recursion depth limit of " + jsonParserConfiguration.getMaxNestingDepth());
180+
}
152181
if (collection == null) {
153182
this.myArrayList = new ArrayList<Object>();
154183
} else {
155184
this.myArrayList = new ArrayList<Object>(collection.size());
156-
this.addAll(collection, true);
185+
this.addAll(collection, true, recursionDepth, jsonParserConfiguration);
157186
}
158187
}
159188

@@ -205,7 +234,7 @@ public JSONArray(Object array) throws JSONException {
205234
throw new JSONException(
206235
"JSONArray initial value should be a string or collection or array.");
207236
}
208-
this.addAll(array, true);
237+
this.addAll(array, true, 0);
209238
}
210239

211240
/**
@@ -1338,7 +1367,27 @@ public JSONArray put(int index, long value) throws JSONException {
13381367
* If a key in the map is <code>null</code>
13391368
*/
13401369
public JSONArray put(int index, Map<?, ?> value) throws JSONException {
1341-
this.put(index, new JSONObject(value));
1370+
this.put(index, new JSONObject(value, new JSONParserConfiguration()));
1371+
return this;
1372+
}
1373+
1374+
/**
1375+
* Put a value in the JSONArray, where the value will be a JSONObject that
1376+
* is produced from a Map.
1377+
*
1378+
* @param index
1379+
* The subscript
1380+
* @param value
1381+
* The Map value.
1382+
* @param jsonParserConfiguration
1383+
* Configuration object for the JSON parser
1384+
* @return
1385+
* @throws JSONException
1386+
* If the index is negative or if the value is an invalid
1387+
* number.
1388+
*/
1389+
public JSONArray put(int index, Map<?, ?> value, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
1390+
this.put(index, new JSONObject(value, jsonParserConfiguration));
13421391
return this;
13431392
}
13441393

@@ -1779,13 +1828,14 @@ public boolean isEmpty() {
17791828
* @param wrap
17801829
* {@code true} to call {@link JSONObject#wrap(Object)} for each item,
17811830
* {@code false} to add the items directly
1782-
*
1831+
* @param recursionDepth
1832+
* Variable for tracking the count of nested object creations.
17831833
*/
1784-
private void addAll(Collection<?> collection, boolean wrap) {
1834+
private void addAll(Collection<?> collection, boolean wrap, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
17851835
this.myArrayList.ensureCapacity(this.myArrayList.size() + collection.size());
17861836
if (wrap) {
17871837
for (Object o: collection){
1788-
this.put(JSONObject.wrap(o));
1838+
this.put(JSONObject.wrap(o, recursionDepth + 1, jsonParserConfiguration));
17891839
}
17901840
} else {
17911841
for (Object o: collection){
@@ -1814,7 +1864,24 @@ private void addAll(Iterable<?> iter, boolean wrap) {
18141864
}
18151865
}
18161866
}
1817-
1867+
1868+
/**
1869+
* Add an array's elements to the JSONArray.
1870+
*
1871+
* @param array
1872+
* Array. If the parameter passed is null, or not an array,
1873+
* JSONArray, Collection, or Iterable, an exception will be
1874+
* thrown.
1875+
* @param wrap
1876+
* {@code true} to call {@link JSONObject#wrap(Object)} for each item,
1877+
* {@code false} to add the items directly
1878+
* @throws JSONException
1879+
* If not an array or if an array value is non-finite number.
1880+
*/
1881+
private void addAll(Object array, boolean wrap) throws JSONException {
1882+
this.addAll(array, wrap, 0);
1883+
}
1884+
18181885
/**
18191886
* Add an array's elements to the JSONArray.
18201887
*
@@ -1823,21 +1890,40 @@ private void addAll(Iterable<?> iter, boolean wrap) {
18231890
* JSONArray, Collection, or Iterable, an exception will be
18241891
* thrown.
18251892
* @param wrap
1893+
* {@code true} to call {@link JSONObject#wrap(Object)} for each item,
1894+
* {@code false} to add the items directly
1895+
* @param recursionDepth
1896+
* Variable for tracking the count of nested object creations.
1897+
*/
1898+
private void addAll(Object array, boolean wrap, int recursionDepth) {
1899+
addAll(array, wrap, recursionDepth, new JSONParserConfiguration());
1900+
}
1901+
/**
1902+
* Add an array's elements to the JSONArray.
1903+
*`
1904+
* @param array
1905+
* Array. If the parameter passed is null, or not an array,
1906+
* JSONArray, Collection, or Iterable, an exception will be
1907+
* thrown.
1908+
* @param wrap
18261909
* {@code true} to call {@link JSONObject#wrap(Object)} for each item,
18271910
* {@code false} to add the items directly
1828-
*
1911+
* @param recursionDepth
1912+
* Variable for tracking the count of nested object creations.
1913+
* @param jsonParserConfiguration
1914+
* Variable to pass parser custom configuration for json parsing.
18291915
* @throws JSONException
18301916
* If not an array or if an array value is non-finite number.
18311917
* @throws NullPointerException
18321918
* Thrown if the array parameter is null.
18331919
*/
1834-
private void addAll(Object array, boolean wrap) throws JSONException {
1920+
private void addAll(Object array, boolean wrap, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
18351921
if (array.getClass().isArray()) {
18361922
int length = Array.getLength(array);
18371923
this.myArrayList.ensureCapacity(this.myArrayList.size() + length);
18381924
if (wrap) {
18391925
for (int i = 0; i < length; i += 1) {
1840-
this.put(JSONObject.wrap(Array.get(array, i)));
1926+
this.put(JSONObject.wrap(Array.get(array, i), recursionDepth + 1, jsonParserConfiguration));
18411927
}
18421928
} else {
18431929
for (int i = 0; i < length; i += 1) {
@@ -1850,7 +1936,7 @@ private void addAll(Object array, boolean wrap) throws JSONException {
18501936
// JSONArray
18511937
this.myArrayList.addAll(((JSONArray)array).myArrayList);
18521938
} else if (array instanceof Collection) {
1853-
this.addAll((Collection<?>)array, wrap);
1939+
this.addAll((Collection<?>)array, wrap, recursionDepth);
18541940
} else if (array instanceof Iterable) {
18551941
this.addAll((Iterable<?>)array, wrap);
18561942
} else {

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

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,30 @@ public JSONObject(JSONTokener x) throws JSONException {
276276
* If a key in the map is <code>null</code>
277277
*/
278278
public JSONObject(Map<?, ?> m) {
279+
this(m, 0, new JSONParserConfiguration());
280+
}
281+
282+
/**
283+
* Construct a JSONObject from a Map with custom json parse configurations.
284+
*
285+
* @param m
286+
* A map object that can be used to initialize the contents of
287+
* the JSONObject.
288+
* @param jsonParserConfiguration
289+
* Variable to pass parser custom configuration for json parsing.
290+
*/
291+
public JSONObject(Map<?, ?> m, JSONParserConfiguration jsonParserConfiguration) {
292+
this(m, 0, jsonParserConfiguration);
293+
}
294+
295+
/**
296+
* Construct a JSONObject from a map with recursion depth.
297+
*
298+
*/
299+
private JSONObject(Map<?, ?> m, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
300+
if (recursionDepth > jsonParserConfiguration.getMaxNestingDepth()) {
301+
throw new JSONException("JSONObject has reached recursion depth limit of " + jsonParserConfiguration.getMaxNestingDepth());
302+
}
279303
if (m == null) {
280304
this.map = new HashMap<String, Object>();
281305
} else {
@@ -287,7 +311,7 @@ public JSONObject(Map<?, ?> m) {
287311
final Object value = e.getValue();
288312
if (value != null) {
289313
testValidity(value);
290-
this.map.put(String.valueOf(e.getKey()), wrap(value));
314+
this.map.put(String.valueOf(e.getKey()), wrap(value, recursionDepth + 1, jsonParserConfiguration));
291315
}
292316
}
293317
}
@@ -2566,7 +2590,31 @@ public static Object wrap(Object object) {
25662590
return wrap(object, null);
25672591
}
25682592

2593+
/**
2594+
* Wrap an object, if necessary. If the object is <code>null</code>, return the NULL
2595+
* object. If it is an array or collection, wrap it in a JSONArray. If it is
2596+
* a map, wrap it in a JSONObject. If it is a standard property (Double,
2597+
* String, et al) then it is already wrapped. Otherwise, if it comes from
2598+
* one of the java packages, turn it into a string. And if it doesn't, try
2599+
* to wrap it in a JSONObject. If the wrapping fails, then null is returned.
2600+
*
2601+
* @param object
2602+
* The object to wrap
2603+
* @param recursionDepth
2604+
* Variable for tracking the count of nested object creations.
2605+
* @param jsonParserConfiguration
2606+
* Variable to pass parser custom configuration for json parsing.
2607+
* @return The wrapped value
2608+
*/
2609+
static Object wrap(Object object, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
2610+
return wrap(object, null, recursionDepth, jsonParserConfiguration);
2611+
}
2612+
25692613
private static Object wrap(Object object, Set<Object> objectsRecord) {
2614+
return wrap(object, objectsRecord, 0, new JSONParserConfiguration());
2615+
}
2616+
2617+
private static Object wrap(Object object, Set<Object> objectsRecord, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
25702618
try {
25712619
if (NULL.equals(object)) {
25722620
return NULL;
@@ -2584,14 +2632,14 @@ private static Object wrap(Object object, Set<Object> objectsRecord) {
25842632

25852633
if (object instanceof Collection) {
25862634
Collection<?> coll = (Collection<?>) object;
2587-
return new JSONArray(coll);
2635+
return new JSONArray(coll, recursionDepth, jsonParserConfiguration);
25882636
}
25892637
if (object.getClass().isArray()) {
25902638
return new JSONArray(object);
25912639
}
25922640
if (object instanceof Map) {
25932641
Map<?, ?> map = (Map<?, ?>) object;
2594-
return new JSONObject(map);
2642+
return new JSONObject(map, recursionDepth, jsonParserConfiguration);
25952643
}
25962644
Package objectPackage = object.getClass().getPackage();
25972645
String objectPackageName = objectPackage != null ? objectPackage
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.json;
2+
3+
/**
4+
* Configuration object for the JSON parser. The configuration is immutable.
5+
*/
6+
public class JSONParserConfiguration extends ParserConfiguration {
7+
8+
/**
9+
* Configuration with the default values.
10+
*/
11+
public JSONParserConfiguration() {
12+
super();
13+
}
14+
15+
@Override
16+
protected JSONParserConfiguration clone() {
17+
return new JSONParserConfiguration();
18+
}
19+
20+
@Override
21+
public JSONParserConfiguration withMaxNestingDepth(final int maxNestingDepth) {
22+
return super.withMaxNestingDepth(maxNestingDepth);
23+
}
24+
25+
}

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

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@
2828
import org.json.JSONArray;
2929
import org.json.JSONException;
3030
import org.json.JSONObject;
31+
import org.json.JSONParserConfiguration;
3132
import org.json.JSONPointerException;
3233
import org.json.JSONString;
3334
import org.json.JSONTokener;
35+
import org.json.ParserConfiguration;
3436
import org.json.junit.data.MyJsonString;
3537
import org.junit.Ignore;
3638
import org.junit.Test;
@@ -1417,4 +1419,78 @@ public String toJSONString() {
14171419
.put(2);
14181420
assertFalse(ja1.similar(ja3));
14191421
}
1422+
1423+
@Test(expected = JSONException.class)
1424+
public void testRecursiveDepth() {
1425+
HashMap<String, Object> map = new HashMap<>();
1426+
map.put("t", map);
1427+
new JSONArray().put(map);
1428+
}
1429+
1430+
@Test(expected = JSONException.class)
1431+
public void testRecursiveDepthAtPosition() {
1432+
HashMap<String, Object> map = new HashMap<>();
1433+
map.put("t", map);
1434+
new JSONArray().put(0, map);
1435+
}
1436+
1437+
@Test(expected = JSONException.class)
1438+
public void testRecursiveDepthArray() {
1439+
ArrayList<Object> array = new ArrayList<>();
1440+
array.add(array);
1441+
new JSONArray(array);
1442+
}
1443+
1444+
@Test
1445+
public void testRecursiveDepthAtPositionDefaultObject() {
1446+
HashMap<String, Object> map = JSONObjectTest.buildNestedMap(ParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
1447+
new JSONArray().put(0, map);
1448+
}
1449+
1450+
@Test
1451+
public void testRecursiveDepthAtPosition1000Object() {
1452+
HashMap<String, Object> map = JSONObjectTest.buildNestedMap(1000);
1453+
new JSONArray().put(0, map, new JSONParserConfiguration().withMaxNestingDepth(1000));
1454+
}
1455+
1456+
@Test(expected = JSONException.class)
1457+
public void testRecursiveDepthAtPosition1001Object() {
1458+
HashMap<String, Object> map = JSONObjectTest.buildNestedMap(1001);
1459+
new JSONArray().put(0, map);
1460+
}
1461+
1462+
@Test(expected = JSONException.class)
1463+
public void testRecursiveDepthArrayLimitedMaps() {
1464+
ArrayList<Object> array = new ArrayList<>();
1465+
array.add(array);
1466+
new JSONArray(array);
1467+
}
1468+
1469+
@Test
1470+
public void testRecursiveDepthArrayForDefaultLevels() {
1471+
ArrayList<Object> array = buildNestedArray(ParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
1472+
new JSONArray(array, new JSONParserConfiguration());
1473+
}
1474+
1475+
@Test
1476+
public void testRecursiveDepthArrayFor1000Levels() {
1477+
ArrayList<Object> array = buildNestedArray(1000);
1478+
JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withMaxNestingDepth(1000);
1479+
new JSONArray(array, parserConfiguration);
1480+
}
1481+
1482+
@Test(expected = JSONException.class)
1483+
public void testRecursiveDepthArrayFor1001Levels() {
1484+
ArrayList<Object> array = buildNestedArray(1001);
1485+
new JSONArray(array);
1486+
}
1487+
1488+
public static ArrayList<Object> buildNestedArray(int maxDepth) {
1489+
if (maxDepth <= 0) {
1490+
return new ArrayList<>();
1491+
}
1492+
ArrayList<Object> nestedArray = new ArrayList<>();
1493+
nestedArray.add(buildNestedArray(maxDepth - 1));
1494+
return nestedArray;
1495+
}
14201496
}

0 commit comments

Comments
 (0)