Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON parsing self reference object and array #823

Merged
merged 5 commits into from
Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 70 additions & 10 deletions src/main/java/org/json/JSONArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,22 @@ public JSONArray(String source) throws JSONException {
* A Collection.
*/
public JSONArray(Collection<?> collection) {
this(collection, 0, new JSONParserConfiguration());
}

public JSONArray(Collection<?> collection, JSONParserConfiguration jsonParserConfiguration) {
stleary marked this conversation as resolved.
Show resolved Hide resolved
this(collection, 0, jsonParserConfiguration);
}

protected JSONArray(Collection<?> collection, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
if (recursionDepth > jsonParserConfiguration.getMaxNestingDepth()) {
throw new JSONException("JSONArray has reached recursion depth limit of " + jsonParserConfiguration.getMaxNestingDepth());
}
if (collection == null) {
this.myArrayList = new ArrayList<Object>();
} else {
this.myArrayList = new ArrayList<Object>(collection.size());
this.addAll(collection, true);
this.addAll(collection, true, recursionDepth, jsonParserConfiguration);
}
}

Expand Down Expand Up @@ -205,7 +216,7 @@ public JSONArray(Object array) throws JSONException {
throw new JSONException(
"JSONArray initial value should be a string or collection or array.");
}
this.addAll(array, true);
this.addAll(array, true, 0);
}

/**
Expand Down Expand Up @@ -1338,7 +1349,27 @@ public JSONArray put(int index, long value) throws JSONException {
* If a key in the map is <code>null</code>
*/
public JSONArray put(int index, Map<?, ?> value) throws JSONException {
this.put(index, new JSONObject(value));
this.put(index, new JSONObject(value, new JSONParserConfiguration()));
return this;
}

/**
* Put a value in the JSONArray, where the value will be a JSONObject that
* is produced from a Map.
*
* @param index
* The subscript
* @param value
* The Map value.
* @param jsonParserConfiguration
* Configuration for recursive depth
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please just call this a configuration object. The things that it can configure will change going forward

* @return
* @throws JSONException
* If the index is negative or if the value is an invalid
* number.
*/
public JSONArray put(int index, Map<?, ?> value, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this.put(index, new JSONObject(value, jsonParserConfiguration));
return this;
}

Expand Down Expand Up @@ -1779,13 +1810,15 @@ public boolean isEmpty() {
* @param wrap
* {@code true} to call {@link JSONObject#wrap(Object)} for each item,
* {@code false} to add the items directly
* @param recursionDepth
* variable to keep the count of how nested the object creation is happening.
*
*/
private void addAll(Collection<?> collection, boolean wrap) {
private void addAll(Collection<?> collection, boolean wrap, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
this.myArrayList.ensureCapacity(this.myArrayList.size() + collection.size());
if (wrap) {
for (Object o: collection){
this.put(JSONObject.wrap(o));
this.put(JSONObject.wrap(o, recursionDepth + 1, jsonParserConfiguration));
}
} else {
for (Object o: collection){
Expand Down Expand Up @@ -1814,7 +1847,18 @@ private void addAll(Iterable<?> iter, boolean wrap) {
}
}
}


/**
* Add an array's elements to the JSONArray.
*
* @param array
stleary marked this conversation as resolved.
Show resolved Hide resolved
* @param wrap
* @throws JSONException
*/
private void addAll(Object array, boolean wrap) throws JSONException {
this.addAll(array, wrap, 0);
}

/**
* Add an array's elements to the JSONArray.
*
Expand All @@ -1823,21 +1867,37 @@ private void addAll(Iterable<?> iter, boolean wrap) {
* JSONArray, Collection, or Iterable, an exception will be
* thrown.
* @param wrap
stleary marked this conversation as resolved.
Show resolved Hide resolved
* @param recursionDepth
*/
private void addAll(Object array, boolean wrap, int recursionDepth) {
addAll(array, wrap, recursionDepth, new JSONParserConfiguration());
}
/**
* Add an array's elements to the JSONArray.
*`
* @param array
* Array. If the parameter passed is null, or not an array,
* JSONArray, Collection, or Iterable, an exception will be
* thrown.
* @param wrap
* {@code true} to call {@link JSONObject#wrap(Object)} for each item,
* {@code false} to add the items directly
*
* @param recursionDepth
* Variable to keep the count of how nested the object creation is happening.
* @param recursionDepth
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change to jsonParserConfiguration

* Variable to pass parser custom configuration for json parsing.
* @throws JSONException
* If not an array or if an array value is non-finite number.
* @throws NullPointerException
* Thrown if the array parameter is null.
*/
private void addAll(Object array, boolean wrap) throws JSONException {
private void addAll(Object array, boolean wrap, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
if (array.getClass().isArray()) {
int length = Array.getLength(array);
this.myArrayList.ensureCapacity(this.myArrayList.size() + length);
if (wrap) {
for (int i = 0; i < length; i += 1) {
this.put(JSONObject.wrap(Array.get(array, i)));
this.put(JSONObject.wrap(Array.get(array, i), recursionDepth + 1, jsonParserConfiguration));
}
} else {
for (int i = 0; i < length; i += 1) {
Expand All @@ -1850,7 +1910,7 @@ private void addAll(Object array, boolean wrap) throws JSONException {
// JSONArray
this.myArrayList.addAll(((JSONArray)array).myArrayList);
} else if (array instanceof Collection) {
this.addAll((Collection<?>)array, wrap);
this.addAll((Collection<?>)array, wrap, recursionDepth);
} else if (array instanceof Iterable) {
this.addAll((Iterable<?>)array, wrap);
} else {
Expand Down
29 changes: 26 additions & 3 deletions src/main/java/org/json/JSONObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,21 @@ public JSONObject(JSONTokener x) throws JSONException {
* If a key in the map is <code>null</code>
*/
public JSONObject(Map<?, ?> m) {
this(m, 0, new JSONParserConfiguration());
}

public JSONObject(Map<?, ?> m, JSONParserConfiguration jsonParserConfiguration) {
stleary marked this conversation as resolved.
Show resolved Hide resolved
this(m, 0, jsonParserConfiguration);
}

/**
* Construct a JSONObject from a map with recursion depth.
*
*/
protected JSONObject(Map<?, ?> m, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
stleary marked this conversation as resolved.
Show resolved Hide resolved
if (recursionDepth > jsonParserConfiguration.getMaxNestingDepth()) {
throw new JSONException("JSONObject has reached recursion depth limit of " + jsonParserConfiguration.getMaxNestingDepth());
}
if (m == null) {
this.map = new HashMap<String, Object>();
} else {
Expand All @@ -287,7 +302,7 @@ public JSONObject(Map<?, ?> m) {
final Object value = e.getValue();
if (value != null) {
testValidity(value);
this.map.put(String.valueOf(e.getKey()), wrap(value));
this.map.put(String.valueOf(e.getKey()), wrap(value, recursionDepth + 1, jsonParserConfiguration));
}
}
}
Expand Down Expand Up @@ -2566,7 +2581,15 @@ public static Object wrap(Object object) {
return wrap(object, null);
}

public static Object wrap(Object object, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
return wrap(object, null, recursionDepth, jsonParserConfiguration);
}

private static Object wrap(Object object, Set<Object> objectsRecord) {
return wrap(object, objectsRecord, 0, new JSONParserConfiguration());
}

private static Object wrap(Object object, Set<Object> objectsRecord, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
try {
if (NULL.equals(object)) {
return NULL;
Expand All @@ -2584,14 +2607,14 @@ private static Object wrap(Object object, Set<Object> objectsRecord) {

if (object instanceof Collection) {
Collection<?> coll = (Collection<?>) object;
return new JSONArray(coll);
return new JSONArray(coll, recursionDepth, jsonParserConfiguration);
}
if (object.getClass().isArray()) {
return new JSONArray(object);
}
if (object instanceof Map) {
Map<?, ?> map = (Map<?, ?>) object;
return new JSONObject(map);
return new JSONObject(map, recursionDepth, jsonParserConfiguration);
}
Package objectPackage = object.getClass().getPackage();
String objectPackageName = objectPackage != null ? objectPackage
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/org/json/JSONParserConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.json;

/**
* Configuration object for the JSON parser. The configuration is immutable.
*/
public class JSONParserConfiguration extends ParserConfiguration {

/**
* We can override the default maximum nesting depth if needed.
*/
public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = ParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH;
stleary marked this conversation as resolved.
Show resolved Hide resolved

/**
* Configuration with the default values.
*/
public JSONParserConfiguration() {
this.maxNestingDepth = DEFAULT_MAXIMUM_NESTING_DEPTH;
stleary marked this conversation as resolved.
Show resolved Hide resolved
}

public JSONParserConfiguration(int maxNestingDepth) {
stleary marked this conversation as resolved.
Show resolved Hide resolved
this.maxNestingDepth = maxNestingDepth;
}

@Override
protected JSONParserConfiguration clone() {
return new JSONParserConfiguration(DEFAULT_MAXIMUM_NESTING_DEPTH);
stleary marked this conversation as resolved.
Show resolved Hide resolved
}

stleary marked this conversation as resolved.
Show resolved Hide resolved
}
75 changes: 75 additions & 0 deletions src/test/java/org/json/junit/JSONArrayTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONParserConfiguration;
import org.json.JSONPointerException;
import org.json.JSONString;
import org.json.JSONTokener;
Expand Down Expand Up @@ -1417,4 +1418,78 @@ public String toJSONString() {
.put(2);
assertFalse(ja1.similar(ja3));
}

@Test(expected = JSONException.class)
public void testRecursiveDepth() {
HashMap<String, Object> map = new HashMap<>();
map.put("t", map);
new JSONArray().put(map);
}

@Test(expected = JSONException.class)
public void testRecursiveDepthAtPosition() {
HashMap<String, Object> map = new HashMap<>();
map.put("t", map);
new JSONArray().put(0, map);
}

@Test(expected = JSONException.class)
public void testRecursiveDepthArray() {
ArrayList<Object> array = new ArrayList<>();
array.add(array);
new JSONArray(array);
}

@Test
public void testRecursiveDepthAtPositionDefaultObject() {
HashMap<String, Object> map = JSONObjectTest.buildNestedMap(JSONParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
stleary marked this conversation as resolved.
Show resolved Hide resolved
new JSONArray().put(0, map);
}

@Test
public void testRecursiveDepthAtPosition1000Object() {
HashMap<String, Object> map = JSONObjectTest.buildNestedMap(1000);
new JSONArray().put(0, map, new JSONParserConfiguration(1000));
stleary marked this conversation as resolved.
Show resolved Hide resolved
}

@Test(expected = JSONException.class)
public void testRecursiveDepthAtPosition1001Object() {
HashMap<String, Object> map = JSONObjectTest.buildNestedMap(1001);
new JSONArray().put(0, map);
}

@Test(expected = JSONException.class)
public void testRecursiveDepthArrayLimitedMaps() {
ArrayList<Object> array = new ArrayList<>();
array.add(array);
new JSONArray(array);
}

@Test
public void testRecursiveDepthArrayForDefaultLevels() {
ArrayList<Object> array = buildNestedArray(JSONParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
stleary marked this conversation as resolved.
Show resolved Hide resolved
new JSONArray(array, new JSONParserConfiguration());
}

@Test
public void testRecursiveDepthArrayFor1000Levels() {
ArrayList<Object> array = buildNestedArray(1000);
JSONParserConfiguration parserConfiguration = new JSONParserConfiguration(1000);
stleary marked this conversation as resolved.
Show resolved Hide resolved
new JSONArray(array, parserConfiguration);
}

@Test(expected = JSONException.class)
public void testRecursiveDepthArrayFor1001Levels() {
ArrayList<Object> array = buildNestedArray(1001);
new JSONArray(array);
}

public static ArrayList<Object> buildNestedArray(int maxDepth) {
if (maxDepth <= 0) {
return new ArrayList<>();
}
ArrayList<Object> nestedArray = new ArrayList<>();
nestedArray.add(buildNestedArray(maxDepth - 1));
return nestedArray;
}
}
56 changes: 56 additions & 0 deletions src/test/java/org/json/junit/JSONObjectTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONPointerException;
import org.json.JSONParserConfiguration;
import org.json.JSONString;
import org.json.JSONTokener;
import org.json.XML;
Expand Down Expand Up @@ -3718,4 +3719,59 @@ public void issue713BeanConstructorWithNonFiniteNumbers() {
assertThrows(JSONException.class, () -> new JSONObject(bean));
}
}

@Test(expected = JSONException.class)
public void issue743SerializationMap() {
HashMap<String, Object> map = new HashMap<>();
map.put("t", map);
JSONObject object = new JSONObject(map);
String jsonString = object.toString();
}

@Test(expected = JSONException.class)
public void testCircularReferenceMultipleLevel() {
HashMap<String, Object> inside = new HashMap<>();
HashMap<String, Object> jsonObject = new HashMap<>();
inside.put("inside", jsonObject);
jsonObject.put("test", inside);
new JSONObject(jsonObject);
}

@Test
public void issue743SerializationMapWith512Objects() {
HashMap<String, Object> map = buildNestedMap(JSONParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
stleary marked this conversation as resolved.
Show resolved Hide resolved
JSONObject object = new JSONObject(map);
String jsonString = object.toString();
}

@Test
public void issue743SerializationMapWith1000Objects() {
HashMap<String, Object> map = buildNestedMap(1000);
JSONParserConfiguration parserConfiguration = new JSONParserConfiguration(1000);
JSONObject object = new JSONObject(map, parserConfiguration);
String jsonString = object.toString();
}

@Test(expected = JSONException.class)
public void issue743SerializationMapWith1001Objects() {
HashMap<String, Object> map = buildNestedMap(1001);
JSONObject object = new JSONObject(map);
String jsonString = object.toString();
}

/**
* Method to build nested map of max maxDepth
*
* @param maxDepth
* @return
*/
public static HashMap<String, Object> buildNestedMap(int maxDepth) {
if (maxDepth <= 0) {
return new HashMap<>();
}
HashMap<String, Object> nestedMap = new HashMap<>();
nestedMap.put("t", buildNestedMap(maxDepth - 1));
return nestedMap;
}

}
Loading