- * type of the annotation
- *
* @param m
* method to check
* @param annotationClass
@@ -1668,6 +1924,10 @@ public class JSONObject {
}
}
+ //If the superclass is Object, no annotations will be found any more
+ if (c.getSuperclass().equals(Object.class))
+ return -1;
+
try {
int d = getAnnotationDepth(
c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()),
@@ -1735,7 +1995,7 @@ public class JSONObject {
public JSONObject put(String key, double value) throws JSONException {
return this.put(key, Double.valueOf(value));
}
-
+
/**
* Put a key/float pair in the JSONObject.
*
@@ -1879,7 +2139,7 @@ public class JSONObject {
}
/**
- * Creates a JSONPointer using an initialization string and tries to
+ * Creates a JSONPointer using an initialization string and tries to
* match it to an item within this JSONObject. For example, given a
* JSONObject initialized with this document:
*
@@ -1887,13 +2147,13 @@ public class JSONObject {
* "a":{"b":"c"}
* }
*
- * and this JSONPointer string:
+ * and this JSONPointer string:
*
* "/a/b"
*
* Then this method will return the String "c".
* A JSONPointerException may be thrown from code called by this method.
- *
+ *
* @param jsonPointer string that can be used to create a JSONPointer
* @return the item matched by the JSONPointer, otherwise null
*/
@@ -1901,7 +2161,7 @@ public class JSONObject {
return query(new JSONPointer(jsonPointer));
}
/**
- * Uses a user initialized JSONPointer and tries to
+ * Uses a user initialized JSONPointer and tries to
* match it to an item within this JSONObject. For example, given a
* JSONObject initialized with this document:
*
@@ -1909,24 +2169,24 @@ public class JSONObject {
* "a":{"b":"c"}
* }
*
- * and this JSONPointer:
+ * and this JSONPointer:
*
* "/a/b"
*
* Then this method will return the String "c".
* A JSONPointerException may be thrown from code called by this method.
- *
+ *
* @param jsonPointer string that can be used to create a JSONPointer
* @return the item matched by the JSONPointer, otherwise null
*/
public Object query(JSONPointer jsonPointer) {
return jsonPointer.queryFrom(this);
}
-
+
/**
* Queries and returns a value from this object using {@code jsonPointer}, or
* returns null if the query fails due to a missing key.
- *
+ *
* @param jsonPointer the string representation of the JSON pointer
* @return the queried value or {@code null}
* @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax
@@ -1934,11 +2194,11 @@ public class JSONObject {
public Object optQuery(String jsonPointer) {
return optQuery(new JSONPointer(jsonPointer));
}
-
+
/**
* Queries and returns a value from this object using {@code jsonPointer}, or
* returns null if the query fails due to a missing key.
- *
+ *
* @param jsonPointer The JSON pointer
* @return the queried value or {@code null}
* @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax
@@ -1962,18 +2222,25 @@ public class JSONObject {
* A String
* @return A String correctly formatted for insertion in a JSON text.
*/
+ @SuppressWarnings("resource")
public static String quote(String string) {
StringWriter sw = new StringWriter();
- synchronized (sw.getBuffer()) {
- try {
- return quote(string, sw).toString();
- } catch (IOException ignored) {
- // will never happen - we are writing to a string writer
- return "";
- }
+ try {
+ return quote(string, sw).toString();
+ } catch (IOException ignored) {
+ // will never happen - we are writing to a string writer
+ return "";
}
}
+ /**
+ * Quotes a string and appends the result to a given Writer.
+ *
+ * @param string The input string to be quoted.
+ * @param w The Writer to which the quoted string will be appended.
+ * @return The same Writer instance after appending the quoted string.
+ * @throws IOException If an I/O error occurs while writing to the Writer.
+ */
public static Writer quote(String string, Writer w) throws IOException {
if (string == null || string.isEmpty()) {
w.write("\"\"");
@@ -2080,7 +2347,13 @@ public class JSONObject {
return false;
}
} else if (valueThis instanceof Number && valueOther instanceof Number) {
- return isNumberSimilar((Number)valueThis, (Number)valueOther);
+ if (!isNumberSimilar((Number)valueThis, (Number)valueOther)) {
+ return false;
+ }
+ } else if (valueThis instanceof JSONString && valueOther instanceof JSONString) {
+ if (!((JSONString) valueThis).toJSONString().equals(((JSONString) valueOther).toJSONString())) {
+ return false;
+ }
} else if (!valueThis.equals(valueOther)) {
return false;
}
@@ -2090,18 +2363,18 @@ public class JSONObject {
return false;
}
}
-
+
/**
* Compares two numbers to see if they are similar.
- *
+ *
* If either of the numbers are Double or Float instances, then they are checked to have
* a finite value. If either value is not finite (NaN or ±infinity), then this
* function will always return false. If both numbers are finite, they are first checked
* to be the same type and implement {@link Comparable}. If they do, then the actual
* {@link Comparable#compareTo(Object)} is called. If they are not the same type, or don't
- * implement Comparable, then they are converted to {@link BigDecimal}s. Finally the
+ * implement Comparable, then they are converted to {@link BigDecimal}s. Finally the
* BigDecimal values are compared using {@link BigDecimal#compareTo(BigDecimal)}.
- *
+ *
* @param l the Left value to compare. Can not be null
.
* @param r the right value to compare. Can not be null
.
* @return true if the numbers are similar, false otherwise.
@@ -2111,7 +2384,7 @@ public class JSONObject {
// non-finite numbers are never similar
return false;
}
-
+
// if the classes are the same and implement Comparable
// then use the built in compare first.
if(l.getClass().equals(r.getClass()) && l instanceof Comparable) {
@@ -2119,18 +2392,18 @@ public class JSONObject {
int compareTo = ((Comparable)l).compareTo(r);
return compareTo==0;
}
-
+
// BigDecimal should be able to handle all of our number types that we support through
// documentation. Convert to BigDecimal first, then use the Compare method to
// decide equality.
- final BigDecimal lBigDecimal = objectToBigDecimal(l, null);
- final BigDecimal rBigDecimal = objectToBigDecimal(r, null);
+ final BigDecimal lBigDecimal = objectToBigDecimal(l, null, false);
+ final BigDecimal rBigDecimal = objectToBigDecimal(r, null, false);
if (lBigDecimal == null || rBigDecimal == null) {
return false;
}
return lBigDecimal.compareTo(rBigDecimal) == 0;
}
-
+
private static boolean numberIsFinite(Number n) {
if (n instanceof Double && (((Double) n).isInfinite() || ((Double) n).isNaN())) {
return false;
@@ -2139,10 +2412,10 @@ public class JSONObject {
}
return true;
}
-
+
/**
* Tests if the value should be tried as a decimal. It makes no test if there are actual digits.
- *
+ *
* @param val value to test
* @return true if the string is "-0" or if it contains '.', 'e', or 'E', false otherwise.
*/
@@ -2150,12 +2423,55 @@ public class JSONObject {
return val.indexOf('.') > -1 || val.indexOf('e') > -1
|| val.indexOf('E') > -1 || "-0".equals(val);
}
-
+
/**
- * Converts a string to a number using the narrowest possible type. Possible
+ * Try to convert a string into a number, boolean, or null. If the string
+ * can't be converted, return the string.
+ *
+ * @param string
+ * A String. can not be null.
+ * @return A simple JSON value.
+ * @throws NullPointerException
+ * Thrown if the string is null.
+ */
+ // Changes to this method must be copied to the corresponding method in
+ // the XML class to keep full support for Android
+ public static Object stringToValue(String string) {
+ if ("".equals(string)) {
+ return string;
+ }
+
+ // check JSON key words true/false/null
+ if ("true".equalsIgnoreCase(string)) {
+ return Boolean.TRUE;
+ }
+ if ("false".equalsIgnoreCase(string)) {
+ return Boolean.FALSE;
+ }
+ if ("null".equalsIgnoreCase(string)) {
+ return JSONObject.NULL;
+ }
+
+ /*
+ * If it might be a number, try converting it. If a number cannot be
+ * produced, then the value will just be a string.
+ */
+
+ char initial = string.charAt(0);
+ if ((initial >= '0' && initial <= '9') || initial == '-') {
+ try {
+ return stringToNumber(string);
+ } catch (Exception ignore) {
+ }
+ }
+ return string;
+ }
+
+ /**
+ * Converts a string to a number using the narrowest possible type. Possible
* returns for this function are BigDecimal, Double, BigInteger, Long, and Integer.
* When a Double is returned, it should always be a valid Double and not NaN or +-infinity.
- *
+ *
* @param val value to convert
* @return Number representation of the value.
* @throws NumberFormatException thrown if the value is not a valid number. A public
@@ -2204,8 +2520,8 @@ public class JSONObject {
// integer representation.
// This will narrow any values to the smallest reasonable Object representation
// (Integer, Long, or BigInteger)
-
- // BigInteger down conversion: We use a similar bitLenth compare as
+
+ // BigInteger down conversion: We use a similar bitLength compare as
// BigInteger#intValueExact uses. Increases GC, but objects hold
// only what they need. i.e. Less runtime overhead if the value is
// long lived.
@@ -2221,49 +2537,6 @@ public class JSONObject {
throw new NumberFormatException("val ["+val+"] is not a valid number.");
}
- /**
- * Try to convert a string into a number, boolean, or null. If the string
- * can't be converted, return the string.
- *
- * @param string
- * A String. can not be null.
- * @return A simple JSON value.
- * @throws NullPointerException
- * Thrown if the string is null.
- */
- // Changes to this method must be copied to the corresponding method in
- // the XML class to keep full support for Android
- public static Object stringToValue(String string) {
- if ("".equals(string)) {
- return string;
- }
-
- // check JSON key words true/false/null
- if ("true".equalsIgnoreCase(string)) {
- return Boolean.TRUE;
- }
- if ("false".equalsIgnoreCase(string)) {
- return Boolean.FALSE;
- }
- if ("null".equalsIgnoreCase(string)) {
- return JSONObject.NULL;
- }
-
- /*
- * If it might be a number, try converting it. If a number cannot be
- * produced, then the value will just be a string.
- */
-
- char initial = string.charAt(0);
- if ((initial >= '0' && initial <= '9') || initial == '-') {
- try {
- return stringToNumber(string);
- } catch (Exception ignore) {
- }
- }
- return string;
- }
-
/**
* Throw an exception if the object is a NaN or infinite number.
*
@@ -2307,7 +2580,7 @@ public class JSONObject {
*
* Warning: This method assumes that the data structure is acyclical.
*
- *
+ *
* @return a printable, displayable, portable, transmittable representation
* of the object, beginning with {
(left
* brace) and ending with }
(right
@@ -2324,11 +2597,11 @@ public class JSONObject {
/**
* Make a pretty-printed JSON text of this JSONObject.
- *
+ *
*
If
{@code indentFactor > 0} and the {@link JSONObject}
* has only one key, then the object will be output on a single line:
* {@code {"key": 1}}
- *
+ *
* If an object has 2 or more keys, then it will be output across
* multiple lines:
{@code {
* "key1": 1,
@@ -2348,11 +2621,10 @@ public class JSONObject {
* @throws JSONException
* If the object contains an invalid number.
*/
+ @SuppressWarnings("resource")
public String toString(int indentFactor) throws JSONException {
StringWriter w = new StringWriter();
- synchronized (w.getBuffer()) {
- return this.write(w, indentFactor, 0).toString();
- }
+ return this.write(w, indentFactor, 0).toString();
}
/**
@@ -2400,6 +2672,34 @@ public class JSONObject {
* @return The wrapped value
*/
public static Object wrap(Object object) {
+ return wrap(object, null);
+ }
+
+ /**
+ * Wrap an object, if necessary. If the object is null
, return the NULL
+ * object. If it is an array or collection, wrap it in a JSONArray. If it is
+ * a map, wrap it in a JSONObject. If it is a standard property (Double,
+ * String, et al) then it is already wrapped. Otherwise, if it comes from
+ * one of the java packages, turn it into a string. And if it doesn't, try
+ * to wrap it in a JSONObject. If the wrapping fails, then null is returned.
+ *
+ * @param object
+ * The object to wrap
+ * @param recursionDepth
+ * Variable for tracking the count of nested object creations.
+ * @param jsonParserConfiguration
+ * Variable to pass parser custom configuration for json parsing.
+ * @return The wrapped value
+ */
+ static Object wrap(Object object, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
+ return wrap(object, null, recursionDepth, jsonParserConfiguration);
+ }
+
+ private static Object wrap(Object object, Set objectsRecord) {
+ return wrap(object, objectsRecord, 0, new JSONParserConfiguration());
+ }
+
+ private static Object wrap(Object object, Set objectsRecord, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
try {
if (NULL.equals(object)) {
return NULL;
@@ -2417,14 +2717,14 @@ public class JSONObject {
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
@@ -2434,7 +2734,13 @@ public class JSONObject {
|| object.getClass().getClassLoader() == null) {
return object.toString();
}
+ if (objectsRecord != null) {
+ return new JSONObject(object, objectsRecord);
+ }
return new JSONObject(object);
+ }
+ catch (JSONException exception) {
+ throw exception;
} catch (Exception exception) {
return null;
}
@@ -2454,6 +2760,7 @@ public class JSONObject {
return this.write(writer, 0, 0);
}
+ @SuppressWarnings("resource")
static final Writer writeValue(Writer writer, Object value,
int indentFactor, int indent) throws JSONException, IOException {
if (value == null || value.equals(null)) {
@@ -2506,11 +2813,11 @@ public class JSONObject {
/**
* Write the contents of the JSONObject as JSON text to a writer.
- *
+ *
* If
{@code indentFactor > 0} and the {@link JSONObject}
* has only one key, then the object will be output on a single line:
* {@code {"key": 1}}
- *
+ *
* If an object has 2 or more keys, then it will be output across
* multiple lines:
{@code {
* "key1": 1,
@@ -2531,6 +2838,7 @@ public class JSONObject {
* @throws JSONException if a called function has an error or a write error
* occurs
*/
+ @SuppressWarnings("resource")
public Writer write(Writer writer, int indentFactor, int indent)
throws JSONException {
try {
@@ -2612,23 +2920,7 @@ public class JSONObject {
}
return results;
}
-
- /**
- * Create a new JSONException in a common format for incorrect conversions.
- * @param key name of the key
- * @param valueType the type of value being coerced to
- * @param cause optional cause of the coercion failure
- * @return JSONException that can be thrown.
- */
- private static JSONException wrongValueFormatException(
- String key,
- String valueType,
- Throwable cause) {
- return new JSONException(
- "JSONObject[" + quote(key) + "] is not a " + valueType + "."
- , cause);
- }
-
+
/**
* Create a new JSONException in a common format for incorrect conversions.
* @param key name of the key
@@ -2641,8 +2933,51 @@ public class JSONObject {
String valueType,
Object value,
Throwable cause) {
+ if(value == null) {
+
+ return new JSONException(
+ "JSONObject[" + quote(key) + "] is not a " + valueType + " (null)."
+ , cause);
+ }
+ // don't try to toString collections or known object types that could be large.
+ if(value instanceof Map || value instanceof Iterable || value instanceof JSONObject) {
+ return new JSONException(
+ "JSONObject[" + quote(key) + "] is not a " + valueType + " (" + value.getClass() + ")."
+ , cause);
+ }
return new JSONException(
- "JSONObject[" + quote(key) + "] is not a " + valueType + " (" + value + ")."
+ "JSONObject[" + quote(key) + "] is not a " + valueType + " (" + value.getClass() + " : " + value + ")."
, cause);
}
+
+ /**
+ * Create a new JSONException in a common format for recursive object definition.
+ * @param key name of the key
+ * @return JSONException that can be thrown.
+ */
+ private static JSONException recursivelyDefinedObjectException(String key) {
+ return new JSONException(
+ "JavaBean object contains recursively defined member variable of key " + quote(key)
+ );
+ }
+
+ /**
+ * For a prospective number, remove the leading zeros
+ * @param value prospective number
+ * @return number without leading zeros
+ */
+ private static String removeLeadingZerosOfNumber(String value){
+ if (value.equals("-")){return value;}
+ boolean negativeFirstChar = (value.charAt(0) == '-');
+ int counter = negativeFirstChar ? 1:0;
+ while (counter < value.length()){
+ if (value.charAt(counter) != '0'){
+ if (negativeFirstChar) {return "-".concat(value.substring(counter));}
+ return value.substring(counter);
+ }
+ ++counter;
+ }
+ if (negativeFirstChar) {return "-0";}
+ return "0";
+ }
}
diff --git a/src/main/java/org/json/JSONParserConfiguration.java b/src/main/java/org/json/JSONParserConfiguration.java
new file mode 100644
index 0000000..f95e244
--- /dev/null
+++ b/src/main/java/org/json/JSONParserConfiguration.java
@@ -0,0 +1,26 @@
+package org.json;
+
+/**
+ * Configuration object for the JSON parser. The configuration is immutable.
+ */
+public class JSONParserConfiguration extends ParserConfiguration {
+
+ /**
+ * Configuration with the default values.
+ */
+ public JSONParserConfiguration() {
+ super();
+ }
+
+ @Override
+ protected JSONParserConfiguration clone() {
+ return new JSONParserConfiguration();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public JSONParserConfiguration withMaxNestingDepth(final int maxNestingDepth) {
+ return super.withMaxNestingDepth(maxNestingDepth);
+ }
+
+}
diff --git a/src/main/java/org/json/JSONPointer.java b/src/main/java/org/json/JSONPointer.java
index e8a0b78..859e1e6 100644
--- a/src/main/java/org/json/JSONPointer.java
+++ b/src/main/java/org/json/JSONPointer.java
@@ -10,27 +10,7 @@ import java.util.Collections;
import java.util.List;
/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
/**
@@ -62,6 +42,12 @@ public class JSONPointer {
*/
public static class Builder {
+ /**
+ * Constructs a new Builder object.
+ */
+ public Builder() {
+ }
+
// Segments for the eventual JSONPointer string
private final List refTokens = new ArrayList();
@@ -183,14 +169,21 @@ public class JSONPointer {
//}
}
+ /**
+ * Constructs a new JSONPointer instance with the provided list of reference tokens.
+ *
+ * @param refTokens A list of strings representing the reference tokens for the JSON Pointer.
+ * Each token identifies a step in the path to the targeted value.
+ */
public JSONPointer(List refTokens) {
this.refTokens = new ArrayList(refTokens);
}
+ /**
+ * @see rfc6901 section 3
+ */
private static String unescape(String token) {
- return token.replace("~1", "/").replace("~0", "~")
- .replace("\\\"", "\"")
- .replace("\\\\", "\\");
+ return token.replace("~1", "/").replace("~0", "~");
}
/**
@@ -263,16 +256,15 @@ public class JSONPointer {
/**
* Escapes path segment values to an unambiguous form.
* The escape char to be inserted is '~'. The chars to be escaped
- * are ~, which maps to ~0, and /, which maps to ~1. Backslashes
- * and double quote chars are also escaped.
+ * are ~, which maps to ~0, and /, which maps to ~1.
* @param token the JSONPointer segment value to be escaped
* @return the escaped value for the token
+ *
+ * @see rfc6901 section 3
*/
private static String escape(String token) {
return token.replace("~", "~0")
- .replace("/", "~1")
- .replace("\\", "\\\\")
- .replace("\"", "\\\"");
+ .replace("/", "~1");
}
/**
diff --git a/src/main/java/org/json/JSONPointerException.java b/src/main/java/org/json/JSONPointerException.java
index 0ce1aeb..dc5a25a 100644
--- a/src/main/java/org/json/JSONPointerException.java
+++ b/src/main/java/org/json/JSONPointerException.java
@@ -1,27 +1,7 @@
package org.json;
/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
/**
@@ -34,10 +14,21 @@ SOFTWARE.
public class JSONPointerException extends JSONException {
private static final long serialVersionUID = 8872944667561856751L;
+ /**
+ * Constructs a new JSONPointerException with the specified error message.
+ *
+ * @param message The detail message describing the reason for the exception.
+ */
public JSONPointerException(String message) {
super(message);
}
+ /**
+ * Constructs a new JSONPointerException with the specified error message and cause.
+ *
+ * @param message The detail message describing the reason for the exception.
+ * @param cause The cause of the exception.
+ */
public JSONPointerException(String message, Throwable cause) {
super(message, cause);
}
diff --git a/src/main/java/org/json/JSONPropertyIgnore.java b/src/main/java/org/json/JSONPropertyIgnore.java
index 682de74..d3a5bc5 100644
--- a/src/main/java/org/json/JSONPropertyIgnore.java
+++ b/src/main/java/org/json/JSONPropertyIgnore.java
@@ -1,27 +1,7 @@
package org.json;
/*
-Copyright (c) 2018 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static java.lang.annotation.ElementType.METHOD;
@@ -31,13 +11,13 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
-@Documented
-@Retention(RUNTIME)
-@Target({METHOD})
/**
* Use this annotation on a getter method to override the Bean name
* parser for Bean -> JSONObject mapping. If this annotation is
* present at any level in the class hierarchy, then the method will
* not be serialized from the bean into the JSONObject.
*/
+@Documented
+@Retention(RUNTIME)
+@Target({METHOD})
public @interface JSONPropertyIgnore { }
diff --git a/src/main/java/org/json/JSONPropertyName.java b/src/main/java/org/json/JSONPropertyName.java
index a1bcd58..0e4123f 100644
--- a/src/main/java/org/json/JSONPropertyName.java
+++ b/src/main/java/org/json/JSONPropertyName.java
@@ -1,27 +1,7 @@
package org.json;
/*
-Copyright (c) 2018 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static java.lang.annotation.ElementType.METHOD;
@@ -31,16 +11,17 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
-@Documented
-@Retention(RUNTIME)
-@Target({METHOD})
/**
* Use this annotation on a getter method to override the Bean name
* parser for Bean -> JSONObject mapping. A value set to empty string ""
* will have the Bean parser fall back to the default field name processing.
*/
+@Documented
+@Retention(RUNTIME)
+@Target({METHOD})
public @interface JSONPropertyName {
/**
+ * The value of the JSON property.
* @return The name of the property as to be used in the JSON Object.
*/
String value();
diff --git a/src/main/java/org/json/JSONString.java b/src/main/java/org/json/JSONString.java
index bcd9a81..cd8d184 100644
--- a/src/main/java/org/json/JSONString.java
+++ b/src/main/java/org/json/JSONString.java
@@ -1,27 +1,7 @@
package org.json;
/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
/**
diff --git a/src/main/java/org/json/JSONStringer.java b/src/main/java/org/json/JSONStringer.java
index bb9e7a4..2f6cf9e 100644
--- a/src/main/java/org/json/JSONStringer.java
+++ b/src/main/java/org/json/JSONStringer.java
@@ -1,27 +1,7 @@
package org.json;
/*
-Copyright (c) 2006 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import java.io.StringWriter;
@@ -50,7 +30,7 @@ import java.io.StringWriter;
*
* The first method called must be array
or object
.
* There are no methods for adding commas or colons. JSONStringer adds them for
- * you. Objects and arrays can be nested up to 20 levels deep.
+ * you. Objects and arrays can be nested up to 200 levels deep.
*
* This can sometimes be easier than using a JSONObject to build a string.
* @author JSON.org
diff --git a/src/main/java/org/json/JSONTokener.java b/src/main/java/org/json/JSONTokener.java
index e6821de..0bc6dfb 100644
--- a/src/main/java/org/json/JSONTokener.java
+++ b/src/main/java/org/json/JSONTokener.java
@@ -1,34 +1,10 @@
package org.json;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.StringReader;
+import java.io.*;
+import java.nio.charset.Charset;
/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
/**
@@ -81,7 +57,7 @@ public class JSONTokener {
* @param inputStream The source.
*/
public JSONTokener(InputStream inputStream) {
- this(new InputStreamReader(inputStream));
+ this(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
}
@@ -145,7 +121,7 @@ public class JSONTokener {
/**
* Checks if the end of the input has been reached.
- *
+ *
* @return true if at the end of the file and we didn't step back
*/
public boolean end() {
@@ -210,6 +186,12 @@ public class JSONTokener {
return this.previous;
}
+ /**
+ * Get the last character read from the input or '\0' if nothing has been read yet.
+ * @return the last character read from the input.
+ */
+ protected char getPrevious() { return this.previous;}
+
/**
* Increments the internal indexes according to the previous character
* read and the character passed as the current character.
@@ -420,18 +402,32 @@ public class JSONTokener {
*/
public Object nextValue() throws JSONException {
char c = this.nextClean();
+ switch (c) {
+ case '{':
+ this.back();
+ try {
+ return new JSONObject(this);
+ } catch (StackOverflowError e) {
+ throw new JSONException("JSON Array or Object depth too large to process.", e);
+ }
+ case '[':
+ this.back();
+ try {
+ return new JSONArray(this);
+ } catch (StackOverflowError e) {
+ throw new JSONException("JSON Array or Object depth too large to process.", e);
+ }
+ }
+ return nextSimpleValue(c);
+ }
+
+ Object nextSimpleValue(char c) {
String string;
switch (c) {
case '"':
case '\'':
return this.nextString(c);
- case '{':
- this.back();
- return new JSONObject(this);
- case '[':
- this.back();
- return new JSONArray(this);
}
/*
@@ -528,4 +524,15 @@ public class JSONTokener {
return " at " + this.index + " [character " + this.character + " line " +
this.line + "]";
}
+
+ /**
+ * Closes the underlying reader, releasing any resources associated with it.
+ *
+ * @throws IOException If an I/O error occurs while closing the reader.
+ */
+ public void close() throws IOException {
+ if(reader!=null){
+ reader.close();
+ }
+ }
}
diff --git a/src/main/java/org/json/JSONWriter.java b/src/main/java/org/json/JSONWriter.java
index dafb1b2..11f4a5c 100644
--- a/src/main/java/org/json/JSONWriter.java
+++ b/src/main/java/org/json/JSONWriter.java
@@ -5,27 +5,7 @@ import java.util.Collection;
import java.util.Map;
/*
-Copyright (c) 2006 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
/**
diff --git a/src/main/java/org/json/ParserConfiguration.java b/src/main/java/org/json/ParserConfiguration.java
new file mode 100644
index 0000000..5cdc10d
--- /dev/null
+++ b/src/main/java/org/json/ParserConfiguration.java
@@ -0,0 +1,126 @@
+package org.json;
+/*
+Public Domain.
+*/
+
+/**
+ * Configuration base object for parsers. The configuration is immutable.
+ */
+@SuppressWarnings({""})
+public class ParserConfiguration {
+ /**
+ * Used to indicate there's no defined limit to the maximum nesting depth when parsing a document.
+ */
+ public static final int UNDEFINED_MAXIMUM_NESTING_DEPTH = -1;
+
+ /**
+ * The default maximum nesting depth when parsing a document.
+ */
+ public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512;
+
+ /**
+ * Specifies if values should be kept as strings (true
), or if
+ * they should try to be guessed into JSON values (numeric, boolean, string)
+ */
+ protected boolean keepStrings;
+
+ /**
+ * The maximum nesting depth when parsing a document.
+ */
+ protected int maxNestingDepth;
+
+ /**
+ * Constructs a new ParserConfiguration with default settings.
+ */
+ public ParserConfiguration() {
+ this.keepStrings = false;
+ this.maxNestingDepth = DEFAULT_MAXIMUM_NESTING_DEPTH;
+ }
+
+ /**
+ * Constructs a new ParserConfiguration with the specified settings.
+ *
+ * @param keepStrings A boolean indicating whether to preserve strings during parsing.
+ * @param maxNestingDepth An integer representing the maximum allowed nesting depth.
+ */
+ protected ParserConfiguration(final boolean keepStrings, final int maxNestingDepth) {
+ this.keepStrings = keepStrings;
+ this.maxNestingDepth = maxNestingDepth;
+ }
+
+ /**
+ * Provides a new instance of the same configuration.
+ */
+ @Override
+ protected ParserConfiguration clone() {
+ // future modifications to this method should always ensure a "deep"
+ // clone in the case of collections. i.e. if a Map is added as a configuration
+ // item, a new map instance should be created and if possible each value in the
+ // map should be cloned as well. If the values of the map are known to also
+ // be immutable, then a shallow clone of the map is acceptable.
+ return new ParserConfiguration(
+ this.keepStrings,
+ this.maxNestingDepth
+ );
+ }
+
+ /**
+ * When parsing the XML into JSONML, specifies if values should be kept as strings (true
), or if
+ * they should try to be guessed into JSON values (numeric, boolean, string)
+ *
+ * @return The keepStrings
configuration value.
+ */
+ public boolean isKeepStrings() {
+ return this.keepStrings;
+ }
+
+ /**
+ * When parsing the XML into JSONML, specifies if values should be kept as strings (true
), or if
+ * they should try to be guessed into JSON values (numeric, boolean, string)
+ *
+ * @param newVal
+ * new value to use for the keepStrings
configuration option.
+ * @param the type of the configuration object
+ *
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ @SuppressWarnings("unchecked")
+ public T withKeepStrings(final boolean newVal) {
+ T newConfig = (T)this.clone();
+ newConfig.keepStrings = newVal;
+ return newConfig;
+ }
+
+ /**
+ * The maximum nesting depth that the parser will descend before throwing an exception
+ * when parsing the XML into JSONML.
+ * @return the maximum nesting depth set for this configuration
+ */
+ public int getMaxNestingDepth() {
+ return maxNestingDepth;
+ }
+
+ /**
+ * Defines the maximum nesting depth that the parser will descend before throwing an exception
+ * when parsing the XML into JSONML. The default max nesting depth is 512, which means the parser
+ * will throw a JsonException if the maximum depth is reached.
+ * Using any negative value as a parameter is equivalent to setting no limit to the nesting depth,
+ * which means the parses will go as deep as the maximum call stack size allows.
+ * @param maxNestingDepth the maximum nesting depth allowed to the XML parser
+ * @param the type of the configuration object
+ *
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ @SuppressWarnings("unchecked")
+ public T withMaxNestingDepth(int maxNestingDepth) {
+ T newConfig = (T)this.clone();
+
+ if (maxNestingDepth > UNDEFINED_MAXIMUM_NESTING_DEPTH) {
+ newConfig.maxNestingDepth = maxNestingDepth;
+ } else {
+ newConfig.maxNestingDepth = UNDEFINED_MAXIMUM_NESTING_DEPTH;
+ }
+
+ return newConfig;
+ }
+}
diff --git a/src/main/java/org/json/Property.java b/src/main/java/org/json/Property.java
index 7caeebb..ba6c569 100644
--- a/src/main/java/org/json/Property.java
+++ b/src/main/java/org/json/Property.java
@@ -1,27 +1,7 @@
package org.json;
/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import java.util.Enumeration;
@@ -33,6 +13,13 @@ import java.util.Properties;
* @version 2015-05-05
*/
public class Property {
+
+ /**
+ * Constructs a new Property object.
+ */
+ public Property() {
+ }
+
/**
* Converts a property file object into a JSONObject. The property file object is a table of name value pairs.
* @param properties java.util.Properties
diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java
index 805a5c3..e59ec7a 100644
--- a/src/main/java/org/json/XML.java
+++ b/src/main/java/org/json/XML.java
@@ -1,37 +1,15 @@
package org.json;
/*
-Copyright (c) 2015 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import java.io.Reader;
import java.io.StringReader;
-import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Iterator;
-
/**
* This provides static methods to convert an XML text into a JSONObject, and to
* covert a JSONObject into an XML text.
@@ -42,6 +20,12 @@ import java.util.Iterator;
@SuppressWarnings("boxing")
public class XML {
+ /**
+ * Constructs a new XML object.
+ */
+ public XML() {
+ }
+
/** The Character '&'. */
public static final Character AMP = '&';
@@ -74,6 +58,9 @@ public class XML {
*/
public static final String NULL_ATTR = "xsi:nil";
+ /**
+ * Represents the XML attribute name for specifying type information.
+ */
public static final String TYPE_ATTR = "xsi:type";
/**
@@ -119,7 +106,7 @@ public class XML {
/**
* Replace special characters with XML escapes:
*
- * {@code
+ * {@code
* & (ampersand) is replaced by &
* < (less than) is replaced by <
* > (greater than) is replaced by >
@@ -250,10 +237,14 @@ public class XML {
* The JSONObject that will include the new material.
* @param name
* The tag name.
+ * @param config
+ * The XML parser configuration.
+ * @param currentNestingDepth
+ * The current nesting depth.
* @return true if the close tag is processed.
- * @throws JSONException
+ * @throws JSONException Thrown if any parsing error occurs.
*/
- private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config)
+ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, int currentNestingDepth)
throws JSONException {
char c;
int i;
@@ -380,12 +371,23 @@ public class XML {
if (x.nextToken() != GT) {
throw x.syntaxError("Misshaped tag");
}
- if (nilAttributeFound) {
- context.accumulate(tagName, JSONObject.NULL);
- } else if (jsonObject.length() > 0) {
- context.accumulate(tagName, jsonObject);
+ if (config.getForceList().contains(tagName)) {
+ // Force the value to be an array
+ if (nilAttributeFound) {
+ context.append(tagName, JSONObject.NULL);
+ } else if (jsonObject.length() > 0) {
+ context.append(tagName, jsonObject);
+ } else {
+ context.put(tagName, new JSONArray());
+ }
} else {
- context.accumulate(tagName, "");
+ if (nilAttributeFound) {
+ context.accumulate(tagName, JSONObject.NULL);
+ } else if (jsonObject.length() > 0) {
+ context.accumulate(tagName, jsonObject);
+ } else {
+ context.accumulate(tagName, "");
+ }
}
return false;
@@ -412,15 +414,35 @@ public class XML {
} else if (token == LT) {
// Nested element
- if (parse(x, jsonObject, tagName, config)) {
- if (jsonObject.length() == 0) {
- context.accumulate(tagName, "");
- } else if (jsonObject.length() == 1
- && jsonObject.opt(config.getcDataTagName()) != null) {
- context.accumulate(tagName, jsonObject.opt(config.getcDataTagName()));
+ if (currentNestingDepth == config.getMaxNestingDepth()) {
+ throw x.syntaxError("Maximum nesting depth of " + config.getMaxNestingDepth() + " reached");
+ }
+
+ if (parse(x, jsonObject, tagName, config, currentNestingDepth + 1)) {
+ if (config.getForceList().contains(tagName)) {
+ // Force the value to be an array
+ if (jsonObject.length() == 0) {
+ context.put(tagName, new JSONArray());
+ } else if (jsonObject.length() == 1
+ && jsonObject.opt(config.getcDataTagName()) != null) {
+ context.append(tagName, jsonObject.opt(config.getcDataTagName()));
+ } else {
+ context.append(tagName, jsonObject);
+ }
} else {
- context.accumulate(tagName, jsonObject);
+ if (jsonObject.length() == 0) {
+ context.accumulate(tagName, "");
+ } else if (jsonObject.length() == 1
+ && jsonObject.opt(config.getcDataTagName()) != null) {
+ context.accumulate(tagName, jsonObject.opt(config.getcDataTagName()));
+ } else {
+ if (!config.shouldTrimWhiteSpace()) {
+ removeEmpty(jsonObject, config);
+ }
+ context.accumulate(tagName, jsonObject);
+ }
}
+
return false;
}
}
@@ -431,6 +453,118 @@ public class XML {
}
}
}
+ /**
+ * This method removes any JSON entry which has the key set by XMLParserConfiguration.cDataTagName
+ * and contains whitespace as this is caused by whitespace between tags. See test XMLTest.testNestedWithWhitespaceTrimmingDisabled.
+ * @param jsonObject JSONObject which may require deletion
+ * @param config The XMLParserConfiguration which includes the cDataTagName
+ */
+ private static void removeEmpty(final JSONObject jsonObject, final XMLParserConfiguration config) {
+ if (jsonObject.has(config.getcDataTagName())) {
+ final Object s = jsonObject.get(config.getcDataTagName());
+ if (s instanceof String) {
+ if (isStringAllWhiteSpace(s.toString())) {
+ jsonObject.remove(config.getcDataTagName());
+ }
+ }
+ else if (s instanceof JSONArray) {
+ final JSONArray sArray = (JSONArray) s;
+ for (int k = sArray.length()-1; k >= 0; k--){
+ final Object eachString = sArray.get(k);
+ if (eachString instanceof String) {
+ String s1 = (String) eachString;
+ if (isStringAllWhiteSpace(s1)) {
+ sArray.remove(k);
+ }
+ }
+ }
+ if (sArray.isEmpty()) {
+ jsonObject.remove(config.getcDataTagName());
+ }
+ }
+ }
+ }
+
+ private static boolean isStringAllWhiteSpace(final String s) {
+ for (int k = 0; k= '0' && initial <= '9') || initial == '-') {
+ // decimal representation
+ if (isDecimalNotation(val)) {
+ // Use a BigDecimal all the time so we keep the original
+ // representation. BigDecimal doesn't support -0.0, ensure we
+ // keep that by forcing a decimal.
+ try {
+ BigDecimal bd = new BigDecimal(val);
+ if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) {
+ return Double.valueOf(-0.0);
+ }
+ return bd;
+ } catch (NumberFormatException retryAsDouble) {
+ // this is to support "Hex Floats" like this: 0x1.0P-1074
+ try {
+ Double d = Double.valueOf(val);
+ if(d.isNaN() || d.isInfinite()) {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ return d;
+ } catch (NumberFormatException ignore) {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ }
+ }
+ // block items like 00 01 etc. Java number parsers treat these as Octal.
+ if(initial == '0' && val.length() > 1) {
+ char at1 = val.charAt(1);
+ if(at1 >= '0' && at1 <= '9') {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ } else if (initial == '-' && val.length() > 2) {
+ char at1 = val.charAt(1);
+ char at2 = val.charAt(2);
+ if(at1 == '0' && at2 >= '0' && at2 <= '9') {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ }
+ // integer representation.
+ // This will narrow any values to the smallest reasonable Object representation
+ // (Integer, Long, or BigInteger)
+
+ // BigInteger down conversion: We use a similar bitLength compare as
+ // BigInteger#intValueExact uses. Increases GC, but objects hold
+ // only what they need. i.e. Less runtime overhead if the value is
+ // long lived.
+ BigInteger bi = new BigInteger(val);
+ if(bi.bitLength() <= 31){
+ return Integer.valueOf(bi.intValue());
+ }
+ if(bi.bitLength() <= 63){
+ return Long.valueOf(bi.longValue());
+ }
+ return bi;
+ }
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+
+ /**
+ * direct copy of {@link JSONObject#isDecimalNotation(String)} to maintain Android support.
+ */
+ private static boolean isDecimalNotation(final String val) {
+ return val.indexOf('.') > -1 || val.indexOf('e') > -1
+ || val.indexOf('E') > -1 || "-0".equals(val);
+ }
/**
* This method tries to convert the given string value to the target object
@@ -484,78 +618,6 @@ public class XML {
}
return string;
}
-
- /**
- * direct copy of {@link JSONObject#stringToNumber(String)} to maintain Android support.
- */
- private static Number stringToNumber(final String val) throws NumberFormatException {
- char initial = val.charAt(0);
- if ((initial >= '0' && initial <= '9') || initial == '-') {
- // decimal representation
- if (isDecimalNotation(val)) {
- // Use a BigDecimal all the time so we keep the original
- // representation. BigDecimal doesn't support -0.0, ensure we
- // keep that by forcing a decimal.
- try {
- BigDecimal bd = new BigDecimal(val);
- if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) {
- return Double.valueOf(-0.0);
- }
- return bd;
- } catch (NumberFormatException retryAsDouble) {
- // this is to support "Hex Floats" like this: 0x1.0P-1074
- try {
- Double d = Double.valueOf(val);
- if(d.isNaN() || d.isInfinite()) {
- throw new NumberFormatException("val ["+val+"] is not a valid number.");
- }
- return d;
- } catch (NumberFormatException ignore) {
- throw new NumberFormatException("val ["+val+"] is not a valid number.");
- }
- }
- }
- // block items like 00 01 etc. Java number parsers treat these as Octal.
- if(initial == '0' && val.length() > 1) {
- char at1 = val.charAt(1);
- if(at1 >= '0' && at1 <= '9') {
- throw new NumberFormatException("val ["+val+"] is not a valid number.");
- }
- } else if (initial == '-' && val.length() > 2) {
- char at1 = val.charAt(1);
- char at2 = val.charAt(2);
- if(at1 == '0' && at2 >= '0' && at2 <= '9') {
- throw new NumberFormatException("val ["+val+"] is not a valid number.");
- }
- }
- // integer representation.
- // This will narrow any values to the smallest reasonable Object representation
- // (Integer, Long, or BigInteger)
-
- // BigInteger down conversion: We use a similar bitLenth compare as
- // BigInteger#intValueExact uses. Increases GC, but objects hold
- // only what they need. i.e. Less runtime overhead if the value is
- // long lived.
- BigInteger bi = new BigInteger(val);
- if(bi.bitLength() <= 31){
- return Integer.valueOf(bi.intValue());
- }
- if(bi.bitLength() <= 63){
- return Long.valueOf(bi.longValue());
- }
- return bi;
- }
- throw new NumberFormatException("val ["+val+"] is not a valid number.");
- }
-
- /**
- * direct copy of {@link JSONObject#isDecimalNotation(String)} to maintain Android support.
- */
- private static boolean isDecimalNotation(final String val) {
- return val.indexOf('.') > -1 || val.indexOf('e') > -1
- || val.indexOf('E') > -1 || "-0".equals(val);
- }
-
/**
* Convert a well-formed (but not necessarily valid) XML string into a
@@ -565,7 +627,7 @@ public class XML {
* name/value pairs and arrays of values. JSON does not does not like to
* distinguish between elements and attributes. Sequences of similar
* elements are represented as JSONArrays. Content text may be placed in a
- * "content" member. Comments, prologs, DTDs, and {@code
+ * "content" member. Comments, prologs, DTDs, and {@code
* <[ [ ]]>}
* are ignored.
*
@@ -586,7 +648,7 @@ public class XML {
* name/value pairs and arrays of values. JSON does not does not like to
* distinguish between elements and attributes. Sequences of similar
* elements are represented as JSONArrays. Content text may be placed in a
- * "content" member. Comments, prologs, DTDs, and {@code
+ * "content" member. Comments, prologs, DTDs, and {@code
* <[ [ ]]>}
* are ignored.
*
@@ -648,11 +710,11 @@ public class XML {
*/
public static JSONObject toJSONObject(Reader reader, XMLParserConfiguration config) throws JSONException {
JSONObject jo = new JSONObject();
- XMLTokener x = new XMLTokener(reader);
+ XMLTokener x = new XMLTokener(reader, config);
while (x.more()) {
x.skipPast("<");
if(x.more()) {
- parse(x, jo, null, config);
+ parse(x, jo, null, config, 0);
}
}
return jo;
@@ -666,7 +728,7 @@ public class XML {
* name/value pairs and arrays of values. JSON does not does not like to
* distinguish between elements and attributes. Sequences of similar
* elements are represented as JSONArrays. Content text may be placed in a
- * "content" member. Comments, prologs, DTDs, and {@code
+ * "content" member. Comments, prologs, DTDs, and {@code
* <[ [ ]]>}
* are ignored.
*
@@ -692,7 +754,7 @@ public class XML {
* name/value pairs and arrays of values. JSON does not does not like to
* distinguish between elements and attributes. Sequences of similar
* elements are represented as JSONArrays. Content text may be placed in a
- * "content" member. Comments, prologs, DTDs, and {@code
+ * "content" member. Comments, prologs, DTDs, and {@code
* <[ [ ]]>}
* are ignored.
*
@@ -749,6 +811,28 @@ public class XML {
*/
public static String toString(final Object object, final String tagName, final XMLParserConfiguration config)
throws JSONException {
+ return toString(object, tagName, config, 0, 0);
+ }
+
+ /**
+ * Convert a JSONObject into a well-formed, element-normal XML string,
+ * either pretty print or single-lined depending on indent factor.
+ *
+ * @param object
+ * A JSONObject.
+ * @param tagName
+ * The optional name of the enclosing tag.
+ * @param config
+ * Configuration that can control output to XML.
+ * @param indentFactor
+ * The number of spaces to add to each level of indentation.
+ * @param indent
+ * The current ident level in spaces.
+ * @return
+ * @throws JSONException
+ */
+ private static String toString(final Object object, final String tagName, final XMLParserConfiguration config, int indentFactor, int indent)
+ throws JSONException {
StringBuilder sb = new StringBuilder();
JSONArray ja;
JSONObject jo;
@@ -758,9 +842,14 @@ public class XML {
// Emit
if (tagName != null) {
+ sb.append(indent(indent));
sb.append('<');
sb.append(tagName);
sb.append('>');
+ if(indentFactor > 0){
+ sb.append("\n");
+ indent += indentFactor;
+ }
}
// Loop thru the keys.
@@ -803,31 +892,52 @@ public class XML {
sb.append('<');
sb.append(key);
sb.append('>');
- sb.append(toString(val, null, config));
+ sb.append(toString(val, null, config, indentFactor, indent));
sb.append("");
sb.append(key);
sb.append('>');
} else {
- sb.append(toString(val, key, config));
+ sb.append(toString(val, key, config, indentFactor, indent));
}
}
} else if ("".equals(value)) {
- sb.append('<');
- sb.append(key);
- sb.append("/>");
+ if (config.isCloseEmptyTag()){
+ sb.append(indent(indent));
+ sb.append('<');
+ sb.append(key);
+ sb.append(">");
+ sb.append("");
+ sb.append(key);
+ sb.append(">");
+ if (indentFactor > 0) {
+ sb.append("\n");
+ }
+ }else {
+ sb.append(indent(indent));
+ sb.append('<');
+ sb.append(key);
+ sb.append("/>");
+ if (indentFactor > 0) {
+ sb.append("\n");
+ }
+ }
// Emit a new tag
} else {
- sb.append(toString(value, key, config));
+ sb.append(toString(value, key, config, indentFactor, indent));
}
}
if (tagName != null) {
// Emit the close tag
+ sb.append(indent(indent - indentFactor));
sb.append("");
sb.append(tagName);
sb.append('>');
+ if(indentFactor > 0){
+ sb.append("\n");
+ }
}
return sb.toString();
@@ -846,15 +956,85 @@ public class XML {
// XML does not have good support for arrays. If an array
// appears in a place where XML is lacking, synthesize an
// element.
- sb.append(toString(val, tagName == null ? "array" : tagName, config));
+ sb.append(toString(val, tagName == null ? "array" : tagName, config, indentFactor, indent));
}
return sb.toString();
}
- string = (object == null) ? "null" : escape(object.toString());
- return (tagName == null) ? "\"" + string + "\""
- : (string.length() == 0) ? "<" + tagName + "/>" : "<" + tagName
- + ">" + string + "" + tagName + ">";
+ string = (object == null) ? "null" : escape(object.toString());
+ String indentationSuffix = (indentFactor > 0) ? "\n" : "";
+ if(tagName == null){
+ return indent(indent) + "\"" + string + "\"" + indentationSuffix;
+ } else if(string.length() == 0){
+ return indent(indent) + "<" + tagName + "/>" + indentationSuffix;
+ } else {
+ return indent(indent) + "<" + tagName
+ + ">" + string + "" + tagName + ">" + indentationSuffix;
+ }
+ }
+
+ /**
+ * Convert a JSONObject into a well-formed, pretty printed element-normal XML string.
+ *
+ * @param object
+ * A JSONObject.
+ * @param indentFactor
+ * The number of spaces to add to each level of indentation.
+ * @return A string.
+ * @throws JSONException Thrown if there is an error parsing the string
+ */
+ public static String toString(Object object, int indentFactor){
+ return toString(object, null, XMLParserConfiguration.ORIGINAL, indentFactor);
+ }
+
+ /**
+ * Convert a JSONObject into a well-formed, pretty printed element-normal XML string.
+ *
+ * @param object
+ * A JSONObject.
+ * @param tagName
+ * The optional name of the enclosing tag.
+ * @param indentFactor
+ * The number of spaces to add to each level of indentation.
+ * @return A string.
+ * @throws JSONException Thrown if there is an error parsing the string
+ */
+ public static String toString(final Object object, final String tagName, int indentFactor) {
+ return toString(object, tagName, XMLParserConfiguration.ORIGINAL, indentFactor);
+ }
+
+ /**
+ * Convert a JSONObject into a well-formed, pretty printed element-normal XML string.
+ *
+ * @param object
+ * A JSONObject.
+ * @param tagName
+ * The optional name of the enclosing tag.
+ * @param config
+ * Configuration that can control output to XML.
+ * @param indentFactor
+ * The number of spaces to add to each level of indentation.
+ * @return A string.
+ * @throws JSONException Thrown if there is an error parsing the string
+ */
+ public static String toString(final Object object, final String tagName, final XMLParserConfiguration config, int indentFactor)
+ throws JSONException {
+ return toString(object, tagName, config, indentFactor, 0);
+ }
+
+ /**
+ * Return a String consisting of a number of space characters specified by indent
+ *
+ * @param indent
+ * The number of spaces to be appended to the String.
+ * @return
+ */
+ private static final String indent(int indent) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < indent; i++) {
+ sb.append(' ');
+ }
+ return sb.toString();
}
}
diff --git a/src/main/java/org/json/XMLParserConfiguration.java b/src/main/java/org/json/XMLParserConfiguration.java
index b9e752c..bc4a800 100644
--- a/src/main/java/org/json/XMLParserConfiguration.java
+++ b/src/main/java/org/json/XMLParserConfiguration.java
@@ -1,31 +1,13 @@
package org.json;
/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
/**
@@ -33,7 +15,13 @@ import java.util.Map;
* @author AylwardJ
*/
@SuppressWarnings({""})
-public class XMLParserConfiguration {
+public class XMLParserConfiguration extends ParserConfiguration {
+
+ /**
+ * The default maximum nesting depth when parsing a XML document to JSON.
+ */
+// public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512; // We could override
+
/** Original Configuration of the XML Parser. */
public static final XMLParserConfiguration ORIGINAL
= new XMLParserConfiguration();
@@ -41,19 +29,13 @@ public class XMLParserConfiguration {
public static final XMLParserConfiguration KEEP_STRINGS
= new XMLParserConfiguration().withKeepStrings(true);
- /**
- * When parsing the XML into JSON, specifies if values should be kept as strings (true
), or if
- * they should try to be guessed into JSON values (numeric, boolean, string)
- */
- private boolean keepStrings;
-
/**
* The name of the key in a JSON Object that indicates a CDATA section. Historically this has
* been the value "content" but can be changed. Use null
to indicate no CDATA
* processing.
*/
private String cDataTagName;
-
+
/**
* When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
* should be kept as attribute(false
), or they should be converted to
@@ -61,20 +43,44 @@ public class XMLParserConfiguration {
*/
private boolean convertNilAttributeToNull;
+ /**
+ * When creating an XML from JSON Object, an empty tag by default will self-close.
+ * If it has to be closed explicitly, with empty content between start and end tag,
+ * this flag is to be turned on.
+ */
+ private boolean closeEmptyTag;
+
/**
* This will allow type conversion for values in XML if xsi:type attribute is defined
*/
private Map> xsiTypeMap;
+ /**
+ * When parsing the XML into JSON, specifies the tags whose values should be converted
+ * to arrays
+ */
+ private Set forceList;
+
+
+ /**
+ * Flag to indicate whether white space should be trimmed when parsing XML.
+ * The default behaviour is to trim white space. When this is set to false, inputting XML
+ * with tags that are the same as the value of cDataTagName is unsupported. It is recommended to set cDataTagName
+ * to a distinct value in this case.
+ */
+ private boolean shouldTrimWhiteSpace;
+
/**
* Default parser configuration. Does not keep strings (tries to implicitly convert
- * values), and the CDATA Tag Name is "content".
+ * values), and the CDATA Tag Name is "content". Trims whitespace.
*/
public XMLParserConfiguration () {
- this.keepStrings = false;
+ super();
this.cDataTagName = "content";
this.convertNilAttributeToNull = false;
this.xsiTypeMap = Collections.emptyMap();
+ this.forceList = Collections.emptySet();
+ this.shouldTrimWhiteSpace = true;
}
/**
@@ -94,7 +100,7 @@ public class XMLParserConfiguration {
* Configure the parser string processing to try and convert XML values to JSON values and
* use the passed CDATA Tag Name the processing value. Pass null
to
* disable CDATA processing
- * @param cDataTagNamenull
to disable CDATA processing. Any other value
+ * @param cDataTagName null
to disable CDATA processing. Any other value
* to use that value as the JSONObject key name to process as CDATA.
* @deprecated This constructor has been deprecated in favor of using the new builder
* pattern for the configuration.
@@ -109,7 +115,7 @@ public class XMLParserConfiguration {
* Configure the parser to use custom settings.
* @param keepStrings true
to parse all values as string.
* false
to try and convert XML string values into a JSON value.
- * @param cDataTagNamenull
to disable CDATA processing. Any other value
+ * @param cDataTagName null
to disable CDATA processing. Any other value
* to use that value as the JSONObject key name to process as CDATA.
* @deprecated This constructor has been deprecated in favor of using the new builder
* pattern for the configuration.
@@ -117,7 +123,7 @@ public class XMLParserConfiguration {
*/
@Deprecated
public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName) {
- this.keepStrings = keepStrings;
+ super(keepStrings, DEFAULT_MAXIMUM_NESTING_DEPTH);
this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = false;
}
@@ -136,7 +142,7 @@ public class XMLParserConfiguration {
*/
@Deprecated
public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, final boolean convertNilAttributeToNull) {
- this.keepStrings = keepStrings;
+ super(keepStrings, DEFAULT_MAXIMUM_NESTING_DEPTH);
this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = convertNilAttributeToNull;
}
@@ -151,13 +157,19 @@ public class XMLParserConfiguration {
* false
to parse values with attribute xsi:nil="true" as {"xsi:nil":true}.
* @param xsiTypeMap new HashMap>()
to parse values with attribute
* xsi:type="integer" as integer, xsi:type="string" as string
+ * @param forceList new HashSet()
to parse the provided tags' values as arrays
+ * @param maxNestingDepth int
to limit the nesting depth
+ * @param closeEmptyTag boolean
to turn on explicit end tag for tag with empty value
*/
private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName,
- final boolean convertNilAttributeToNull, final Map> xsiTypeMap ) {
- this.keepStrings = keepStrings;
+ final boolean convertNilAttributeToNull, final Map> xsiTypeMap, final Set forceList,
+ final int maxNestingDepth, final boolean closeEmptyTag) {
+ super(keepStrings, maxNestingDepth);
this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = convertNilAttributeToNull;
this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap);
+ this.forceList = Collections.unmodifiableSet(forceList);
+ this.closeEmptyTag = closeEmptyTag;
}
/**
@@ -170,45 +182,40 @@ public class XMLParserConfiguration {
// item, a new map instance should be created and if possible each value in the
// map should be cloned as well. If the values of the map are known to also
// be immutable, then a shallow clone of the map is acceptable.
- return new XMLParserConfiguration(
+ final XMLParserConfiguration config = new XMLParserConfiguration(
this.keepStrings,
this.cDataTagName,
this.convertNilAttributeToNull,
- this.xsiTypeMap
+ this.xsiTypeMap,
+ this.forceList,
+ this.maxNestingDepth,
+ this.closeEmptyTag
);
- }
-
- /**
- * When parsing the XML into JSON, specifies if values should be kept as strings (true
), or if
- * they should try to be guessed into JSON values (numeric, boolean, string)
- *
- * @return The {@link #keepStrings} configuration value.
- */
- public boolean isKeepStrings() {
- return this.keepStrings;
+ config.shouldTrimWhiteSpace = this.shouldTrimWhiteSpace;
+ return config;
}
/**
* When parsing the XML into JSON, specifies if values should be kept as strings (true
), or if
* they should try to be guessed into JSON values (numeric, boolean, string)
- *
+ *
* @param newVal
- * new value to use for the {@link #keepStrings} configuration option.
- *
+ * new value to use for the keepStrings
configuration option.
+ *
* @return The existing configuration will not be modified. A new configuration is returned.
*/
+ @SuppressWarnings("unchecked")
+ @Override
public XMLParserConfiguration withKeepStrings(final boolean newVal) {
- XMLParserConfiguration newConfig = this.clone();
- newConfig.keepStrings = newVal;
- return newConfig;
+ return super.withKeepStrings(newVal);
}
/**
* The name of the key in a JSON Object that indicates a CDATA section. Historically this has
* been the value "content" but can be changed. Use null
to indicate no CDATA
* processing.
- *
- * @return The {@link #cDataTagName} configuration value.
+ *
+ * @return The cDataTagName
configuration value.
*/
public String getcDataTagName() {
return this.cDataTagName;
@@ -218,10 +225,10 @@ public class XMLParserConfiguration {
* The name of the key in a JSON Object that indicates a CDATA section. Historically this has
* been the value "content" but can be changed. Use null
to indicate no CDATA
* processing.
- *
+ *
* @param newVal
- * new value to use for the {@link #cDataTagName} configuration option.
- *
+ * new value to use for the cDataTagName
configuration option.
+ *
* @return The existing configuration will not be modified. A new configuration is returned.
*/
public XMLParserConfiguration withcDataTagName(final String newVal) {
@@ -234,8 +241,8 @@ public class XMLParserConfiguration {
* When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
* should be kept as attribute(false
), or they should be converted to
* null
(true
)
- *
- * @return The {@link #convertNilAttributeToNull} configuration value.
+ *
+ * @return The convertNilAttributeToNull
configuration value.
*/
public boolean isConvertNilAttributeToNull() {
return this.convertNilAttributeToNull;
@@ -245,10 +252,10 @@ public class XMLParserConfiguration {
* When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
* should be kept as attribute(false
), or they should be converted to
* null
(true
)
- *
+ *
* @param newVal
- * new value to use for the {@link #convertNilAttributeToNull} configuration option.
- *
+ * new value to use for the convertNilAttributeToNull
configuration option.
+ *
* @return The existing configuration will not be modified. A new configuration is returned.
*/
public XMLParserConfiguration withConvertNilAttributeToNull(final boolean newVal) {
@@ -262,7 +269,7 @@ public class XMLParserConfiguration {
* will be converted to target type defined to client in this configuration
* {@code Map>} to parse values with attribute
* xsi:type="integer" as integer, xsi:type="string" as string
- * @return {@link #xsiTypeMap} unmodifiable configuration map.
+ * @return xsiTypeMap
unmodifiable configuration map.
*/
public Map> getXsiTypeMap() {
return this.xsiTypeMap;
@@ -283,4 +290,83 @@ public class XMLParserConfiguration {
newConfig.xsiTypeMap = Collections.unmodifiableMap(cloneXsiTypeMap);
return newConfig;
}
+
+ /**
+ * When parsing the XML into JSON, specifies that tags that will be converted to arrays
+ * in this configuration {@code Set} to parse the provided tags' values as arrays
+ * @return forceList
unmodifiable configuration set.
+ */
+ public Set getForceList() {
+ return this.forceList;
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies that tags that will be converted to arrays
+ * in this configuration {@code Set} to parse the provided tags' values as arrays
+ * @param forceList {@code new HashSet()} to parse the provided tags' values as arrays
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public XMLParserConfiguration withForceList(final Set forceList) {
+ XMLParserConfiguration newConfig = this.clone();
+ Set cloneForceList = new HashSet(forceList);
+ newConfig.forceList = Collections.unmodifiableSet(cloneForceList);
+ return newConfig;
+ }
+
+ /**
+ * Defines the maximum nesting depth that the parser will descend before throwing an exception
+ * when parsing the XML into JSON. The default max nesting depth is 512, which means the parser
+ * will throw a JsonException if the maximum depth is reached.
+ * Using any negative value as a parameter is equivalent to setting no limit to the nesting depth,
+ * which means the parses will go as deep as the maximum call stack size allows.
+ * @param maxNestingDepth the maximum nesting depth allowed to the XML parser
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public XMLParserConfiguration withMaxNestingDepth(int maxNestingDepth) {
+ return super.withMaxNestingDepth(maxNestingDepth);
+ }
+
+ /**
+ * To enable explicit end tag with empty value.
+ * @param closeEmptyTag new value for the closeEmptyTag property
+ * @return same instance of configuration with empty tag config updated
+ */
+ public XMLParserConfiguration withCloseEmptyTag(boolean closeEmptyTag){
+ XMLParserConfiguration clonedConfiguration = this.clone();
+ clonedConfiguration.closeEmptyTag = closeEmptyTag;
+ return clonedConfiguration;
+ }
+
+ /**
+ * Sets whether whitespace should be trimmed inside of tags. *NOTE* Do not use this if
+ * you expect your XML tags to have names that are the same as cDataTagName as this is unsupported.
+ * cDataTagName should be set to a distinct value in these cases.
+ * @param shouldTrimWhiteSpace boolean to set trimming on or off. Off is default.
+ * @return same instance of configuration with empty tag config updated
+ */
+ public XMLParserConfiguration withShouldTrimWhitespace(boolean shouldTrimWhiteSpace){
+ XMLParserConfiguration clonedConfiguration = this.clone();
+ clonedConfiguration.shouldTrimWhiteSpace = shouldTrimWhiteSpace;
+ return clonedConfiguration;
+ }
+
+ /**
+ * Checks if the parser should automatically close empty XML tags.
+ *
+ * @return {@code true} if empty XML tags should be automatically closed, {@code false} otherwise.
+ */
+ public boolean isCloseEmptyTag() {
+ return this.closeEmptyTag;
+ }
+
+ /**
+ * Checks if the parser should trim white spaces from XML content.
+ *
+ * @return {@code true} if white spaces should be trimmed, {@code false} otherwise.
+ */
+ public boolean shouldTrimWhiteSpace() {
+ return this.shouldTrimWhiteSpace;
+ }
}
diff --git a/src/main/java/org/json/XMLTokener.java b/src/main/java/org/json/XMLTokener.java
index 3bbd382..bc18b31 100644
--- a/src/main/java/org/json/XMLTokener.java
+++ b/src/main/java/org/json/XMLTokener.java
@@ -1,27 +1,7 @@
package org.json;
/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import java.io.Reader;
@@ -40,6 +20,8 @@ public class XMLTokener extends JSONTokener {
*/
public static final java.util.HashMap entity;
+ private XMLParserConfiguration configuration = XMLParserConfiguration.ORIGINAL;
+
static {
entity = new java.util.HashMap(8);
entity.put("amp", XML.AMP);
@@ -65,6 +47,16 @@ public class XMLTokener extends JSONTokener {
super(s);
}
+ /**
+ * Construct an XMLTokener from a Reader and an XMLParserConfiguration.
+ * @param r A source reader.
+ * @param configuration the configuration that can be used to set certain flags
+ */
+ public XMLTokener(Reader r, XMLParserConfiguration configuration) {
+ super(r);
+ this.configuration = configuration;
+ }
+
/**
* Get the text in the CDATA block.
* @return The string up to the ]]>
.
@@ -103,7 +95,7 @@ public class XMLTokener extends JSONTokener {
StringBuilder sb;
do {
c = next();
- } while (Character.isWhitespace(c));
+ } while (Character.isWhitespace(c) && configuration.shouldTrimWhiteSpace());
if (c == 0) {
return null;
}
@@ -117,7 +109,9 @@ public class XMLTokener extends JSONTokener {
}
if (c == '<') {
back();
- return sb.toString().trim();
+ if (configuration.shouldTrimWhiteSpace()) {
+ return sb.toString().trim();
+ } else return sb.toString();
}
if (c == '&') {
sb.append(nextEntity(c));
diff --git a/src/main/java/org/json/XMLXsiTypeConverter.java b/src/main/java/org/json/XMLXsiTypeConverter.java
index 0f8a8c3..ea6739d 100644
--- a/src/main/java/org/json/XMLXsiTypeConverter.java
+++ b/src/main/java/org/json/XMLXsiTypeConverter.java
@@ -1,26 +1,6 @@
package org.json;
/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
/**
@@ -62,5 +42,12 @@ SOFTWARE.
* @param return type of convert method
*/
public interface XMLXsiTypeConverter {
+
+ /**
+ * Converts an XML xsi:type attribute value to the specified type {@code T}.
+ *
+ * @param value The string representation of the XML xsi:type attribute value to be converted.
+ * @return An object of type {@code T} representing the converted value.
+ */
T convert(String value);
}
diff --git a/src/test/java/org/json/junit/CDLTest.java b/src/test/java/org/json/junit/CDLTest.java
index 48586b7..cc3da29 100644
--- a/src/test/java/org/json/junit/CDLTest.java
+++ b/src/test/java/org/json/junit/CDLTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
@@ -44,14 +24,13 @@ public class CDLTest {
* String of lines where the column names are in the first row,
* and all subsequent rows are values. All keys and values should be legal.
*/
- String lines = new String(
- "Col 1, Col 2, \tCol 3, Col 4, Col 5, Col 6, Col 7\n" +
- "val1, val2, val3, val4, val5, val6, val7\n" +
- "1, 2, 3, 4\t, 5, 6, 7\n" +
- "true, false, true, true, false, false, false\n" +
- "0.23, 57.42, 5e27, -234.879, 2.34e5, 0.0, 9e-3\n" +
- "\"va\tl1\", \"v\bal2\", \"val3\", \"val\f4\", \"val5\", va\'l6, val7\n"
- );
+ private static final String LINES = "Col 1, Col 2, \tCol 3, Col 4, Col 5, Col 6, Col 7\n" +
+ "val1, val2, val3, val4, val5, val6, val7\n" +
+ "1, 2, 3, 4\t, 5, 6, 7\n" +
+ "true, false, true, true, false, false, false\n" +
+ "0.23, 57.42, 5e27, -234.879, 2.34e5, 0.0, 9e-3\n" +
+ "\"va\tl1\", \"v\bal2\", \"val3\", \"val\f4\", \"val5\", va'l6, val7\n";
+
/**
* CDL.toJSONArray() adds all values as strings, with no filtering or
@@ -59,12 +38,11 @@ public class CDLTest {
* values all must be quoted in the cases where the JSONObject parsing
* might normally convert the value into a non-string.
*/
- String expectedLines = new String(
- "[{Col 1:val1, Col 2:val2, Col 3:val3, Col 4:val4, Col 5:val5, Col 6:val6, Col 7:val7}, "+
- "{Col 1:\"1\", Col 2:\"2\", Col 3:\"3\", Col 4:\"4\", Col 5:\"5\", Col 6:\"6\", Col 7:\"7\"}, "+
- "{Col 1:\"true\", Col 2:\"false\", Col 3:\"true\", Col 4:\"true\", Col 5:\"false\", Col 6:\"false\", Col 7:\"false\"}, "+
- "{Col 1:\"0.23\", Col 2:\"57.42\", Col 3:\"5e27\", Col 4:\"-234.879\", Col 5:\"2.34e5\", Col 6:\"0.0\", Col 7:\"9e-3\"}, "+
- "{Col 1:\"va\tl1\", Col 2:\"v\bal2\", Col 3:val3, Col 4:\"val\f4\", Col 5:val5, Col 6:va\'l6, Col 7:val7}]");
+ private static final String EXPECTED_LINES = "[{Col 1:val1, Col 2:val2, Col 3:val3, Col 4:val4, Col 5:val5, Col 6:val6, Col 7:val7}, " +
+ "{Col 1:\"1\", Col 2:\"2\", Col 3:\"3\", Col 4:\"4\", Col 5:\"5\", Col 6:\"6\", Col 7:\"7\"}, " +
+ "{Col 1:\"true\", Col 2:\"false\", Col 3:\"true\", Col 4:\"true\", Col 5:\"false\", Col 6:\"false\", Col 7:\"false\"}, " +
+ "{Col 1:\"0.23\", Col 2:\"57.42\", Col 3:\"5e27\", Col 4:\"-234.879\", Col 5:\"2.34e5\", Col 6:\"0.0\", Col 7:\"9e-3\"}, " +
+ "{Col 1:\"va\tl1\", Col 2:\"v\bal2\", Col 3:val3, Col 4:\"val\f4\", Col 5:val5, Col 6:va'l6, Col 7:val7}]";
/**
* Attempts to create a JSONArray from a null string.
@@ -190,7 +168,7 @@ public class CDLTest {
CDL.toJSONArray(badLine);
fail("Expecting an exception");
} catch (JSONException e) {
- System.out.println("Message" + e.getMessage());
+ //System.out.println("Message" + e.getMessage());
assertEquals("Expecting an exception message",
"Bad character 'V' (86). at 20 [character 9 line 2]",
e.getMessage());
@@ -214,8 +192,7 @@ public class CDLTest {
public void emptyString() {
String emptyStr = "";
JSONArray jsonArray = CDL.toJSONArray(emptyStr);
- assertTrue("CDL should return null when the input string is empty",
- jsonArray == null);
+ assertNull("CDL should return null when the input string is empty", jsonArray);
}
/**
@@ -274,7 +251,7 @@ public class CDLTest {
jsonObject.put("Col \r1", "V1");
// \r will be filtered from value
jsonObject.put("Col 2", "V2\r");
- assertTrue("expected length should be 1",jsonArray.length() == 1);
+ assertEquals("expected length should be 1", 1, jsonArray.length());
String cdlStr = CDL.toString(jsonArray);
jsonObject = jsonArray.getJSONObject(0);
assertTrue(cdlStr.contains("\"Col 1\""));
@@ -288,8 +265,15 @@ public class CDLTest {
*/
@Test
public void textToJSONArray() {
- JSONArray jsonArray = CDL.toJSONArray(this.lines);
- JSONArray expectedJsonArray = new JSONArray(this.expectedLines);
+ JSONArray jsonArray = CDL.toJSONArray(LINES);
+ JSONArray expectedJsonArray = new JSONArray(EXPECTED_LINES);
+ Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
+ }
+ @Test
+ public void textToJSONArrayPipeDelimited() {
+ char delimiter = '|';
+ JSONArray jsonArray = CDL.toJSONArray(LINES.replaceAll(",", String.valueOf(delimiter)), delimiter);
+ JSONArray expectedJsonArray = new JSONArray(EXPECTED_LINES);
Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
}
@@ -313,10 +297,24 @@ public class CDLTest {
*/
@Test
public void textToJSONArrayAndBackToString() {
- JSONArray jsonArray = CDL.toJSONArray(this.lines);
+ JSONArray jsonArray = CDL.toJSONArray(LINES);
String jsonStr = CDL.toString(jsonArray);
JSONArray finalJsonArray = CDL.toJSONArray(jsonStr);
- JSONArray expectedJsonArray = new JSONArray(this.expectedLines);
+ JSONArray expectedJsonArray = new JSONArray(EXPECTED_LINES);
+ Util.compareActualVsExpectedJsonArrays(finalJsonArray, expectedJsonArray);
+ }
+
+ /**
+ * Create a JSONArray from a string of lines,
+ * then convert to string and then back to JSONArray
+ * with a custom delimiter
+ */
+ @Test
+ public void textToJSONArrayAndBackToStringCustomDelimiter() {
+ JSONArray jsonArray = CDL.toJSONArray(LINES, ',');
+ String jsonStr = CDL.toString(jsonArray, ';');
+ JSONArray finalJsonArray = CDL.toJSONArray(jsonStr, ';');
+ JSONArray expectedJsonArray = new JSONArray(EXPECTED_LINES);
Util.compareActualVsExpectedJsonArrays(finalJsonArray, expectedJsonArray);
}
diff --git a/src/test/java/org/json/junit/CookieListTest.java b/src/test/java/org/json/junit/CookieListTest.java
index c3f647f..0af9640 100644
--- a/src/test/java/org/json/junit/CookieListTest.java
+++ b/src/test/java/org/json/junit/CookieListTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
diff --git a/src/test/java/org/json/junit/CookieTest.java b/src/test/java/org/json/junit/CookieTest.java
index 7e7b62b..edd8a7e 100644
--- a/src/test/java/org/json/junit/CookieTest.java
+++ b/src/test/java/org/json/junit/CookieTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
diff --git a/src/test/java/org/json/junit/EnumTest.java b/src/test/java/org/json/junit/EnumTest.java
index ed2c87a..1496a63 100644
--- a/src/test/java/org/json/junit/EnumTest.java
+++ b/src/test/java/org/json/junit/EnumTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.assertEquals;
@@ -93,7 +73,7 @@ public class EnumTest {
/**
* To serialize an enum by its set of allowed values, use getNames()
- * and the the JSONObject Object with names constructor.
+ * and the JSONObject Object with names constructor.
*/
@Test
public void jsonObjectFromEnumWithNames() {
diff --git a/src/test/java/org/json/junit/HTTPTest.java b/src/test/java/org/json/junit/HTTPTest.java
index 8182b60..703d5ad 100644
--- a/src/test/java/org/json/junit/HTTPTest.java
+++ b/src/test/java/org/json/junit/HTTPTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
diff --git a/src/test/java/org/json/junit/JSONArrayTest.java b/src/test/java/org/json/junit/JSONArrayTest.java
index 1c04251..fcaa8ce 100644
--- a/src/test/java/org/json/junit/JSONArrayTest.java
+++ b/src/test/java/org/json/junit/JSONArrayTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.assertEquals;
@@ -29,8 +9,10 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.IOException;
+import java.io.InputStream;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
@@ -46,7 +28,13 @@ import java.util.Map;
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;
+import org.json.ParserConfiguration;
+import org.json.junit.data.MyJsonString;
+import org.junit.Ignore;
import org.junit.Test;
import com.jayway.jsonpath.Configuration;
@@ -87,6 +75,7 @@ public class JSONArrayTest {
@Test
public void verifySimilar() {
final String string1 = "HasSameRef";
+ final String string2 = "HasDifferentRef";
JSONArray obj1 = new JSONArray()
.put("abc")
.put(string1)
@@ -101,10 +90,20 @@ public class JSONArrayTest {
.put("abc")
.put(new String(string1))
.put(2);
+
+ JSONArray obj4 = new JSONArray()
+ .put("abc")
+ .put(2.0)
+ .put(new String(string1));
+
+ JSONArray obj5 = new JSONArray()
+ .put("abc")
+ .put(2.0)
+ .put(new String(string2));
- assertFalse("Should eval to false", obj1.similar(obj2));
-
- assertTrue("Should eval to true", obj1.similar(obj3));
+ assertFalse("obj1-obj2 Should eval to false", obj1.similar(obj2));
+ assertTrue("obj1-obj3 Should eval to true", obj1.similar(obj3));
+ assertFalse("obj4-obj5 Should eval to false", obj4.similar(obj5));
}
/**
@@ -122,7 +121,7 @@ public class JSONArrayTest {
* Expects a JSONException.
*/
@Test
- public void emptStr() {
+ public void emptyStr() {
String str = "";
try {
assertNull("Should throw an exception", new JSONArray(str));
@@ -223,6 +222,10 @@ public class JSONArrayTest {
assertTrue(
"The RAW Collection should give me the same as the Typed Collection",
expected.similar(jaObj));
+ Util.checkJSONArrayMaps(expected);
+ Util.checkJSONArrayMaps(jaObj);
+ Util.checkJSONArrayMaps(jaRaw);
+ Util.checkJSONArrayMaps(jaInt);
}
/**
@@ -261,6 +264,7 @@ public class JSONArrayTest {
myList.get(i),
jsonArray.getString(myInts.length + i));
}
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -294,6 +298,9 @@ public class JSONArrayTest {
assertTrue(
"The RAW Collection should give me the same as the Typed Collection",
expected.similar(jaInt));
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ jaRaw, jaObj, jaInt
+ )));
}
@@ -337,6 +344,9 @@ public class JSONArrayTest {
assertTrue(
"The RAW Collection should give me the same as the Typed Collection",
expected.similar(jaObjObj));
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ expected, jaRaw, jaStrObj, jaStrInt, jaObjObj
+ )));
}
/**
@@ -361,14 +371,16 @@ public class JSONArrayTest {
"hello".equals(jsonArray.getString(4)));
// doubles
assertTrue("Array double",
- new Double(23.45e-4).equals(jsonArray.getDouble(5)));
+ Double.valueOf(23.45e-4).equals(jsonArray.getDouble(5)));
assertTrue("Array string double",
- new Double(23.45).equals(jsonArray.getDouble(6)));
+ Double.valueOf(23.45).equals(jsonArray.getDouble(6)));
+ assertTrue("Array double can be float",
+ Float.valueOf(23.45e-4f).equals(jsonArray.getFloat(5)));
// ints
assertTrue("Array value int",
- new Integer(42).equals(jsonArray.getInt(7)));
+ Integer.valueOf(42).equals(jsonArray.getInt(7)));
assertTrue("Array value string int",
- new Integer(43).equals(jsonArray.getInt(8)));
+ Integer.valueOf(43).equals(jsonArray.getInt(8)));
// nested objects
JSONArray nestedJsonArray = jsonArray.getJSONArray(9);
assertTrue("Array value JSONArray", nestedJsonArray != null);
@@ -376,11 +388,12 @@ public class JSONArrayTest {
assertTrue("Array value JSONObject", nestedJsonObject != null);
// longs
assertTrue("Array value long",
- new Long(0).equals(jsonArray.getLong(11)));
+ Long.valueOf(0).equals(jsonArray.getLong(11)));
assertTrue("Array value string long",
- new Long(-1).equals(jsonArray.getLong(12)));
+ Long.valueOf(-1).equals(jsonArray.getLong(12)));
assertTrue("Array value null", jsonArray.isNull(-1));
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -396,7 +409,7 @@ public class JSONArrayTest {
assertTrue("expected getBoolean to fail", false);
} catch (JSONException e) {
assertEquals("Expected an exception message",
- "JSONArray[4] is not a boolean.",e.getMessage());
+ "JSONArray[4] is not a boolean (class java.lang.String : hello).",e.getMessage());
}
try {
jsonArray.get(-1);
@@ -410,43 +423,58 @@ public class JSONArrayTest {
assertTrue("expected getDouble to fail", false);
} catch (JSONException e) {
assertEquals("Expected an exception message",
- "JSONArray[4] is not a double.",e.getMessage());
+ "JSONArray[4] is not a double (class java.lang.String : hello).",e.getMessage());
}
try {
jsonArray.getInt(4);
assertTrue("expected getInt to fail", false);
} catch (JSONException e) {
assertEquals("Expected an exception message",
- "JSONArray[4] is not a int.",e.getMessage());
+ "JSONArray[4] is not a int (class java.lang.String : hello).",e.getMessage());
}
try {
jsonArray.getJSONArray(4);
assertTrue("expected getJSONArray to fail", false);
} catch (JSONException e) {
assertEquals("Expected an exception message",
- "JSONArray[4] is not a JSONArray.",e.getMessage());
+ "JSONArray[4] is not a JSONArray (class java.lang.String : hello).",e.getMessage());
}
try {
jsonArray.getJSONObject(4);
assertTrue("expected getJSONObject to fail", false);
} catch (JSONException e) {
assertEquals("Expected an exception message",
- "JSONArray[4] is not a JSONObject.",e.getMessage());
+ "JSONArray[4] is not a JSONObject (class java.lang.String : hello).",e.getMessage());
}
try {
jsonArray.getLong(4);
assertTrue("expected getLong to fail", false);
} catch (JSONException e) {
assertEquals("Expected an exception message",
- "JSONArray[4] is not a long.",e.getMessage());
+ "JSONArray[4] is not a long (class java.lang.String : hello).",e.getMessage());
}
try {
jsonArray.getString(5);
assertTrue("expected getString to fail", false);
} catch (JSONException e) {
assertEquals("Expected an exception message",
- "JSONArray[5] is not a String.",e.getMessage());
+ "JSONArray[5] is not a String (class java.math.BigDecimal : 0.002345).",e.getMessage());
}
+ Util.checkJSONArrayMaps(jsonArray);
+ }
+
+ /**
+ * The JSON parser is permissive of unambiguous unquoted keys and values.
+ * Such JSON text should be allowed, even if it does not strictly conform
+ * to the spec. However, after being parsed, toString() should emit strictly
+ * conforming JSON text.
+ */
+ @Test
+ public void unquotedText() {
+ String str = "[value1, something!, (parens), foo@bar.com, 23, 23+45]";
+ JSONArray jsonArray = new JSONArray(str);
+ List expected = Arrays.asList("value1", "something!", "(parens)", "foo@bar.com", 23, "23+45");
+ assertEquals(expected, jsonArray.toList());
}
/**
@@ -483,6 +511,7 @@ public class JSONArrayTest {
assertTrue("expected value4", "value4".equals(jsonArray.query("/10/key4")));
assertTrue("expected 0", Integer.valueOf(0).equals(jsonArray.query("/11")));
assertTrue("expected \"-1\"", "-1".equals(jsonArray.query("/12")));
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -496,6 +525,9 @@ public class JSONArrayTest {
assertTrue("expected JSONArray length 13. instead found "+jsonArray.length(), jsonArray.length() == 13);
JSONArray nestedJsonArray = jsonArray.getJSONArray(9);
assertTrue("expected JSONArray length 1", nestedJsonArray.length() == 1);
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ jsonArray, nestedJsonArray
+ )));
}
/**
@@ -522,43 +554,75 @@ public class JSONArrayTest {
assertTrue("Array opt boolean implicit default",
Boolean.FALSE == jsonArray.optBoolean(-1));
+ assertTrue("Array opt boolean object",
+ Boolean.TRUE.equals(jsonArray.optBooleanObject(0)));
+ assertTrue("Array opt boolean object default",
+ Boolean.FALSE.equals(jsonArray.optBooleanObject(-1, Boolean.FALSE)));
+ assertTrue("Array opt boolean object implicit default",
+ Boolean.FALSE.equals(jsonArray.optBooleanObject(-1)));
+
assertTrue("Array opt double",
- new Double(23.45e-4).equals(jsonArray.optDouble(5)));
+ Double.valueOf(23.45e-4).equals(jsonArray.optDouble(5)));
assertTrue("Array opt double default",
- new Double(1).equals(jsonArray.optDouble(0, 1)));
+ Double.valueOf(1).equals(jsonArray.optDouble(0, 1)));
assertTrue("Array opt double default implicit",
- new Double(jsonArray.optDouble(99)).isNaN());
+ Double.valueOf(jsonArray.optDouble(99)).isNaN());
+
+ assertTrue("Array opt double object",
+ Double.valueOf(23.45e-4).equals(jsonArray.optDoubleObject(5)));
+ assertTrue("Array opt double object default",
+ Double.valueOf(1).equals(jsonArray.optDoubleObject(0, 1D)));
+ assertTrue("Array opt double object default implicit",
+ jsonArray.optDoubleObject(99).isNaN());
assertTrue("Array opt float",
- new Float(23.45e-4).equals(jsonArray.optFloat(5)));
+ Float.valueOf(Double.valueOf(23.45e-4).floatValue()).equals(jsonArray.optFloat(5)));
assertTrue("Array opt float default",
- new Float(1).equals(jsonArray.optFloat(0, 1)));
+ Float.valueOf(1).equals(jsonArray.optFloat(0, 1)));
assertTrue("Array opt float default implicit",
- new Float(jsonArray.optFloat(99)).isNaN());
+ Float.valueOf(jsonArray.optFloat(99)).isNaN());
+
+ assertTrue("Array opt float object",
+ Float.valueOf(23.45e-4F).equals(jsonArray.optFloatObject(5)));
+ assertTrue("Array opt float object default",
+ Float.valueOf(1).equals(jsonArray.optFloatObject(0, 1F)));
+ assertTrue("Array opt float object default implicit",
+ jsonArray.optFloatObject(99).isNaN());
assertTrue("Array opt Number",
BigDecimal.valueOf(23.45e-4).equals(jsonArray.optNumber(5)));
assertTrue("Array opt Number default",
- new Double(1).equals(jsonArray.optNumber(0, 1d)));
+ Double.valueOf(1).equals(jsonArray.optNumber(0, 1d)));
assertTrue("Array opt Number default implicit",
- new Double(jsonArray.optNumber(99,Double.NaN).doubleValue()).isNaN());
+ Double.valueOf(jsonArray.optNumber(99,Double.NaN).doubleValue()).isNaN());
assertTrue("Array opt int",
- new Integer(42).equals(jsonArray.optInt(7)));
+ Integer.valueOf(42).equals(jsonArray.optInt(7)));
assertTrue("Array opt int default",
- new Integer(-1).equals(jsonArray.optInt(0, -1)));
+ Integer.valueOf(-1).equals(jsonArray.optInt(0, -1)));
assertTrue("Array opt int default implicit",
0 == jsonArray.optInt(0));
+ assertTrue("Array opt int object",
+ Integer.valueOf(42).equals(jsonArray.optIntegerObject(7)));
+ assertTrue("Array opt int object default",
+ Integer.valueOf(-1).equals(jsonArray.optIntegerObject(0, -1)));
+ assertTrue("Array opt int object default implicit",
+ Integer.valueOf(0).equals(jsonArray.optIntegerObject(0)));
+
JSONArray nestedJsonArray = jsonArray.optJSONArray(9);
assertTrue("Array opt JSONArray", nestedJsonArray != null);
- assertTrue("Array opt JSONArray default",
+ assertTrue("Array opt JSONArray null",
null == jsonArray.optJSONArray(99));
+ assertTrue("Array opt JSONArray default",
+ "value".equals(jsonArray.optJSONArray(99, new JSONArray("[\"value\"]")).getString(0)));
JSONObject nestedJsonObject = jsonArray.optJSONObject(10);
assertTrue("Array opt JSONObject", nestedJsonObject != null);
- assertTrue("Array opt JSONObject default",
+ assertTrue("Array opt JSONObject null",
null == jsonArray.optJSONObject(99));
+ assertTrue("Array opt JSONObject default",
+ "value".equals(jsonArray.optJSONObject(99, new JSONObject("{\"key\":\"value\"}")).getString("key")));
assertTrue("Array opt long",
0 == jsonArray.optLong(11));
@@ -567,10 +631,21 @@ public class JSONArrayTest {
assertTrue("Array opt long default implicit",
0 == jsonArray.optLong(-1));
+ assertTrue("Array opt long object",
+ Long.valueOf(0).equals(jsonArray.optLongObject(11)));
+ assertTrue("Array opt long object default",
+ Long.valueOf(-2).equals(jsonArray.optLongObject(-1, -2L)));
+ assertTrue("Array opt long object default implicit",
+ Long.valueOf(0).equals(jsonArray.optLongObject(-1)));
+
assertTrue("Array opt string",
"hello".equals(jsonArray.optString(4)));
assertTrue("Array opt string default implicit",
"".equals(jsonArray.optString(-1)));
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ jsonArray, nestedJsonArray
+ )));
+ Util.checkJSONObjectMaps(nestedJsonObject);
}
/**
@@ -580,12 +655,19 @@ public class JSONArrayTest {
public void optStringConversion(){
JSONArray ja = new JSONArray("[\"123\",\"true\",\"false\"]");
assertTrue("unexpected optBoolean value",ja.optBoolean(1,false)==true);
+ assertTrue("unexpected optBooleanObject value",Boolean.valueOf(true).equals(ja.optBooleanObject(1,false)));
assertTrue("unexpected optBoolean value",ja.optBoolean(2,true)==false);
+ assertTrue("unexpected optBooleanObject value",Boolean.valueOf(false).equals(ja.optBooleanObject(2,true)));
assertTrue("unexpected optInt value",ja.optInt(0,0)==123);
+ assertTrue("unexpected optIntegerObject value",Integer.valueOf(123).equals(ja.optIntegerObject(0,0)));
assertTrue("unexpected optLong value",ja.optLong(0,0)==123);
+ assertTrue("unexpected optLongObject value",Long.valueOf(123).equals(ja.optLongObject(0,0L)));
assertTrue("unexpected optDouble value",ja.optDouble(0,0.0)==123.0);
+ assertTrue("unexpected optDoubleObject value",Double.valueOf(123.0).equals(ja.optDoubleObject(0,0.0)));
assertTrue("unexpected optBigInteger value",ja.optBigInteger(0,BigInteger.ZERO).compareTo(new BigInteger("123"))==0);
- assertTrue("unexpected optBigDecimal value",ja.optBigDecimal(0,BigDecimal.ZERO).compareTo(new BigDecimal("123"))==0); }
+ assertTrue("unexpected optBigDecimal value",ja.optBigDecimal(0,BigDecimal.ZERO).compareTo(new BigDecimal("123"))==0);
+ Util.checkJSONArrayMaps(ja);
+ }
/**
* Exercise the JSONArray.put(value) method with various parameters
@@ -661,6 +743,8 @@ public class JSONArrayTest {
assertTrue("expected 2 items in [9]", ((List>)(JsonPath.read(doc, "$[9]"))).size() == 2);
assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/9/0")));
assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/9/1")));
+ Util.checkJSONArrayMaps(jsonArray);
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -740,6 +824,8 @@ public class JSONArrayTest {
assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/9/1")));
assertTrue("expected 1 item in [10]", ((Map,?>)(JsonPath.read(doc, "$[10]"))).size() == 1);
assertTrue("expected v1", "v1".equals(jsonArray.query("/10/k1")));
+ Util.checkJSONObjectMaps(jsonObject);
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -756,6 +842,7 @@ public class JSONArrayTest {
jsonArray.remove(0);
assertTrue("array should be empty", null == jsonArray.remove(5));
assertTrue("jsonArray should be empty", jsonArray.isEmpty());
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -795,6 +882,12 @@ public class JSONArrayTest {
otherJsonArray.put("world");
assertTrue("arrays values differ",
!jsonArray.similar(otherJsonArray));
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ jsonArray, otherJsonArray
+ )));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObject, otherJsonObject
+ )));
}
/**
@@ -878,6 +971,7 @@ public class JSONArrayTest {
for (String s : jsonArray4Strs) {
list.contains(s);
}
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -889,6 +983,9 @@ public class JSONArrayTest {
JSONArray jsonArray = new JSONArray();
assertTrue("toJSONObject should return null",
null == jsonArray.toJSONObject(names));
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ names, jsonArray
+ )));
}
/**
@@ -910,6 +1007,7 @@ public class JSONArrayTest {
assertTrue("expected 5", Integer.valueOf(5).equals(jsonArray.query("/4")));
assertTrue("expected 6", Integer.valueOf(6).equals(jsonArray.query("/5")));
assertTrue("expected 7", Integer.valueOf(7).equals(jsonArray.query("/6")));
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -934,12 +1032,12 @@ public class JSONArrayTest {
assertTrue("Array double [23.45e-4]",
new BigDecimal("0.002345").equals(it.next()));
assertTrue("Array string double",
- new Double(23.45).equals(Double.parseDouble((String)it.next())));
+ Double.valueOf(23.45).equals(Double.parseDouble((String)it.next())));
assertTrue("Array value int",
- new Integer(42).equals(it.next()));
+ Integer.valueOf(42).equals(it.next()));
assertTrue("Array value string int",
- new Integer(43).equals(Integer.parseInt((String)it.next())));
+ Integer.valueOf(43).equals(Integer.parseInt((String)it.next())));
JSONArray nestedJsonArray = (JSONArray)it.next();
assertTrue("Array value JSONArray", nestedJsonArray != null);
@@ -948,10 +1046,14 @@ public class JSONArrayTest {
assertTrue("Array value JSONObject", nestedJsonObject != null);
assertTrue("Array value long",
- new Long(0).equals(((Number) it.next()).longValue()));
+ Long.valueOf(0).equals(((Number) it.next()).longValue()));
assertTrue("Array value string long",
- new Long(-1).equals(Long.parseLong((String) it.next())));
+ Long.valueOf(-1).equals(Long.parseLong((String) it.next())));
assertTrue("should be at end of array", !it.hasNext());
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ jsonArray, nestedJsonArray
+ )));
+ Util.checkJSONObjectMaps(nestedJsonObject);
}
@Test(expected = JSONPointerException.class)
@@ -994,6 +1096,7 @@ public class JSONArrayTest {
} finally {
stringWriter.close();
}
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -1053,9 +1156,11 @@ public class JSONArrayTest {
&& actualStr.contains("\"key2\": false")
&& actualStr.contains("\"key3\": 3.14")
);
+ Util.checkJSONArrayMaps(finalArray);
} finally {
stringWriter.close();
}
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -1166,6 +1271,7 @@ public class JSONArrayTest {
// assert that the new list is mutable
assertTrue("Removing an entry should succeed", list.remove(2) != null);
assertTrue("List should have 2 elements", list.size() == 2);
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -1174,13 +1280,13 @@ public class JSONArrayTest {
*/
@Test
public void testJSONArrayInt() {
- assertNotNull(new JSONArray(0));
- assertNotNull(new JSONArray(5));
- // Check Size -> Even though the capacity of the JSONArray can be specified using a positive
- // integer but the length of JSONArray always reflects upon the items added into it.
- assertEquals(0l, new JSONArray(10).length());
+ assertNotNull(new JSONArray(0));
+ assertNotNull(new JSONArray(5));
+ // Check Size -> Even though the capacity of the JSONArray can be specified using a positive
+ // integer but the length of JSONArray always reflects upon the items added into it.
+ // assertEquals(0l, new JSONArray(10).length());
try {
- assertNotNull("Should throw an exception", new JSONArray(-1));
+ assertNotNull("Should throw an exception", new JSONArray(-1));
} catch (JSONException e) {
assertEquals("Expected an exception message",
"JSONArray initial capacity cannot be negative.",
@@ -1207,8 +1313,8 @@ public class JSONArrayTest {
((Collection)o).add("test");
((Collection)o).add(false);
try {
- a = new JSONArray(o);
- assertNull("Should error", a);
+ JSONArray a0 = new JSONArray(o);
+ assertNull("Should error", a0);
} catch (JSONException ex) {
}
@@ -1216,10 +1322,11 @@ public class JSONArrayTest {
// this is required for backwards compatibility
o = a;
try {
- a = new JSONArray(o);
- assertNull("Should error", a);
+ JSONArray a1 = new JSONArray(o);
+ assertNull("Should error", a1);
} catch (JSONException ex) {
}
+ Util.checkJSONArrayMaps(a);
}
/**
@@ -1236,6 +1343,9 @@ public class JSONArrayTest {
for(int i = 0; i < a1.length(); i++) {
assertEquals("index " + i + " are equal", a1.get(i), a2.get(i));
}
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ a1, a2
+ )));
}
/**
@@ -1253,6 +1363,9 @@ public class JSONArrayTest {
for(int i = 0; i < a1.length(); i++) {
assertEquals("index " + i + " are equal", a1.get(i), a2.get(i));
}
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ a1, a2
+ )));
}
/**
@@ -1268,5 +1381,130 @@ public class JSONArrayTest {
jsonArray.clear(); //Clears the JSONArray
assertTrue("expected jsonArray.length() == 0", jsonArray.length() == 0); //Check if its length is 0
jsonArray.getInt(0); //Should throws org.json.JSONException: JSONArray[0] not found
+ Util.checkJSONArrayMaps(jsonArray);
}
+
+ /**
+ * Tests for stack overflow. See https://github.com/stleary/JSON-java/issues/654
+ */
+ @Ignore("This test relies on system constraints and may not always pass. See: https://github.com/stleary/JSON-java/issues/821")
+ @Test(expected = JSONException.class)
+ public void issue654StackOverflowInputWellFormed() {
+ //String input = new String(java.util.Base64.getDecoder().decode(base64Bytes));
+ final InputStream resourceAsStream = JSONArrayTest.class.getClassLoader().getResourceAsStream("Issue654WellFormedArray.json");
+ JSONTokener tokener = new JSONTokener(resourceAsStream);
+ JSONArray json_input = new JSONArray(tokener);
+ assertNotNull(json_input);
+ fail("Excepected Exception due to stack overflow.");
+ Util.checkJSONArrayMaps(json_input);
+ }
+
+ @Test
+ public void testIssue682SimilarityOfJSONString() {
+ JSONArray ja1 = new JSONArray()
+ .put(new MyJsonString())
+ .put(2);
+ JSONArray ja2 = new JSONArray()
+ .put(new MyJsonString())
+ .put(2);
+ assertTrue(ja1.similar(ja2));
+
+ JSONArray ja3 = new JSONArray()
+ .put(new JSONString() {
+ @Override
+ public String toJSONString() {
+ return "\"different value\"";
+ }
+ })
+ .put(2);
+ assertFalse(ja1.similar(ja3));
+ }
+
+ @Test(expected = JSONException.class)
+ public void testRecursiveDepth() {
+ HashMap map = new HashMap<>();
+ map.put("t", map);
+ new JSONArray().put(map);
+ }
+
+ @Test(expected = JSONException.class)
+ public void testRecursiveDepthAtPosition() {
+ HashMap map = new HashMap<>();
+ map.put("t", map);
+ new JSONArray().put(0, map);
+ }
+
+ @Test(expected = JSONException.class)
+ public void testRecursiveDepthArray() {
+ ArrayList array = new ArrayList<>();
+ array.add(array);
+ new JSONArray(array);
+ }
+
+ @Test
+ public void testRecursiveDepthAtPositionDefaultObject() {
+ HashMap map = JSONObjectTest.buildNestedMap(ParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
+ new JSONArray().put(0, map);
+ }
+
+ @Test
+ public void testRecursiveDepthAtPosition1000Object() {
+ HashMap map = JSONObjectTest.buildNestedMap(1000);
+ new JSONArray().put(0, map, new JSONParserConfiguration().withMaxNestingDepth(1000));
+ }
+
+ @Test(expected = JSONException.class)
+ public void testRecursiveDepthAtPosition1001Object() {
+ HashMap map = JSONObjectTest.buildNestedMap(1001);
+ new JSONArray().put(0, map);
+ }
+
+ @Test(expected = JSONException.class)
+ public void testRecursiveDepthArrayLimitedMaps() {
+ ArrayList array = new ArrayList<>();
+ array.add(array);
+ new JSONArray(array);
+ }
+
+ @Test
+ public void testRecursiveDepthArrayForDefaultLevels() {
+ ArrayList array = buildNestedArray(ParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
+ new JSONArray(array, new JSONParserConfiguration());
+ }
+
+ @Test
+ public void testRecursiveDepthArrayFor1000Levels() {
+ try {
+ ArrayList array = buildNestedArray(1000);
+ JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withMaxNestingDepth(1000);
+ new JSONArray(array, parserConfiguration);
+ } catch (StackOverflowError e) {
+ String javaVersion = System.getProperty("java.version");
+ if (javaVersion.startsWith("11.")) {
+ System.out.println(
+ "testRecursiveDepthArrayFor1000Levels() allowing intermittent stackoverflow, Java Version: "
+ + javaVersion);
+ } else {
+ String errorStr = "testRecursiveDepthArrayFor1000Levels() unexpected stackoverflow, Java Version: "
+ + javaVersion;
+ System.out.println(errorStr);
+ throw new RuntimeException(errorStr);
+ }
+ }
+ }
+
+ @Test(expected = JSONException.class)
+ public void testRecursiveDepthArrayFor1001Levels() {
+ ArrayList array = buildNestedArray(1001);
+ new JSONArray(array);
+ }
+
+ public static ArrayList buildNestedArray(int maxDepth) {
+ if (maxDepth <= 0) {
+ return new ArrayList<>();
+ }
+ ArrayList nestedArray = new ArrayList<>();
+ nestedArray.add(buildNestedArray(maxDepth - 1));
+ return nestedArray;
+ }
}
diff --git a/src/test/java/org/json/junit/JSONMLTest.java b/src/test/java/org/json/junit/JSONMLTest.java
index 8f3de42..154af64 100644
--- a/src/test/java/org/json/junit/JSONMLTest.java
+++ b/src/test/java/org/json/junit/JSONMLTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
@@ -31,19 +11,19 @@ import org.junit.Test;
/**
* Tests for org.json.JSONML.java
- *
+ *
* Certain inputs are expected to result in exceptions. These tests are
* executed first. JSONML provides an API to:
- * Convert an XML string into a JSONArray or a JSONObject.
+ * Convert an XML string into a JSONArray or a JSONObject.
* Convert a JSONArray or JSONObject into an XML string.
* Both fromstring and tostring operations operations should be symmetrical
- * within the limits of JSONML.
+ * within the limits of JSONML.
* It should be possible to perform the following operations, which should
* result in the original string being recovered, within the limits of the
* underlying classes:
* Convert a string -> JSONArray -> string -> JSONObject -> string
* Convert a string -> JSONObject -> string -> JSONArray -> string
- *
+ *
*/
public class JSONMLTest {
@@ -76,7 +56,7 @@ public class JSONMLTest {
/**
* Attempts to call JSONML.toString() with a null JSONArray.
- * Expects a NullPointerException.
+ * Expects a NullPointerException.
*/
@Test(expected=NullPointerException.class)
public void nullJSONXMLException() {
@@ -89,7 +69,7 @@ public class JSONMLTest {
/**
* Attempts to call JSONML.toString() with a null JSONArray.
- * Expects a JSONException.
+ * Expects a JSONException.
*/
@Test
public void emptyJSONXMLException() {
@@ -145,7 +125,7 @@ public class JSONMLTest {
"[\"addresses\","+
"{\"xsi:noNamespaceSchemaLocation\":\"test.xsd\","+
"\"xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"},"+
- // this array has no name
+ // this array has no name
"["+
"[\"name\"],"+
"[\"nocontent\"],"+
@@ -158,7 +138,7 @@ public class JSONMLTest {
assertTrue("Expecting an exception", false);
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONArray[0] is not a String.",
+ "JSONArray[0] is not a String (class org.json.JSONArray).",
e.getMessage());
}
}
@@ -200,7 +180,7 @@ public class JSONMLTest {
}
/**
- * Attempts to transform a malformed XML document
+ * Attempts to transform a malformed XML document
* (element tag has a frontslash) to a JSONArray.\
* Expects a JSONException
*/
@@ -211,7 +191,7 @@ public class JSONMLTest {
* In this case, the XML is invalid because the 'name' element
* contains an invalid frontslash.
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -236,7 +216,7 @@ public class JSONMLTest {
*/
@Test
public void invalidBangInTagException() {
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -266,7 +246,7 @@ public class JSONMLTest {
* In this case, the XML is invalid because an element
* starts with '!' and has no closing tag
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -296,7 +276,7 @@ public class JSONMLTest {
* In this case, the XML is invalid because an element
* has no closing '>'.
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -326,7 +306,7 @@ public class JSONMLTest {
* In this case, the XML is invalid because an element
* has no name after the closing tag ''.
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -356,7 +336,7 @@ public class JSONMLTest {
* In this case, the XML is invalid because an element
* has '>' after the closing tag '' and name.
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -384,9 +364,9 @@ public class JSONMLTest {
/**
* xmlStr contains XML text which is transformed into a JSONArray.
* In this case, the XML is invalid because an element
- * does not have a complete CDATA string.
+ * does not have a complete CDATA string.
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -408,7 +388,7 @@ public class JSONMLTest {
/**
* Convert an XML document into a JSONArray, then use JSONML.toString()
* to convert it into a string. This string is then converted back into
- * a JSONArray. Both JSONArrays are compared against a control to
+ * a JSONArray. Both JSONArrays are compared against a control to
* confirm the contents.
*/
@Test
@@ -425,7 +405,7 @@ public class JSONMLTest {
* which is used to create a final JSONArray, which is also compared
* against the expected JSONArray.
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -434,7 +414,7 @@ public class JSONMLTest {
" >\n"+
"\n"+
" ";
- String expectedStr =
+ String expectedStr =
"[\"addresses\","+
"{\"xsi:noNamespaceSchemaLocation\":\"test.xsd\","+
"\"xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"},"+
@@ -454,12 +434,12 @@ public class JSONMLTest {
}
/**
- * Convert an XML document into a JSONObject. Use JSONML.toString() to
+ * Convert an XML document into a JSONObject. Use JSONML.toString() to
* convert it back into a string, and then re-convert it into a JSONObject.
* Both JSONObjects are compared against a control JSONObject to confirm
* the contents.
*
- * Next convert the XML document into a JSONArray. Use JSONML.toString() to
+ * Next convert the XML document into a JSONArray. Use JSONML.toString() to
* convert it back into a string, and then re-convert it into a JSONArray.
* Both JSONArrays are compared against a control JSONArray to confirm
* the contents.
@@ -472,23 +452,23 @@ public class JSONMLTest {
/**
* xmlStr contains XML text which is transformed into a JSONObject,
* restored to XML, transformed into a JSONArray, and then restored
- * to XML again. Both JSONObject and JSONArray should contain the same
+ * to XML again. Both JSONObject and JSONArray should contain the same
* information and should produce the same XML, allowing for non-ordered
* attributes.
- *
+ *
* Transformation to JSONObject:
* The elementName is stored as a string where key="tagName"
* Attributes are simply stored as key/value pairs
* If the element has either content or child elements, they are stored
* in a jsonArray with key="childNodes".
- *
+ *
* Transformation to JSONArray:
* 1st entry = elementname
* 2nd entry = attributes object (if present)
* 3rd entry = content (if present)
* 4th entry = child element JSONArrays (if present)
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -605,7 +585,7 @@ public class JSONMLTest {
"\"tagName\":\"addresses\""+
"}";
- String expectedJSONArrayStr =
+ String expectedJSONArrayStr =
"["+
"\"addresses\","+
"{"+
@@ -665,12 +645,12 @@ public class JSONMLTest {
JSONObject finalJsonObject = JSONML.toJSONObject(jsonObjectXmlToStr);
Util.compareActualVsExpectedJsonObjects(finalJsonObject, expectedJsonObject);
- // create a JSON array from the original string and make sure it
+ // create a JSON array from the original string and make sure it
// looks as expected
JSONArray jsonArray = JSONML.toJSONArray(xmlStr);
JSONArray expectedJsonArray = new JSONArray(expectedJSONArrayStr);
Util.compareActualVsExpectedJsonArrays(jsonArray,expectedJsonArray);
-
+
// restore the XML, then make another JSONArray and make sure it
// looks as expected
String jsonArrayXmlToStr = JSONML.toString(jsonArray);
@@ -688,14 +668,14 @@ public class JSONMLTest {
* Convert an XML document which contains embedded comments into
* a JSONArray. Use JSONML.toString() to turn it into a string, then
* reconvert it into a JSONArray. Compare both JSONArrays to a control
- * JSONArray to confirm the contents.
+ * JSONArray to confirm the contents.
*
* This test shows how XML comments are handled.
*/
@Test
public void commentsInXML() {
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
"\n"+
@@ -754,7 +734,7 @@ public class JSONMLTest {
final String expectedJsonString = "[\"root\",[\"id\",\"01\"],[\"id\",\"1\"],[\"id\",\"00\"],[\"id\",\"0\"],[\"item\",{\"id\":\"01\"}],[\"title\",\"True\"]]";
final JSONArray json = JSONML.toJSONArray(originalXml,true);
assertEquals(expectedJsonString, json.toString());
-
+
final String reverseXml = JSONML.toString(json);
assertEquals(originalXml, reverseXml);
}
@@ -769,7 +749,7 @@ public class JSONMLTest {
final String revertedXml = JSONML.toString(jsonArray);
assertEquals(revertedXml, originalXml);
}
-
+
/**
* JSON string cannot be reverted to original xml. See test result in
* comment below.
@@ -782,15 +762,15 @@ public class JSONMLTest {
final String xml = JSONML.toString(originalObject);
final JSONObject revertedObject = JSONML.toJSONObject(xml, false);
final String newJson = revertedObject.toString();
- assertTrue("JSON Objects are not similar",originalObject.similar(revertedObject));
- assertEquals("original JSON does not equal the new JSON",originalJson, newJson);
+ assertTrue("JSON Objects are not similar", originalObject.similar(revertedObject));
+ assertTrue("JSON Strings are not similar", new JSONObject(originalJson).similar(new JSONObject(newJson)));
}
// these tests do not pass for the following reasons:
// 1. Our XML parser does not handle generic HTML entities, only valid XML entities. Hence
// or other HTML specific entities would fail on reversability
// 2. Our JSON implementation for storing the XML attributes uses the standard unordered map.
-// This means that can not be reversed reliably.
+// This means that can not be reversed reliably.
//
// /**
// * Test texts taken from jsonml.org. Currently our implementation FAILS this conversion but shouldn't.
@@ -803,13 +783,13 @@ public class JSONMLTest {
// final String expectedJsonString = "[\"table\",{\"class\" : \"MyTable\",\"style\" : \"background-color:yellow\"},[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#550758\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:red\"},\"Example text here\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#993101\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:green\"},\"127624015\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#E33D87\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:blue\"},\"\u00A0\",[\"span\",{ \"style\" : \"background-color:maroon\" },\"\u00A9\"],\"\u00A0\"]]]";
// final JSONArray json = JSONML.toJSONArray(originalXml,true);
// final String actualJsonString = json.toString();
-//
+//
// final String reverseXml = JSONML.toString(json);
// assertNotEquals(originalXml, reverseXml);
//
// assertNotEquals(expectedJsonString, actualJsonString);
// }
-//
+//
// /**
// * Test texts taken from jsonml.org but modified to have XML entities only.
// */
@@ -819,15 +799,15 @@ public class JSONMLTest {
// final String expectedJsonString = "[\"table\",{\"class\" : \"MyTable\",\"style\" : \"background-color:yellow\"},[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#550758\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:red\"},\"Example text here\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#993101\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:green\"},\"127624015\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#E33D87\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:blue\"},\"&\",[\"span\",{ \"style\" : \"background-color:maroon\" },\">\"],\"<\"]]]";
// final JSONArray jsonML = JSONML.toJSONArray(originalXml,true);
// final String actualJsonString = jsonML.toString();
-//
+//
// final String reverseXml = JSONML.toString(jsonML);
// // currently not equal because the hashing of the attribute objects makes the attribute
-// // order not happen the same way twice
+// // order not happen the same way twice
// assertEquals(originalXml, reverseXml);
//
// assertEquals(expectedJsonString, actualJsonString);
// }
-
+
@Test (timeout = 6000)
public void testIssue484InfinteLoop1() {
try {
@@ -839,11 +819,11 @@ public class JSONMLTest {
ex.getMessage());
}
}
-
+
@Test (timeout = 6000)
public void testIssue484InfinteLoop2() {
try {
- String input = "??*\n" +
+ String input = "??*\n" +
"??|?CglR??`??>?w??PIlr??D?$?-?o??O?*??{OD?Y??`2a????NM?bq?:O?>S$?J?B.gUK?m\b??zE???!v]???????c??????h???s???g???`?qbi??:Zl?)?}1^??k?0??:$V?$?Ovs(}J??????2;gQ????Tg?K?`?h%c?hmGA?");
+
+ final int maxNestingDepth = 42;
+
+ try {
+ JSONML.toJSONArray(wayTooLongMalformedXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
+ e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
+ }
+ }
+
+
+ @Test
+ public void testToJSONArrayMaxNestingDepthIsRespectedWithValidXML() {
+ final String perfectlyFineXML = "\n" +
+ " \n" +
+ " sonoo \n" +
+ " 56000 \n" +
+ " true \n" +
+ " \n" +
+ " \n";
+
+ final int maxNestingDepth = 1;
+
+ try {
+ JSONML.toJSONArray(perfectlyFineXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
+ e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
+ }
+ }
+
+ @Test
+ public void testToJSONArrayMaxNestingDepthWithValidFittingXML() {
+ final String perfectlyFineXML = "\n" +
+ " \n" +
+ " sonoo \n" +
+ " 56000 \n" +
+ " true \n" +
+ " \n" +
+ " \n";
+
+ final int maxNestingDepth = 3;
+
+ try {
+ JSONML.toJSONArray(perfectlyFineXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ fail("XML document should be parsed as its maximum depth fits the maxNestingDepth " +
+ "parameter of the JSONMLParserConfiguration used");
+ }
+ }
+
+
+ @Test
+ public void testToJSONObjectMaxDefaultNestingDepthIsRespected() {
+ final String wayTooLongMalformedXML = new String(new char[6000]).replace("\0", "");
+
+ try {
+ JSONML.toJSONObject(wayTooLongMalformedXML, JSONMLParserConfiguration.ORIGINAL);
+
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
+ e.getMessage().startsWith("Maximum nesting depth of " + JSONMLParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH));
+ }
+ }
+
+ @Test
+ public void testToJSONObjectUnlimitedNestingDepthIsPossible() {
+ int actualDepth = JSONMLParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH +10;
+ final String deeperThanDefaultMax = new String(new char[actualDepth]).replace("\0", " ") +
+ "value" +
+ new String(new char[actualDepth]).replace("\0", " ");
+
+ try {
+ JSONML.toJSONObject(deeperThanDefaultMax, JSONMLParserConfiguration.ORIGINAL
+ .withMaxNestingDepth(JSONMLParserConfiguration.UNDEFINED_MAXIMUM_NESTING_DEPTH));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ fail("XML document should be parsed beyond the default maximum depth if maxNestingDepth " +
+ "parameter is set to -1 in JSONMLParserConfiguration");
+ }
+ }
+
+
+ @Test
+ public void testToJSONObjectMaxNestingDepthOf42IsRespected() {
+ final String wayTooLongMalformedXML = new String(new char[6000]).replace("\0", "");
+
+ final int maxNestingDepth = 42;
+
+ try {
+ JSONML.toJSONObject(wayTooLongMalformedXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
+ e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
+ }
+ }
+
+ @Test
+ public void testToJSONObjectMaxNestingDepthIsRespectedWithValidXML() {
+ final String perfectlyFineXML = "\n" +
+ " \n" +
+ " sonoo \n" +
+ " 56000 \n" +
+ " true \n" +
+ " \n" +
+ " \n";
+
+ final int maxNestingDepth = 1;
+
+ try {
+ JSONML.toJSONObject(perfectlyFineXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
+ e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
+ }
+ }
+
+ @Test
+ public void testToJSONObjectMaxNestingDepthWithValidFittingXML() {
+ final String perfectlyFineXML = "\n" +
+ " \n" +
+ " sonoo \n" +
+ " 56000 \n" +
+ " true \n" +
+ " \n" +
+ " \n";
+
+ final int maxNestingDepth = 3;
+
+ try {
+ JSONML.toJSONObject(perfectlyFineXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ fail("XML document should be parsed as its maximum depth fits the maxNestingDepth " +
+ "parameter of the JSONMLParserConfiguration used");
+ }
+ }
+
}
diff --git a/src/test/java/org/json/junit/JSONObjectLocaleTest.java b/src/test/java/org/json/junit/JSONObjectLocaleTest.java
index 5112bf5..1cdaf74 100755
--- a/src/test/java/org/json/junit/JSONObjectLocaleTest.java
+++ b/src/test/java/org/json/junit/JSONObjectLocaleTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
diff --git a/src/test/java/org/json/junit/JSONObjectNumberTest.java b/src/test/java/org/json/junit/JSONObjectNumberTest.java
new file mode 100644
index 0000000..43173a2
--- /dev/null
+++ b/src/test/java/org/json/junit/JSONObjectNumberTest.java
@@ -0,0 +1,146 @@
+package org.json.junit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(value = Parameterized.class)
+public class JSONObjectNumberTest {
+ private final String objectString;
+ private Integer value = 50;
+
+ @Parameters(name = "{index}: {0}")
+ public static Collection data() {
+ return Arrays.asList(new Object[][]{
+ {"{value:50}", 1},
+ {"{value:50.0}", 1},
+ {"{value:5e1}", 1},
+ {"{value:5E1}", 1},
+ {"{value:5e1}", 1},
+ {"{value:'50'}", 1},
+ {"{value:-50}", -1},
+ {"{value:-50.0}", -1},
+ {"{value:-5e1}", -1},
+ {"{value:-5E1}", -1},
+ {"{value:-5e1}", -1},
+ {"{value:'-50'}", -1}
+ // JSON does not support octal or hex numbers;
+ // see https://stackoverflow.com/a/52671839/6323312
+ // "{value:062}", // octal 50
+ // "{value:0x32}" // hex 50
+ });
+ }
+
+ public JSONObjectNumberTest(String objectString, int resultIsNegative) {
+ this.objectString = objectString;
+ this.value *= resultIsNegative;
+ }
+
+ private JSONObject object;
+
+ @Before
+ public void setJsonObject() {
+ object = new JSONObject(objectString);
+ }
+
+ @Test
+ public void testGetNumber() {
+ assertEquals(value.intValue(), object.getNumber("value").intValue());
+ }
+
+ @Test
+ public void testGetBigDecimal() {
+ assertTrue(BigDecimal.valueOf(value).compareTo(object.getBigDecimal("value")) == 0);
+ }
+
+ @Test
+ public void testGetBigInteger() {
+ assertEquals(BigInteger.valueOf(value), object.getBigInteger("value"));
+ }
+
+ @Test
+ public void testGetFloat() {
+ assertEquals(value.floatValue(), object.getFloat("value"), 0.0f);
+ }
+
+ @Test
+ public void testGetDouble() {
+ assertEquals(value.doubleValue(), object.getDouble("value"), 0.0d);
+ }
+
+ @Test
+ public void testGetInt() {
+ assertEquals(value.intValue(), object.getInt("value"));
+ }
+
+ @Test
+ public void testGetLong() {
+ assertEquals(value.longValue(), object.getLong("value"));
+ }
+
+ @Test
+ public void testOptNumber() {
+ assertEquals(value.intValue(), object.optNumber("value").intValue());
+ }
+
+ @Test
+ public void testOptBigDecimal() {
+ assertTrue(BigDecimal.valueOf(value).compareTo(object.optBigDecimal("value", null)) == 0);
+ }
+
+ @Test
+ public void testOptBigInteger() {
+ assertEquals(BigInteger.valueOf(value), object.optBigInteger("value", null));
+ }
+
+ @Test
+ public void testOptFloat() {
+ assertEquals(value.floatValue(), object.optFloat("value"), 0.0f);
+ }
+
+ @Test
+ public void testOptFloatObject() {
+ assertEquals((Float) value.floatValue(), object.optFloatObject("value"), 0.0f);
+ }
+
+ @Test
+ public void testOptDouble() {
+ assertEquals(value.doubleValue(), object.optDouble("value"), 0.0d);
+ }
+
+ @Test
+ public void testOptDoubleObject() {
+ assertEquals((Double) value.doubleValue(), object.optDoubleObject("value"), 0.0d);
+ }
+
+ @Test
+ public void testOptInt() {
+ assertEquals(value.intValue(), object.optInt("value"));
+ }
+
+ @Test
+ public void testOptIntegerObject() {
+ assertEquals((Integer) value.intValue(), object.optIntegerObject("value"));
+ }
+
+ @Test
+ public void testOptLong() {
+ assertEquals(value.longValue(), object.optLong("value"));
+ }
+
+ @Test
+ public void testOptLongObject() {
+ assertEquals((Long) value.longValue(), object.optLongObject("value"));
+ }
+}
diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java
index 2e296f0..fac8c53 100644
--- a/src/test/java/org/json/junit/JSONObjectTest.java
+++ b/src/test/java/org/json/junit/JSONObjectTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.assertEquals;
@@ -29,25 +9,20 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.IOException;
+import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
+import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
@@ -56,7 +31,10 @@ import org.json.JSONArray;
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.ParserConfiguration;
import org.json.XML;
import org.json.junit.data.BrokenToString;
import org.json.junit.data.ExceptionalBean;
@@ -73,9 +51,12 @@ import org.json.junit.data.MyJsonString;
import org.json.junit.data.MyNumber;
import org.json.junit.data.MyNumberContainer;
import org.json.junit.data.MyPublicClass;
+import org.json.junit.data.RecursiveBean;
+import org.json.junit.data.RecursiveBeanEquals;
import org.json.junit.data.Singleton;
import org.json.junit.data.SingletonEnum;
import org.json.junit.data.WeirdList;
+import org.junit.Ignore;
import org.junit.Test;
import com.jayway.jsonpath.Configuration;
@@ -100,6 +81,7 @@ public class JSONObjectTest {
@Test
public void verifySimilar() {
final String string1 = "HasSameRef";
+ final String string2 = "HasDifferentRef";
JSONObject obj1 = new JSONObject()
.put("key1", "abc")
.put("key2", 2)
@@ -119,15 +101,29 @@ public class JSONObjectTest {
.put("key1", "abc")
.put("key2", 2.0)
.put("key3", new String(string1));
-
- assertFalse("Should eval to false", obj1.similar(obj2));
- assertTrue("Should eval to true", obj1.similar(obj3));
-
- assertTrue("Should eval to true", obj1.similar(obj4));
+ JSONObject obj5 = new JSONObject()
+ .put("key1", "abc")
+ .put("key2", 2.0)
+ .put("key3", new String(string2));
+ assertFalse("obj1-obj2 Should eval to false", obj1.similar(obj2));
+ assertTrue("obj1-obj3 Should eval to true", obj1.similar(obj3));
+ assertTrue("obj1-obj4 Should eval to true", obj1.similar(obj4));
+ assertFalse("obj1-obj5 Should eval to false", obj1.similar(obj5));
+ // verify that a double and big decimal are "similar"
+ assertTrue("should eval to true",new JSONObject().put("a",1.1d).similar(new JSONObject("{\"a\":1.1}")));
+ // Confirm #618 is fixed (compare should not exit early if similar numbers are found)
+ // Note that this test may not work if the JSONObject map entry order changes
+ JSONObject first = new JSONObject("{\"a\": 1, \"b\": 2, \"c\": 3}");
+ JSONObject second = new JSONObject("{\"a\": 1, \"b\": 2.0, \"c\": 4}");
+ assertFalse("first-second should eval to false", first.similar(second));
+ List jsonObjects = new ArrayList(
+ Arrays.asList(obj1, obj2, obj3, obj4, obj5)
+ );
+ Util.checkJSONObjectsMaps(jsonObjects);
}
-
+
@Test
public void timeNumberParsing() {
// test data to use
@@ -200,7 +196,9 @@ public class JSONObjectTest {
*/
@Test(expected=NullPointerException.class)
public void jsonObjectByNullBean() {
- assertNull("Expected an exception",new JSONObject((MyBean)null));
+ JSONObject jsonObject = new JSONObject((MyBean)null);
+ assertNull("Expected an exception", jsonObject);
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -211,13 +209,18 @@ public class JSONObjectTest {
*/
@Test
public void unquotedText() {
- String str = "{key1:value1, key2:42}";
+ String str = "{key1:value1, key2:42, 1.2 : 3.4, -7e5 : something!}";
JSONObject jsonObject = new JSONObject(str);
String textStr = jsonObject.toString();
assertTrue("expected key1", textStr.contains("\"key1\""));
assertTrue("expected value1", textStr.contains("\"value1\""));
assertTrue("expected key2", textStr.contains("\"key2\""));
assertTrue("expected 42", textStr.contains("42"));
+ assertTrue("expected 1.2", textStr.contains("\"1.2\""));
+ assertTrue("expected 3.4", textStr.contains("3.4"));
+ assertTrue("expected -7E+5", textStr.contains("\"-7E+5\""));
+ assertTrue("expected something!", textStr.contains("\"something!\""));
+ Util.checkJSONObjectMaps(jsonObject);
}
@Test
@@ -235,9 +238,15 @@ public class JSONObjectTest {
assert 26315000000253009L == actualLong : "Incorrect key value. Got "
+ actualLong + " expected " + str;
+ final Long actualLongObject = json.optLongObject("key");
+ assert actualLongObject != 0L : "Unable to extract Long value for string " + str;
+ assert Long.valueOf(26315000000253009L).equals(actualLongObject) : "Incorrect key value. Got "
+ + actualLongObject + " expected " + str;
+
final String actualString = json.optString("key");
assert str.equals(actualString) : "Incorrect key value. Got "
+ actualString + " expected " + str;
+ Util.checkJSONObjectMaps(json);
}
/**
@@ -247,6 +256,7 @@ public class JSONObjectTest {
public void emptyJsonObject() {
JSONObject jsonObject = new JSONObject();
assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -277,6 +287,7 @@ public class JSONObjectTest {
assertTrue("expected \"nullKey\":null", JSONObject.NULL.equals(jsonObjectByName.query("/nullKey")));
assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObjectByName.query("/stringKey")));
assertTrue("expected \"doubleKey\":-23.45e67", new BigDecimal("-23.45e67").equals(jsonObjectByName.query("/doubleKey")));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(jsonObject, jsonObjectByName)));
}
/**
@@ -290,6 +301,7 @@ public class JSONObjectTest {
Map map = null;
JSONObject jsonObject = new JSONObject(map);
assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -299,12 +311,12 @@ public class JSONObjectTest {
@Test
public void jsonObjectByMap() {
Map map = new HashMap();
- map.put("trueKey", new Boolean(true));
- map.put("falseKey", new Boolean(false));
+ map.put("trueKey", Boolean.valueOf(true));
+ map.put("falseKey", Boolean.valueOf(false));
map.put("stringKey", "hello world!");
map.put("escapeStringKey", "h\be\tllo w\u1234orld!");
- map.put("intKey", new Long(42));
- map.put("doubleKey", new Double(-23.45e67));
+ map.put("intKey", Long.valueOf(42));
+ map.put("doubleKey", Double.valueOf(-23.45e67));
JSONObject jsonObject = new JSONObject(map);
// validate JSON
@@ -315,6 +327,7 @@ public class JSONObjectTest {
assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObject.query("/stringKey")));
assertTrue("expected \"escapeStringKey\":\"h\be\tllo w\u1234orld!\"", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey")));
assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -353,6 +366,9 @@ public class JSONObjectTest {
assertTrue(
"The RAW Collection should give me the same as the Typed Collection",
expected.similar(jaObjObj));
+ Util.checkJSONObjectsMaps(new ArrayList(
+ Arrays.asList(jaRaw, jaStrObj, jaStrInt, jaObjObj))
+ );
}
/**
@@ -370,8 +386,8 @@ public class JSONObjectTest {
* The only getter is getNumber (key=number), whose return value is
* BigDecimal(42).
*/
- JSONObject jsonObject = new JSONObject(new MyNumberContainer());
- String actual = jsonObject.toString();
+ JSONObject jsonObject0 = new JSONObject(new MyNumberContainer());
+ String actual = jsonObject0.toString();
String expected = "{\"myNumber\":{\"number\":42}}";
assertEquals("Equal", expected , actual);
@@ -383,9 +399,9 @@ public class JSONObjectTest {
* The MyNumber.toString() method is responsible for
* returning a reasonable value: the string '42'.
*/
- jsonObject = new JSONObject();
- jsonObject.put("myNumber", new MyNumber());
- actual = jsonObject.toString();
+ JSONObject jsonObject1 = new JSONObject();
+ jsonObject1.put("myNumber", new MyNumber());
+ actual = jsonObject1.toString();
expected = "{\"myNumber\":42}";
assertEquals("Equal", expected , actual);
@@ -397,8 +413,8 @@ public class JSONObjectTest {
* wrap() inserts the value as a string. That is why 42 comes back
* wrapped in quotes.
*/
- jsonObject = new JSONObject(Collections.singletonMap("myNumber", new AtomicInteger(42)));
- actual = jsonObject.toString();
+ JSONObject jsonObject2 = new JSONObject(Collections.singletonMap("myNumber", new AtomicInteger(42)));
+ actual = jsonObject2.toString();
expected = "{\"myNumber\":\"42\"}";
assertEquals("Equal", expected , actual);
@@ -408,9 +424,9 @@ public class JSONObjectTest {
* AtomicInteger is recognized as a Number, and converted via
* numberToString() into the unquoted string '42'.
*/
- jsonObject = new JSONObject();
- jsonObject.put("myNumber", new AtomicInteger(42));
- actual = jsonObject.toString();
+ JSONObject jsonObject3 = new JSONObject();
+ jsonObject3.put("myNumber", new AtomicInteger(42));
+ actual = jsonObject3.toString();
expected = "{\"myNumber\":42}";
assertEquals("Equal", expected , actual);
@@ -421,11 +437,11 @@ public class JSONObjectTest {
* bean and inserted into a contained JSONObject. It has 2 getters,
* for numerator and denominator.
*/
- jsonObject = new JSONObject(Collections.singletonMap("myNumber", new Fraction(4,2)));
- assertEquals(1, jsonObject.length());
- assertEquals(2, ((JSONObject)(jsonObject.get("myNumber"))).length());
- assertEquals("Numerator", BigInteger.valueOf(4) , jsonObject.query("/myNumber/numerator"));
- assertEquals("Denominator", BigInteger.valueOf(2) , jsonObject.query("/myNumber/denominator"));
+ JSONObject jsonObject4 = new JSONObject(Collections.singletonMap("myNumber", new Fraction(4,2)));
+ assertEquals(1, jsonObject4.length());
+ assertEquals(2, ((JSONObject)(jsonObject4.get("myNumber"))).length());
+ assertEquals("Numerator", BigInteger.valueOf(4) , jsonObject4.query("/myNumber/numerator"));
+ assertEquals("Denominator", BigInteger.valueOf(2) , jsonObject4.query("/myNumber/denominator"));
/**
* JSONObject.put() inserts the Fraction directly into the
@@ -435,11 +451,15 @@ public class JSONObjectTest {
* BigDecimal sanity check fails, so writeValue() defaults
* to returning a safe JSON quoted string. Pretty slick!
*/
- jsonObject = new JSONObject();
- jsonObject.put("myNumber", new Fraction(4,2));
- actual = jsonObject.toString();
+ JSONObject jsonObject5 = new JSONObject();
+ jsonObject5.put("myNumber", new Fraction(4,2));
+ actual = jsonObject5.toString();
expected = "{\"myNumber\":\"4/2\"}"; // valid JSON, bug fixed
assertEquals("Equal", expected , actual);
+
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObject0, jsonObject1, jsonObject2, jsonObject3, jsonObject4, jsonObject5
+ )));
}
/**
@@ -474,6 +494,10 @@ public class JSONObjectTest {
assertTrue(
"The RAW Collection should give me the same as the Typed Collection",
expected.similar(jaInt));
+
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jaRaw, jaObj, jaInt
+ )));
}
@@ -517,6 +541,10 @@ public class JSONObjectTest {
assertTrue(
"The RAW Collection should give me the same as the Typed Collection",
expected.similar(jaObjObj));
+
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jaRaw, jaStrObj, jaStrInt, jaStrObj
+ )));
}
@@ -539,6 +567,7 @@ public class JSONObjectTest {
assertTrue("expected 2 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 2);
assertTrue("expected 0 key1 items", ((Map,?>)(JsonPath.read(doc, "$.key1"))).size() == 0);
assertTrue("expected \"key2\":java.lang.Exception","java.lang.Exception".equals(jsonObject.query("/key2")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -548,13 +577,13 @@ public class JSONObjectTest {
@Test
public void jsonObjectByMapWithNullValue() {
Map map = new HashMap();
- map.put("trueKey", new Boolean(true));
- map.put("falseKey", new Boolean(false));
+ map.put("trueKey", Boolean.valueOf(true));
+ map.put("falseKey", Boolean.valueOf(false));
map.put("stringKey", "hello world!");
map.put("nullKey", null);
map.put("escapeStringKey", "h\be\tllo w\u1234orld!");
- map.put("intKey", new Long(42));
- map.put("doubleKey", new Double(-23.45e67));
+ map.put("intKey", Long.valueOf(42));
+ map.put("doubleKey", Double.valueOf(-23.45e67));
JSONObject jsonObject = new JSONObject(map);
// validate JSON
@@ -566,6 +595,7 @@ public class JSONObjectTest {
assertTrue("expected \"escapeStringKey\":\"h\be\tllo w\u1234orld!\"", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey")));
assertTrue("expected \"intKey\":42", Long.valueOf("42").equals(jsonObject.query("/intKey")));
assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -603,9 +633,10 @@ public class JSONObjectTest {
assertTrue("expected 42", Integer.valueOf("42").equals(jsonObject.query("/intKey")));
assertTrue("expected -23.45e7", Double.valueOf("-23.45e7").equals(jsonObject.query("/doubleKey")));
// sorry, mockito artifact
- assertTrue("expected 2 callbacks items", ((List>)(JsonPath.read(doc, "$.callbacks"))).size() == 2);
- assertTrue("expected 0 handler items", ((Map,?>)(JsonPath.read(doc, "$.callbacks[0].handler"))).size() == 0);
- assertTrue("expected 0 callbacks[1] items", ((Map,?>)(JsonPath.read(doc, "$.callbacks[1]"))).size() == 0);
+ assertTrue("expected 2 mockitoInterceptor items", ((Map,?>)(JsonPath.read(doc, "$.mockitoInterceptor"))).size() == 2);
+ assertTrue("expected 0 mockitoInterceptor.serializationSupport items",
+ ((Map,?>)(JsonPath.read(doc, "$.mockitoInterceptor.serializationSupport"))).size() == 0);
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -641,6 +672,7 @@ public class JSONObjectTest {
// InterfaceField replaces someFloat property name via user-defined annotation
assertTrue("Overridden String field name (InterfaceField) should have been found",
jsonObject.has("InterfaceField"));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -691,6 +723,7 @@ public class JSONObjectTest {
// property name able was replaced by Getable via user-defined annotation
assertTrue("Overridden boolean field name (Getable) should have been found",
jsonObject.has("Getable"));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -711,6 +744,7 @@ public class JSONObjectTest {
assertTrue("expected 2 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 2);
assertTrue("expected \"publicString\":\"abc\"", "abc".equals(jsonObject.query("/publicString")));
assertTrue("expected \"publicInt\":42", Integer.valueOf(42).equals(jsonObject.query("/publicInt")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -732,6 +766,7 @@ public class JSONObjectTest {
assertTrue("expected 2 farewells items", ((Map,?>)(JsonPath.read(doc, "$.farewells"))).size() == 2);
assertTrue("expected \"later\":\"Later, \"", "Later, ".equals(jsonObject.query("/farewells/later")));
assertTrue("expected \"world\":\"World!\"", "Alligator!".equals(jsonObject.query("/farewells/gator")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -764,6 +799,7 @@ public class JSONObjectTest {
assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/myArray/3")));
assertTrue("expected 42", Integer.valueOf(42).equals(jsonObject.query("/myArray/4")));
assertTrue("expected -23.45e7", Double.valueOf(-23.45e7).equals(jsonObject.query("/myArray/5")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -795,6 +831,7 @@ public class JSONObjectTest {
assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/myArray/3")));
assertTrue("expected 42", Integer.valueOf(42).equals(jsonObject.query("/myArray/4")));
assertTrue("expected -23.45e7", Double.valueOf(-23.45e7).equals(jsonObject.query("/myArray/5")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -805,7 +842,7 @@ public class JSONObjectTest {
public void jsonObjectDoubleToString() {
String [] expectedStrs = {"1", "1", "-23.4", "-2.345E68", "null", "null" };
Double [] doubles = { 1.0, 00001.00000, -23.4, -23.45e67,
- Double.NaN, Double.NEGATIVE_INFINITY };
+ Double.NaN, Double.NEGATIVE_INFINITY };
for (int i = 0; i < expectedStrs.length; ++i) {
String actualStr = JSONObject.doubleToString(doubles[i]);
assertTrue("value expected ["+expectedStrs[i]+
@@ -841,9 +878,11 @@ public class JSONObjectTest {
JSONObject jsonObject = new JSONObject(str);
assertTrue("trueKey should be true", jsonObject.getBoolean("trueKey"));
assertTrue("opt trueKey should be true", jsonObject.optBoolean("trueKey"));
+ assertTrue("opt trueKey should be true", jsonObject.optBooleanObject("trueKey"));
assertTrue("falseKey should be false", !jsonObject.getBoolean("falseKey"));
assertTrue("trueStrKey should be true", jsonObject.getBoolean("trueStrKey"));
assertTrue("trueStrKey should be true", jsonObject.optBoolean("trueStrKey"));
+ assertTrue("trueStrKey should be true", jsonObject.optBooleanObject("trueStrKey"));
assertTrue("falseStrKey should be false", !jsonObject.getBoolean("falseStrKey"));
assertTrue("stringKey should be string",
jsonObject.getString("stringKey").equals("hello world!"));
@@ -859,6 +898,10 @@ public class JSONObjectTest {
jsonObject.optDouble("doubleKey") == -23.45e7);
assertTrue("opt doubleKey with Default should be double",
jsonObject.optDouble("doubleStrKey", Double.NaN) == 1);
+ assertTrue("opt doubleKey should be Double",
+ Double.valueOf(-23.45e7).equals(jsonObject.optDoubleObject("doubleKey")));
+ assertTrue("opt doubleKey with Default should be Double",
+ Double.valueOf(1).equals(jsonObject.optDoubleObject("doubleStrKey", Double.NaN)));
assertTrue("opt negZeroKey should be a Double",
jsonObject.opt("negZeroKey") instanceof Double);
assertTrue("get negZeroKey should be a Double",
@@ -871,6 +914,10 @@ public class JSONObjectTest {
Double.compare(jsonObject.optDouble("negZeroKey"), -0.0d) == 0);
assertTrue("opt negZeroStrKey with Default should be double",
Double.compare(jsonObject.optDouble("negZeroStrKey"), -0.0d) == 0);
+ assertTrue("opt negZeroKey should be Double",
+ Double.valueOf(-0.0d).equals(jsonObject.optDoubleObject("negZeroKey")));
+ assertTrue("opt negZeroStrKey with Default should be Double",
+ Double.valueOf(-0.0d).equals(jsonObject.optDoubleObject("negZeroStrKey")));
assertTrue("optNumber negZeroKey should be -0.0",
Double.compare(jsonObject.optNumber("negZeroKey").doubleValue(), -0.0d) == 0);
assertTrue("optNumber negZeroStrKey should be -0.0",
@@ -879,10 +926,18 @@ public class JSONObjectTest {
jsonObject.optFloat("doubleKey") == -23.45e7f);
assertTrue("optFloat doubleKey with Default should be float",
jsonObject.optFloat("doubleStrKey", Float.NaN) == 1f);
+ assertTrue("optFloat doubleKey should be Float",
+ Float.valueOf(-23.45e7f).equals(jsonObject.optFloatObject("doubleKey")));
+ assertTrue("optFloat doubleKey with Default should be Float",
+ Float.valueOf(1f).equals(jsonObject.optFloatObject("doubleStrKey", Float.NaN)));
assertTrue("intKey should be int",
jsonObject.optInt("intKey") == 42);
assertTrue("opt intKey should be int",
jsonObject.optInt("intKey", 0) == 42);
+ assertTrue("intKey should be Integer",
+ Integer.valueOf(42).equals(jsonObject.optIntegerObject("intKey")));
+ assertTrue("opt intKey should be Integer",
+ Integer.valueOf(42).equals(jsonObject.optIntegerObject("intKey", 0)));
assertTrue("opt intKey with default should be int",
jsonObject.getInt("intKey") == 42);
assertTrue("intStrKey should be int",
@@ -893,6 +948,10 @@ public class JSONObjectTest {
jsonObject.optLong("longKey") == 1234567890123456789L);
assertTrue("opt longKey with default should be long",
jsonObject.optLong("longKey", 0) == 1234567890123456789L);
+ assertTrue("opt longKey should be Long",
+ Long.valueOf(1234567890123456789L).equals(jsonObject.optLongObject("longKey")));
+ assertTrue("opt longKey with default should be Long",
+ Long.valueOf(1234567890123456789L).equals(jsonObject.optLongObject("longKey", 0L)));
assertTrue("longStrKey should be long",
jsonObject.getLong("longStrKey") == 987654321098765432L);
assertTrue("optNumber int should return Integer",
@@ -930,6 +989,7 @@ public class JSONObjectTest {
JSONObject jsonObjectInner = jsonObject.getJSONObject("objectKey");
assertTrue("objectKey should be JSONObject",
jsonObjectInner.get("myKey").equals("myVal"));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -940,10 +1000,10 @@ public class JSONObjectTest {
assertTrue("-0 Should be a Double!",JSONObject.stringToValue("-0") instanceof Double);
assertTrue("-0.0 Should be a Double!",JSONObject.stringToValue("-0.0") instanceof Double);
assertTrue("'-' Should be a String!",JSONObject.stringToValue("-") instanceof String);
- assertTrue( "0.2 should be a Double!",
+ assertTrue( "0.2 should be a BigDecimal!",
JSONObject.stringToValue( "0.2" ) instanceof BigDecimal );
assertTrue( "Doubles should be BigDecimal, even when incorrectly converting floats!",
- JSONObject.stringToValue( new Double( "0.2f" ).toString() ) instanceof BigDecimal );
+ JSONObject.stringToValue( Double.valueOf( "0.2f" ).toString() ) instanceof BigDecimal );
/**
* This test documents a need for BigDecimal conversion.
*/
@@ -953,13 +1013,13 @@ public class JSONObjectTest {
assertTrue( "1 should be an Integer!",
JSONObject.stringToValue( "1" ) instanceof Integer );
assertTrue( "Integer.MAX_VALUE should still be an Integer!",
- JSONObject.stringToValue( new Integer( Integer.MAX_VALUE ).toString() ) instanceof Integer );
+ JSONObject.stringToValue( Integer.valueOf( Integer.MAX_VALUE ).toString() ) instanceof Integer );
assertTrue( "Large integers should be a Long!",
JSONObject.stringToValue( Long.valueOf(((long)Integer.MAX_VALUE) + 1 ) .toString() ) instanceof Long );
assertTrue( "Long.MAX_VALUE should still be an Integer!",
- JSONObject.stringToValue( new Long( Long.MAX_VALUE ).toString() ) instanceof Long );
+ JSONObject.stringToValue( Long.valueOf( Long.MAX_VALUE ).toString() ) instanceof Long );
- String str = new BigInteger( new Long( Long.MAX_VALUE ).toString() ).add( BigInteger.ONE ).toString();
+ String str = new BigInteger( Long.valueOf( Long.MAX_VALUE ).toString() ).add( BigInteger.ONE ).toString();
assertTrue( "Really large integers currently evaluate to BigInteger",
JSONObject.stringToValue(str).equals(new BigInteger("9223372036854775808")));
}
@@ -992,6 +1052,7 @@ public class JSONObjectTest {
obj = jsonObject.get( "largeExponent" );
assertTrue("largeExponent should evaluate as a BigDecimal",
new BigDecimal("-23.45e2327").equals(obj));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -1031,7 +1092,7 @@ public class JSONObjectTest {
assertTrue( "negativeFraction currently evaluates to double -0.01",
jsonObject.get( "negativeFraction" ).equals(BigDecimal.valueOf(-0.01)));
assertTrue( "tooManyZerosFraction currently evaluates to double 0.001",
- jsonObject.get( "tooManyZerosFraction" ).equals(BigDecimal.valueOf(0.001)));
+ jsonObject.optLong( "tooManyZerosFraction" )==0);
assertTrue( "negativeHexFloat currently evaluates to double -3.99951171875",
jsonObject.get( "negativeHexFloat" ).equals(Double.valueOf(-3.99951171875)));
assertTrue("hexFloat currently evaluates to double 4.9E-324",
@@ -1040,6 +1101,7 @@ public class JSONObjectTest {
jsonObject.get("floatIdentifier").equals(Double.valueOf(0.1)));
assertTrue("doubleIdentifier currently evaluates to double 0.1",
jsonObject.get("doubleIdentifier").equals(Double.valueOf(0.1)));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -1076,7 +1138,7 @@ public class JSONObjectTest {
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a Boolean.",
+ "JSONObject[\"stringKey\"] is not a Boolean (class java.lang.String : hello world!).",
e.getMessage());
}
try {
@@ -1092,7 +1154,7 @@ public class JSONObjectTest {
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONObject[\"trueKey\"] is not a string.",
+ "JSONObject[\"trueKey\"] is not a string (class java.lang.Boolean : true).",
e.getMessage());
}
try {
@@ -1108,7 +1170,7 @@ public class JSONObjectTest {
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a double.",
+ "JSONObject[\"stringKey\"] is not a double (class java.lang.String : hello world!).",
e.getMessage());
}
try {
@@ -1124,7 +1186,7 @@ public class JSONObjectTest {
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a float.",
+ "JSONObject[\"stringKey\"] is not a float (class java.lang.String : hello world!).",
e.getMessage());
}
try {
@@ -1140,7 +1202,7 @@ public class JSONObjectTest {
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a int.",
+ "JSONObject[\"stringKey\"] is not a int (class java.lang.String : hello world!).",
e.getMessage());
}
try {
@@ -1156,7 +1218,7 @@ public class JSONObjectTest {
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a long.",
+ "JSONObject[\"stringKey\"] is not a long (class java.lang.String : hello world!).",
e.getMessage());
}
try {
@@ -1172,7 +1234,7 @@ public class JSONObjectTest {
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a JSONArray.",
+ "JSONObject[\"stringKey\"] is not a JSONArray (class java.lang.String : hello world!).",
e.getMessage());
}
try {
@@ -1188,9 +1250,10 @@ public class JSONObjectTest {
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a JSONObject.",
+ "JSONObject[\"stringKey\"] is not a JSONObject (class java.lang.String : hello world!).",
e.getMessage());
}
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -1203,8 +1266,8 @@ public class JSONObjectTest {
String key30 = "key30";
String key31 = "key31";
JSONObject jsonObject = new JSONObject();
- jsonObject.put(key30, new Double(3.0));
- jsonObject.put(key31, new Double(3.1));
+ jsonObject.put(key30, Double.valueOf(3.0));
+ jsonObject.put(key31, Double.valueOf(3.1));
assertTrue("3.0 should remain a double",
jsonObject.getDouble(key30) == 3);
@@ -1218,6 +1281,7 @@ public class JSONObjectTest {
assertTrue("3.0 can still be interpreted as a double",
deserialized.getDouble(key30) == 3.0);
assertTrue("3.1 remains a double", deserialized.getDouble(key31) == 3.1);
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -1233,9 +1297,9 @@ public class JSONObjectTest {
* value is stored. This should be fixed.
*/
BigInteger bigInteger = new BigInteger("123456789012345678901234567890");
- JSONObject jsonObject = new JSONObject(bigInteger);
- Object obj = jsonObject.get("lowestSetBit");
- assertTrue("JSONObject only has 1 value", jsonObject.length() == 1);
+ JSONObject jsonObject0 = new JSONObject(bigInteger);
+ Object obj = jsonObject0.get("lowestSetBit");
+ assertTrue("JSONObject only has 1 value", jsonObject0.length() == 1);
assertTrue("JSONObject parses BigInteger as the Integer lowestBitSet",
obj instanceof Integer);
assertTrue("this bigInteger lowestBitSet happens to be 1",
@@ -1248,57 +1312,57 @@ public class JSONObjectTest {
*/
BigDecimal bigDecimal = new BigDecimal(
"123456789012345678901234567890.12345678901234567890123456789");
- jsonObject = new JSONObject(bigDecimal);
- assertTrue("large bigDecimal is not stored", jsonObject.isEmpty());
+ JSONObject jsonObject1 = new JSONObject(bigDecimal);
+ assertTrue("large bigDecimal is not stored", jsonObject1.isEmpty());
/**
* JSONObject put(String, Object) method stores and serializes
* bigInt and bigDec correctly. Nothing needs to change.
*/
- jsonObject = new JSONObject();
- jsonObject.put("bigInt", bigInteger);
+ JSONObject jsonObject2 = new JSONObject();
+ jsonObject2.put("bigInt", bigInteger);
assertTrue("jsonObject.put() handles bigInt correctly",
- jsonObject.get("bigInt").equals(bigInteger));
+ jsonObject2.get("bigInt").equals(bigInteger));
assertTrue("jsonObject.getBigInteger() handles bigInt correctly",
- jsonObject.getBigInteger("bigInt").equals(bigInteger));
+ jsonObject2.getBigInteger("bigInt").equals(bigInteger));
assertTrue("jsonObject.optBigInteger() handles bigInt correctly",
- jsonObject.optBigInteger("bigInt", BigInteger.ONE).equals(bigInteger));
+ jsonObject2.optBigInteger("bigInt", BigInteger.ONE).equals(bigInteger));
assertTrue("jsonObject serializes bigInt correctly",
- jsonObject.toString().equals("{\"bigInt\":123456789012345678901234567890}"));
+ jsonObject2.toString().equals("{\"bigInt\":123456789012345678901234567890}"));
assertTrue("BigInteger as BigDecimal",
- jsonObject.getBigDecimal("bigInt").equals(new BigDecimal(bigInteger)));
+ jsonObject2.getBigDecimal("bigInt").equals(new BigDecimal(bigInteger)));
- jsonObject = new JSONObject();
- jsonObject.put("bigDec", bigDecimal);
+ JSONObject jsonObject3 = new JSONObject();
+ jsonObject3.put("bigDec", bigDecimal);
assertTrue("jsonObject.put() handles bigDec correctly",
- jsonObject.get("bigDec").equals(bigDecimal));
+ jsonObject3.get("bigDec").equals(bigDecimal));
assertTrue("jsonObject.getBigDecimal() handles bigDec correctly",
- jsonObject.getBigDecimal("bigDec").equals(bigDecimal));
+ jsonObject3.getBigDecimal("bigDec").equals(bigDecimal));
assertTrue("jsonObject.optBigDecimal() handles bigDec correctly",
- jsonObject.optBigDecimal("bigDec", BigDecimal.ONE).equals(bigDecimal));
+ jsonObject3.optBigDecimal("bigDec", BigDecimal.ONE).equals(bigDecimal));
assertTrue("jsonObject serializes bigDec correctly",
- jsonObject.toString().equals(
+ jsonObject3.toString().equals(
"{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}"));
assertTrue("BigDecimal as BigInteger",
- jsonObject.getBigInteger("bigDec").equals(bigDecimal.toBigInteger()));
+ jsonObject3.getBigInteger("bigDec").equals(bigDecimal.toBigInteger()));
/**
* exercise some exceptions
*/
try {
// bigInt key does not exist
- jsonObject.getBigDecimal("bigInt");
+ jsonObject3.getBigDecimal("bigInt");
fail("expected an exeption");
} catch (JSONException ignored) {}
- obj = jsonObject.optBigDecimal("bigInt", BigDecimal.ONE);
+ obj = jsonObject3.optBigDecimal("bigInt", BigDecimal.ONE);
assertTrue("expected BigDecimal", obj.equals(BigDecimal.ONE));
- jsonObject.put("stringKey", "abc");
+ jsonObject3.put("stringKey", "abc");
try {
- jsonObject.getBigDecimal("stringKey");
+ jsonObject3.getBigDecimal("stringKey");
fail("expected an exeption");
} catch (JSONException ignored) {}
- obj = jsonObject.optBigInteger("bigDec", BigInteger.ONE);
+ obj = jsonObject3.optBigInteger("bigDec", BigInteger.ONE);
assertTrue("expected BigInteger", obj instanceof BigInteger);
assertEquals(bigDecimal.toBigInteger(), obj);
@@ -1331,79 +1395,79 @@ public class JSONObjectTest {
// bigInt map ctor
Map map = new HashMap();
map.put("bigInt", bigInteger);
- jsonObject = new JSONObject(map);
- String actualFromMapStr = jsonObject.toString();
+ JSONObject jsonObject4 = new JSONObject(map);
+ String actualFromMapStr = jsonObject4.toString();
assertTrue("bigInt in map (or array or bean) is a string",
actualFromMapStr.equals(
"{\"bigInt\":123456789012345678901234567890}"));
// bigInt put
- jsonObject = new JSONObject();
- jsonObject.put("bigInt", bigInteger);
- String actualFromPutStr = jsonObject.toString();
+ JSONObject jsonObject5 = new JSONObject();
+ jsonObject5.put("bigInt", bigInteger);
+ String actualFromPutStr = jsonObject5.toString();
assertTrue("bigInt from put is a number",
actualFromPutStr.equals(
"{\"bigInt\":123456789012345678901234567890}"));
// bigDec map ctor
map = new HashMap();
map.put("bigDec", bigDecimal);
- jsonObject = new JSONObject(map);
- actualFromMapStr = jsonObject.toString();
+ JSONObject jsonObject6 = new JSONObject(map);
+ actualFromMapStr = jsonObject6.toString();
assertTrue("bigDec in map (or array or bean) is a bigDec",
actualFromMapStr.equals(
"{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}"));
// bigDec put
- jsonObject = new JSONObject();
- jsonObject.put("bigDec", bigDecimal);
- actualFromPutStr = jsonObject.toString();
+ JSONObject jsonObject7 = new JSONObject();
+ jsonObject7.put("bigDec", bigDecimal);
+ actualFromPutStr = jsonObject7.toString();
assertTrue("bigDec from put is a number",
actualFromPutStr.equals(
"{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}"));
// bigInt,bigDec put
- JSONArray jsonArray = new JSONArray();
- jsonArray.put(bigInteger);
- jsonArray.put(bigDecimal);
- actualFromPutStr = jsonArray.toString();
+ JSONArray jsonArray0 = new JSONArray();
+ jsonArray0.put(bigInteger);
+ jsonArray0.put(bigDecimal);
+ actualFromPutStr = jsonArray0.toString();
assertTrue("bigInt, bigDec from put is a number",
actualFromPutStr.equals(
"[123456789012345678901234567890,123456789012345678901234567890.12345678901234567890123456789]"));
- assertTrue("getBigInt is bigInt", jsonArray.getBigInteger(0).equals(bigInteger));
- assertTrue("getBigDec is bigDec", jsonArray.getBigDecimal(1).equals(bigDecimal));
- assertTrue("optBigInt is bigInt", jsonArray.optBigInteger(0, BigInteger.ONE).equals(bigInteger));
- assertTrue("optBigDec is bigDec", jsonArray.optBigDecimal(1, BigDecimal.ONE).equals(bigDecimal));
- jsonArray.put(Boolean.TRUE);
+ assertTrue("getBigInt is bigInt", jsonArray0.getBigInteger(0).equals(bigInteger));
+ assertTrue("getBigDec is bigDec", jsonArray0.getBigDecimal(1).equals(bigDecimal));
+ assertTrue("optBigInt is bigInt", jsonArray0.optBigInteger(0, BigInteger.ONE).equals(bigInteger));
+ assertTrue("optBigDec is bigDec", jsonArray0.optBigDecimal(1, BigDecimal.ONE).equals(bigDecimal));
+ jsonArray0.put(Boolean.TRUE);
try {
- jsonArray.getBigInteger(2);
+ jsonArray0.getBigInteger(2);
fail("should not be able to get big int");
} catch (Exception ignored) {}
try {
- jsonArray.getBigDecimal(2);
+ jsonArray0.getBigDecimal(2);
fail("should not be able to get big dec");
} catch (Exception ignored) {}
- assertTrue("optBigInt is default", jsonArray.optBigInteger(2, BigInteger.ONE).equals(BigInteger.ONE));
- assertTrue("optBigDec is default", jsonArray.optBigDecimal(2, BigDecimal.ONE).equals(BigDecimal.ONE));
+ assertTrue("optBigInt is default", jsonArray0.optBigInteger(2, BigInteger.ONE).equals(BigInteger.ONE));
+ assertTrue("optBigDec is default", jsonArray0.optBigDecimal(2, BigDecimal.ONE).equals(BigDecimal.ONE));
// bigInt,bigDec list ctor
List list = new ArrayList();
list.add(bigInteger);
list.add(bigDecimal);
- jsonArray = new JSONArray(list);
- String actualFromListStr = jsonArray.toString();
+ JSONArray jsonArray1 = new JSONArray(list);
+ String actualFromListStr = jsonArray1.toString();
assertTrue("bigInt, bigDec in list is a bigInt, bigDec",
actualFromListStr.equals(
"[123456789012345678901234567890,123456789012345678901234567890.12345678901234567890123456789]"));
// bigInt bean ctor
MyBigNumberBean myBigNumberBean = mock(MyBigNumberBean.class);
when(myBigNumberBean.getBigInteger()).thenReturn(new BigInteger("123456789012345678901234567890"));
- jsonObject = new JSONObject(myBigNumberBean);
- String actualFromBeanStr = jsonObject.toString();
+ JSONObject jsonObject8 = new JSONObject(myBigNumberBean);
+ String actualFromBeanStr = jsonObject8.toString();
// can't do a full string compare because mockery adds an extra key/value
assertTrue("bigInt from bean ctor is a bigInt",
actualFromBeanStr.contains("123456789012345678901234567890"));
// bigDec bean ctor
myBigNumberBean = mock(MyBigNumberBean.class);
when(myBigNumberBean.getBigDecimal()).thenReturn(new BigDecimal("123456789012345678901234567890.12345678901234567890123456789"));
- jsonObject = new JSONObject(myBigNumberBean);
- actualFromBeanStr = jsonObject.toString();
+ jsonObject8 = new JSONObject(myBigNumberBean);
+ actualFromBeanStr = jsonObject8.toString();
// can't do a full string compare because mockery adds an extra key/value
assertTrue("bigDec from bean ctor is a bigDec",
actualFromBeanStr.contains("123456789012345678901234567890.12345678901234567890123456789"));
@@ -1412,7 +1476,12 @@ public class JSONObjectTest {
assertTrue("wrap() returns big num",obj.equals(bigInteger));
obj = JSONObject.wrap(bigDecimal);
assertTrue("wrap() returns string",obj.equals(bigDecimal));
-
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObject0, jsonObject1, jsonObject2, jsonObject3, jsonObject4,
+ jsonObject5, jsonObject6, jsonObject7, jsonObject8
+ )));
+ Util.checkJSONArrayMaps(jsonArray0, jsonObject0.getMapType());
+ Util.checkJSONArrayMaps(jsonArray1, jsonObject0.getMapType());
}
/**
@@ -1424,7 +1493,6 @@ public class JSONObjectTest {
*/
@Test
public void jsonObjectNames() {
- JSONObject jsonObject;
// getNames() from null JSONObject
assertTrue("null names from null Object",
@@ -1435,16 +1503,16 @@ public class JSONObjectTest {
null == JSONObject.getNames(new MyJsonString()));
// getNames from new JSONOjbect
- jsonObject = new JSONObject();
- String [] names = JSONObject.getNames(jsonObject);
+ JSONObject jsonObject0 = new JSONObject();
+ String [] names = JSONObject.getNames(jsonObject0);
assertTrue("names should be null", names == null);
// getNames() from empty JSONObject
String emptyStr = "{}";
- jsonObject = new JSONObject(emptyStr);
+ JSONObject jsonObject1 = new JSONObject(emptyStr);
assertTrue("empty JSONObject should have null names",
- null == JSONObject.getNames(jsonObject));
+ null == JSONObject.getNames(jsonObject1));
// getNames() from JSONObject
String str =
@@ -1453,13 +1521,13 @@ public class JSONObjectTest {
"\"falseKey\":false,"+
"\"stringKey\":\"hello world!\","+
"}";
- jsonObject = new JSONObject(str);
- names = JSONObject.getNames(jsonObject);
- JSONArray jsonArray = new JSONArray(names);
+ JSONObject jsonObject2 = new JSONObject(str);
+ names = JSONObject.getNames(jsonObject2);
+ JSONArray jsonArray0 = new JSONArray(names);
// validate JSON
Object doc = Configuration.defaultConfiguration().jsonProvider()
- .parse(jsonArray.toString());
+ .parse(jsonArray0.toString());
List> docList = JsonPath.read(doc, "$");
assertTrue("expected 3 items", docList.size() == 3);
assertTrue(
@@ -1480,9 +1548,9 @@ public class JSONObjectTest {
names = JSONObject.getNames(myEnumField);
// validate JSON
- jsonArray = new JSONArray(names);
+ JSONArray jsonArray1 = new JSONArray(names);
doc = Configuration.defaultConfiguration().jsonProvider()
- .parse(jsonArray.toString());
+ .parse(jsonArray1.toString());
docList = JsonPath.read(doc, "$");
assertTrue("expected 3 items", docList.size() == 3);
assertTrue(
@@ -1504,9 +1572,9 @@ public class JSONObjectTest {
names = JSONObject.getNames(myPublicClass);
// validate JSON
- jsonArray = new JSONArray(names);
+ JSONArray jsonArray2 = new JSONArray(names);
doc = Configuration.defaultConfiguration().jsonProvider()
- .parse(jsonArray.toString());
+ .parse(jsonArray2.toString());
docList = JsonPath.read(doc, "$");
assertTrue("expected 2 items", docList.size() == 2);
assertTrue(
@@ -1515,6 +1583,12 @@ public class JSONObjectTest {
assertTrue(
"expected to find publicInt",
((List>) JsonPath.read(doc, "$[?(@=='publicInt')]")).size() == 1);
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObject0, jsonObject1, jsonObject2
+ )));
+ Util.checkJSONArrayMaps(jsonArray0, jsonObject0.getMapType());
+ Util.checkJSONArrayMaps(jsonArray1, jsonObject0.getMapType());
+ Util.checkJSONArrayMaps(jsonArray2, jsonObject0.getMapType());
}
/**
@@ -1526,6 +1600,8 @@ public class JSONObjectTest {
JSONObject jsonObject = new JSONObject();
JSONArray jsonArray = jsonObject.names();
assertTrue("jsonArray should be null", jsonArray == null);
+ Util.checkJSONObjectMaps(jsonObject);
+ Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
}
/**
@@ -1550,6 +1626,8 @@ public class JSONObjectTest {
assertTrue("expected to find trueKey", ((List>) JsonPath.read(doc, "$[?(@=='trueKey')]")).size() == 1);
assertTrue("expected to find falseKey", ((List>) JsonPath.read(doc, "$[?(@=='falseKey')]")).size() == 1);
assertTrue("expected to find stringKey", ((List>) JsonPath.read(doc, "$[?(@=='stringKey')]")).size() == 1);
+ Util.checkJSONObjectMaps(jsonObject);
+ Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
}
/**
@@ -1642,19 +1720,19 @@ public class JSONObjectTest {
*/
assertFalse("Document unexpected behaviour with explicit type-casting float as double!", (double)0.2f == 0.2d );
assertFalse("Document unexpected behaviour with implicit type-cast!", 0.2f == 0.2d );
- Double d1 = new Double( 1.1f );
- Double d2 = new Double( "1.1f" );
+ Double d1 = Double.valueOf( 1.1f );
+ Double d2 = Double.valueOf( "1.1f" );
assertFalse( "Document implicit type cast from float to double before calling Double(double d) constructor", d1.equals( d2 ) );
- assertTrue( "Correctly converting float to double via base10 (string) representation!", new Double( 3.1d ).equals( new Double( new Float( 3.1f ).toString() ) ) );
+ assertTrue( "Correctly converting float to double via base10 (string) representation!", Double.valueOf( 3.1d ).equals( Double.valueOf( Float.valueOf( 3.1f ).toString() ) ) );
// Pinpointing the not so obvious "buggy" conversion from float to double in JSONObject
JSONObject jo = new JSONObject();
jo.put( "bug", 3.1f ); // will call put( String key, double value ) with implicit and "buggy" type-cast from float to double
- assertFalse( "The java-compiler did add some zero bits for you to the mantissa (unexpected, but well documented)", jo.get( "bug" ).equals( new Double( 3.1d ) ) );
+ assertFalse( "The java-compiler did add some zero bits for you to the mantissa (unexpected, but well documented)", jo.get( "bug" ).equals( Double.valueOf( 3.1d ) ) );
JSONObject inc = new JSONObject();
- inc.put( "bug", new Float( 3.1f ) ); // This will put in instance of Float into JSONObject, i.e. call put( String key, Object value )
+ inc.put( "bug", Float.valueOf( 3.1f ) ); // This will put in instance of Float into JSONObject, i.e. call put( String key, Object value )
assertTrue( "Everything is ok here!", inc.get( "bug" ) instanceof Float );
inc.increment( "bug" ); // after adding 1, increment will call put( String key, double value ) with implicit and "buggy" type-cast from float to double!
// this.put(key, (Float) value + 1);
@@ -1667,8 +1745,10 @@ public class JSONObjectTest {
// correct implementation (with change of behavior) would be:
// this.put(key, new Float((Float) value + 1));
// Probably it would be better to deprecate the method and remove some day, while convenient processing the "payload" is not
- // really in the the scope of a JSON-library (IMHO.)
-
+ // really in the scope of a JSON-library (IMHO.)
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObject, inc
+ )));
}
/**
@@ -1766,6 +1846,12 @@ public class JSONObjectTest {
JSONObject bCompareArrayJsonObject = new JSONObject(bCompareArrayStr);
assertTrue("different nested JSONArrays should not be similar",
!aCompareArrayJsonObject.similar(bCompareArrayJsonObject));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObject, expectedJsonObject, aCompareValueJsonObject,
+ aCompareArrayJsonObject, aCompareObjectJsonObject, aCompareArrayJsonObject,
+ bCompareValueJsonObject, bCompareArrayJsonObject, bCompareObjectJsonObject,
+ bCompareArrayJsonObject
+ )));
}
/**
@@ -1801,6 +1887,7 @@ public class JSONObjectTest {
assertTrue("expected myVal2", "myVal2".equals(jsonObject.query("/objectKey/myKey2")));
assertTrue("expected myVal3", "myVal3".equals(jsonObject.query("/objectKey/myKey3")));
assertTrue("expected myVal4", "myVal4".equals(jsonObject.query("/objectKey/myKey4")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -1874,6 +1961,9 @@ public class JSONObjectTest {
JSONObject jo = new JSONObject().put("TABLE", new JSONObject().put("yhoo", new JSONObject()));
assertEquals("toString(2)","{\"TABLE\": {\"yhoo\": {}}}", jo.toString(2));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObject, jo
+ )));
}
/**
@@ -1886,7 +1976,7 @@ public class JSONObjectTest {
@Test
public void jsonObjectToStringSuppressWarningOnCastToMap() {
JSONObject jsonObject = new JSONObject();
- Map map = new HashMap();
+ Map map = new HashMap<>();
map.put("abc", "def");
jsonObject.put("key", map);
@@ -1895,6 +1985,7 @@ public class JSONObjectTest {
assertTrue("expected 1 top level item", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 1);
assertTrue("expected 1 key item", ((Map,?>)(JsonPath.read(doc, "$.key"))).size() == 1);
assertTrue("expected def", "def".equals(jsonObject.query("/key/abc")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -1917,6 +2008,7 @@ public class JSONObjectTest {
assertTrue("expected 1 top level item", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 1);
assertTrue("expected 1 key item", ((List>)(JsonPath.read(doc, "$.key"))).size() == 1);
assertTrue("expected abc", "abc".equals(jsonObject.query("/key/0")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -1942,7 +2034,9 @@ public class JSONObjectTest {
"}";
JSONObject jsonObject = new JSONObject(jsonObjectStr);
assertTrue("jsonObject valueToString() incorrect",
- JSONObject.valueToString(jsonObject).equals(jsonObject.toString()));
+ new JSONObject(JSONObject.valueToString(jsonObject))
+ .similar(new JSONObject(jsonObject.toString()))
+ );
String jsonArrayStr =
"[1,2,3]";
JSONArray jsonArray = new JSONArray(jsonArrayStr);
@@ -1953,18 +2047,21 @@ public class JSONObjectTest {
map.put("key2", "val2");
map.put("key3", "val3");
assertTrue("map valueToString() incorrect",
- jsonObject.toString().equals(JSONObject.valueToString(map)));
+ new JSONObject(jsonObject.toString())
+ .similar(new JSONObject(JSONObject.valueToString(map))));
Collection collection = new ArrayList();
- collection.add(new Integer(1));
- collection.add(new Integer(2));
- collection.add(new Integer(3));
+ collection.add(Integer.valueOf(1));
+ collection.add(Integer.valueOf(2));
+ collection.add(Integer.valueOf(3));
assertTrue("collection valueToString() expected: "+
jsonArray.toString()+ " actual: "+
JSONObject.valueToString(collection),
jsonArray.toString().equals(JSONObject.valueToString(collection)));
- Integer[] array = { new Integer(1), new Integer(2), new Integer(3) };
+ Integer[] array = { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3) };
assertTrue("array valueToString() incorrect",
- jsonArray.toString().equals(JSONObject.valueToString(array)));
+ jsonArray.toString().equals(JSONObject.valueToString(array)));
+ Util.checkJSONObjectMaps(jsonObject);
+ Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
}
/**
@@ -1998,7 +2095,7 @@ public class JSONObjectTest {
JSONObject.NULL == JSONObject.wrap(null));
// wrap(Integer) returns Integer
- Integer in = new Integer(1);
+ Integer in = Integer.valueOf(1);
assertTrue("Integer wrap() incorrect",
in == JSONObject.wrap(in));
@@ -2025,9 +2122,9 @@ public class JSONObjectTest {
// wrap collection returns JSONArray
Collection collection = new ArrayList();
- collection.add(new Integer(1));
- collection.add(new Integer(2));
- collection.add(new Integer(3));
+ collection.add(Integer.valueOf(1));
+ collection.add(Integer.valueOf(2));
+ collection.add(Integer.valueOf(3));
JSONArray jsonArray = (JSONArray) (JSONObject.wrap(collection));
// validate JSON
@@ -2038,7 +2135,7 @@ public class JSONObjectTest {
assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2")));
// wrap Array returns JSONArray
- Integer[] array = { new Integer(1), new Integer(2), new Integer(3) };
+ Integer[] array = { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3) };
JSONArray integerArrayJsonArray = (JSONArray)(JSONObject.wrap(array));
// validate JSON
@@ -2068,6 +2165,11 @@ public class JSONObjectTest {
assertTrue("expected val1", "val1".equals(mapJsonObject.query("/key1")));
assertTrue("expected val2", "val2".equals(mapJsonObject.query("/key2")));
assertTrue("expected val3", "val3".equals(mapJsonObject.query("/key3")));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObject, mapJsonObject
+ )));
+ Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
+ Util.checkJSONArrayMaps(integerArrayJsonArray, jsonObject.getMapType());
}
@@ -2082,6 +2184,7 @@ public class JSONObjectTest {
try {
JSONObject jo = new JSONObject(source);
assertTrue("Expected "+charString+"("+i+") in the JSON Object but did not find it.",charString.equals(jo.getString("key")));
+ Util.checkJSONObjectMaps(jo);
} catch (JSONException ex) {
assertTrue("Only \\0 (U+0000), \\n (U+000A), and \\r (U+000D) should cause an error. Instead "+charString+"("+i+") caused an error",
i=='\0' || i=='\n' || i=='\r'
@@ -2132,6 +2235,51 @@ public class JSONObjectTest {
"Expected a ',' or '}' at 15 [character 16 line 1]",
e.getMessage());
}
+ try {
+ // key is a nested map
+ String str = "{{\"foo\": \"bar\"}: \"baz\"}";
+ assertNull("Expected an exception",new JSONObject(str));
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Missing value at 1 [character 2 line 1]",
+ e.getMessage());
+ }
+ try {
+ // key is a nested array containing a map
+ String str = "{\"a\": 1, [{\"foo\": \"bar\"}]: \"baz\"}";
+ assertNull("Expected an exception",new JSONObject(str));
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Missing value at 9 [character 10 line 1]",
+ e.getMessage());
+ }
+ try {
+ // key contains }
+ String str = "{foo}: 2}";
+ assertNull("Expected an exception",new JSONObject(str));
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Expected a ':' after a key at 5 [character 6 line 1]",
+ e.getMessage());
+ }
+ try {
+ // key contains ]
+ String str = "{foo]: 2}";
+ assertNull("Expected an exception",new JSONObject(str));
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Expected a ':' after a key at 5 [character 6 line 1]",
+ e.getMessage());
+ }
+ try {
+ // \0 after ,
+ String str = "{\"myKey\":true, \0\"myOtherKey\":false}";
+ assertNull("Expected an exception",new JSONObject(str));
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "A JSONObject text must end with '}' at 15 [character 16 line 1]",
+ e.getMessage());
+ }
try {
// append to wrong key
String str = "{\"myKey\":true, \"myOtherKey\":false}";
@@ -2381,6 +2529,7 @@ public class JSONObjectTest {
assertTrue("jsonObject should be empty", jsonObject.isEmpty());
jsonObject.putOnce(null, "");
assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -2398,24 +2547,37 @@ public class JSONObjectTest {
BigInteger.TEN.compareTo(jsonObject.optBigInteger("myKey",BigInteger.TEN ))==0);
assertTrue("optBoolean() should return default boolean",
jsonObject.optBoolean("myKey", true));
+ assertTrue("optBooleanObject() should return default Boolean",
+ jsonObject.optBooleanObject("myKey", true));
assertTrue("optInt() should return default int",
42 == jsonObject.optInt("myKey", 42));
+ assertTrue("optIntegerObject() should return default Integer",
+ Integer.valueOf(42).equals(jsonObject.optIntegerObject("myKey", 42)));
assertTrue("optEnum() should return default Enum",
MyEnum.VAL1.equals(jsonObject.optEnum(MyEnum.class, "myKey", MyEnum.VAL1)));
assertTrue("optJSONArray() should return null ",
null==jsonObject.optJSONArray("myKey"));
- assertTrue("optJSONObject() should return null ",
- null==jsonObject.optJSONObject("myKey"));
+ assertTrue("optJSONArray() should return default JSONArray",
+ "value".equals(jsonObject.optJSONArray("myKey", new JSONArray("[\"value\"]")).getString(0)));
+ assertTrue("optJSONObject() should return default JSONObject ",
+ jsonObject.optJSONObject("myKey", new JSONObject("{\"testKey\":\"testValue\"}")).getString("testKey").equals("testValue"));
assertTrue("optLong() should return default long",
42l == jsonObject.optLong("myKey", 42l));
+ assertTrue("optLongObject() should return default Long",
+ Long.valueOf(42l).equals(jsonObject.optLongObject("myKey", 42l)));
assertTrue("optDouble() should return default double",
42.3d == jsonObject.optDouble("myKey", 42.3d));
+ assertTrue("optDoubleObject() should return default Double",
+ Double.valueOf(42.3d).equals(jsonObject.optDoubleObject("myKey", 42.3d)));
assertTrue("optFloat() should return default float",
42.3f == jsonObject.optFloat("myKey", 42.3f));
+ assertTrue("optFloatObject() should return default Float",
+ Float.valueOf(42.3f).equals(jsonObject.optFloatObject("myKey", 42.3f)));
assertTrue("optNumber() should return default Number",
42l == jsonObject.optNumber("myKey", Long.valueOf(42)).longValue());
assertTrue("optString() should return default string",
"hi".equals(jsonObject.optString("hiKey", "hi")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -2434,24 +2596,37 @@ public class JSONObjectTest {
BigInteger.TEN.compareTo(jsonObject.optBigInteger("myKey",BigInteger.TEN ))==0);
assertTrue("optBoolean() should return default boolean",
jsonObject.optBoolean("myKey", true));
+ assertTrue("optBooleanObject() should return default Boolean",
+ jsonObject.optBooleanObject("myKey", true));
assertTrue("optInt() should return default int",
42 == jsonObject.optInt("myKey", 42));
+ assertTrue("optIntegerObject() should return default Integer",
+ Integer.valueOf(42).equals(jsonObject.optIntegerObject("myKey", 42)));
assertTrue("optEnum() should return default Enum",
MyEnum.VAL1.equals(jsonObject.optEnum(MyEnum.class, "myKey", MyEnum.VAL1)));
+ assertTrue("optJSONArray() should return default JSONArray",
+ "value".equals(jsonObject.optJSONArray("myKey", new JSONArray("[\"value\"]")).getString(0)));
assertTrue("optJSONArray() should return null ",
null==jsonObject.optJSONArray("myKey"));
- assertTrue("optJSONObject() should return null ",
- null==jsonObject.optJSONObject("myKey"));
+ assertTrue("optJSONObject() should return default JSONObject ",
+ jsonObject.optJSONObject("myKey", new JSONObject("{\"testKey\":\"testValue\"}")).getString("testKey").equals("testValue"));
assertTrue("optLong() should return default long",
42l == jsonObject.optLong("myKey", 42l));
+ assertTrue("optLongObject() should return default Long",
+ Long.valueOf(42l).equals(jsonObject.optLongObject("myKey", 42l)));
assertTrue("optDouble() should return default double",
42.3d == jsonObject.optDouble("myKey", 42.3d));
+ assertTrue("optDoubleObject() should return default Double",
+ Double.valueOf(42.3d).equals(jsonObject.optDoubleObject("myKey", 42.3d)));
assertTrue("optFloat() should return default float",
42.3f == jsonObject.optFloat("myKey", 42.3f));
+ assertTrue("optFloatObject() should return default Float",
+ Float.valueOf(42.3f).equals(jsonObject.optFloatObject("myKey", 42.3f)));
assertTrue("optNumber() should return default Number",
42l == jsonObject.optNumber("myKey", Long.valueOf(42)).longValue());
assertTrue("optString() should return default string",
"hi".equals(jsonObject.optString("hiKey", "hi")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -2461,15 +2636,22 @@ public class JSONObjectTest {
public void jsonObjectOptStringConversion() {
JSONObject jo = new JSONObject("{\"int\":\"123\",\"true\":\"true\",\"false\":\"false\"}");
assertTrue("unexpected optBoolean value",jo.optBoolean("true",false)==true);
+ assertTrue("unexpected optBooleanObject value",Boolean.valueOf(true).equals(jo.optBooleanObject("true",false)));
assertTrue("unexpected optBoolean value",jo.optBoolean("false",true)==false);
+ assertTrue("unexpected optBooleanObject value",Boolean.valueOf(false).equals(jo.optBooleanObject("false",true)));
assertTrue("unexpected optInt value",jo.optInt("int",0)==123);
+ assertTrue("unexpected optIntegerObject value",Integer.valueOf(123).equals(jo.optIntegerObject("int",0)));
assertTrue("unexpected optLong value",jo.optLong("int",0)==123l);
+ assertTrue("unexpected optLongObject value",Long.valueOf(123l).equals(jo.optLongObject("int",0L)));
assertTrue("unexpected optDouble value",jo.optDouble("int",0.0d)==123.0d);
+ assertTrue("unexpected optDoubleObject value",Double.valueOf(123.0d).equals(jo.optDoubleObject("int",0.0d)));
assertTrue("unexpected optFloat value",jo.optFloat("int",0.0f)==123.0f);
+ assertTrue("unexpected optFloatObject value",Float.valueOf(123.0f).equals(jo.optFloatObject("int",0.0f)));
assertTrue("unexpected optBigInteger value",jo.optBigInteger("int",BigInteger.ZERO).compareTo(new BigInteger("123"))==0);
assertTrue("unexpected optBigDecimal value",jo.optBigDecimal("int",BigDecimal.ZERO).compareTo(new BigDecimal("123"))==0);
assertTrue("unexpected optBigDecimal value",jo.optBigDecimal("int",BigDecimal.ZERO).compareTo(new BigDecimal("123"))==0);
assertTrue("unexpected optNumber value",jo.optNumber("int",BigInteger.ZERO).longValue()==123l);
+ Util.checkJSONObjectMaps(jo);
}
/**
@@ -2485,25 +2667,38 @@ public class JSONObjectTest {
assertEquals(new BigDecimal("19007199254740993.35481234487103587486413587843213584"), jo.optBigDecimal("largeNumber",null));
assertEquals(new BigInteger("19007199254740993"), jo.optBigInteger("largeNumber",null));
assertEquals(1.9007199254740992E16, jo.optDouble("largeNumber"),0.0);
+ assertEquals(1.9007199254740992E16, jo.optDoubleObject("largeNumber"),0.0);
assertEquals(1.90071995E16f, jo.optFloat("largeNumber"),0.0f);
+ assertEquals(1.90071995E16f, jo.optFloatObject("largeNumber"),0.0f);
assertEquals(19007199254740993l, jo.optLong("largeNumber"));
+ assertEquals(Long.valueOf(19007199254740993l), jo.optLongObject("largeNumber"));
assertEquals(1874919425, jo.optInt("largeNumber"));
+ assertEquals(Integer.valueOf(1874919425), jo.optIntegerObject("largeNumber"));
// conversion from a string
assertEquals(new BigDecimal("19007199254740993.35481234487103587486413587843213584"), jo.optBigDecimal("largeNumberStr",null));
assertEquals(new BigInteger("19007199254740993"), jo.optBigInteger("largeNumberStr",null));
assertEquals(1.9007199254740992E16, jo.optDouble("largeNumberStr"),0.0);
+ assertEquals(1.9007199254740992E16, jo.optDoubleObject("largeNumberStr"),0.0);
assertEquals(1.90071995E16f, jo.optFloat("largeNumberStr"),0.0f);
+ assertEquals(1.90071995E16f, jo.optFloatObject("largeNumberStr"),0.0f);
assertEquals(19007199254740993l, jo.optLong("largeNumberStr"));
+ assertEquals(Long.valueOf(19007199254740993l), jo.optLongObject("largeNumberStr"));
assertEquals(1874919425, jo.optInt("largeNumberStr"));
+ assertEquals(Integer.valueOf(1874919425), jo.optIntegerObject("largeNumberStr"));
// the integer portion of the actual value is larger than a double can hold.
assertNotEquals((long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optLong("largeNumber"));
+ assertNotEquals(Long.valueOf((long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584")), jo.optLongObject("largeNumber"));
assertNotEquals((int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optInt("largeNumber"));
+ assertNotEquals(Integer.valueOf((int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584")), jo.optIntegerObject("largeNumber"));
assertNotEquals((long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optLong("largeNumberStr"));
+ assertNotEquals(Long.valueOf((long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584")), jo.optLongObject("largeNumberStr"));
assertNotEquals((int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optInt("largeNumberStr"));
+ assertNotEquals(Integer.valueOf((int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584")), jo.optIntegerObject("largeNumberStr"));
assertEquals(19007199254740992l, (long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"));
assertEquals(2147483647, (int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"));
+ Util.checkJSONObjectMaps(jo);
}
/**
@@ -2526,6 +2721,7 @@ public class JSONObjectTest {
assertNull(jo.optBigDecimal("nullVal", null));
assertEquals(jo.optBigDecimal("float", null),jo.getBigDecimal("float"));
assertEquals(jo.optBigDecimal("double", null),jo.getBigDecimal("double"));
+ Util.checkJSONObjectMaps(jo);
}
/**
@@ -2546,6 +2742,7 @@ public class JSONObjectTest {
assertEquals(new BigInteger("1234"),jo.optBigInteger("bigInteger", null));
assertEquals(new BigInteger("1234"),jo.optBigInteger("bigDecimal", null));
assertNull(jo.optBigDecimal("nullVal", null));
+ Util.checkJSONObjectMaps(jo);
}
/**
@@ -2563,8 +2760,9 @@ public class JSONObjectTest {
JSONObject jsonObjectPutNull = new JSONObject(str);
jsonObjectPutNull.put("myKey", (Object) null);
assertTrue("jsonObject should be empty", jsonObjectPutNull.isEmpty());
-
-
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObjectRemove, jsonObjectPutNull
+ )));
}
/**
@@ -2649,6 +2847,7 @@ public class JSONObjectTest {
} finally {
stringWriter.close();
}
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -2731,7 +2930,7 @@ public class JSONObjectTest {
writer.close();
} catch (Exception e) {}
}
-
+ Util.checkJSONObjectMaps(jsonObject);
}
@@ -2799,6 +2998,7 @@ public class JSONObjectTest {
stringWriter.close();
} catch (Exception e) {}
}
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -2841,6 +3041,7 @@ public class JSONObjectTest {
JSONObject aJsonObject = new JSONObject(str);
assertTrue("Same JSONObject should be equal to itself",
aJsonObject.equals(aJsonObject));
+ Util.checkJSONObjectMaps(aJsonObject);
}
/**
@@ -2926,6 +3127,9 @@ public class JSONObjectTest {
"null ".equals(sJONull));
String sNull = XML.toString(jsonObjectNull);
assertTrue("null should emit an empty string", "".equals(sNull));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObjectJONull, jsonObjectNull
+ )));
}
@Test(expected = JSONPointerException.class)
@@ -3023,6 +3227,7 @@ public class JSONObjectTest {
// assert that the new map is mutable
assertTrue("Removing a key should succeed", map.remove("key3") != null);
assertTrue("Map should have 2 elements", map.size() == 2);
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -3047,6 +3252,9 @@ public class JSONObjectTest {
// ensure our original jo hasn't changed.
assertEquals(0, jo.get("someInt"));
assertEquals(null, jo.opt("someString"));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jo, jo2
+ )));
}
/**
@@ -3071,6 +3279,9 @@ public class JSONObjectTest {
// ensure our original jo hasn't changed.
assertEquals(0, jo.get("someInt"));
assertEquals(null, jo.opt("someString"));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jo, jo2
+ )));
}
/**
@@ -3079,13 +3290,14 @@ public class JSONObjectTest {
@SuppressWarnings("boxing")
@Test
public void testGenericBean() {
- GenericBean bean = new GenericBean(42);
+ GenericBean bean = new GenericBean<>(42);
final JSONObject jo = new JSONObject(bean);
assertEquals(jo.keySet().toString(), 8, jo.length());
assertEquals(42, jo.get("genericValue"));
assertEquals("Expected the getter to only be called once",
1, bean.genericGetCounter);
assertEquals(0, bean.genericSetCounter);
+ Util.checkJSONObjectMaps(jo);
}
/**
@@ -3101,6 +3313,7 @@ public class JSONObjectTest {
assertEquals("Expected the getter to only be called once",
1, bean.genericGetCounter);
assertEquals(0, bean.genericSetCounter);
+ Util.checkJSONObjectMaps(jo);
}
/**
@@ -3119,12 +3332,14 @@ public class JSONObjectTest {
assertEquals("Expected 1 key to be mapped. Instead found: "+jo.keySet().toString(),
1, jo.length());
assertNotNull(jo.get("ALL"));
+ Util.checkJSONObjectMaps(jo);
}
/**
* Sample test case from https://github.com/stleary/JSON-java/issues/531
* which verifies that no regression in double/BigDecimal support is present.
*/
+ @Test
public void testObjectToBigDecimal() {
double value = 1412078745.01074;
Reader reader = new StringReader("[{\"value\": " + value + "}]");
@@ -3136,6 +3351,8 @@ public class JSONObjectTest {
BigDecimal wantedValue = BigDecimal.valueOf(value);
assertEquals(current, wantedValue);
+ Util.checkJSONObjectMaps(jsonObject);
+ Util.checkJSONArrayMaps(array, jsonObject.getMapType());
}
/**
@@ -3149,6 +3366,7 @@ public class JSONObjectTest {
1, jo.length());
assertTrue(jo.get("closeable") instanceof JSONObject);
assertTrue(jo.getJSONObject("closeable").has("string"));
+ Util.checkJSONObjectMaps(jo);
}
@Test(expected=NullPointerException.class)
@@ -3207,6 +3425,122 @@ public class JSONObjectTest {
jsonObject.put(null, new Object());
fail("Expected an exception");
}
+ @Test(expected=JSONException.class)
+ public void testSelfRecursiveObject() {
+ // A -> A ...
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ ObjA.setRef(ObjA);
+ new JSONObject(ObjA);
+ fail("Expected an exception");
+ }
+ @Test(expected=JSONException.class)
+ public void testLongSelfRecursiveObject() {
+ // B -> A -> A ...
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ ObjB.setRef(ObjA);
+ ObjA.setRef(ObjA);
+ new JSONObject(ObjB);
+ fail("Expected an exception");
+ }
+ @Test(expected=JSONException.class)
+ public void testSimpleRecursiveObject() {
+ // B -> A -> B ...
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ ObjB.setRef(ObjA);
+ ObjA.setRef(ObjB);
+ new JSONObject(ObjA);
+ fail("Expected an exception");
+ }
+ @Test(expected=JSONException.class)
+ public void testLongRecursiveObject() {
+ // D -> C -> B -> A -> D ...
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ RecursiveBean ObjC = new RecursiveBean("ObjC");
+ RecursiveBean ObjD = new RecursiveBean("ObjD");
+ ObjC.setRef(ObjB);
+ ObjB.setRef(ObjA);
+ ObjD.setRef(ObjC);
+ ObjA.setRef(ObjD);
+ new JSONObject(ObjB);
+ fail("Expected an exception");
+ }
+ @Test(expected=JSONException.class)
+ public void testRepeatObjectRecursive() {
+ // C -> B -> A -> D -> C ...
+ // -> D -> C ...
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ RecursiveBean ObjC = new RecursiveBean("ObjC");
+ RecursiveBean ObjD = new RecursiveBean("ObjD");
+ ObjC.setRef(ObjB);
+ ObjB.setRef(ObjA);
+ ObjB.setRef2(ObjD);
+ ObjA.setRef(ObjD);
+ ObjD.setRef(ObjC);
+ new JSONObject(ObjC);
+ fail("Expected an exception");
+ }
+ @Test
+ public void testRepeatObjectNotRecursive() {
+ // C -> B -> A
+ // -> A
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ RecursiveBean ObjC = new RecursiveBean("ObjC");
+ ObjC.setRef(ObjA);
+ ObjB.setRef(ObjA);
+ ObjB.setRef2(ObjA);
+ JSONObject j0 = new JSONObject(ObjC);
+ JSONObject j1 = new JSONObject(ObjB);
+ JSONObject j2 = new JSONObject(ObjA);
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ j0, j1, j2
+ )));
+ }
+ @Test
+ public void testLongRepeatObjectNotRecursive() {
+ // C -> B -> A -> D -> E
+ // -> D -> E
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ RecursiveBean ObjC = new RecursiveBean("ObjC");
+ RecursiveBean ObjD = new RecursiveBean("ObjD");
+ RecursiveBean ObjE = new RecursiveBean("ObjE");
+ ObjC.setRef(ObjB);
+ ObjB.setRef(ObjA);
+ ObjB.setRef2(ObjD);
+ ObjA.setRef(ObjD);
+ ObjD.setRef(ObjE);
+ JSONObject j0 = new JSONObject(ObjC);
+ JSONObject j1 = new JSONObject(ObjB);
+ JSONObject j2 = new JSONObject(ObjA);
+ JSONObject j3 = new JSONObject(ObjD);
+ JSONObject j4 = new JSONObject(ObjE);
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ j0, j1, j2, j3, j4
+ )));
+ }
+ @Test(expected=JSONException.class)
+ public void testRecursiveEquals() {
+ RecursiveBeanEquals a = new RecursiveBeanEquals("same");
+ a.setRef(a);
+ JSONObject j0 = new JSONObject(a);
+ Util.checkJSONObjectMaps(j0);
+ }
+ @Test
+ public void testNotRecursiveEquals() {
+ RecursiveBeanEquals a = new RecursiveBeanEquals("same");
+ RecursiveBeanEquals b = new RecursiveBeanEquals("same");
+ RecursiveBeanEquals c = new RecursiveBeanEquals("same");
+ a.setRef(b);
+ b.setRef(c);
+ JSONObject j0 = new JSONObject(a);
+ Util.checkJSONObjectMaps(j0);
+ }
+
@Test
public void testIssue548ObjectWithEmptyJsonArray() {
@@ -3214,6 +3548,7 @@ public class JSONObjectTest {
assertTrue("missing expected key 'empty_json_array'", jsonObject.has("empty_json_array"));
assertNotNull("'empty_json_array' should be an array", jsonObject.getJSONArray("empty_json_array"));
assertEquals("'empty_json_array' should have a length of 0", 0, jsonObject.getJSONArray("empty_json_array").length());
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -3229,5 +3564,230 @@ public class JSONObjectTest {
jsonObject.clear(); //Clears the JSONObject
assertTrue("expected jsonObject.length() == 0", jsonObject.length() == 0); //Check if its length is 0
jsonObject.getInt("key1"); //Should throws org.json.JSONException: JSONObject["asd"] not found
+ Util.checkJSONObjectMaps(jsonObject);
}
+
+ /**
+ * Tests for stack overflow. See https://github.com/stleary/JSON-java/issues/654
+ */
+ @Test(expected = JSONException.class)
+ public void issue654StackOverflowInput() {
+ //String base64Bytes ="eyJHWiI6Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMCkwLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMCkwLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sJe3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTApMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sJe3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTApMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMCkwLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMCkwLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sJe3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTApMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sJe3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTApMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7c3t7e3t7e3vPAAAAAAAAAHt7e3t7e3t7e3t7e3t7e3t7e3t7e1ste3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e88AAAAAAAAAe3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7f3syMv//e3t7e3t7e3t7e3t7e3sx//////8=";
+ //String input = new String(java.util.Base64.getDecoder().decode(base64Bytes));
+ String input = "{\"GZ\":[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{s{{{{{{{";
+ JSONObject json_input = new JSONObject(input);
+ assertNotNull(json_input);
+ fail("Excepected Exception.");
+ Util.checkJSONObjectMaps(json_input);
+ }
+
+ /**
+ * Tests for incorrect object/array nesting. See https://github.com/stleary/JSON-java/issues/654
+ */
+ @Test(expected = JSONException.class)
+ public void issue654IncorrectNestingNoKey1() {
+ JSONObject json_input = new JSONObject("{{\"a\":0}}");
+ assertNotNull(json_input);
+ fail("Expected Exception.");
+ }
+
+ /**
+ * Tests for incorrect object/array nesting. See https://github.com/stleary/JSON-java/issues/654
+ */
+ @Test(expected = JSONException.class)
+ public void issue654IncorrectNestingNoKey2() {
+ JSONObject json_input = new JSONObject("{[\"a\"]}");
+ assertNotNull(json_input);
+ fail("Excepected Exception.");
+ }
+
+ /**
+ * Tests for stack overflow. See https://github.com/stleary/JSON-java/issues/654
+ */
+ @Ignore("This test relies on system constraints and may not always pass. See: https://github.com/stleary/JSON-java/issues/821")
+ @Test(expected = JSONException.class)
+ public void issue654StackOverflowInputWellFormed() {
+ //String input = new String(java.util.Base64.getDecoder().decode(base64Bytes));
+ final InputStream resourceAsStream = JSONObjectTest.class.getClassLoader().getResourceAsStream("Issue654WellFormedObject.json");
+ JSONTokener tokener = new JSONTokener(resourceAsStream);
+ JSONObject json_input = new JSONObject(tokener);
+ assertNotNull(json_input);
+ fail("Excepected Exception due to stack overflow.");
+ }
+
+ @Test
+ public void testIssue682SimilarityOfJSONString() {
+ JSONObject jo1 = new JSONObject()
+ .put("a", new MyJsonString())
+ .put("b", 2);
+ JSONObject jo2 = new JSONObject()
+ .put("a", new MyJsonString())
+ .put("b", 2);
+ assertTrue(jo1.similar(jo2));
+
+ JSONObject jo3 = new JSONObject()
+ .put("a", new JSONString() {
+ @Override
+ public String toJSONString() {
+ return "\"different value\"";
+ }
+ })
+ .put("b", 2);
+ assertFalse(jo1.similar(jo3));
+ }
+
+ private static final Number[] NON_FINITE_NUMBERS = { Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN,
+ Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NaN };
+
+ @Test
+ public void issue713MapConstructorWithNonFiniteNumbers() {
+ for (Number nonFinite : NON_FINITE_NUMBERS) {
+ Map map = new HashMap<>();
+ map.put("a", nonFinite);
+
+ assertThrows(JSONException.class, () -> new JSONObject(map));
+ }
+ }
+
+ @Test
+ public void issue713BeanConstructorWithNonFiniteNumbers() {
+ for (Number nonFinite : NON_FINITE_NUMBERS) {
+ GenericBean bean = new GenericBean<>(nonFinite);
+ assertThrows(JSONException.class, () -> new JSONObject(bean));
+ }
+ }
+
+ @Test(expected = JSONException.class)
+ public void issue743SerializationMap() {
+ HashMap map = new HashMap<>();
+ map.put("t", map);
+ JSONObject object = new JSONObject(map);
+ String jsonString = object.toString();
+ }
+
+ @Test(expected = JSONException.class)
+ public void testCircularReferenceMultipleLevel() {
+ HashMap inside = new HashMap<>();
+ HashMap jsonObject = new HashMap<>();
+ inside.put("inside", jsonObject);
+ jsonObject.put("test", inside);
+ new JSONObject(jsonObject);
+ }
+
+ @Test
+ public void issue743SerializationMapWith512Objects() {
+ HashMap map = buildNestedMap(ParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
+ JSONObject object = new JSONObject(map);
+ String jsonString = object.toString();
+ }
+
+ @Test
+ public void issue743SerializationMapWith1000Objects() {
+ HashMap map = buildNestedMap(1000);
+ JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withMaxNestingDepth(1000);
+ JSONObject object = new JSONObject(map, parserConfiguration);
+ String jsonString = object.toString();
+ }
+
+ @Test(expected = JSONException.class)
+ public void issue743SerializationMapWith1001Objects() {
+ HashMap map = buildNestedMap(1001);
+ JSONObject object = new JSONObject(map);
+ String jsonString = object.toString();
+ }
+
+ @Test(expected = JSONException.class)
+ public void testCircleReferenceFirstLevel() {
+ Map jsonObject = new HashMap<>();
+
+ jsonObject.put("test", jsonObject);
+
+ new JSONObject(jsonObject, new JSONParserConfiguration());
+ }
+
+ @Test(expected = StackOverflowError.class)
+ public void testCircleReferenceMultiplyLevel_notConfigured_expectedStackOverflow() {
+ Map inside = new HashMap<>();
+
+ Map jsonObject = new HashMap<>();
+ inside.put("test", jsonObject);
+ jsonObject.put("test", inside);
+
+ new JSONObject(jsonObject, new JSONParserConfiguration().withMaxNestingDepth(99999));
+ }
+
+ @Test(expected = JSONException.class)
+ public void testCircleReferenceMultiplyLevel_configured_expectedJSONException() {
+ Map inside = new HashMap<>();
+
+ Map jsonObject = new HashMap<>();
+ inside.put("test", jsonObject);
+ jsonObject.put("test", inside);
+
+ new JSONObject(jsonObject, new JSONParserConfiguration());
+ }
+
+ @Test
+ public void testDifferentKeySameInstanceNotACircleReference() {
+ Map map1 = new HashMap<>();
+ Map map2 = new HashMap<>();
+
+ map1.put("test1", map2);
+ map1.put("test2", map2);
+
+ new JSONObject(map1);
+ }
+
+ @Test
+ public void clarifyCurrentBehavior() {
+ // Behavior documented in #653 optLong vs getLong inconsistencies
+ // This problem still exists.
+ // Internally, both number_1 and number_2 are stored as strings. This is reasonable since they are parsed as strings.
+ // However, getLong and optLong should return similar results
+ JSONObject json = new JSONObject("{\"number_1\":\"01234\", \"number_2\": \"332211\"}");
+ assertEquals(json.getLong("number_1"), 1234L);
+ assertEquals(json.optLong("number_1"), 0); //THIS VALUE IS NOT RETURNED AS A NUMBER
+ assertEquals(json.getLong("number_2"), 332211L);
+ assertEquals(json.optLong("number_2"), 332211L);
+
+ // Behavior documented in #826 JSONObject parsing 0-led numeric strings as ints
+ // After reverting the code, personId is stored as a string, and the behavior is as expected
+ String personId = "0123";
+ JSONObject j1 = new JSONObject("{personId: " + personId + "}");
+ assertEquals(j1.getString("personId"), "0123");
+
+ // Also #826. Here is input with missing quotes. Because of the leading zero, it should not be parsed as a number.
+ // This example was mentioned in the same ticket
+ // After reverting the code, personId is stored as a string, and the behavior is as expected
+ JSONObject j2 = new JSONObject("{\"personId\":0123}");
+ assertEquals(j2.getString("personId"), "0123");
+
+ // Behavior uncovered while working on the code
+ // All of the values are stored as strings except for hex4, which is stored as a number. This is probably incorrect
+ JSONObject j3 = new JSONObject("{ " +
+ "\"hex1\": \"010e4\", \"hex2\": \"00f0\", \"hex3\": \"0011\", " +
+ "\"hex4\": 00e0, \"hex5\": 00f0, \"hex6\": 0011 }");
+ assertEquals(j3.getString("hex1"), "010e4");
+ assertEquals(j3.getString("hex2"), "00f0");
+ assertEquals(j3.getString("hex3"), "0011");
+ assertEquals(j3.getLong("hex4"), 0, .1);
+ assertEquals(j3.getString("hex5"), "00f0");
+ assertEquals(j3.getString("hex6"), "0011");
+ }
+
+ /**
+ * Method to build nested map of max maxDepth
+ *
+ * @param maxDepth
+ * @return
+ */
+ public static HashMap buildNestedMap(int maxDepth) {
+ if (maxDepth <= 0) {
+ return new HashMap<>();
+ }
+ HashMap nestedMap = new HashMap<>();
+ nestedMap.put("t", buildNestedMap(maxDepth - 1));
+ return nestedMap;
+ }
+
}
diff --git a/src/test/java/org/json/junit/JSONPointerTest.java b/src/test/java/org/json/junit/JSONPointerTest.java
index e06851e..45c7dbd 100644
--- a/src/test/java/org/json/junit/JSONPointerTest.java
+++ b/src/test/java/org/json/junit/JSONPointerTest.java
@@ -1,31 +1,10 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -41,7 +20,12 @@ import org.junit.Test;
public class JSONPointerTest {
private static final JSONObject document;
+ private static final String EXPECTED_COMPLETE_DOCUMENT = "{\"\":0,\" \":7,\"g|h\":4,\"c%d\":2,\"k\\\"l\":6,\"a/b\":1,\"i\\\\j\":5," +
+ "\"obj\":{\"\":{\"\":\"empty key of an object with an empty key\",\"subKey\":\"Some other value\"}," +
+ "\"other~key\":{\"another/key\":[\"val\"]},\"key\":\"value\"},\"foo\":[\"bar\",\"baz\"],\"e^f\":3," +
+ "\"m~n\":8}";
+
static {
@SuppressWarnings("resource")
InputStream resourceAsStream = JSONPointerTest.class.getClassLoader().getResourceAsStream("jsonpointer-testdoc.json");
@@ -57,7 +41,7 @@ public class JSONPointerTest {
@Test
public void emptyPointer() {
- assertSame(document, query(""));
+ assertTrue(new JSONObject(EXPECTED_COMPLETE_DOCUMENT).similar(query("")));
}
@SuppressWarnings("unused")
@@ -68,12 +52,12 @@ public class JSONPointerTest {
@Test
public void objectPropertyQuery() {
- assertSame(document.get("foo"), query("/foo"));
+ assertEquals("[\"bar\",\"baz\"]", query("/foo").toString());
}
@Test
public void arrayIndexQuery() {
- assertSame(document.getJSONArray("foo").get(0), query("/foo/0"));
+ assertEquals("bar", query("/foo/0"));
}
@Test(expected = JSONPointerException.class)
@@ -83,71 +67,78 @@ public class JSONPointerTest {
@Test
public void queryByEmptyKey() {
- assertSame(document.get(""), query("/"));
+ assertEquals(0, query("/"));
}
@Test
public void queryByEmptyKeySubObject() {
- assertSame(document.getJSONObject("obj").getJSONObject(""), query("/obj/"));
+ JSONObject json = new JSONObject("{\"\":\"empty key of an object with an empty key\",\"subKey\":\"Some" +
+ " other value\"}");
+ JSONObject obj = (JSONObject) query("/obj/");
+ assertTrue(json.similar(obj));
}
@Test
public void queryByEmptyKeySubObjectSubOject() {
- assertSame(
- document.getJSONObject("obj").getJSONObject("").get(""),
- query("/obj//")
- );
+ assertEquals("empty key of an object with an empty key", query("/obj//"));
}
@Test
public void queryByEmptyKeySubObjectValue() {
- assertSame(
- document.getJSONObject("obj").getJSONObject("").get("subKey"),
- query("/obj//subKey")
- );
+ assertEquals("Some other value", query("/obj//subKey"));
}
@Test
public void slashEscaping() {
- assertSame(document.get("a/b"), query("/a~1b"));
+ assertEquals(1, query("/a~1b"));
}
@Test
public void tildeEscaping() {
- assertSame(document.get("m~n"), query("/m~0n"));
+ assertEquals(8, query("/m~0n"));
}
+ /**
+ * We pass backslashes as-is
+ *
+ * @see rfc6901 section 3
+ */
@Test
- public void backslashEscaping() {
- assertSame(document.get("i\\j"), query("/i\\\\j"));
+ public void backslashHandling() {
+ assertEquals(5, query("/i\\j"));
}
-
+
+ /**
+ * We pass quotations as-is
+ *
+ * @see rfc6901 section 3
+ */
@Test
- public void quotationEscaping() {
- assertSame(document.get("k\"l"), query("/k\\\\\\\"l"));
+ public void quotationHandling() {
+ assertEquals(6, query("/k\"l"));
}
-
+
@Test
public void whitespaceKey() {
- assertSame(document.get(" "), query("/ "));
+ assertEquals(7, query("/ "));
}
@Test
public void uriFragmentNotation() {
- assertSame(document.get("foo"), query("#/foo"));
+ assertEquals("[\"bar\",\"baz\"]", query("#/foo").toString());
}
@Test
public void uriFragmentNotationRoot() {
- assertSame(document, query("#"));
+ assertTrue(new JSONObject(EXPECTED_COMPLETE_DOCUMENT).similar(query("#")));
}
@Test
public void uriFragmentPercentHandling() {
- assertSame(document.get("c%d"), query("#/c%25d"));
- assertSame(document.get("e^f"), query("#/e%5Ef"));
- assertSame(document.get("g|h"), query("#/g%7Ch"));
- assertSame(document.get("m~n"), query("#/m~0n"));
+ assertEquals(2, query("#/c%25d"));
+ assertEquals(3, query("#/e%5Ef"));
+ assertEquals(4, query("#/g%7Ch"));
+ assertEquals(8, query("#/m~0n"));
}
@SuppressWarnings("unused")
@@ -189,7 +180,7 @@ public class JSONPointerTest {
.append("\"")
.append(0)
.build();
- assertEquals("/obj/other~0key/another~1key/\\\"/0", pointer.toString());
+ assertEquals("/obj/other~0key/another~1key/\"/0", pointer.toString());
}
@Test
@@ -381,4 +372,28 @@ public class JSONPointerTest {
obj = jsonArray.optQuery(new JSONPointer("/a/b/c"));
assertTrue("Expected null", obj == null);
}
+
+ /**
+ * When creating a jsonObject we need to parse escaped characters "\\\\"
+ * --> it's the string representation of "\\", so when query'ing via the JSONPointer
+ * we DON'T escape them
+ *
+ */
+ @Test
+ public void queryFromJSONObjectUsingPointer0() {
+ String str = "{"+
+ "\"string\\\\\\\\Key\":\"hello world!\","+
+
+ "\"\\\\\":\"slash test\"," +
+ "}"+
+ "}";
+ JSONObject jsonObject = new JSONObject(str);
+ //Summary of issue: When a KEY in the jsonObject is "\\\\" --> it's held
+ // as "\\" which means when querying, we need to use "\\"
+ Object twoBackslahObj = jsonObject.optQuery(new JSONPointer("/\\"));
+ assertEquals("slash test", twoBackslahObj);
+
+ Object fourBackslashObj = jsonObject.optQuery(new JSONPointer("/string\\\\Key"));
+ assertEquals("hello world!", fourBackslashObj);
+ }
}
diff --git a/src/test/java/org/json/junit/JSONStringTest.java b/src/test/java/org/json/junit/JSONStringTest.java
index a199611..b4fee3e 100644
--- a/src/test/java/org/json/junit/JSONStringTest.java
+++ b/src/test/java/org/json/junit/JSONStringTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
diff --git a/src/test/java/org/json/junit/JSONStringerTest.java b/src/test/java/org/json/junit/JSONStringerTest.java
index a99db3b..0ecb9d6 100644
--- a/src/test/java/org/json/junit/JSONStringerTest.java
+++ b/src/test/java/org/json/junit/JSONStringerTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
diff --git a/src/test/java/org/json/junit/JSONTokenerTest.java b/src/test/java/org/json/junit/JSONTokenerTest.java
index e8e0f98..59ca6d8 100644
--- a/src/test/java/org/json/junit/JSONTokenerTest.java
+++ b/src/test/java/org/json/junit/JSONTokenerTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.assertEquals;
@@ -333,4 +313,16 @@ public class JSONTokenerTest {
assertEquals(0, t2.next());
assertFalse(t2.more());
}
+
+ @Test
+ public void testAutoClose(){
+ Reader reader = new StringReader("some test string");
+ try {
+ JSONTokener tokener = new JSONTokener(reader);
+ tokener.close();
+ tokener.next();
+ } catch (Exception exception){
+ assertEquals("Stream closed", exception.getMessage());
+ }
+ }
}
diff --git a/src/test/java/org/json/junit/PropertyTest.java b/src/test/java/org/json/junit/PropertyTest.java
index e1a9b8d..eee482f 100644
--- a/src/test/java/org/json/junit/PropertyTest.java
+++ b/src/test/java/org/json/junit/PropertyTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import java.util.*;
diff --git a/src/test/java/org/json/junit/Util.java b/src/test/java/org/json/junit/Util.java
index 8dc27dd..b676045 100644
--- a/src/test/java/org/json/junit/Util.java
+++ b/src/test/java/org/json/junit/Util.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
@@ -78,7 +58,6 @@ public class Util {
* or something else.
* @param value created by the code to be tested
* @param expectedValue created specifically for comparing
- * @param key key to the jsonObject entry to be compared
*/
private static void compareActualVsExpectedObjects(Object value,
Object expectedValue) {
@@ -117,4 +96,106 @@ public class Util {
);
}
}
+
+ /**
+ * Asserts that all JSONObject maps are the same as the default ctor
+ * @param jsonObjects list of objects to be tested
+ */
+ public static void checkJSONObjectsMaps(List jsonObjects) {
+ if (jsonObjects == null || jsonObjects.size() == 0) {
+ return;
+ }
+ Class extends Map> mapType = new JSONObject().getMapType();
+ for (JSONObject jsonObject : jsonObjects) {
+ if (jsonObject != null) {
+ assertTrue(mapType == jsonObject.getMapType());
+ checkJSONObjectMaps(jsonObject, mapType);
+ }
+ }
+ }
+
+ /**
+ * Asserts that all JSONObject maps are the same as the default ctor
+ * @param jsonObject the object to be tested
+ */
+ public static void checkJSONObjectMaps(JSONObject jsonObject) {
+ if (jsonObject != null) {
+ checkJSONObjectMaps(jsonObject, jsonObject.getMapType());
+ }
+ }
+
+ /**
+ * Asserts that all JSONObject maps are the same as mapType
+ * @param jsonObject object to be tested
+ * @param mapType mapType to test against
+ */
+ public static void checkJSONObjectMaps(JSONObject jsonObject, Class extends Map> mapType) {
+ if (mapType == null) {
+ mapType = new JSONObject().getMapType();
+ }
+ Set keys = jsonObject.keySet();
+ for (String key : keys) {
+ Object val = jsonObject.get(key);
+ if (val instanceof JSONObject) {
+ JSONObject jsonObjectVal = (JSONObject) val;
+ assertTrue(mapType == ((JSONObject) val).getMapType());
+ checkJSONObjectMaps(jsonObjectVal, mapType);
+ } else if (val instanceof JSONArray) {
+ JSONArray jsonArrayVal = (JSONArray)val;
+ checkJSONArrayMaps(jsonArrayVal, mapType);
+ }
+ }
+ }
+
+ /**
+ * Asserts that all JSONObject maps in the JSONArray object match the default map
+ * @param jsonArrays list of JSONArray objects to be tested
+ */
+ public static void checkJSONArraysMaps(List jsonArrays) {
+ if (jsonArrays == null || jsonArrays.size() == 0) {
+ return;
+ }
+ Class extends Map> mapType = new JSONObject().getMapType();
+ for (JSONArray jsonArray : jsonArrays) {
+ if (jsonArray != null) {
+ checkJSONArrayMaps(jsonArray, mapType);
+ }
+ }
+ }
+
+ /**
+ * Asserts that all JSONObject maps in the JSONArray object match mapType
+ * @param jsonArray object to be tested
+ * @param mapType map type to be tested against
+ */
+ public static void checkJSONArrayMaps(JSONArray jsonArray, Class extends Map> mapType) {
+ if (jsonArray == null) {
+ return;
+ }
+ if (mapType == null) {
+ mapType = new JSONObject().getMapType();
+ }
+ Iterator it = jsonArray.iterator();
+ while (it.hasNext()) {
+ Object val = it.next();
+ if (val instanceof JSONObject) {
+ JSONObject jsonObjectVal = (JSONObject)val;
+ checkJSONObjectMaps(jsonObjectVal, mapType);
+ } else if (val instanceof JSONArray) {
+ JSONArray jsonArrayVal = (JSONArray)val;
+ checkJSONArrayMaps(jsonArrayVal, mapType);
+ }
+ }
+ }
+
+ /**
+ * Asserts that all JSONObject maps nested in the JSONArray match
+ * the default mapType
+ * @param jsonArray the object to be tested
+ */
+ public static void checkJSONArrayMaps(JSONArray jsonArray) {
+ if (jsonArray != null) {
+ checkJSONArrayMaps(jsonArray, null);
+ }
+ }
}
diff --git a/src/test/java/org/json/junit/XMLConfigurationTest.java b/src/test/java/org/json/junit/XMLConfigurationTest.java
index 28b20dd..e9714af 100755
--- a/src/test/java/org/json/junit/XMLConfigurationTest.java
+++ b/src/test/java/org/json/junit/XMLConfigurationTest.java
@@ -1,40 +1,17 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
+import java.util.HashSet;
+import java.util.Set;
import org.json.JSONArray;
import org.json.JSONException;
@@ -45,6 +22,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import static org.junit.Assert.*;
+
/**
* Tests for JSON-Java XML.java with XMLParserConfiguration.java
@@ -575,6 +554,37 @@ public class XMLConfigurationTest {
assertEquals(actualXML, resultXML);
}
+ @Test
+ public void shouldHandleEmptyNodeValue()
+ {
+ JSONObject inputJSON = new JSONObject();
+ inputJSON.put("Emptyness", "");
+ String expectedXmlWithoutExplicitEndTag = " ";
+ String expectedXmlWithExplicitEndTag = " ";
+ assertEquals(expectedXmlWithoutExplicitEndTag, XML.toString(inputJSON, null,
+ new XMLParserConfiguration().withCloseEmptyTag(false)));
+ assertEquals(expectedXmlWithExplicitEndTag, XML.toString(inputJSON, null,
+ new XMLParserConfiguration().withCloseEmptyTag(true)));
+ }
+
+ @Test
+ public void shouldKeepConfigurationIntactAndUpdateCloseEmptyTagChoice()
+ {
+ XMLParserConfiguration keepStrings = XMLParserConfiguration.KEEP_STRINGS;
+ XMLParserConfiguration keepStringsAndCloseEmptyTag = keepStrings.withCloseEmptyTag(true);
+ XMLParserConfiguration keepDigits = keepStringsAndCloseEmptyTag.withKeepStrings(false);
+ XMLParserConfiguration keepDigitsAndNoCloseEmptyTag = keepDigits.withCloseEmptyTag(false);
+ assertTrue(keepStrings.isKeepStrings());
+ assertFalse(keepStrings.isCloseEmptyTag());
+ assertTrue(keepStringsAndCloseEmptyTag.isKeepStrings());
+ assertTrue(keepStringsAndCloseEmptyTag.isCloseEmptyTag());
+ assertFalse(keepDigits.isKeepStrings());
+ assertTrue(keepDigits.isCloseEmptyTag());
+ assertFalse(keepDigitsAndNoCloseEmptyTag.isKeepStrings());
+ assertFalse(keepDigitsAndNoCloseEmptyTag.isCloseEmptyTag());
+
+ }
+
/**
* Investigate exactly how the "content" keyword works
*/
@@ -903,7 +913,195 @@ public class XMLConfigurationTest {
Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
}
-
+
+ /**
+ * Test forceList parameter
+ */
+ @Test
+ public void testSimpleForceList() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " Sherlock Holmes \n"+
+ " \n"+
+ " ";
+
+ String expectedStr =
+ "{\"addresses\":[{\"address\":{\"name\":\"Sherlock Holmes\"}}]}";
+
+ Set forceList = new HashSet();
+ forceList.add("addresses");
+
+ XMLParserConfiguration config =
+ new XMLParserConfiguration()
+ .withForceList(forceList);
+ JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
+ JSONObject expetedJsonObject = new JSONObject(expectedStr);
+
+ Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
+ }
+ @Test
+ public void testLongForceList() {
+ String xmlStr =
+ ""+
+ ""+
+ "host1 "+
+ "Linux "+
+ ""+
+ ""+
+ "em0 "+
+ "10.0.0.1 "+
+ " "+
+ " "+
+ " "+
+ " ";
+
+ String expectedStr =
+ "{"+
+ "\"servers\": ["+
+ "{"+
+ "\"server\": {"+
+ "\"name\": \"host1\","+
+ "\"os\": \"Linux\","+
+ "\"interfaces\": ["+
+ "{"+
+ "\"interface\": {"+
+ "\"name\": \"em0\","+
+ "\"ip_address\": \"10.0.0.1\""+
+ "}}]}}]}";
+
+ Set forceList = new HashSet();
+ forceList.add("servers");
+ forceList.add("interfaces");
+
+ XMLParserConfiguration config =
+ new XMLParserConfiguration()
+ .withForceList(forceList);
+ JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
+ JSONObject expetedJsonObject = new JSONObject(expectedStr);
+
+ Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
+ }
+ @Test
+ public void testMultipleTagForceList() {
+ String xmlStr =
+ "\n"+
+ " \n"+
+ " Sherlock Holmes \n"+
+ " John H. Watson \n"+
+ " \n"+
+ " ";
+
+ String expectedStr =
+ "{"+
+ "\"addresses\":["+
+ "{"+
+ "\"address\":["+
+ "{"+
+ "\"name\":["+
+ "\"Sherlock Holmes\","+
+ "\"John H. Watson\""+
+ "]"+
+ "}"+
+ "]"+
+ "}"+
+ "]"+
+ "}";
+
+ Set forceList = new HashSet();
+ forceList.add("addresses");
+ forceList.add("address");
+ forceList.add("name");
+
+ XMLParserConfiguration config =
+ new XMLParserConfiguration()
+ .withForceList(forceList);
+ JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
+ JSONObject expetedJsonObject = new JSONObject(expectedStr);
+
+ Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
+ }
+ @Test
+ public void testEmptyForceList() {
+ String xmlStr =
+ " ";
+
+ String expectedStr =
+ "{\"addresses\":[]}";
+
+ Set forceList = new HashSet();
+ forceList.add("addresses");
+
+ XMLParserConfiguration config =
+ new XMLParserConfiguration()
+ .withForceList(forceList);
+ JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
+ JSONObject expetedJsonObject = new JSONObject(expectedStr);
+
+ Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
+ }
+ @Test
+ public void testContentForceList() {
+ String xmlStr =
+ "Baker Street ";
+
+ String expectedStr =
+ "{\"addresses\":[\"Baker Street\"]}";
+
+ Set forceList = new HashSet();
+ forceList.add("addresses");
+
+ XMLParserConfiguration config =
+ new XMLParserConfiguration()
+ .withForceList(forceList);
+ JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
+ JSONObject expetedJsonObject = new JSONObject(expectedStr);
+
+ Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
+ }
+ @Test
+ public void testEmptyTagForceList() {
+ String xmlStr =
+ " ";
+
+ String expectedStr =
+ "{\"addresses\":[]}";
+
+ Set forceList = new HashSet();
+ forceList.add("addresses");
+
+ XMLParserConfiguration config =
+ new XMLParserConfiguration()
+ .withForceList(forceList);
+ JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
+ JSONObject expetedJsonObject = new JSONObject(expectedStr);
+
+ Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
+ }
+
+ @Test
+ public void testMaxNestingDepthIsSet() {
+ XMLParserConfiguration xmlParserConfiguration = XMLParserConfiguration.ORIGINAL;
+
+ assertEquals(xmlParserConfiguration.getMaxNestingDepth(), XMLParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
+
+ xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(42);
+
+ assertEquals(xmlParserConfiguration.getMaxNestingDepth(), 42);
+
+ xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(0);
+
+ assertEquals(xmlParserConfiguration.getMaxNestingDepth(), 0);
+
+ xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(-31415926);
+
+ assertEquals(xmlParserConfiguration.getMaxNestingDepth(), XMLParserConfiguration.UNDEFINED_MAXIMUM_NESTING_DEPTH);
+
+ xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(Integer.MIN_VALUE);
+
+ assertEquals(xmlParserConfiguration.getMaxNestingDepth(), XMLParserConfiguration.UNDEFINED_MAXIMUM_NESTING_DEPTH);
+ }
/**
* Convenience method, given an input string and expected result,
@@ -983,4 +1181,4 @@ public class XMLConfigurationTest {
assertTrue("Error: " +e.getMessage(), false);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java
index 62ee516..3b26b22 100644
--- a/src/test/java/org/json/junit/XMLTest.java
+++ b/src/test/java/org/json/junit/XMLTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.assertEquals;
@@ -38,16 +18,11 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
+import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONTokener;
-import org.json.XML;
-import org.json.XMLParserConfiguration;
-import org.json.XMLXsiTypeConverter;
+import org.json.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -65,6 +40,7 @@ public class XMLTest {
@Rule
public TemporaryFolder testFolder = new TemporaryFolder();
+
/**
* JSONObject from a null XML string.
* Expects a NullPointerException
@@ -940,7 +916,7 @@ public class XMLTest {
InputStream xmlStream = null;
try {
xmlStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue537.xml");
- Reader xmlReader = new InputStreamReader(xmlStream);
+ Reader xmlReader = new InputStreamReader(xmlStream, Charset.forName("UTF-8"));
JSONObject actual = XML.toJSONObject(xmlReader, true);
InputStream jsonStream = null;
try {
@@ -1068,4 +1044,389 @@ public class XMLTest {
fail("Expected to be unable to modify the config");
} catch (Exception ignored) { }
}
+
+ @Test
+ public void testIndentComplicatedJsonObject(){
+ String str = "{\n" +
+ " \"success\": true,\n" +
+ " \"error\": null,\n" +
+ " \"response\": [\n" +
+ " {\n" +
+ " \"timestamp\": 1664917200,\n" +
+ " \"dateTimeISO\": \"2022-10-05T00:00:00+03:00\",\n" +
+ " \"loc\": {\n" +
+ " \"lat\": 39.91987,\n" +
+ " \"long\": 32.85427\n" +
+ " },\n" +
+ " \"place\": {\n" +
+ " \"name\": \"ankara\",\n" +
+ " \"state\": \"an\",\n" +
+ " \"country\": \"tr\"\n" +
+ " },\n" +
+ " \"profile\": {\n" +
+ " \"tz\": \"Europe/Istanbul\"\n" +
+ " },\n" +
+ " \"sun\": {\n" +
+ " \"rise\": 1664941721,\n" +
+ " \"riseISO\": \"2022-10-05T06:48:41+03:00\",\n" +
+ " \"set\": 1664983521,\n" +
+ " \"setISO\": \"2022-10-05T18:25:21+03:00\",\n" +
+ " \"transit\": 1664962621,\n" +
+ " \"transitISO\": \"2022-10-05T12:37:01+03:00\",\n" +
+ " \"midnightSun\": false,\n" +
+ " \"polarNight\": false,\n" +
+ " \"twilight\": {\n" +
+ " \"civilBegin\": 1664940106,\n" +
+ " \"civilBeginISO\": \"2022-10-05T06:21:46+03:00\",\n" +
+ " \"civilEnd\": 1664985136,\n" +
+ " \"civilEndISO\": \"2022-10-05T18:52:16+03:00\",\n" +
+ " \"nauticalBegin\": 1664938227,\n" +
+ " \"nauticalBeginISO\": \"2022-10-05T05:50:27+03:00\",\n" +
+ " \"nauticalEnd\": 1664987015,\n" +
+ " \"nauticalEndISO\": \"2022-10-05T19:23:35+03:00\",\n" +
+ " \"astronomicalBegin\": 1664936337,\n" +
+ " \"astronomicalBeginISO\": \"2022-10-05T05:18:57+03:00\",\n" +
+ " \"astronomicalEnd\": 1664988905,\n" +
+ " \"astronomicalEndISO\": \"2022-10-05T19:55:05+03:00\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"moon\": {\n" +
+ " \"rise\": 1664976480,\n" +
+ " \"riseISO\": \"2022-10-05T16:28:00+03:00\",\n" +
+ " \"set\": 1664921520,\n" +
+ " \"setISO\": \"2022-10-05T01:12:00+03:00\",\n" +
+ " \"transit\": 1664994240,\n" +
+ " \"transitISO\": \"2022-10-05T21:24:00+03:00\",\n" +
+ " \"underfoot\": 1664949360,\n" +
+ " \"underfootISO\": \"2022-10-05T08:56:00+03:00\",\n" +
+ " \"phase\": {\n" +
+ " \"phase\": 0.3186,\n" +
+ " \"name\": \"waxing gibbous\",\n" +
+ " \"illum\": 71,\n" +
+ " \"age\": 9.41,\n" +
+ " \"angle\": 0.55\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}" ;
+ JSONObject jsonObject = new JSONObject(str);
+ String actualIndentedXmlString = XML.toString(jsonObject, 1);
+ JSONObject actualJsonObject = XML.toJSONObject(actualIndentedXmlString);
+ String expected = "true \n" +
+ "\n" +
+ " 2022-10-05T00:00:00+03:00 \n" +
+ " \n" +
+ " 39.91987 \n" +
+ " 32.85427 \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " 0.3186 \n" +
+ " waxing gibbous \n" +
+ " 0.55 \n" +
+ " 71 \n" +
+ " 9.41 \n" +
+ " \n" +
+ " 2022-10-05T01:12:00+03:00 \n" +
+ " 1664949360 \n" +
+ " 1664921520 \n" +
+ " 1664994240 \n" +
+ " 2022-10-05T21:24:00+03:00 \n" +
+ " 2022-10-05T16:28:00+03:00 \n" +
+ " 1664976480 \n" +
+ " 2022-10-05T08:56:00+03:00 \n" +
+ " \n" +
+ " \n" +
+ " Europe/Istanbul \n" +
+ " \n" +
+ " \n" +
+ " tr \n" +
+ " ankara \n" +
+ " an \n" +
+ " \n" +
+ " \n" +
+ " 2022-10-05T18:25:21+03:00 \n" +
+ " false \n" +
+ " 1664983521 \n" +
+ " 1664962621 \n" +
+ " false \n" +
+ " 2022-10-05T12:37:01+03:00 \n" +
+ " 2022-10-05T06:48:41+03:00 \n" +
+ " 1664941721 \n" +
+ " \n" +
+ " 1664985136 \n" +
+ " 1664936337 \n" +
+ " 1664988905 \n" +
+ " 2022-10-05T05:18:57+03:00 \n" +
+ " 1664940106 \n" +
+ " 2022-10-05T19:23:35+03:00 \n" +
+ " 2022-10-05T19:55:05+03:00 \n" +
+ " 1664938227 \n" +
+ " 1664987015 \n" +
+ " 2022-10-05T05:50:27+03:00 \n" +
+ " 2022-10-05T06:21:46+03:00 \n" +
+ " 2022-10-05T18:52:16+03:00 \n" +
+ " \n" +
+ " \n" +
+ " 1664917200 \n" +
+ " \n" +
+ "null \n";
+ JSONObject expectedJsonObject = XML.toJSONObject(expected);
+ assertTrue(expectedJsonObject.similar(actualJsonObject));
+
+
+ }
+
+ @Test
+ public void shouldCreateExplicitEndTagWithEmptyValueWhenConfigured(){
+ String jsonString = "{outer:{innerOne:\"\", innerTwo:\"two\"}}";
+ JSONObject jsonObject = new JSONObject(jsonString);
+ String expectedXmlString = "two ";
+ String xmlForm = XML.toString(jsonObject,"encloser", new XMLParserConfiguration().withCloseEmptyTag(true));
+ JSONObject actualJsonObject = XML.toJSONObject(xmlForm);
+ JSONObject expectedJsonObject = XML.toJSONObject(expectedXmlString);
+ assertTrue(expectedJsonObject.similar(actualJsonObject));
+ }
+
+ @Test
+ public void shouldNotCreateExplicitEndTagWithEmptyValueWhenNotConfigured(){
+ String jsonString = "{outer:{innerOne:\"\", innerTwo:\"two\"}}";
+ JSONObject jsonObject = new JSONObject(jsonString);
+ String expectedXmlString = "two ";
+ String xmlForm = XML.toString(jsonObject,"encloser", new XMLParserConfiguration().withCloseEmptyTag(false));
+ JSONObject actualJsonObject = XML.toJSONObject(xmlForm);
+ JSONObject expectedJsonObject = XML.toJSONObject(expectedXmlString);
+ assertTrue(expectedJsonObject.similar(actualJsonObject));
+ }
+
+
+ @Test
+ public void testIndentSimpleJsonObject(){
+ String str = "{ \"employee\": { \n" +
+ " \"name\": \"sonoo\", \n" +
+ " \"salary\": 56000, \n" +
+ " \"married\": true \n" +
+ " }}";
+ JSONObject jsonObject = new JSONObject(str);
+ String actual = XML.toString(jsonObject, "Test", 2);
+ JSONObject actualJsonObject = XML.toJSONObject(actual);
+ String expected = "\n" +
+ " \n" +
+ " sonoo \n" +
+ " 56000 \n" +
+ " true \n" +
+ " \n" +
+ " \n";
+ JSONObject expectedJsonObject = XML.toJSONObject(expected);
+ assertTrue(expectedJsonObject.similar(actualJsonObject));
+ }
+
+ @Test
+ public void testIndentSimpleJsonArray(){
+ String str = "[ \n" +
+ " {\"name\":\"Ram\", \"email\":\"Ram@gmail.com\"}, \n" +
+ " {\"name\":\"Bob\", \"email\":\"bob32@gmail.com\"} \n" +
+ "] ";
+ JSONArray jsonObject = new JSONArray(str);
+ String actual = XML.toString(jsonObject, 2);
+ JSONObject actualJsonObject = XML.toJSONObject(actual);
+ String expected = "\n" +
+ " Ram \n" +
+ " Ram@gmail.com \n" +
+ " \n" +
+ "\n" +
+ " Bob \n" +
+ " bob32@gmail.com \n" +
+ " \n";
+ JSONObject expectedJsonObject = XML.toJSONObject(expected);
+ assertTrue(expectedJsonObject.similar(actualJsonObject));
+
+
+ }
+
+ @Test
+ public void testIndentComplicatedJsonObjectWithArrayAndWithConfig(){
+ try (InputStream jsonStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue593.json")) {
+ final JSONObject object = new JSONObject(new JSONTokener(jsonStream));
+ String actualString = XML.toString(object, null, XMLParserConfiguration.KEEP_STRINGS, 2);
+ try (InputStream xmlStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue593.xml")) {
+ int bufferSize = 1024;
+ char[] buffer = new char[bufferSize];
+ StringBuilder expected = new StringBuilder();
+ Reader in = new InputStreamReader(xmlStream, "UTF-8");
+ for (int numRead; (numRead = in.read(buffer, 0, buffer.length)) > 0; ) {
+ expected.append(buffer, 0, numRead);
+ }
+ assertTrue(XML.toJSONObject(expected.toString()).similar(XML.toJSONObject(actualString)));
+ }
+ } catch (IOException e) {
+ fail("file writer error: " +e.getMessage());
+ }
+ }
+
+ @Test
+ public void testMaxNestingDepthOf42IsRespected() {
+ final String wayTooLongMalformedXML = new String(new char[6000]).replace("\0", "");
+
+ final int maxNestingDepth = 42;
+
+ try {
+ XML.toJSONObject(wayTooLongMalformedXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
+ e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
+ }
+ }
+
+ @Test
+ public void testMaxNestingDepthIsRespectedWithValidXML() {
+ final String perfectlyFineXML = "\n" +
+ " \n" +
+ " sonoo \n" +
+ " 56000 \n" +
+ " true \n" +
+ " \n" +
+ " \n";
+
+ final int maxNestingDepth = 1;
+
+ try {
+ XML.toJSONObject(perfectlyFineXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
+ e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
+ }
+ }
+
+ @Test
+ public void testMaxNestingDepthWithValidFittingXML() {
+ final String perfectlyFineXML = "\n" +
+ " \n" +
+ " sonoo \n" +
+ " 56000 \n" +
+ " true \n" +
+ " \n" +
+ " \n";
+
+ final int maxNestingDepth = 3;
+
+ try {
+ XML.toJSONObject(perfectlyFineXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ fail("XML document should be parsed as its maximum depth fits the maxNestingDepth " +
+ "parameter of the XMLParserConfiguration used");
+ }
+ }
+ @Test
+ public void testWithWhitespaceTrimmingDisabled() {
+ String originalXml = " Test Whitespace String \t ";
+
+ JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(false));
+ String expectedJsonString = "{\"testXml\":\" Test Whitespace String \t \"}";
+ JSONObject expectedJson = new JSONObject(expectedJsonString);
+ Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
+ }
+ @Test
+ public void testNestedWithWhitespaceTrimmingDisabled() {
+ String originalXml =
+ "\n"+
+ "\n"+
+ " \n"+
+ " Sherlock Holmes \n"+
+ " \n"+
+ " ";
+
+ JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(false));
+ String expectedJsonString = "{\"addresses\":{\"address\":{\"name\":\" Sherlock Holmes \"}}}";
+ JSONObject expectedJson = new JSONObject(expectedJsonString);
+ Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
+ }
+ @Test
+ public void shouldTrimWhitespaceDoesNotSupportTagsEqualingCDataTagName() {
+ // When using withShouldTrimWhitespace = true, input containing tags with same name as cDataTagName is unsupported and should not be used in conjunction
+ String originalXml =
+ "\n"+
+ "\n"+
+ " \n"+
+ " Sherlock Holmes \n"+
+ " \n"+
+ " ";
+
+ JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(false).withcDataTagName("content"));
+ String expectedJsonString = "{\"addresses\":{\"address\":[[\"\\n \",\" Sherlock Holmes \",\"\\n \"]]}}";
+ JSONObject expectedJson = new JSONObject(expectedJsonString);
+ Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
+ }
+ @Test
+ public void shouldTrimWhitespaceEnabledDropsTagsEqualingCDataTagNameButValueRemains() {
+ String originalXml =
+ "\n"+
+ "\n"+
+ " \n"+
+ " Sherlock Holmes \n"+
+ " \n"+
+ " ";
+
+ JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(true).withcDataTagName("content"));
+ String expectedJsonString = "{\"addresses\":{\"address\":\"Sherlock Holmes\"}}";
+ JSONObject expectedJson = new JSONObject(expectedJsonString);
+ Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
+ }
+ @Test
+ public void testWithWhitespaceTrimmingEnabled() {
+ String originalXml = " Test Whitespace String \t ";
+
+ JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(true));
+ String expectedJsonString = "{\"testXml\":\"Test Whitespace String\"}";
+ JSONObject expectedJson = new JSONObject(expectedJsonString);
+ Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
+ }
+ @Test
+ public void testWithWhitespaceTrimmingEnabledByDefault() {
+ String originalXml = " Test Whitespace String \t ";
+
+ JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration());
+ String expectedJsonString = "{\"testXml\":\"Test Whitespace String\"}";
+ JSONObject expectedJson = new JSONObject(expectedJsonString);
+ Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
+ }
+
+ @Test
+ public void clarifyCurrentBehavior() {
+
+ // Behavior documented in #826
+ // After reverting the code, amount is stored as numeric, and phone is stored as string
+ String str1 =
+ " \n" +
+ " 0123456789 \n" +
+ " 0.1230 \n" +
+ " true \n" +
+ " ";
+ JSONObject jsonObject1 = XML.toJSONObject(str1,
+ new XMLParserConfiguration().withKeepStrings(false));
+ assertEquals(jsonObject1.getJSONObject("datatypes").getFloat("amount"), 0.123, .1);
+ assertEquals(jsonObject1.getJSONObject("datatypes").getString("telephone"), "0123456789");
+
+
+ // Behavior documented in #852
+ // After reverting the code, value is still stored as a number. This is due to how XML.isDecimalNotation() works
+ // and is probably a bug. JSONObject has a similar problem.
+ String str2 = " primary 008E97 ";
+ JSONObject jsonObject2 = XML.toJSONObject(str2);
+ assertEquals(jsonObject2.getJSONObject("color").getLong("value"), 0e897, .1);
+
+ // Workaround for now is to use keepStrings
+ JSONObject jsonObject3 = XML.toJSONObject(str2, new XMLParserConfiguration().withKeepStrings(true));
+ assertEquals(jsonObject3.getJSONObject("color").getString("value"), "008E97");
+ }
+
}
+
+
+
diff --git a/src/test/java/org/json/junit/data/ExceptionalBean.java b/src/test/java/org/json/junit/data/ExceptionalBean.java
index 72d6c0c..91067b8 100644
--- a/src/test/java/org/json/junit/data/ExceptionalBean.java
+++ b/src/test/java/org/json/junit/data/ExceptionalBean.java
@@ -8,7 +8,7 @@ import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
/**
- * Object for testing the exception handling in {@link JSONObject#populateMap}.
+ * Object for testing the exception handling in {@link org.json.JSONObject#populateMap}.
*
* @author John Aylward
*/
diff --git a/src/test/java/org/json/junit/data/GenericBean.java b/src/test/java/org/json/junit/data/GenericBean.java
index da6370d..dd46b88 100644
--- a/src/test/java/org/json/junit/data/GenericBean.java
+++ b/src/test/java/org/json/junit/data/GenericBean.java
@@ -9,7 +9,7 @@ import java.io.StringReader;
* @param
* generic number value
*/
-public class GenericBean> implements MyBean {
+public class GenericBean implements MyBean {
/**
* @param genericValue
* value to initiate with
diff --git a/src/test/java/org/json/junit/data/RecursiveBean.java b/src/test/java/org/json/junit/data/RecursiveBean.java
new file mode 100644
index 0000000..dad6e7a
--- /dev/null
+++ b/src/test/java/org/json/junit/data/RecursiveBean.java
@@ -0,0 +1,23 @@
+package org.json.junit.data;
+
+/**
+ * test class for verifying if recursively defined bean can be correctly identified
+ * @author Zetmas
+ *
+ */
+public class RecursiveBean {
+ private String name;
+ private Object reference;
+ private Object reference2;
+ public String getName() { return name; }
+ public Object getRef() {return reference;}
+ public Object getRef2() {return reference2;}
+ public void setRef(Object refObj) {reference = refObj;}
+ public void setRef2(Object refObj) {reference2 = refObj;}
+
+ public RecursiveBean(String name) {
+ this.name = name;
+ reference = null;
+ reference2 = null;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/json/junit/data/RecursiveBeanEquals.java b/src/test/java/org/json/junit/data/RecursiveBeanEquals.java
new file mode 100644
index 0000000..1016648
--- /dev/null
+++ b/src/test/java/org/json/junit/data/RecursiveBeanEquals.java
@@ -0,0 +1,33 @@
+package org.json.junit.data;
+
+/** test class for verifying if recursively defined bean can be correctly identified */
+public class RecursiveBeanEquals {
+ private final String name;
+ private Object reference;
+
+ public RecursiveBeanEquals(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Object getRef() {
+ return reference;
+ }
+
+ public void setRef(Object refObj) {
+ reference = refObj;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof RecursiveBeanEquals && name.equals(((RecursiveBeanEquals) other).name);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+}
diff --git a/src/test/java/org/json/junit/data/WeirdList.java b/src/test/java/org/json/junit/data/WeirdList.java
index 834b81e..3560586 100644
--- a/src/test/java/org/json/junit/data/WeirdList.java
+++ b/src/test/java/org/json/junit/data/WeirdList.java
@@ -12,7 +12,7 @@ import java.util.List;
*/
public class WeirdList {
/** */
- private final List list = new ArrayList();
+ private final List list = new ArrayList<>();
/**
* @param vals
@@ -25,14 +25,14 @@ public class WeirdList {
* @return a copy of the list
*/
public List get() {
- return new ArrayList(this.list);
+ return new ArrayList<>(this.list);
}
/**
* @return a copy of the list
*/
public List getALL() {
- return new ArrayList(this.list);
+ return new ArrayList<>(this.list);
}
/**
diff --git a/src/test/resources/Issue593.json b/src/test/resources/Issue593.json
new file mode 100644
index 0000000..213625a
--- /dev/null
+++ b/src/test/resources/Issue593.json
@@ -0,0 +1,704 @@
+{
+ "success": true,
+ "error": null,
+ "response": [
+ {
+ "loc": {
+ "long": 31.25,
+ "lat": 30.063
+ },
+ "interval": "day",
+ "place": {
+ "name": "cairo",
+ "state": "qh",
+ "country": "eg"
+ },
+ "periods": [
+ {
+ "timestamp": 1665032400,
+ "validTime": "2022-10-06T07:00:00+02:00",
+ "dateTimeISO": "2022-10-06T07:00:00+02:00",
+ "maxTempC": 32,
+ "maxTempF": 90,
+ "minTempC": 19,
+ "minTempF": 66,
+ "avgTempC": 25,
+ "avgTempF": 78,
+ "tempC": null,
+ "tempF": null,
+ "maxFeelslikeC": 32,
+ "maxFeelslikeF": 89,
+ "minFeelslikeC": 21,
+ "minFeelslikeF": 70,
+ "avgFeelslikeC": 26,
+ "avgFeelslikeF": 80,
+ "feelslikeC": 21,
+ "feelslikeF": 70,
+ "maxDewpointC": 17,
+ "maxDewpointF": 63,
+ "minDewpointC": 11,
+ "minDewpointF": 52,
+ "avgDewpointC": 14,
+ "avgDewpointF": 58,
+ "dewpointC": 17,
+ "dewpointF": 63,
+ "maxHumidity": 77,
+ "minHumidity": 29,
+ "humidity": 77,
+ "pop": 0,
+ "precipMM": 0,
+ "precipIN": 0,
+ "iceaccum": null,
+ "iceaccumMM": null,
+ "iceaccumIN": null,
+ "snowCM": 0,
+ "snowIN": 0,
+ "pressureMB": 1015,
+ "pressureIN": 29.97,
+ "windDir": "N",
+ "windDirDEG": 353,
+ "windSpeedKTS": 5,
+ "windSpeedKPH": 9,
+ "windSpeedMPH": 6,
+ "windGustKTS": 21,
+ "windGustKPH": 40,
+ "windGustMPH": 25,
+ "windDirMax": "NNW",
+ "windDirMaxDEG": 342,
+ "windSpeedMaxKTS": 9,
+ "windSpeedMaxKPH": 16,
+ "windSpeedMaxMPH": 10,
+ "windDirMin": "N",
+ "windDirMinDEG": 353,
+ "windSpeedMinKTS": 1,
+ "windSpeedMinKPH": 2,
+ "windSpeedMinMPH": 1,
+ "windDir80m": "N",
+ "windDir80mDEG": 11,
+ "windSpeed80mKTS": 12,
+ "windSpeed80mKPH": 22,
+ "windSpeed80mMPH": 13,
+ "windGust80mKTS": 22,
+ "windGust80mKPH": 41,
+ "windGust80mMPH": 25,
+ "windDirMax80m": "NNW",
+ "windDirMax80mDEG": 343,
+ "windSpeedMax80mKTS": 22,
+ "windSpeedMax80mKPH": 41,
+ "windSpeedMax80mMPH": 25,
+ "windDirMin80m": "E",
+ "windDirMin80mDEG": 95,
+ "windSpeedMin80mKTS": 8,
+ "windSpeedMin80mKPH": 15,
+ "windSpeedMin80mMPH": 10,
+ "sky": 22,
+ "cloudsCoded": "FW",
+ "weather": "Mostly Sunny",
+ "weatherCoded": [],
+ "weatherPrimary": "Mostly Sunny",
+ "weatherPrimaryCoded": "::FW",
+ "icon": "fair.png",
+ "visibilityKM": 24.135,
+ "visibilityMI": 15,
+ "uvi": 6,
+ "solradWM2": 5608,
+ "solradMinWM2": 0,
+ "solradMaxWM2": 778,
+ "isDay": true,
+ "maxCoverage": "",
+ "sunrise": 1665028274,
+ "sunset": 1665070502,
+ "sunriseISO": "2022-10-06T05:51:14+02:00",
+ "sunsetISO": "2022-10-06T17:35:02+02:00"
+ },
+ {
+ "timestamp": 1665118800,
+ "validTime": "2022-10-07T07:00:00+02:00",
+ "dateTimeISO": "2022-10-07T07:00:00+02:00",
+ "maxTempC": 30,
+ "maxTempF": 86,
+ "minTempC": 19,
+ "minTempF": 66,
+ "avgTempC": 24,
+ "avgTempF": 76,
+ "tempC": null,
+ "tempF": null,
+ "maxFeelslikeC": 29,
+ "maxFeelslikeF": 85,
+ "minFeelslikeC": 19,
+ "minFeelslikeF": 67,
+ "avgFeelslikeC": 24,
+ "avgFeelslikeF": 76,
+ "feelslikeC": 19,
+ "feelslikeF": 67,
+ "maxDewpointC": 15,
+ "maxDewpointF": 60,
+ "minDewpointC": 10,
+ "minDewpointF": 50,
+ "avgDewpointC": 12,
+ "avgDewpointF": 54,
+ "dewpointC": 15,
+ "dewpointF": 60,
+ "maxHumidity": 77,
+ "minHumidity": 30,
+ "humidity": 77,
+ "pop": 0,
+ "precipMM": 0,
+ "precipIN": 0,
+ "iceaccum": null,
+ "iceaccumMM": null,
+ "iceaccumIN": null,
+ "snowCM": 0,
+ "snowIN": 0,
+ "pressureMB": 1014,
+ "pressureIN": 29.95,
+ "windDir": "NW",
+ "windDirDEG": 325,
+ "windSpeedKTS": 1,
+ "windSpeedKPH": 2,
+ "windSpeedMPH": 1,
+ "windGustKTS": 16,
+ "windGustKPH": 29,
+ "windGustMPH": 18,
+ "windDirMax": "WNW",
+ "windDirMaxDEG": 298,
+ "windSpeedMaxKTS": 7,
+ "windSpeedMaxKPH": 13,
+ "windSpeedMaxMPH": 8,
+ "windDirMin": "NW",
+ "windDirMinDEG": 325,
+ "windSpeedMinKTS": 1,
+ "windSpeedMinKPH": 2,
+ "windSpeedMinMPH": 1,
+ "windDir80m": "NNW",
+ "windDir80mDEG": 347,
+ "windSpeed80mKTS": 6,
+ "windSpeed80mKPH": 10,
+ "windSpeed80mMPH": 6,
+ "windGust80mKTS": 20,
+ "windGust80mKPH": 37,
+ "windGust80mMPH": 23,
+ "windDirMax80m": "NW",
+ "windDirMax80mDEG": 316,
+ "windSpeedMax80mKTS": 20,
+ "windSpeedMax80mKPH": 37,
+ "windSpeedMax80mMPH": 23,
+ "windDirMin80m": "NNW",
+ "windDirMin80mDEG": 347,
+ "windSpeedMin80mKTS": 6,
+ "windSpeedMin80mKPH": 10,
+ "windSpeedMin80mMPH": 6,
+ "sky": 30,
+ "cloudsCoded": "FW",
+ "weather": "Mostly Sunny",
+ "weatherCoded": [],
+ "weatherPrimary": "Mostly Sunny",
+ "weatherPrimaryCoded": "::FW",
+ "icon": "fair.png",
+ "visibilityKM": 24.135,
+ "visibilityMI": 15,
+ "uvi": 6,
+ "solradWM2": 5486,
+ "solradMinWM2": 0,
+ "solradMaxWM2": 742,
+ "isDay": true,
+ "maxCoverage": "",
+ "sunrise": 1665114710,
+ "sunset": 1665156831,
+ "sunriseISO": "2022-10-07T05:51:50+02:00",
+ "sunsetISO": "2022-10-07T17:33:51+02:00"
+ },
+ {
+ "timestamp": 1665205200,
+ "validTime": "2022-10-08T07:00:00+02:00",
+ "dateTimeISO": "2022-10-08T07:00:00+02:00",
+ "maxTempC": 30,
+ "maxTempF": 87,
+ "minTempC": 19,
+ "minTempF": 66,
+ "avgTempC": 25,
+ "avgTempF": 76,
+ "tempC": null,
+ "tempF": null,
+ "maxFeelslikeC": 30,
+ "maxFeelslikeF": 86,
+ "minFeelslikeC": 19,
+ "minFeelslikeF": 67,
+ "avgFeelslikeC": 25,
+ "avgFeelslikeF": 76,
+ "feelslikeC": 19,
+ "feelslikeF": 67,
+ "maxDewpointC": 15,
+ "maxDewpointF": 59,
+ "minDewpointC": 11,
+ "minDewpointF": 52,
+ "avgDewpointC": 13,
+ "avgDewpointF": 56,
+ "dewpointC": 15,
+ "dewpointF": 59,
+ "maxHumidity": 76,
+ "minHumidity": 32,
+ "humidity": 76,
+ "pop": 0,
+ "precipMM": 0,
+ "precipIN": 0,
+ "iceaccum": null,
+ "iceaccumMM": null,
+ "iceaccumIN": null,
+ "snowCM": 0,
+ "snowIN": 0,
+ "pressureMB": 1014,
+ "pressureIN": 29.94,
+ "windDir": "NNE",
+ "windDirDEG": 21,
+ "windSpeedKTS": 1,
+ "windSpeedKPH": 2,
+ "windSpeedMPH": 1,
+ "windGustKTS": 17,
+ "windGustKPH": 32,
+ "windGustMPH": 20,
+ "windDirMax": "WNW",
+ "windDirMaxDEG": 301,
+ "windSpeedMaxKTS": 7,
+ "windSpeedMaxKPH": 13,
+ "windSpeedMaxMPH": 8,
+ "windDirMin": "NNE",
+ "windDirMinDEG": 21,
+ "windSpeedMinKTS": 1,
+ "windSpeedMinKPH": 2,
+ "windSpeedMinMPH": 1,
+ "windDir80m": "NW",
+ "windDir80mDEG": 309,
+ "windSpeed80mKTS": 5,
+ "windSpeed80mKPH": 9,
+ "windSpeed80mMPH": 5,
+ "windGust80mKTS": 17,
+ "windGust80mKPH": 31,
+ "windGust80mMPH": 19,
+ "windDirMax80m": "NW",
+ "windDirMax80mDEG": 322,
+ "windSpeedMax80mKTS": 17,
+ "windSpeedMax80mKPH": 31,
+ "windSpeedMax80mMPH": 19,
+ "windDirMin80m": "NW",
+ "windDirMin80mDEG": 309,
+ "windSpeedMin80mKTS": 5,
+ "windSpeedMin80mKPH": 9,
+ "windSpeedMin80mMPH": 5,
+ "sky": 47,
+ "cloudsCoded": "SC",
+ "weather": "Partly Cloudy",
+ "weatherCoded": [],
+ "weatherPrimary": "Partly Cloudy",
+ "weatherPrimaryCoded": "::SC",
+ "icon": "pcloudy.png",
+ "visibilityKM": 24.135,
+ "visibilityMI": 15,
+ "uvi": 7,
+ "solradWM2": 4785,
+ "solradMinWM2": 0,
+ "solradMaxWM2": 682,
+ "isDay": true,
+ "maxCoverage": "",
+ "sunrise": 1665201146,
+ "sunset": 1665243161,
+ "sunriseISO": "2022-10-08T05:52:26+02:00",
+ "sunsetISO": "2022-10-08T17:32:41+02:00"
+ },
+ {
+ "timestamp": 1665291600,
+ "validTime": "2022-10-09T07:00:00+02:00",
+ "dateTimeISO": "2022-10-09T07:00:00+02:00",
+ "maxTempC": 31,
+ "maxTempF": 87,
+ "minTempC": 19,
+ "minTempF": 67,
+ "avgTempC": 25,
+ "avgTempF": 77,
+ "tempC": null,
+ "tempF": null,
+ "maxFeelslikeC": 30,
+ "maxFeelslikeF": 86,
+ "minFeelslikeC": 20,
+ "minFeelslikeF": 67,
+ "avgFeelslikeC": 25,
+ "avgFeelslikeF": 77,
+ "feelslikeC": 20,
+ "feelslikeF": 67,
+ "maxDewpointC": 17,
+ "maxDewpointF": 63,
+ "minDewpointC": 11,
+ "minDewpointF": 52,
+ "avgDewpointC": 14,
+ "avgDewpointF": 57,
+ "dewpointC": 17,
+ "dewpointF": 63,
+ "maxHumidity": 86,
+ "minHumidity": 31,
+ "humidity": 86,
+ "pop": 0,
+ "precipMM": 0,
+ "precipIN": 0,
+ "iceaccum": null,
+ "iceaccumMM": null,
+ "iceaccumIN": null,
+ "snowCM": 0,
+ "snowIN": 0,
+ "pressureMB": 1016,
+ "pressureIN": 29.99,
+ "windDir": "N",
+ "windDirDEG": 356,
+ "windSpeedKTS": 2,
+ "windSpeedKPH": 4,
+ "windSpeedMPH": 2,
+ "windGustKTS": 19,
+ "windGustKPH": 36,
+ "windGustMPH": 22,
+ "windDirMax": "NNW",
+ "windDirMaxDEG": 343,
+ "windSpeedMaxKTS": 8,
+ "windSpeedMaxKPH": 14,
+ "windSpeedMaxMPH": 9,
+ "windDirMin": "N",
+ "windDirMinDEG": 356,
+ "windSpeedMinKTS": 2,
+ "windSpeedMinKPH": 4,
+ "windSpeedMinMPH": 2,
+ "windDir80m": "NW",
+ "windDir80mDEG": 316,
+ "windSpeed80mKTS": 5,
+ "windSpeed80mKPH": 9,
+ "windSpeed80mMPH": 6,
+ "windGust80mKTS": 20,
+ "windGust80mKPH": 36,
+ "windGust80mMPH": 23,
+ "windDirMax80m": "N",
+ "windDirMax80mDEG": 354,
+ "windSpeedMax80mKTS": 20,
+ "windSpeedMax80mKPH": 36,
+ "windSpeedMax80mMPH": 23,
+ "windDirMin80m": "NW",
+ "windDirMin80mDEG": 316,
+ "windSpeedMin80mKTS": 5,
+ "windSpeedMin80mKPH": 9,
+ "windSpeedMin80mMPH": 6,
+ "sky": 47,
+ "cloudsCoded": "SC",
+ "weather": "Partly Cloudy",
+ "weatherCoded": [],
+ "weatherPrimary": "Partly Cloudy",
+ "weatherPrimaryCoded": "::SC",
+ "icon": "pcloudy.png",
+ "visibilityKM": 24.135,
+ "visibilityMI": 15,
+ "uvi": 7,
+ "solradWM2": 4768,
+ "solradMinWM2": 0,
+ "solradMaxWM2": 726,
+ "isDay": true,
+ "maxCoverage": "",
+ "sunrise": 1665287583,
+ "sunset": 1665329491,
+ "sunriseISO": "2022-10-09T05:53:03+02:00",
+ "sunsetISO": "2022-10-09T17:31:31+02:00"
+ },
+ {
+ "timestamp": 1665378000,
+ "validTime": "2022-10-10T07:00:00+02:00",
+ "dateTimeISO": "2022-10-10T07:00:00+02:00",
+ "maxTempC": 31,
+ "maxTempF": 87,
+ "minTempC": 21,
+ "minTempF": 70,
+ "avgTempC": 26,
+ "avgTempF": 78,
+ "tempC": null,
+ "tempF": null,
+ "maxFeelslikeC": 30,
+ "maxFeelslikeF": 86,
+ "minFeelslikeC": 21,
+ "minFeelslikeF": 69,
+ "avgFeelslikeC": 25,
+ "avgFeelslikeF": 78,
+ "feelslikeC": 21,
+ "feelslikeF": 69,
+ "maxDewpointC": 16,
+ "maxDewpointF": 61,
+ "minDewpointC": 13,
+ "minDewpointF": 55,
+ "avgDewpointC": 14,
+ "avgDewpointF": 58,
+ "dewpointC": 16,
+ "dewpointF": 61,
+ "maxHumidity": 75,
+ "minHumidity": 35,
+ "humidity": 75,
+ "pop": 0,
+ "precipMM": 0,
+ "precipIN": 0,
+ "iceaccum": null,
+ "iceaccumMM": null,
+ "iceaccumIN": null,
+ "snowCM": 0,
+ "snowIN": 0,
+ "pressureMB": 1017,
+ "pressureIN": 30.03,
+ "windDir": "N",
+ "windDirDEG": 358,
+ "windSpeedKTS": 2,
+ "windSpeedKPH": 4,
+ "windSpeedMPH": 2,
+ "windGustKTS": 16,
+ "windGustKPH": 30,
+ "windGustMPH": 19,
+ "windDirMax": "N",
+ "windDirMaxDEG": 10,
+ "windSpeedMaxKTS": 8,
+ "windSpeedMaxKPH": 15,
+ "windSpeedMaxMPH": 9,
+ "windDirMin": "N",
+ "windDirMinDEG": 358,
+ "windSpeedMinKTS": 2,
+ "windSpeedMinKPH": 4,
+ "windSpeedMinMPH": 2,
+ "windDir80m": "N",
+ "windDir80mDEG": 8,
+ "windSpeed80mKTS": 7,
+ "windSpeed80mKPH": 13,
+ "windSpeed80mMPH": 8,
+ "windGust80mKTS": 19,
+ "windGust80mKPH": 36,
+ "windGust80mMPH": 22,
+ "windDirMax80m": "N",
+ "windDirMax80mDEG": 10,
+ "windSpeedMax80mKTS": 19,
+ "windSpeedMax80mKPH": 36,
+ "windSpeedMax80mMPH": 22,
+ "windDirMin80m": "E",
+ "windDirMin80mDEG": 91,
+ "windSpeedMin80mKTS": 7,
+ "windSpeedMin80mKPH": 13,
+ "windSpeedMin80mMPH": 8,
+ "sky": 64,
+ "cloudsCoded": "SC",
+ "weather": "Partly Cloudy",
+ "weatherCoded": [],
+ "weatherPrimary": "Partly Cloudy",
+ "weatherPrimaryCoded": "::SC",
+ "icon": "pcloudy.png",
+ "visibilityKM": 24.135,
+ "visibilityMI": 15,
+ "uvi": 6,
+ "solradWM2": 4494,
+ "solradMinWM2": 0,
+ "solradMaxWM2": 597,
+ "isDay": true,
+ "maxCoverage": "",
+ "sunrise": 1665374020,
+ "sunset": 1665415821,
+ "sunriseISO": "2022-10-10T05:53:40+02:00",
+ "sunsetISO": "2022-10-10T17:30:21+02:00"
+ },
+ {
+ "timestamp": 1665464400,
+ "validTime": "2022-10-11T07:00:00+02:00",
+ "dateTimeISO": "2022-10-11T07:00:00+02:00",
+ "maxTempC": 31,
+ "maxTempF": 87,
+ "minTempC": 21,
+ "minTempF": 70,
+ "avgTempC": 26,
+ "avgTempF": 78,
+ "tempC": null,
+ "tempF": null,
+ "maxFeelslikeC": 31,
+ "maxFeelslikeF": 87,
+ "minFeelslikeC": 22,
+ "minFeelslikeF": 72,
+ "avgFeelslikeC": 26,
+ "avgFeelslikeF": 79,
+ "feelslikeC": 22,
+ "feelslikeF": 72,
+ "maxDewpointC": 17,
+ "maxDewpointF": 62,
+ "minDewpointC": 11,
+ "minDewpointF": 51,
+ "avgDewpointC": 13,
+ "avgDewpointF": 55,
+ "dewpointC": 17,
+ "dewpointF": 62,
+ "maxHumidity": 71,
+ "minHumidity": 30,
+ "humidity": 71,
+ "pop": 0,
+ "precipMM": 0,
+ "precipIN": 0,
+ "iceaccum": null,
+ "iceaccumMM": null,
+ "iceaccumIN": null,
+ "snowCM": 0,
+ "snowIN": 0,
+ "pressureMB": 1015,
+ "pressureIN": 29.98,
+ "windDir": "NNE",
+ "windDirDEG": 13,
+ "windSpeedKTS": 8,
+ "windSpeedKPH": 15,
+ "windSpeedMPH": 9,
+ "windGustKTS": 15,
+ "windGustKPH": 28,
+ "windGustMPH": 17,
+ "windDirMax": "NNE",
+ "windDirMaxDEG": 28,
+ "windSpeedMaxKTS": 15,
+ "windSpeedMaxKPH": 28,
+ "windSpeedMaxMPH": 18,
+ "windDirMin": "NNE",
+ "windDirMinDEG": 14,
+ "windSpeedMinKTS": 7,
+ "windSpeedMinKPH": 14,
+ "windSpeedMinMPH": 8,
+ "windDir80m": "NNE",
+ "windDir80mDEG": 16,
+ "windSpeed80mKTS": 10,
+ "windSpeed80mKPH": 19,
+ "windSpeed80mMPH": 12,
+ "windGust80mKTS": 17,
+ "windGust80mKPH": 31,
+ "windGust80mMPH": 19,
+ "windDirMax80m": "NNE",
+ "windDirMax80mDEG": 28,
+ "windSpeedMax80mKTS": 17,
+ "windSpeedMax80mKPH": 31,
+ "windSpeedMax80mMPH": 19,
+ "windDirMin80m": "NNE",
+ "windDirMin80mDEG": 13,
+ "windSpeedMin80mKTS": 9,
+ "windSpeedMin80mKPH": 18,
+ "windSpeedMin80mMPH": 11,
+ "sky": 0,
+ "cloudsCoded": "CL",
+ "weather": "Sunny",
+ "weatherCoded": [],
+ "weatherPrimary": "Sunny",
+ "weatherPrimaryCoded": "::CL",
+ "icon": "sunny.png",
+ "visibilityKM": 24.135,
+ "visibilityMI": 15,
+ "uvi": null,
+ "solradWM2": 5450,
+ "solradMinWM2": 0,
+ "solradMaxWM2": 758,
+ "isDay": true,
+ "maxCoverage": "",
+ "sunrise": 1665460458,
+ "sunset": 1665502153,
+ "sunriseISO": "2022-10-11T05:54:18+02:00",
+ "sunsetISO": "2022-10-11T17:29:13+02:00"
+ },
+ {
+ "timestamp": 1665550800,
+ "validTime": "2022-10-12T07:00:00+02:00",
+ "dateTimeISO": "2022-10-12T07:00:00+02:00",
+ "maxTempC": 31,
+ "maxTempF": 88,
+ "minTempC": 21,
+ "minTempF": 69,
+ "avgTempC": 26,
+ "avgTempF": 79,
+ "tempC": null,
+ "tempF": null,
+ "maxFeelslikeC": 31,
+ "maxFeelslikeF": 88,
+ "minFeelslikeC": 22,
+ "minFeelslikeF": 72,
+ "avgFeelslikeC": 26,
+ "avgFeelslikeF": 80,
+ "feelslikeC": 22,
+ "feelslikeF": 72,
+ "maxDewpointC": 16,
+ "maxDewpointF": 60,
+ "minDewpointC": 11,
+ "minDewpointF": 51,
+ "avgDewpointC": 13,
+ "avgDewpointF": 55,
+ "dewpointC": 16,
+ "dewpointF": 60,
+ "maxHumidity": 68,
+ "minHumidity": 29,
+ "humidity": 68,
+ "pop": 0,
+ "precipMM": 0,
+ "precipIN": 0,
+ "iceaccum": null,
+ "iceaccumMM": null,
+ "iceaccumIN": null,
+ "snowCM": 0,
+ "snowIN": 0,
+ "pressureMB": 1014,
+ "pressureIN": 29.95,
+ "windDir": "NNE",
+ "windDirDEG": 12,
+ "windSpeedKTS": 8,
+ "windSpeedKPH": 15,
+ "windSpeedMPH": 9,
+ "windGustKTS": 15,
+ "windGustKPH": 28,
+ "windGustMPH": 17,
+ "windDirMax": "E",
+ "windDirMaxDEG": 96,
+ "windSpeedMaxKTS": 14,
+ "windSpeedMaxKPH": 26,
+ "windSpeedMaxMPH": 16,
+ "windDirMin": "NNE",
+ "windDirMinDEG": 12,
+ "windSpeedMinKTS": 7,
+ "windSpeedMinKPH": 13,
+ "windSpeedMinMPH": 8,
+ "windDir80m": "NNE",
+ "windDir80mDEG": 15,
+ "windSpeed80mKTS": 10,
+ "windSpeed80mKPH": 19,
+ "windSpeed80mMPH": 12,
+ "windGust80mKTS": 18,
+ "windGust80mKPH": 33,
+ "windGust80mMPH": 21,
+ "windDirMax80m": "E",
+ "windDirMax80mDEG": 96,
+ "windSpeedMax80mKTS": 18,
+ "windSpeedMax80mKPH": 33,
+ "windSpeedMax80mMPH": 21,
+ "windDirMin80m": "NNE",
+ "windDirMin80mDEG": 15,
+ "windSpeedMin80mKTS": 10,
+ "windSpeedMin80mKPH": 18,
+ "windSpeedMin80mMPH": 11,
+ "sky": 27,
+ "cloudsCoded": "FW",
+ "weather": "Mostly Sunny",
+ "weatherCoded": [],
+ "weatherPrimary": "Mostly Sunny",
+ "weatherPrimaryCoded": "::FW",
+ "icon": "fair.png",
+ "visibilityKM": 24.135,
+ "visibilityMI": 15,
+ "uvi": null,
+ "solradWM2": 4740,
+ "solradMinWM2": 0,
+ "solradMaxWM2": 743,
+ "isDay": true,
+ "maxCoverage": "",
+ "sunrise": 1665546895,
+ "sunset": 1665588484,
+ "sunriseISO": "2022-10-12T05:54:55+02:00",
+ "sunsetISO": "2022-10-12T17:28:04+02:00"
+ }
+ ],
+ "profile": {
+ "tz": "Africa/Cairo",
+ "elevM": 23,
+ "elevFT": 75
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/test/resources/Issue593.xml b/src/test/resources/Issue593.xml
new file mode 100644
index 0000000..0c6c038
--- /dev/null
+++ b/src/test/resources/Issue593.xml
@@ -0,0 +1,691 @@
+true
+
+
+ 31.25
+ 30.063
+
+
+ 23
+ Africa/Cairo
+ 75
+
+
+ 2022-10-06T07:00:00+02:00
+ E
+ 95
+ 21
+ 15
+ 10
+ 353
+ N
+ 2022-10-06T05:51:14+02:00
+ null
+ 9
+ null
+ 66
+ 0
+ Mostly Sunny
+ 2022-10-06T17:35:02+02:00
+ 32
+ 77
+ N
+ 89
+ 0
+ 22
+ 25
+ 25
+ Mostly Sunny
+ 41
+ 58
+ 41
+ 22
+ 14
+ 0
+ 22
+ 353
+ 16
+ 8
+ 70
+ 2022-10-06T07:00:00+02:00
+ 10
+ 778
+ 25
+ 15
+ ::FW
+ 1665028274
+ 78
+ N
+
+ fair.png
+ 21
+ 17
+ FW
+ 70
+ 29
+ 63
+ 12
+ 0
+ 0
+ NNW
+ 13
+ 22
+ 11
+ 32
+ 1015
+ 24.135
+ 1665032400
+ 90
+ null
+ 11
+ 0
+ 1
+ 343
+ 21
+ 2
+ 63
+ 1
+ 26
+ 6
+ NNW
+ 17
+ 29.97
+ 80
+ null
+ true
+ 19
+ 52
+ 5
+ 1665070502
+ 5608
+ 9
+ 25
+ 77
+ 6
+ 40
+ 342
+ null
+
+
+ 2022-10-07T07:00:00+02:00
+ NNW
+ 347
+ 19
+ 15
+ 8
+ 325
+ NW
+ 2022-10-07T05:51:50+02:00
+ null
+ 7
+ null
+ 66
+ 0
+ Mostly Sunny
+ 2022-10-07T17:33:51+02:00
+ 29
+ 77
+ NNW
+ 85
+ 0
+ 30
+ 23
+ 23
+ Mostly Sunny
+ 37
+ 54
+ 37
+ 20
+ 12
+ 0
+ 20
+ 325
+ 13
+ 6
+ 67
+ 2022-10-07T07:00:00+02:00
+ 6
+ 742
+ 24
+ 10
+ ::FW
+ 1665114710
+ 76
+ NW
+
+ fair.png
+ 19
+ 15
+ FW
+ 67
+ 30
+ 60
+ 6
+ 0
+ 0
+ WNW
+ 6
+ 10
+ 347
+ 30
+ 1014
+ 24.135
+ 1665118800
+ 86
+ null
+ 10
+ 0
+ 1
+ 316
+ 16
+ 2
+ 60
+ 1
+ 24
+ 6
+ NW
+ 15
+ 29.95
+ 76
+ null
+ true
+ 19
+ 50
+ 1
+ 1665156831
+ 5486
+ 2
+ 18
+ 77
+ 1
+ 29
+ 298
+ null
+
+
+ 2022-10-08T07:00:00+02:00
+ NW
+ 309
+ 19
+ 15
+ 8
+ 21
+ NNE
+ 2022-10-08T05:52:26+02:00
+ null
+ 7
+ null
+ 66
+ 0
+ Partly Cloudy
+ 2022-10-08T17:32:41+02:00
+ 30
+ 76
+ NW
+ 86
+ 0
+ 47
+ 19
+ 19
+ Partly Cloudy
+ 31
+ 56
+ 31
+ 17
+ 13
+ 0
+ 17
+ 21
+ 13
+ 5
+ 67
+ 2022-10-08T07:00:00+02:00
+ 5
+ 682
+ 25
+ 9
+ ::SC
+ 1665201146
+ 76
+ NNE
+
+ pcloudy.png
+ 19
+ 15
+ SC
+ 67
+ 32
+ 59
+ 5
+ 0
+ 0
+ WNW
+ 5
+ 9
+ 309
+ 30
+ 1014
+ 24.135
+ 1665205200
+ 87
+ null
+ 11
+ 0
+ 1
+ 322
+ 17
+ 2
+ 59
+ 1
+ 25
+ 7
+ NW
+ 15
+ 29.94
+ 76
+ null
+ true
+ 19
+ 52
+ 1
+ 1665243161
+ 4785
+ 2
+ 20
+ 76
+ 1
+ 32
+ 301
+ null
+
+
+ 2022-10-09T07:00:00+02:00
+ NW
+ 316
+ 20
+ 15
+ 9
+ 356
+ N
+ 2022-10-09T05:53:03+02:00
+ null
+ 8
+ null
+ 67
+ 0
+ Partly Cloudy
+ 2022-10-09T17:31:31+02:00
+ 30
+ 86
+ NW
+ 86
+ 0
+ 47
+ 23
+ 23
+ Partly Cloudy
+ 36
+ 57
+ 36
+ 20
+ 14
+ 0
+ 20
+ 356
+ 14
+ 5
+ 67
+ 2022-10-09T07:00:00+02:00
+ 6
+ 726
+ 25
+ 9
+ ::SC
+ 1665287583
+ 77
+ N
+
+ pcloudy.png
+ 20
+ 17
+ SC
+ 67
+ 31
+ 63
+ 5
+ 0
+ 0
+ NNW
+ 6
+ 9
+ 316
+ 31
+ 1016
+ 24.135
+ 1665291600
+ 87
+ null
+ 11
+ 0
+ 2
+ 354
+ 19
+ 4
+ 63
+ 2
+ 25
+ 7
+ N
+ 17
+ 29.99
+ 77
+ null
+ true
+ 19
+ 52
+ 2
+ 1665329491
+ 4768
+ 4
+ 22
+ 86
+ 2
+ 36
+ 343
+ null
+
+
+ 2022-10-10T07:00:00+02:00
+ E
+ 91
+ 21
+ 15
+ 9
+ 358
+ N
+ 2022-10-10T05:53:40+02:00
+ null
+ 8
+ null
+ 70
+ 0
+ Partly Cloudy
+ 2022-10-10T17:30:21+02:00
+ 30
+ 75
+ N
+ 86
+ 0
+ 64
+ 22
+ 22
+ Partly Cloudy
+ 36
+ 58
+ 36
+ 19
+ 14
+ 0
+ 19
+ 358
+ 15
+ 7
+ 69
+ 2022-10-10T07:00:00+02:00
+ 8
+ 597
+ 26
+ 13
+ ::SC
+ 1665374020
+ 78
+ N
+
+ pcloudy.png
+ 21
+ 16
+ SC
+ 69
+ 35
+ 61
+ 7
+ 0
+ 0
+ N
+ 8
+ 13
+ 8
+ 31
+ 1017
+ 24.135
+ 1665378000
+ 87
+ null
+ 13
+ 0
+ 2
+ 10
+ 16
+ 4
+ 61
+ 2
+ 25
+ 6
+ N
+ 16
+ 30.03
+ 78
+ null
+ true
+ 21
+ 55
+ 2
+ 1665415821
+ 4494
+ 4
+ 19
+ 75
+ 2
+ 30
+ 10
+ null
+
+
+ 2022-10-11T07:00:00+02:00
+ NNE
+ 13
+ 22
+ 15
+ 18
+ 13
+ NNE
+ 2022-10-11T05:54:18+02:00
+ null
+ 15
+ null
+ 70
+ 0
+ Sunny
+ 2022-10-11T17:29:13+02:00
+ 31
+ 71
+ NNE
+ 87
+ 0
+ 0
+ 19
+ 19
+ Sunny
+ 31
+ 55
+ 31
+ 17
+ 13
+ 0
+ 17
+ 14
+ 28
+ 9
+ 72
+ 2022-10-11T07:00:00+02:00
+ 11
+ 758
+ 26
+ 18
+ ::CL
+ 1665460458
+ 78
+ NNE
+
+ sunny.png
+ 22
+ 17
+ CL
+ 72
+ 30
+ 62
+ 10
+ 0
+ 0
+ NNE
+ 12
+ 19
+ 16
+ 31
+ 1015
+ 24.135
+ 1665464400
+ 87
+ null
+ 11
+ 0
+ 7
+ 28
+ 15
+ 14
+ 62
+ 8
+ 26
+ null
+ NNE
+ 17
+ 29.98
+ 79
+ null
+ true
+ 21
+ 51
+ 8
+ 1665502153
+ 5450
+ 15
+ 17
+ 71
+ 9
+ 28
+ 28
+ null
+
+
+ 2022-10-12T07:00:00+02:00
+ NNE
+ 15
+ 22
+ 15
+ 16
+ 12
+ NNE
+ 2022-10-12T05:54:55+02:00
+ null
+ 14
+ null
+ 69
+ 0
+ Mostly Sunny
+ 2022-10-12T17:28:04+02:00
+ 31
+ 68
+ NNE
+ 88
+ 0
+ 27
+ 21
+ 21
+ Mostly Sunny
+ 33
+ 55
+ 33
+ 18
+ 13
+ 0
+ 18
+ 12
+ 26
+ 10
+ 72
+ 2022-10-12T07:00:00+02:00
+ 11
+ 743
+ 26
+ 18
+ ::FW
+ 1665546895
+ 79
+ NNE
+
+ fair.png
+ 22
+ 16
+ FW
+ 72
+ 29
+ 60
+ 10
+ 0
+ 0
+ E
+ 12
+ 19
+ 15
+ 31
+ 1014
+ 24.135
+ 1665550800
+ 88
+ null
+ 11
+ 0
+ 7
+ 96
+ 15
+ 13
+ 60
+ 8
+ 26
+ null
+ E
+ 16
+ 29.95
+ 80
+ null
+ true
+ 21
+ 51
+ 8
+ 1665588484
+ 4740
+ 15
+ 17
+ 68
+ 9
+ 28
+ 96
+ null
+
+ day
+
+ eg
+ cairo
+ qh
+
+
+null
diff --git a/src/test/resources/Issue654WellFormedArray.json b/src/test/resources/Issue654WellFormedArray.json
new file mode 100644
index 0000000..513e1b4
--- /dev/null
+++ b/src/test/resources/Issue654WellFormedArray.json
@@ -0,0 +1,822 @@
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",
+["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",["a",[]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
+]]]]]]]]]]]]
diff --git a/src/test/resources/Issue654WellFormedObject.json b/src/test/resources/Issue654WellFormedObject.json
new file mode 100644
index 0000000..70344c1
--- /dev/null
+++ b/src/test/resources/Issue654WellFormedObject.json
@@ -0,0 +1,822 @@
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
+{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}