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 7caeebb07..ba6c56967 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 @@ of this software and associated documentation files (the "Software"), to deal
* @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/StringBuilderWriter.java b/src/main/java/org/json/StringBuilderWriter.java
new file mode 100644
index 000000000..4aaa4903f
--- /dev/null
+++ b/src/main/java/org/json/StringBuilderWriter.java
@@ -0,0 +1,92 @@
+package org.json;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Performance optimised alternative for {@link java.io.StringWriter}
+ * using internally a {@link StringBuilder} instead of a {@link StringBuffer}.
+ */
+public class StringBuilderWriter extends Writer {
+ private final StringBuilder builder;
+
+ /**
+ * Create a new string builder writer using the default initial string-builder buffer size.
+ */
+ public StringBuilderWriter() {
+ builder = new StringBuilder();
+ lock = builder;
+ }
+
+ /**
+ * Create a new string builder writer using the specified initial string-builder buffer size.
+ *
+ * @param initialSize The number of {@code char} values that will fit into this buffer
+ * before it is automatically expanded
+ *
+ * @throws IllegalArgumentException If {@code initialSize} is negative
+ */
+ public StringBuilderWriter(int initialSize) {
+ builder = new StringBuilder(initialSize);
+ lock = builder;
+ }
+
+ @Override
+ public void write(int c) {
+ builder.append((char) c);
+ }
+
+ @Override
+ public void write(char[] cbuf, int offset, int length) {
+ if ((offset < 0) || (offset > cbuf.length) || (length < 0) ||
+ ((offset + length) > cbuf.length) || ((offset + length) < 0)) {
+ throw new IndexOutOfBoundsException();
+ } else if (length == 0) {
+ return;
+ }
+ builder.append(cbuf, offset, length);
+ }
+
+ @Override
+ public void write(String str) {
+ builder.append(str);
+ }
+
+ @Override
+ public void write(String str, int offset, int length) {
+ builder.append(str, offset, offset + length);
+ }
+
+ @Override
+ public StringBuilderWriter append(CharSequence csq) {
+ write(String.valueOf(csq));
+ return this;
+ }
+
+ @Override
+ public StringBuilderWriter append(CharSequence csq, int start, int end) {
+ if (csq == null) {
+ csq = "null";
+ }
+ return append(csq.subSequence(start, end));
+ }
+
+ @Override
+ public StringBuilderWriter append(char c) {
+ write(c);
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return builder.toString();
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void close() throws IOException {
+ }
+}
diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java
index c09fb035c..3eb948c77 100644
--- a/src/main/java/org/json/XML.java
+++ b/src/main/java/org/json/XML.java
@@ -1,27 +1,7 @@
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;
@@ -40,6 +20,12 @@ of this software and associated documentation files (the "Software"), to deal
@SuppressWarnings("boxing")
public class XML {
+ /**
+ * Constructs a new XML object.
+ */
+ public XML() {
+ }
+
/** The Character '&'. */
public static final Character AMP = '&';
@@ -72,6 +58,11 @@ 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";
+
/**
* Creates an iterator for navigating Code Points in a string instead of
* characters. Once Java7 support is dropped, this can be replaced with
@@ -115,7 +106,7 @@ public void remove() {
/**
* Replace special characters with XML escapes:
*
- * {@code
+ * {@code
* & (ampersand) is replaced by &
* < (less than) is replaced by <
* > (greater than) is replaced by >
@@ -246,10 +237,14 @@ public static void noSpace(String string) throws JSONException {
* 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;
@@ -257,6 +252,7 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
String string;
String tagName;
Object token;
+ XMLXsiTypeConverter> xmlXsiTypeConverter;
// Test for and skip past these forms:
//
@@ -286,7 +282,7 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
if (x.next() == '[') {
string = x.nextCDATA();
if (string.length() > 0) {
- context.accumulate(config.cDataTagName, string);
+ context.accumulate(config.getcDataTagName(), string);
}
return false;
}
@@ -336,6 +332,7 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
token = null;
jsonObject = new JSONObject();
boolean nilAttributeFound = false;
+ xmlXsiTypeConverter = null;
for (;;) {
if (token == null) {
token = x.nextToken();
@@ -350,15 +347,28 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
throw x.syntaxError("Missing value");
}
- if (config.convertNilAttributeToNull
+ if (config.isConvertNilAttributeToNull()
&& NULL_ATTR.equals(string)
&& Boolean.parseBoolean((String) token)) {
nilAttributeFound = true;
+ } else if(config.getXsiTypeMap() != null && !config.getXsiTypeMap().isEmpty()
+ && TYPE_ATTR.equals(string)) {
+ xmlXsiTypeConverter = config.getXsiTypeMap().get(token);
} else if (!nilAttributeFound) {
- jsonObject.accumulate(string,
- config.keepStrings
- ? ((String) token)
- : stringToValue((String) token));
+ Object obj = stringToValue((String) token);
+ if (obj instanceof Boolean) {
+ jsonObject.accumulate(string,
+ config.isKeepBooleanAsString()
+ ? ((String) token)
+ : obj);
+ } else if (obj instanceof Number) {
+ jsonObject.accumulate(string,
+ config.isKeepNumberAsString()
+ ? ((String) token)
+ : obj);
+ } else {
+ jsonObject.accumulate(string, stringToValue((String) token));
+ }
}
token = null;
} else {
@@ -371,12 +381,23 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
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;
@@ -392,21 +413,61 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
} else if (token instanceof String) {
string = (String) token;
if (string.length() > 0) {
- jsonObject.accumulate(config.cDataTagName,
- config.keepStrings ? string : stringToValue(string));
+ if(xmlXsiTypeConverter != null) {
+ jsonObject.accumulate(config.getcDataTagName(),
+ stringToValue(string, xmlXsiTypeConverter));
+ } else {
+ Object obj = stringToValue((String) token);
+ if (obj instanceof Boolean) {
+ jsonObject.accumulate(config.getcDataTagName(),
+ config.isKeepBooleanAsString()
+ ? ((String) token)
+ : obj);
+ } else if (obj instanceof Number) {
+ jsonObject.accumulate(config.getcDataTagName(),
+ config.isKeepNumberAsString()
+ ? ((String) token)
+ : obj);
+ } else if (obj == JSONObject.NULL) {
+ jsonObject.accumulate(config.getcDataTagName(),
+ config.isKeepStrings() ? ((String) token) : obj);
+ } else {
+ jsonObject.accumulate(config.getcDataTagName(), stringToValue((String) token));
+ }
+ }
}
} 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.cDataTagName) != null) {
- context.accumulate(tagName, jsonObject.opt(config.cDataTagName));
+ 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;
}
}
@@ -417,47 +478,48 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
}
}
}
-
/**
- * This method is the same as {@link JSONObject#stringToValue(String)}.
- *
- * @param string String to convert
- * @return JSON value of this string or the string
+ * 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
*/
- // To maintain compatibility with the Android API, this method is a direct copy of
- // the one in JSONObject. Changes made here should be reflected there.
- // This method should not make calls out of the XML object.
- 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;
+ 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());
+ }
+ }
}
+ }
- /*
- * 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) {
+ private static boolean isStringAllWhiteSpace(final String s) {
+ for (int k = 0; k -1 || "-0".equals(val);
}
+ /**
+ * This method tries to convert the given string value to the target object
+ * @param string String to convert
+ * @param typeConverter value converter to convert string to integer, boolean e.t.c
+ * @return JSON value of this string or the string
+ */
+ public static Object stringToValue(String string, XMLXsiTypeConverter> typeConverter) {
+ if(typeConverter != null) {
+ return typeConverter.convert(string);
+ }
+ return stringToValue(string);
+ }
+
+ /**
+ * This method is the same as {@link JSONObject#stringToValue(String)}.
+ *
+ * @param string String to convert
+ * @return JSON value of this string or the string
+ */
+ // To maintain compatibility with the Android API, this method is a direct copy of
+ // the one in JSONObject. Changes made here should be reflected there.
+ // This method should not make calls out of the XML object.
+ 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;
+ }
/**
* Convert a well-formed (but not necessarily valid) XML string into a
@@ -538,7 +652,7 @@ private static boolean isDecimalNotation(final String val) {
* 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.
*
@@ -559,7 +673,7 @@ public static JSONObject toJSONObject(String string) throws JSONException {
* 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.
*
@@ -599,6 +713,44 @@ public static JSONObject toJSONObject(Reader reader, boolean keepStrings) throws
return toJSONObject(reader, XMLParserConfiguration.ORIGINAL);
}
+ /**
+ * Convert a well-formed (but not necessarily valid) XML into a
+ * JSONObject. Some information may be lost in this transformation because
+ * JSON is a data format and XML is a document format. XML uses elements,
+ * attributes, and content text, while JSON uses unordered collections of
+ * 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
+ * <[ [ ]]>}
+ * are ignored.
+ *
+ * All numbers are converted as strings, for 1, 01, 29.0 will not be coerced to
+ * numbers but will instead be the exact value as seen in the XML document depending
+ * on how flag is set.
+ * All booleans are converted as strings, for true, false will not be coerced to
+ * booleans but will instead be the exact value as seen in the XML document depending
+ * on how flag is set.
+ *
+ * @param reader The XML source reader.
+ * @param keepNumberAsString If true, then numeric values will not be coerced into
+ * numeric values and will instead be left as strings
+ * @param keepBooleanAsString If true, then boolean values will not be coerced into
+ * * numeric values and will instead be left as strings
+ * @return A JSONObject containing the structured data from the XML string.
+ * @throws JSONException Thrown if there is an errors while parsing the string
+ */
+ public static JSONObject toJSONObject(Reader reader, boolean keepNumberAsString, boolean keepBooleanAsString) throws JSONException {
+ XMLParserConfiguration xmlParserConfiguration = new XMLParserConfiguration();
+ if(keepNumberAsString) {
+ xmlParserConfiguration = xmlParserConfiguration.withKeepNumberAsString(keepNumberAsString);
+ }
+ if(keepBooleanAsString) {
+ xmlParserConfiguration = xmlParserConfiguration.withKeepBooleanAsString(keepBooleanAsString);
+ }
+ return toJSONObject(reader, xmlParserConfiguration);
+ }
+
/**
* Convert a well-formed (but not necessarily valid) XML into a
* JSONObject. Some information may be lost in this transformation because
@@ -621,11 +773,11 @@ public static JSONObject toJSONObject(Reader reader, boolean keepStrings) throws
*/
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;
@@ -639,7 +791,7 @@ public static JSONObject toJSONObject(Reader reader, XMLParserConfiguration conf
* 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.
*
@@ -665,7 +817,39 @@ public static JSONObject toJSONObject(String string, boolean keepStrings) throws
* 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.
+ *
+ * All numbers are converted as strings, for 1, 01, 29.0 will not be coerced to
+ * numbers but will instead be the exact value as seen in the XML document depending
+ * on how flag is set.
+ * All booleans are converted as strings, for true, false will not be coerced to
+ * booleans but will instead be the exact value as seen in the XML document depending
+ * on how flag is set.
+ *
+ * @param string
+ * The source string.
+ * @param keepNumberAsString If true, then numeric values will not be coerced into
+ * numeric values and will instead be left as strings
+ * @param keepBooleanAsString If true, then boolean values will not be coerced into
+ * numeric values and will instead be left as strings
+ * @return A JSONObject containing the structured data from the XML string.
+ * @throws JSONException Thrown if there is an errors while parsing the string
+ */
+ public static JSONObject toJSONObject(String string, boolean keepNumberAsString, boolean keepBooleanAsString) throws JSONException {
+ return toJSONObject(new StringReader(string), keepNumberAsString, keepBooleanAsString);
+ }
+
+ /**
+ * Convert a well-formed (but not necessarily valid) XML string into a
+ * JSONObject. Some information may be lost in this transformation because
+ * JSON is a data format and XML is a document format. XML uses elements,
+ * attributes, and content text, while JSON uses unordered collections of
+ * 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
* <[ [ ]]>}
* are ignored.
*
@@ -722,6 +906,28 @@ public static String toString(final Object object, final String tagName) {
*/
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;
@@ -731,9 +937,14 @@ public static String toString(final Object object, final String tagName, final X
// 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.
@@ -748,7 +959,7 @@ public static String toString(final Object object, final String tagName, final X
}
// Emit content in body
- if (key.equals(config.cDataTagName)) {
+ if (key.equals(config.getcDataTagName())) {
if (value instanceof JSONArray) {
ja = (JSONArray) value;
int jaLength = ja.length();
@@ -776,31 +987,52 @@ public static String toString(final Object object, final String tagName, final X
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();
@@ -819,15 +1051,85 @@ public static String toString(final Object object, final String tagName, final X
// 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 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 c0186f72d..de84b90cb 100644
--- a/src/main/java/org/json/XMLParserConfiguration.java
+++ b/src/main/java/org/json/XMLParserConfiguration.java
@@ -1,67 +1,107 @@
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;
+
+
/**
- * Configuration object for the XML parser.
+ * Configuration object for the XML parser. The configuration is immutable.
* @author AylwardJ
- *
*/
-public class XMLParserConfiguration {
- /** Original Configuration of the XML Parser. */
- public static final XMLParserConfiguration ORIGINAL = new XMLParserConfiguration();
- /** Original configuration of the XML Parser except that values are kept as strings. */
- public static final XMLParserConfiguration KEEP_STRINGS = new XMLParserConfiguration(true);
+@SuppressWarnings({""})
+public class XMLParserConfiguration extends ParserConfiguration {
+
/**
- * 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)
+ * The default maximum nesting depth when parsing a XML document to JSON.
*/
- public final boolean keepStrings;
+// public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512; // We could override
+
+ /**
+ * Allow user to control how numbers are parsed
+ */
+ private boolean keepNumberAsString;
+
+ /**
+ * Allow user to control how booleans are parsed
+ */
+ private boolean keepBooleanAsString;
+
+ /** Original Configuration of the XML Parser. */
+ public static final XMLParserConfiguration ORIGINAL
+ = new XMLParserConfiguration();
+ /** Original configuration of the XML Parser except that values are kept as strings. */
+ public static final XMLParserConfiguration KEEP_STRINGS
+ = new XMLParserConfiguration().withKeepStrings(true);
+
/**
* 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.
*/
- public final String cDataTagName;
+ 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 null(true)
+ * should be kept as attribute(false), or they should be converted to
+ * null(true)
*/
- public final boolean convertNilAttributeToNull;
+ private boolean convertNilAttributeToNull;
/**
- * Default parser configuration. Does not keep strings, and the CDATA Tag Name is "content".
+ * 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". Trims whitespace.
*/
public XMLParserConfiguration () {
- this(false, "content", false);
+ super();
+ this.cDataTagName = "content";
+ this.convertNilAttributeToNull = false;
+ this.xsiTypeMap = Collections.emptyMap();
+ this.forceList = Collections.emptySet();
+ this.shouldTrimWhiteSpace = true;
}
/**
* Configure the parser string processing and use the default CDATA Tag Name as "content".
* @param keepStrings true to parse all values as string.
* false to try and convert XML string values into a JSON value.
+ * @deprecated This constructor has been deprecated in favor of using the new builder
+ * pattern for the configuration.
+ * This constructor may be removed in a future release.
*/
+ @Deprecated
public XMLParserConfiguration (final boolean keepStrings) {
this(keepStrings, "content", false);
}
@@ -70,9 +110,13 @@ public XMLParserConfiguration (final boolean keepStrings) {
* 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.
+ * This constructor may be removed in a future release.
*/
+ @Deprecated
public XMLParserConfiguration (final String cDataTagName) {
this(false, cDataTagName, false);
}
@@ -81,11 +125,15 @@ public XMLParserConfiguration (final String cDataTagName) {
* 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.
+ * This constructor may be removed in a future release.
*/
+ @Deprecated
public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName) {
- this.keepStrings = keepStrings;
+ super(keepStrings, DEFAULT_MAXIMUM_NESTING_DEPTH);
this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = false;
}
@@ -98,10 +146,299 @@ public XMLParserConfiguration (final boolean keepStrings, final String cDataTagN
* to use that value as the JSONObject key name to process as CDATA.
* @param convertNilAttributeToNull true to parse values with attribute xsi:nil="true" as null.
* false to parse values with attribute xsi:nil="true" as {"xsi:nil":true}.
+ * @deprecated This constructor has been deprecated in favor of using the new builder
+ * pattern for the configuration.
+ * This constructor may be removed or marked private in a future release.
*/
+ @Deprecated
public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, final boolean convertNilAttributeToNull) {
- this.keepStrings = keepStrings;
+ super(false, DEFAULT_MAXIMUM_NESTING_DEPTH);
+ this.keepNumberAsString = keepStrings;
+ this.keepBooleanAsString = keepStrings;
this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = convertNilAttributeToNull;
}
+
+ /**
+ * 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 cDataTagName null to disable CDATA processing. Any other value
+ * to use that value as the JSONObject key name to process as CDATA.
+ * @param convertNilAttributeToNull true to parse values with attribute xsi:nil="true" as null.
+ * 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, final Set forceList,
+ final int maxNestingDepth, final boolean closeEmptyTag, final boolean keepNumberAsString, final boolean keepBooleanAsString) {
+ super(false, maxNestingDepth);
+ this.keepNumberAsString = keepNumberAsString;
+ this.keepBooleanAsString = keepBooleanAsString;
+ this.cDataTagName = cDataTagName;
+ this.convertNilAttributeToNull = convertNilAttributeToNull;
+ this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap);
+ this.forceList = Collections.unmodifiableSet(forceList);
+ this.closeEmptyTag = closeEmptyTag;
+ }
+
+ /**
+ * Provides a new instance of the same configuration.
+ */
+ @Override
+ protected XMLParserConfiguration 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.
+ final XMLParserConfiguration config = new XMLParserConfiguration(
+ this.keepStrings,
+ this.cDataTagName,
+ this.convertNilAttributeToNull,
+ this.xsiTypeMap,
+ this.forceList,
+ this.maxNestingDepth,
+ this.closeEmptyTag,
+ this.keepNumberAsString,
+ this.keepBooleanAsString
+ );
+ 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 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;
+ newConfig.keepNumberAsString = newVal;
+ newConfig.keepBooleanAsString = newVal;
+ return newConfig;
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies if numbers should be kept as strings (1), or if
+ * they should try to be guessed into JSON values (numeric, boolean, string)
+ *
+ * @param newVal
+ * new value to use for the keepNumberAsString configuration option.
+ *
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public XMLParserConfiguration withKeepNumberAsString(final boolean newVal) {
+ XMLParserConfiguration newConfig = this.clone();
+ newConfig.keepNumberAsString = newVal;
+ newConfig.keepStrings = newConfig.keepBooleanAsString && newConfig.keepNumberAsString;
+ return newConfig;
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies if booleans 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 withKeepBooleanAsString configuration option.
+ *
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public XMLParserConfiguration withKeepBooleanAsString(final boolean newVal) {
+ XMLParserConfiguration newConfig = this.clone();
+ newConfig.keepBooleanAsString = newVal;
+ newConfig.keepStrings = newConfig.keepBooleanAsString && newConfig.keepNumberAsString;
+ return newConfig;
+ }
+
+ /**
+ * 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 cDataTagName configuration value.
+ */
+ public String getcDataTagName() {
+ return this.cDataTagName;
+ }
+
+ /**
+ * When parsing the XML into JSONML, specifies if numbers 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 isKeepNumberAsString() {
+ return this.keepNumberAsString;
+ }
+
+ /**
+ * When parsing the XML into JSONML, specifies if booleans 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 isKeepBooleanAsString() {
+ return this.keepBooleanAsString;
+ }
+
+ /**
+ * 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 cDataTagName configuration option.
+ *
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public XMLParserConfiguration withcDataTagName(final String newVal) {
+ XMLParserConfiguration newConfig = this.clone();
+ newConfig.cDataTagName = newVal;
+ return newConfig;
+ }
+
+ /**
+ * 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 convertNilAttributeToNull configuration value.
+ */
+ public boolean isConvertNilAttributeToNull() {
+ return this.convertNilAttributeToNull;
+ }
+
+ /**
+ * 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 convertNilAttributeToNull configuration option.
+ *
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public XMLParserConfiguration withConvertNilAttributeToNull(final boolean newVal) {
+ XMLParserConfiguration newConfig = this.clone();
+ newConfig.convertNilAttributeToNull = newVal;
+ return newConfig;
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies that the values with attribute xsi:type
+ * 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 xsiTypeMap unmodifiable configuration map.
+ */
+ public Map> getXsiTypeMap() {
+ return this.xsiTypeMap;
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies that the values with attribute xsi:type
+ * 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
+ * @param xsiTypeMap {@code new HashMap>()} to parse values with attribute
+ * xsi:type="integer" as integer, xsi:type="string" as string
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public XMLParserConfiguration withXsiTypeMap(final Map> xsiTypeMap) {
+ XMLParserConfiguration newConfig = this.clone();
+ Map> cloneXsiTypeMap = new HashMap>(xsiTypeMap);
+ 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 0ecdb4f45..bc18b31c9 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 XMLTokener(String s) {
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 Object nextContent() throws JSONException {
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 Object nextContent() throws JSONException {
}
if (c == '<') {
back();
- return sb.toString().trim();
+ if (configuration.shouldTrimWhiteSpace()) {
+ return sb.toString().trim();
+ } else return sb.toString();
}
if (c == '&') {
sb.append(nextEntity(c));
@@ -167,7 +161,7 @@ static String unescapeEntity(String e) {
// if our entity is an encoded unicode point, parse it.
if (e.charAt(0) == '#') {
int cp;
- if (e.charAt(1) == 'x') {
+ if (e.charAt(1) == 'x' || e.charAt(1) == 'X') {
// hex encoded unicode
cp = Integer.parseInt(e.substring(2), 16);
} else {
diff --git a/src/main/java/org/json/XMLXsiTypeConverter.java b/src/main/java/org/json/XMLXsiTypeConverter.java
new file mode 100644
index 000000000..ea6739d34
--- /dev/null
+++ b/src/main/java/org/json/XMLXsiTypeConverter.java
@@ -0,0 +1,53 @@
+package org.json;
+/*
+Public Domain.
+*/
+
+/**
+ * Type conversion configuration interface to be used with xsi:type attributes.
+ *
+ * XML Sample
+ * {@code
+ *
+ * 12345
+ * 54321
+ *
+ * }
+ * JSON Output
+ * {@code
+ * {
+ * "root" : {
+ * "asString" : "12345",
+ * "asInt": 54321
+ * }
+ * }
+ * }
+ *
+ * Usage
+ * {@code
+ * Map> xsiTypeMap = new HashMap>();
+ * xsiTypeMap.put("string", new XMLXsiTypeConverter() {
+ * @Override public String convert(final String value) {
+ * return value;
+ * }
+ * });
+ * xsiTypeMap.put("integer", new XMLXsiTypeConverter() {
+ * @Override public Integer convert(final String value) {
+ * return Integer.valueOf(value);
+ * }
+ * });
+ * }
+ *
+ * @author kumar529
+ * @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 48586b741..e5eb9eda8 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,54 @@ 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.
@@ -147,6 +168,33 @@ public void unbalancedEscapedQuote(){
}
}
+ /**
+ * Csv parsing skip last row if last field of this row is empty #943
+ */
+ @Test
+ public void csvParsingCatchesLastRow(){
+ String data = "Field 1,Field 2,Field 3\n" +
+ "value11,value12,\n" +
+ "value21,value22,";
+
+ JSONArray jsonArray = CDL.toJSONArray(data);
+
+ JSONArray expectedJsonArray = new JSONArray();
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("Field 1", "value11");
+ jsonObject.put("Field 2", "value12");
+ jsonObject.put("Field 3", "");
+ expectedJsonArray.put(jsonObject);
+
+ jsonObject = new JSONObject();
+ jsonObject.put("Field 1", "value21");
+ jsonObject.put("Field 2", "value22");
+ jsonObject.put("Field 3", "");
+ expectedJsonArray.put(jsonObject);
+
+ Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
+ }
+
/**
* Assert that there is no error for a single escaped quote within a properly embedded quote.
*/
@@ -190,7 +238,7 @@ public void badEscapedQuote(){
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 +262,7 @@ public void nullJSONArrayToString() {
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 +321,7 @@ public void checkSpecialChars() {
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 +335,15 @@ public void checkSpecialChars() {
*/
@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);
}
@@ -299,11 +353,11 @@ public void textToJSONArray() {
*/
@Test
public void jsonArrayToJSONArray() {
- String nameArrayStr = "[Col1, Col2]";
+ String nameArrayStr = "[\"Col1\", \"Col2\"]";
String values = "V1, V2";
JSONArray nameJSONArray = new JSONArray(nameArrayStr);
JSONArray jsonArray = CDL.toJSONArray(nameJSONArray, values);
- JSONArray expectedJsonArray = new JSONArray("[{Col1:V1,Col2:V2}]");
+ JSONArray expectedJsonArray = new JSONArray("[{\"Col1\":\"V1\",\"Col2\":\"V2\"}]");
Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
}
@@ -313,10 +367,24 @@ public void jsonArrayToJSONArray() {
*/
@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 c3f647f90..0af96401b 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 7e7b62b45..edd8a7eeb 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 ed2c87a6b..1496a636a 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 void jsonObjectFromEnum() {
/**
* 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 8182b6059..703d5ad2f 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/HTTPTokenerTest.java b/src/test/java/org/json/junit/HTTPTokenerTest.java
new file mode 100644
index 000000000..28dd40353
--- /dev/null
+++ b/src/test/java/org/json/junit/HTTPTokenerTest.java
@@ -0,0 +1,107 @@
+package org.json.junit;
+
+import org.json.HTTPTokener;
+import org.json.JSONException;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+/**
+ * Tests for JSON-Java HTTPTokener.java
+ */
+public class HTTPTokenerTest {
+
+ /**
+ * Test parsing a simple unquoted token.
+ */
+ @Test
+ public void parseSimpleToken() {
+ HTTPTokener tokener = new HTTPTokener("Content-Type");
+ String token = tokener.nextToken();
+ assertEquals("Content-Type", token);
+ }
+
+ /**
+ * Test parsing multiple tokens separated by whitespace.
+ */
+ @Test
+ public void parseMultipleTokens() {
+ HTTPTokener tokener = new HTTPTokener("Content-Type application/json");
+ String token1 = tokener.nextToken();
+ String token2 = tokener.nextToken();
+ assertEquals("Content-Type", token1);
+ assertEquals("application/json", token2);
+ }
+
+ /**
+ * Test parsing a double-quoted token.
+ */
+ @Test
+ public void parseDoubleQuotedToken() {
+ HTTPTokener tokener = new HTTPTokener("\"application/json\"");
+ String token = tokener.nextToken();
+ assertEquals("application/json", token);
+ }
+
+ /**
+ * Test parsing a single-quoted token.
+ */
+ @Test
+ public void parseSingleQuotedToken() {
+ HTTPTokener tokener = new HTTPTokener("'application/json'");
+ String token = tokener.nextToken();
+ assertEquals("application/json", token);
+ }
+
+ /**
+ * Test parsing a quoted token that includes spaces and semicolons.
+ */
+ @Test
+ public void parseQuotedTokenWithSpaces() {
+ HTTPTokener tokener = new HTTPTokener("\"text/html; charset=UTF-8\"");
+ String token = tokener.nextToken();
+ assertEquals("text/html; charset=UTF-8", token);
+ }
+
+ /**
+ * Test that unterminated quoted strings throw a JSONException.
+ */
+ @Test
+ public void throwExceptionOnUnterminatedString() {
+ HTTPTokener tokener = new HTTPTokener("\"incomplete");
+ JSONException exception = assertThrows(JSONException.class, tokener::nextToken);
+ assertTrue(exception.getMessage().contains("Unterminated string"));
+ }
+
+ /**
+ * Test behavior with empty input string.
+ */
+ @Test
+ public void parseEmptyInput() {
+ HTTPTokener tokener = new HTTPTokener("");
+ String token = tokener.nextToken();
+ assertEquals("", token);
+ }
+
+ /**
+ * Test behavior with input consisting only of whitespace.
+ */
+ @Test
+ public void parseWhitespaceOnly() {
+ HTTPTokener tokener = new HTTPTokener(" \t \n ");
+ String token = tokener.nextToken();
+ assertEquals("", token);
+ }
+
+ /**
+ * Test parsing tokens separated by multiple whitespace characters.
+ */
+ @Test
+ public void parseTokensWithMultipleWhitespace() {
+ HTTPTokener tokener = new HTTPTokener("GET /index.html");
+ String method = tokener.nextToken();
+ String path = tokener.nextToken();
+ assertEquals("GET", method);
+ assertEquals("/index.html", path);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/json/junit/JSONArrayTest.java b/src/test/java/org/json/junit/JSONArrayTest.java
index eda3c06bd..429620396 100644
--- a/src/test/java/org/json/junit/JSONArrayTest.java
+++ b/src/test/java/org/json/junit/JSONArrayTest.java
@@ -1,36 +1,19 @@
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.assertFalse;
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 java.io.IOException;
+import java.io.InputStream;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
@@ -46,7 +29,13 @@ of this software and associated documentation files (the "Software"), to deal
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 +76,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 +91,20 @@ public void verifySimilar() {
.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 +122,7 @@ public void nullException() {
* Expects a JSONException.
*/
@Test
- public void emptStr() {
+ public void emptyStr() {
String str = "";
try {
assertNull("Should throw an exception", new JSONArray(str));
@@ -223,6 +223,67 @@ public void verifyConstructor() {
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);
+ }
+
+ @Test
+ public void jsonArrayByListWithNestedNullValue() {
+ List> list = new ArrayList>();
+ Map sub = new HashMap();
+ sub.put("nullKey", null);
+ list.add(sub);
+ JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withUseNativeNulls(true);
+ JSONArray jsonArray = new JSONArray(list, parserConfiguration);
+ JSONObject subObject = jsonArray.getJSONObject(0);
+ assertTrue(subObject.has("nullKey"));
+ assertEquals(JSONObject.NULL, subObject.get("nullKey"));
+ }
+
+ /**
+ * Tests consecutive calls to putAll with array and collection.
+ */
+ @Test
+ public void verifyPutAll() {
+ final JSONArray jsonArray = new JSONArray();
+
+ // array
+ int[] myInts = { 1, 2, 3, 4, 5 };
+ jsonArray.putAll(myInts);
+
+ assertEquals("int arrays lengths should be equal",
+ jsonArray.length(),
+ myInts.length);
+
+ for (int i = 0; i < myInts.length; i++) {
+ assertEquals("int arrays elements should be equal",
+ myInts[i],
+ jsonArray.getInt(i));
+ }
+
+ // collection
+ List myList = Arrays.asList("one", "two", "three", "four", "five");
+ jsonArray.putAll(myList);
+
+ int len = myInts.length + myList.size();
+
+ assertEquals("arrays lengths should be equal",
+ jsonArray.length(),
+ len);
+
+ // collection as object
+ @SuppressWarnings("RedundantCast")
+ Object myListAsObject = (Object) myList;
+ jsonArray.putAll(myListAsObject);
+
+ for (int i = 0; i < myList.size(); i++) {
+ assertEquals("collection elements should be equal",
+ myList.get(i),
+ jsonArray.getString(myInts.length + i));
+ }
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -256,6 +317,9 @@ public void verifyPutCollection() {
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
+ )));
}
@@ -299,6 +363,9 @@ public void verifyPutMap() {
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
+ )));
}
/**
@@ -323,14 +390,16 @@ public void getArrayValues() {
"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);
@@ -338,11 +407,12 @@ public void getArrayValues() {
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);
}
/**
@@ -358,7 +428,7 @@ public void failedGetArrayValues() {
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);
@@ -372,42 +442,67 @@ public void failedGetArrayValues() {
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]";
+ List expected = Arrays.asList("value1", "something!", "(parens)", "foo@bar.com", 23, "23+45");
+
+ // Test should fail if default strictMode is true, pass if false
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+ if (jsonParserConfiguration.isStrictMode()) {
+ try {
+ JSONArray jsonArray = new JSONArray(str);
+ assertEquals("Expected to throw exception due to invalid string", true, false);
+ } catch (JSONException e) { }
+ } else {
+ JSONArray jsonArray = new JSONArray(str);
+ assertEquals(expected, jsonArray.toList());
}
}
@@ -445,6 +540,7 @@ public void join() {
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);
}
/**
@@ -458,6 +554,9 @@ public void length() {
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
+ )));
}
/**
@@ -484,43 +583,75 @@ public void opt() {
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));
@@ -529,10 +660,21 @@ public void opt() {
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);
}
/**
@@ -542,12 +684,19 @@ public void opt() {
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
@@ -565,8 +714,8 @@ public void put() {
String jsonArrayStr =
"["+
- "hello,"+
- "world"+
+ "\"hello\","+
+ "\"world\""+
"]";
// 2
jsonArray.put(new JSONArray(jsonArrayStr));
@@ -623,6 +772,8 @@ public void put() {
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);
}
/**
@@ -641,8 +792,8 @@ public void putIndex() {
String jsonArrayStr =
"["+
- "hello,"+
- "world"+
+ "\"hello\","+
+ "\"world\""+
"]";
// 2
jsonArray.put(2, new JSONArray(jsonArrayStr));
@@ -702,6 +853,8 @@ public void putIndex() {
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);
}
/**
@@ -718,6 +871,7 @@ public void remove() {
jsonArray.remove(0);
assertTrue("array should be empty", null == jsonArray.remove(5));
assertTrue("jsonArray should be empty", jsonArray.isEmpty());
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -757,6 +911,12 @@ public void notSimilar() {
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
+ )));
}
/**
@@ -840,6 +1000,7 @@ public void jsonArrayToStringIndent() {
for (String s : jsonArray4Strs) {
list.contains(s);
}
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -851,6 +1012,9 @@ public void toJSONObject() {
JSONArray jsonArray = new JSONArray();
assertTrue("toJSONObject should return null",
null == jsonArray.toJSONObject(names));
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ names, jsonArray
+ )));
}
/**
@@ -872,6 +1036,7 @@ public void objectArrayVsIsArray() {
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);
}
/**
@@ -896,12 +1061,12 @@ public void iteratorTest() {
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);
@@ -910,10 +1075,14 @@ public void iteratorTest() {
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)
@@ -940,19 +1109,23 @@ public void write() throws IOException {
String str = "[\"value1\",\"value2\",{\"key1\":1,\"key2\":2,\"key3\":3}]";
JSONArray jsonArray = new JSONArray(str);
String expectedStr = str;
- try (StringWriter stringWriter = new StringWriter();) {
+ StringWriter stringWriter = new StringWriter();
+ try {
jsonArray.write(stringWriter);
String actualStr = stringWriter.toString();
JSONArray finalArray = new JSONArray(actualStr);
Util.compareActualVsExpectedJsonArrays(jsonArray, finalArray);
assertTrue("write() expected " + expectedStr +
- " but found " + actualStr,
- actualStr.startsWith("[\"value1\",\"value2\",{")
- && actualStr.contains("\"key1\":1")
- && actualStr.contains("\"key2\":2")
- && actualStr.contains("\"key3\":3")
- );
+ " but found " + actualStr,
+ actualStr.startsWith("[\"value1\",\"value2\",{")
+ && actualStr.contains("\"key1\":1")
+ && actualStr.contains("\"key2\":2")
+ && actualStr.contains("\"key3\":3")
+ );
+ } finally {
+ stringWriter.close();
}
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -981,7 +1154,8 @@ public void write3Param() throws IOException {
String str0 = "[\"value1\",\"value2\",{\"key1\":1,\"key2\":false,\"key3\":3.14}]";
JSONArray jsonArray = new JSONArray(str0);
String expectedStr = str0;
- try (StringWriter stringWriter = new StringWriter();) {
+ StringWriter stringWriter = new StringWriter();
+ try {
String actualStr = jsonArray.write(stringWriter, 0, 0).toString();
JSONArray finalArray = new JSONArray(actualStr);
Util.compareActualVsExpectedJsonArrays(jsonArray, finalArray);
@@ -992,9 +1166,12 @@ public void write3Param() throws IOException {
&& actualStr.contains("\"key2\":false")
&& actualStr.contains("\"key3\":3.14")
);
+ } finally {
+ stringWriter.close();
}
- try (StringWriter stringWriter = new StringWriter();) {
+ stringWriter = new StringWriter();
+ try {
String actualStr = jsonArray.write(stringWriter, 2, 1).toString();
JSONArray finalArray = new JSONArray(actualStr);
Util.compareActualVsExpectedJsonArrays(jsonArray, finalArray);
@@ -1008,7 +1185,11 @@ public void write3Param() throws IOException {
&& actualStr.contains("\"key2\": false")
&& actualStr.contains("\"key3\": 3.14")
);
+ Util.checkJSONArrayMaps(finalArray);
+ } finally {
+ stringWriter.close();
}
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -1119,6 +1300,7 @@ public void toList() {
// 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);
}
/**
@@ -1127,17 +1309,239 @@ public void toList() {
*/
@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, (long)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.",
e.getMessage());
}
}
+
+ /**
+ * Verifies that the object constructor can properly handle any supported collection object.
+ */
+ @Test
+ @SuppressWarnings({ "unchecked", "boxing" })
+ public void testObjectConstructor() {
+ // should copy the array
+ Object o = new Object[] {2, "test2", true};
+ JSONArray a = new JSONArray(o);
+ assertNotNull("Should not error", a);
+ assertEquals("length", 3, a.length());
+
+ // should NOT copy the collection
+ // this is required for backwards compatibility
+ o = new ArrayList();
+ ((Collection)o).add(1);
+ ((Collection)o).add("test");
+ ((Collection)o).add(false);
+ try {
+ JSONArray a0 = new JSONArray(o);
+ assertNull("Should error", a0);
+ } catch (JSONException ex) {
+ }
+
+ // should NOT copy the JSONArray
+ // this is required for backwards compatibility
+ o = a;
+ try {
+ JSONArray a1 = new JSONArray(o);
+ assertNull("Should error", a1);
+ } catch (JSONException ex) {
+ }
+ Util.checkJSONArrayMaps(a);
+ }
+
+ /**
+ * Verifies that the JSONArray constructor properly copies the original.
+ */
+ @Test
+ public void testJSONArrayConstructor() {
+ // should copy the array
+ JSONArray a1 = new JSONArray("[2, \"test2\", true]");
+ JSONArray a2 = new JSONArray(a1);
+ assertNotNull("Should not error", a2);
+ assertEquals("length", a1.length(), a2.length());
+
+ 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
+ )));
+ }
+
+ /**
+ * Verifies that the object constructor can properly handle any supported collection object.
+ */
+ @Test
+ public void testJSONArrayPutAll() {
+ // should copy the array
+ JSONArray a1 = new JSONArray("[2, \"test2\", true]");
+ JSONArray a2 = new JSONArray();
+ a2.putAll(a1);
+ assertNotNull("Should not error", a2);
+ assertEquals("length", a1.length(), a2.length());
+
+ 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
+ )));
+ }
+
+ /**
+ * Tests if calling JSONArray clear() method actually makes the JSONArray empty
+ */
+ @Test(expected = JSONException.class)
+ public void jsonArrayClearMethodTest() {
+ //Adds random stuff to the JSONArray
+ JSONArray jsonArray = new JSONArray();
+ jsonArray.put(123);
+ jsonArray.put("456");
+ jsonArray.put(new JSONArray());
+ 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);
+ }
+
+ @Test
+ public void testStrictModeJSONTokener_expectException(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
+ JSONTokener tokener = new JSONTokener("[\"value\"]invalidCharacters", jsonParserConfiguration);
+
+ assertThrows(JSONException.class, () -> { new JSONArray(tokener); });
+ }
+
+ 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 8f3de42cf..5a360dd59 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 @@ of this software and associated documentation files (the "Software"), to deal
/**
* 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 void emptyXMLException() {
/**
* 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 void nullJSONXMLException() {
/**
* Attempts to call JSONML.toString() with a null JSONArray.
- * Expects a JSONException.
+ * Expects a JSONException.
*/
@Test
public void emptyJSONXMLException() {
@@ -145,7 +125,7 @@ public void emptyTagException() {
"[\"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 void emptyTagException() {
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 void spaceInTagException() {
}
/**
- * 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 void invalidSlashInTagException() {
* 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 void invalidSlashInTagException() {
*/
@Test
public void invalidBangInTagException() {
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -266,7 +246,7 @@ public void invalidBangNoCloseInTagException() {
* 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 void noCloseStartTagException() {
* In this case, the XML is invalid because an element
* has no closing '>'.
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -326,7 +306,7 @@ public void noCloseEndTagException() {
* 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 void noCloseEndBraceException() {
* 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 void invalidCDATABangInTagException() {
/**
* 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 void invalidCDATABangInTagException() {
/**
* 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 void toJSONArray() {
* 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 void toJSONArray() {
" >\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 void toJSONArray() {
}
/**
- * 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 void toJSONObjectToJSONArray() {
/**
* 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 void toJSONObjectToJSONArray() {
"\"tagName\":\"addresses\""+
"}";
- String expectedJSONArrayStr =
+ String expectedJSONArrayStr =
"["+
"\"addresses\","+
"{"+
@@ -645,7 +625,7 @@ public void toJSONObjectToJSONArray() {
"\"subValue\","+
"{\"svAttr\":\"svValue\"},"+
"\"abc\""+
- "],"+
+ "]"+
"],"+
"[\"value\",3],"+
"[\"value\",4.1],"+
@@ -665,12 +645,12 @@ public void toJSONObjectToJSONArray() {
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 void toJSONObjectToJSONArray() {
* 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 void testToJSONArray_reversibility2() {
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 void testToJSONArray_reversibility3() {
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 void testToJSONObject_reversibility() {
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 void testToJSONObject_reversibility() {
// 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 void testToJSONObject_reversibility() {
// 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 void testIssue484InfinteLoop1() {
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 5112bf56e..1cdaf743d 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 000000000..0f2af2902
--- /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/JSONObjectRecordTest.java b/src/test/java/org/json/junit/JSONObjectRecordTest.java
new file mode 100644
index 000000000..f1a673d28
--- /dev/null
+++ b/src/test/java/org/json/junit/JSONObjectRecordTest.java
@@ -0,0 +1,179 @@
+package org.json.junit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.StringReader;
+
+import org.json.JSONObject;
+import org.json.junit.data.GenericBeanInt;
+import org.json.junit.data.MyEnum;
+import org.json.junit.data.MyNumber;
+import org.json.junit.data.PersonRecord;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Tests for JSONObject support of Java record types.
+ *
+ * NOTE: These tests are currently ignored because PersonRecord is not an actual Java record.
+ * The implementation now correctly detects actual Java records using reflection (Class.isRecord()).
+ * These tests will need to be enabled and run with Java 17+ where PersonRecord can be converted
+ * to an actual record type.
+ *
+ * This ensures backward compatibility - regular classes with lowercase method names will not
+ * be treated as records unless they are actual Java record types.
+ */
+public class JSONObjectRecordTest {
+
+ /**
+ * Tests that JSONObject can be created from a record-style class.
+ * Record-style classes use accessor methods like name() instead of getName().
+ *
+ * NOTE: Ignored until PersonRecord is converted to an actual Java record (requires Java 17+)
+ */
+ @Test
+ @Ignore("Requires actual Java record type - PersonRecord needs to be a real record (Java 17+)")
+ public void jsonObjectByRecord() {
+ PersonRecord person = new PersonRecord("John Doe", 30, true);
+ JSONObject jsonObject = new JSONObject(person);
+
+ assertEquals("Expected 3 keys in the JSONObject", 3, jsonObject.length());
+ assertEquals("John Doe", jsonObject.get("name"));
+ assertEquals(30, jsonObject.get("age"));
+ assertEquals(true, jsonObject.get("active"));
+ }
+
+ /**
+ * Test that Object methods (toString, hashCode, equals, etc.) are not included
+ *
+ * NOTE: Ignored until PersonRecord is converted to an actual Java record (requires Java 17+)
+ */
+ @Test
+ @Ignore("Requires actual Java record type - PersonRecord needs to be a real record (Java 17+)")
+ public void recordStyleClassShouldNotIncludeObjectMethods() {
+ PersonRecord person = new PersonRecord("Jane Doe", 25, false);
+ JSONObject jsonObject = new JSONObject(person);
+
+ // Should NOT include Object methods
+ assertFalse("Should not include toString", jsonObject.has("toString"));
+ assertFalse("Should not include hashCode", jsonObject.has("hashCode"));
+ assertFalse("Should not include equals", jsonObject.has("equals"));
+ assertFalse("Should not include clone", jsonObject.has("clone"));
+ assertFalse("Should not include wait", jsonObject.has("wait"));
+ assertFalse("Should not include notify", jsonObject.has("notify"));
+ assertFalse("Should not include notifyAll", jsonObject.has("notifyAll"));
+
+ // Should only have the 3 record fields
+ assertEquals("Should only have 3 fields", 3, jsonObject.length());
+ }
+
+ /**
+ * Test that enum methods are not included when processing an enum
+ */
+ @Test
+ public void enumsShouldNotIncludeEnumMethods() {
+ MyEnum myEnum = MyEnum.VAL1;
+ JSONObject jsonObject = new JSONObject(myEnum);
+
+ // Should NOT include enum-specific methods like name(), ordinal(), values(), valueOf()
+ assertFalse("Should not include name method", jsonObject.has("name"));
+ assertFalse("Should not include ordinal method", jsonObject.has("ordinal"));
+ assertFalse("Should not include declaringClass", jsonObject.has("declaringClass"));
+
+ // Enums should still work with traditional getters if they have any
+ // But should not pick up the built-in enum methods
+ }
+
+ /**
+ * Test that Number subclass methods are not included
+ */
+ @Test
+ public void numberSubclassesShouldNotIncludeNumberMethods() {
+ MyNumber myNumber = new MyNumber();
+ JSONObject jsonObject = new JSONObject(myNumber);
+
+ // Should NOT include Number methods like intValue(), longValue(), etc.
+ assertFalse("Should not include intValue", jsonObject.has("intValue"));
+ assertFalse("Should not include longValue", jsonObject.has("longValue"));
+ assertFalse("Should not include doubleValue", jsonObject.has("doubleValue"));
+ assertFalse("Should not include floatValue", jsonObject.has("floatValue"));
+
+ // Should include the actual getter
+ assertTrue("Should include number", jsonObject.has("number"));
+ assertEquals("Should have 1 field", 1, jsonObject.length());
+ }
+
+ /**
+ * Test that generic bean with get() and is() methods works correctly
+ */
+ @Test
+ public void genericBeanWithGetAndIsMethodsShouldNotBeIncluded() {
+ GenericBeanInt bean = new GenericBeanInt(42);
+ JSONObject jsonObject = new JSONObject(bean);
+
+ // Should NOT include standalone get() or is() methods
+ assertFalse("Should not include standalone 'get' method", jsonObject.has("get"));
+ assertFalse("Should not include standalone 'is' method", jsonObject.has("is"));
+
+ // Should include the actual getters
+ assertTrue("Should include genericValue field", jsonObject.has("genericValue"));
+ assertTrue("Should include a field", jsonObject.has("a"));
+ }
+
+ /**
+ * Test that java.* classes don't have their methods picked up
+ */
+ @Test
+ public void javaLibraryClassesShouldNotIncludeTheirMethods() {
+ StringReader reader = new StringReader("test");
+ JSONObject jsonObject = new JSONObject(reader);
+
+ // Should NOT include java.io.Reader methods like read(), reset(), etc.
+ assertFalse("Should not include read method", jsonObject.has("read"));
+ assertFalse("Should not include reset method", jsonObject.has("reset"));
+ assertFalse("Should not include ready method", jsonObject.has("ready"));
+ assertFalse("Should not include skip method", jsonObject.has("skip"));
+
+ // Reader should produce empty JSONObject (no valid properties)
+ assertEquals("Reader should produce empty JSON", 0, jsonObject.length());
+ }
+
+ /**
+ * Test mixed case - object with both traditional getters and record-style accessors
+ *
+ * NOTE: Ignored until PersonRecord is converted to an actual Java record (requires Java 17+)
+ */
+ @Test
+ @Ignore("Requires actual Java record type - PersonRecord needs to be a real record (Java 17+)")
+ public void mixedGettersAndRecordStyleAccessors() {
+ // PersonRecord has record-style accessors: name(), age(), active()
+ // These should all be included
+ PersonRecord person = new PersonRecord("Mixed Test", 40, true);
+ JSONObject jsonObject = new JSONObject(person);
+
+ assertEquals("Should have all 3 record-style fields", 3, jsonObject.length());
+ assertTrue("Should include name", jsonObject.has("name"));
+ assertTrue("Should include age", jsonObject.has("age"));
+ assertTrue("Should include active", jsonObject.has("active"));
+ }
+
+ /**
+ * Test that methods starting with uppercase are not included (not valid record accessors)
+ *
+ * NOTE: Ignored until PersonRecord is converted to an actual Java record (requires Java 17+)
+ */
+ @Test
+ @Ignore("Requires actual Java record type - PersonRecord needs to be a real record (Java 17+)")
+ public void methodsStartingWithUppercaseShouldNotBeIncluded() {
+ PersonRecord person = new PersonRecord("Test", 50, false);
+ JSONObject jsonObject = new JSONObject(person);
+
+ // Record-style accessors must start with lowercase
+ // Methods like Name(), Age() (uppercase) should not be picked up
+ // Our PersonRecord only has lowercase accessors, which is correct
+
+ assertEquals("Should only have lowercase accessors", 3, jsonObject.length());
+ }
+}
diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java
index 56e110edd..7ca6093b7 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 @@ of this software and associated documentation files (the "Software"), to deal
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 @@ of this software and associated documentation files (the "Software"), to deal
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,24 @@ of this software and associated documentation files (the "Software"), to deal
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.json.junit.data.CustomClass;
+import org.json.junit.data.CustomClassA;
+import org.json.junit.data.CustomClassB;
+import org.json.junit.data.CustomClassC;
+import org.json.junit.data.CustomClassD;
+import org.json.junit.data.CustomClassE;
+import org.json.junit.data.CustomClassF;
+import org.json.junit.data.CustomClassG;
+import org.json.junit.data.CustomClassH;
+import org.json.junit.data.CustomClassI;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Ignore;
import org.junit.Test;
import com.jayway.jsonpath.Configuration;
@@ -94,12 +87,21 @@ public class JSONObjectTest {
*/
static final Pattern NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?");
+ @After
+ public void tearDown() {
+ SingletonEnum.getInstance().setSomeInt(0);
+ SingletonEnum.getInstance().setSomeString(null);
+ Singleton.getInstance().setSomeInt(0);
+ Singleton.getInstance().setSomeString(null);
+ }
+
/**
* Tests that the similar method is working as expected.
*/
@Test
public void verifySimilar() {
final String string1 = "HasSameRef";
+ final String string2 = "HasDifferentRef";
JSONObject obj1 = new JSONObject()
.put("key1", "abc")
.put("key2", 2)
@@ -115,12 +117,33 @@ public void verifySimilar() {
.put("key2", 2)
.put("key3", new String(string1));
- assertFalse("Should eval to false", obj1.similar(obj2));
-
- assertTrue("Should eval to true", obj1.similar(obj3));
+ JSONObject obj4 = new JSONObject()
+ .put("key1", "abc")
+ .put("key2", 2.0)
+ .put("key3", new String(string1));
+
+ 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
@@ -193,7 +216,9 @@ public void timeNumberParsing() {
*/
@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);
}
/**
@@ -204,13 +229,28 @@ public void jsonObjectByNullBean() {
*/
@Test
public void unquotedText() {
- String str = "{key1:value1, key2:42}";
- 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"));
+ String str = "{key1:value1, key2:42, 1.2 : 3.4, -7e5 : something!}";
+
+ // Test should fail if default strictMode is true, pass if false
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+ if (jsonParserConfiguration.isStrictMode()) {
+ try {
+ JSONObject jsonObject = new JSONObject(str);
+ assertEquals("Expected to throw exception due to invalid string", true, false);
+ } catch (JSONException e) { }
+ } else {
+ 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
@@ -228,9 +268,15 @@ public void testLongFromString(){
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);
}
/**
@@ -240,6 +286,7 @@ public void testLongFromString(){
public void emptyJsonObject() {
JSONObject jsonObject = new JSONObject();
assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -270,6 +317,7 @@ public void jsonObjectByNames() {
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)));
}
/**
@@ -283,6 +331,7 @@ public void jsonObjectByNullMap() {
Map map = null;
JSONObject jsonObject = new JSONObject(map);
assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -292,12 +341,12 @@ public void jsonObjectByNullMap() {
@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
@@ -308,6 +357,7 @@ public void jsonObjectByMap() {
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);
}
/**
@@ -346,6 +396,9 @@ public void verifyConstructor() {
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))
+ );
}
/**
@@ -363,8 +416,8 @@ public void verifyNumberOutput(){
* 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);
@@ -376,9 +429,9 @@ public void verifyNumberOutput(){
* 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);
@@ -390,8 +443,8 @@ public void verifyNumberOutput(){
* 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);
@@ -401,9 +454,9 @@ public void verifyNumberOutput(){
* 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);
@@ -414,11 +467,11 @@ public void verifyNumberOutput(){
* 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
@@ -428,11 +481,15 @@ public void verifyNumberOutput(){
* 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
+ )));
}
/**
@@ -467,6 +524,10 @@ public void verifyPutCollection() {
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
+ )));
}
@@ -510,6 +571,10 @@ public void verifyPutMap() {
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
+ )));
}
@@ -532,6 +597,7 @@ public void jsonObjectByMapWithUnsupportedValues() {
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);
}
/**
@@ -541,13 +607,13 @@ public void jsonObjectByMapWithUnsupportedValues() {
@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
@@ -559,6 +625,47 @@ public void jsonObjectByMapWithNullValue() {
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);
+ }
+
+ @Test
+ public void jsonObjectByMapWithNullValueAndParserConfiguration() {
+ Map map = new HashMap();
+ map.put("nullKey", null);
+
+ // by default, null values are ignored
+ JSONObject obj1 = new JSONObject(map);
+ assertTrue("expected null value to be ignored by default", obj1.isEmpty());
+
+ // if configured, null values are written as such into the JSONObject.
+ JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withUseNativeNulls(true);
+ JSONObject obj2 = new JSONObject(map, parserConfiguration);
+ assertFalse("expected null value to accepted when configured", obj2.isEmpty());
+ assertTrue(obj2.has("nullKey"));
+ assertEquals(JSONObject.NULL, obj2.get("nullKey"));
+ }
+
+ @Test
+ public void jsonObjectByMapWithNestedNullValueAndParserConfiguration() {
+ Map map = new HashMap();
+ Map nestedMap = new HashMap();
+ nestedMap.put("nullKey", null);
+ map.put("nestedMap", nestedMap);
+ List> nestedList = new ArrayList>();
+ nestedList.add(nestedMap);
+ map.put("nestedList", nestedList);
+
+ JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withUseNativeNulls(true);
+ JSONObject jsonObject = new JSONObject(map, parserConfiguration);
+
+ JSONObject nestedObject = jsonObject.getJSONObject("nestedMap");
+ assertTrue(nestedObject.has("nullKey"));
+ assertEquals(JSONObject.NULL, nestedObject.get("nullKey"));
+
+ JSONArray nestedArray = jsonObject.getJSONArray("nestedList");
+ assertEquals(1, nestedArray.length());
+ assertTrue(nestedArray.getJSONObject(0).has("nullKey"));
+ assertEquals(JSONObject.NULL, nestedArray.getJSONObject(0).get("nullKey"));
}
/**
@@ -596,9 +703,10 @@ public void jsonObjectByBean1() {
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);
}
/**
@@ -634,6 +742,7 @@ public void jsonObjectByBean2() {
// 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);
}
/**
@@ -684,6 +793,7 @@ public void jsonObjectByBean3() {
// 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);
}
/**
@@ -704,6 +814,7 @@ public void jsonObjectByObjectAndNames() {
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);
}
/**
@@ -725,6 +836,7 @@ public void jsonObjectByResourceBundle() {
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);
}
/**
@@ -757,6 +869,7 @@ public void jsonObjectAccumulate() {
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);
}
/**
@@ -788,6 +901,7 @@ public void jsonObjectAppend() {
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);
}
/**
@@ -798,7 +912,7 @@ public void jsonObjectAppend() {
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]+
@@ -834,9 +948,11 @@ public void jsonObjectValues() {
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!"));
@@ -852,6 +968,10 @@ public void jsonObjectValues() {
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",
@@ -864,6 +984,10 @@ public void jsonObjectValues() {
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",
@@ -872,10 +996,18 @@ public void jsonObjectValues() {
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",
@@ -886,6 +1018,10 @@ public void jsonObjectValues() {
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",
@@ -923,6 +1059,7 @@ public void jsonObjectValues() {
JSONObject jsonObjectInner = jsonObject.getJSONObject("objectKey");
assertTrue("objectKey should be JSONObject",
jsonObjectInner.get("myKey").equals("myVal"));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -933,10 +1070,10 @@ public void stringToValueNumbersTest() {
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.
*/
@@ -946,13 +1083,13 @@ public void stringToValueNumbersTest() {
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")));
}
@@ -985,6 +1122,7 @@ public void jsonValidNumberValuesNeitherLongNorIEEE754Compatible() {
obj = jsonObject.get( "largeExponent" );
assertTrue("largeExponent should evaluate as a BigDecimal",
new BigDecimal("-23.45e2327").equals(obj));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -992,47 +1130,58 @@ public void jsonValidNumberValuesNeitherLongNorIEEE754Compatible() {
*/
@Test
public void jsonInvalidNumberValues() {
- // Number-notations supported by Java and invalid as JSON
- String str =
- "{"+
- "\"hexNumber\":-0x123,"+
- "\"tooManyZeros\":00,"+
- "\"negativeInfinite\":-Infinity,"+
- "\"negativeNaN\":-NaN,"+
- "\"negativeFraction\":-.01,"+
- "\"tooManyZerosFraction\":00.001,"+
- "\"negativeHexFloat\":-0x1.fffp1,"+
- "\"hexFloat\":0x1.0P-1074,"+
- "\"floatIdentifier\":0.1f,"+
- "\"doubleIdentifier\":0.1d"+
- "}";
- JSONObject jsonObject = new JSONObject(str);
- Object obj;
- obj = jsonObject.get( "hexNumber" );
- assertFalse( "hexNumber must not be a number (should throw exception!?)",
- obj instanceof Number );
- assertTrue("hexNumber currently evaluates to string",
- obj.equals("-0x123"));
- assertTrue( "tooManyZeros currently evaluates to string",
- jsonObject.get( "tooManyZeros" ).equals("00"));
- obj = jsonObject.get("negativeInfinite");
- assertTrue( "negativeInfinite currently evaluates to string",
- obj.equals("-Infinity"));
- obj = jsonObject.get("negativeNaN");
- assertTrue( "negativeNaN currently evaluates to string",
- obj.equals("-NaN"));
- 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)));
- 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",
- jsonObject.get("hexFloat").equals(Double.valueOf(4.9E-324)));
- assertTrue("floatIdentifier currently evaluates to double 0.1",
- jsonObject.get("floatIdentifier").equals(Double.valueOf(0.1)));
- assertTrue("doubleIdentifier currently evaluates to double 0.1",
- jsonObject.get("doubleIdentifier").equals(Double.valueOf(0.1)));
+ // Number-notations supported by Java and invalid as JSON
+ String str =
+ "{" +
+ "\"hexNumber\":-0x123," +
+ "\"tooManyZeros\":00," +
+ "\"negativeInfinite\":-Infinity," +
+ "\"negativeNaN\":-NaN," +
+ "\"negativeFraction\":-.01," +
+ "\"tooManyZerosFraction\":00.001," +
+ "\"negativeHexFloat\":-0x1.fffp1," +
+ "\"hexFloat\":0x1.0P-1074," +
+ "\"floatIdentifier\":0.1f," +
+ "\"doubleIdentifier\":0.1d" +
+ "}";
+
+ // Test should fail if default strictMode is true, pass if false
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+ if (jsonParserConfiguration.isStrictMode()) {
+ try {
+ JSONObject jsonObject = new JSONObject(str);
+ assertEquals("Expected to throw exception due to invalid string", true, false);
+ } catch (JSONException e) { }
+ } else {
+ JSONObject jsonObject = new JSONObject(str);
+ Object obj;
+ obj = jsonObject.get("hexNumber");
+ assertFalse("hexNumber must not be a number (should throw exception!?)",
+ obj instanceof Number);
+ assertTrue("hexNumber currently evaluates to string",
+ obj.equals("-0x123"));
+ assertTrue("tooManyZeros currently evaluates to string",
+ jsonObject.get("tooManyZeros").equals("00"));
+ obj = jsonObject.get("negativeInfinite");
+ assertTrue("negativeInfinite currently evaluates to string",
+ obj.equals("-Infinity"));
+ obj = jsonObject.get("negativeNaN");
+ assertTrue("negativeNaN currently evaluates to string",
+ obj.equals("-NaN"));
+ 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.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",
+ jsonObject.get("hexFloat").equals(Double.valueOf(4.9E-324)));
+ assertTrue("floatIdentifier currently evaluates to double 0.1",
+ 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);
+ }
}
/**
@@ -1069,7 +1218,7 @@ public void jsonObjectNonAndWrongValues() {
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 {
@@ -1085,7 +1234,7 @@ public void jsonObjectNonAndWrongValues() {
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 {
@@ -1101,7 +1250,7 @@ public void jsonObjectNonAndWrongValues() {
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 {
@@ -1117,7 +1266,7 @@ public void jsonObjectNonAndWrongValues() {
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 {
@@ -1133,7 +1282,7 @@ public void jsonObjectNonAndWrongValues() {
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 {
@@ -1149,7 +1298,7 @@ public void jsonObjectNonAndWrongValues() {
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 {
@@ -1165,7 +1314,7 @@ public void jsonObjectNonAndWrongValues() {
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 {
@@ -1181,9 +1330,10 @@ public void jsonObjectNonAndWrongValues() {
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);
}
/**
@@ -1196,8 +1346,8 @@ public void unexpectedDoubleToIntConversion() {
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);
@@ -1211,6 +1361,7 @@ public void unexpectedDoubleToIntConversion() {
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);
}
/**
@@ -1226,9 +1377,9 @@ public void bigNumberOperations() {
* 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",
@@ -1241,57 +1392,57 @@ public void bigNumberOperations() {
*/
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);
@@ -1324,79 +1475,79 @@ public void bigNumberOperations() {
// 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"));
@@ -1405,7 +1556,12 @@ public void bigNumberOperations() {
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());
}
/**
@@ -1417,7 +1573,6 @@ public void bigNumberOperations() {
*/
@Test
public void jsonObjectNames() {
- JSONObject jsonObject;
// getNames() from null JSONObject
assertTrue("null names from null Object",
@@ -1428,31 +1583,31 @@ public void jsonObjectNames() {
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 =
"{"+
"\"trueKey\":true,"+
"\"falseKey\":false,"+
- "\"stringKey\":\"hello world!\","+
+ "\"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(
@@ -1473,9 +1628,9 @@ public void jsonObjectNames() {
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(
@@ -1497,9 +1652,9 @@ public void jsonObjectNames() {
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(
@@ -1508,6 +1663,12 @@ public void jsonObjectNames() {
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());
}
/**
@@ -1519,6 +1680,8 @@ public void emptyJsonObjectNamesToJsonAray() {
JSONObject jsonObject = new JSONObject();
JSONArray jsonArray = jsonObject.names();
assertTrue("jsonArray should be null", jsonArray == null);
+ Util.checkJSONObjectMaps(jsonObject);
+ Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
}
/**
@@ -1531,7 +1694,7 @@ public void jsonObjectNamesToJsonAray() {
"{"+
"\"trueKey\":true,"+
"\"falseKey\":false,"+
- "\"stringKey\":\"hello world!\","+
+ "\"stringKey\":\"hello world!\""+
"}";
JSONObject jsonObject = new JSONObject(str);
@@ -1543,6 +1706,8 @@ public void jsonObjectNamesToJsonAray() {
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());
}
/**
@@ -1635,19 +1800,19 @@ public void jsonObjectIncrement() {
*/
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);
@@ -1660,8 +1825,10 @@ public void jsonObjectIncrement() {
// 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
+ )));
}
/**
@@ -1759,6 +1926,12 @@ public void jsonObjectPut() {
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
+ )));
}
/**
@@ -1794,6 +1967,7 @@ public void jsonObjectToString() {
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);
}
/**
@@ -1867,6 +2041,9 @@ public void jsonObjectToStringIndent() {
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
+ )));
}
/**
@@ -1888,6 +2065,7 @@ public void jsonObjectToStringSuppressWarningOnCastToMap() {
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);
}
/**
@@ -1910,6 +2088,7 @@ public void jsonObjectToStringSuppressWarningOnCastToCollection() {
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);
}
/**
@@ -1935,7 +2114,9 @@ public void valueToString() {
"}";
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);
@@ -1946,18 +2127,21 @@ public void valueToString() {
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());
}
/**
@@ -1991,7 +2175,7 @@ public void wrapObject() {
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));
@@ -2018,9 +2202,9 @@ public void wrapObject() {
// 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
@@ -2031,7 +2215,7 @@ public void wrapObject() {
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
@@ -2061,6 +2245,11 @@ public void wrapObject() {
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());
}
@@ -2075,6 +2264,7 @@ public void jsonObjectParseControlCharacters(){
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'
@@ -2083,122 +2273,279 @@ public void jsonObjectParseControlCharacters(){
}
}
- /**
- * Explore how JSONObject handles parsing errors.
- */
- @SuppressWarnings({"boxing", "unused"})
@Test
- public void jsonObjectParsingErrors() {
+ public void jsonObjectParseControlCharacterEOFAssertExceptionMessage(){
+ char c = '\0';
+ final String source = "{\"key\":\"" + c + "\"}";
try {
- // does not start with '{'
- String str = "abc";
- assertNull("Expected an exception",new JSONObject(str));
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "A JSONObject text must begin with '{' at 1 [character 2 line 1]",
- e.getMessage());
+ JSONObject jo = new JSONObject(source);
+ fail("JSONException should be thrown");
+ } catch (JSONException ex) {
+ assertEquals("Unterminated string. " + "Character with int code 0" +
+ " is not allowed within a quoted string. at 8 [character 9 line 1]", ex.getMessage());
+ }
+ }
+
+ @Test
+ public void jsonObjectParseControlCharacterNewLineAssertExceptionMessage(){
+ char[] chars = {'\n', '\r'};
+ for( char c : chars) {
+ final String source = "{\"key\":\"" + c + "\"}";
+ try {
+ JSONObject jo = new JSONObject(source);
+ fail("JSONException should be thrown");
+ } catch (JSONException ex) {
+ assertEquals("Unterminated string. " + "Character with int code " + (int) c +
+ " is not allowed within a quoted string. at 9 [character 0 line 2]", ex.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void jsonObjectParseUTF8EncodingAssertExceptionMessage(){
+ String c = "\\u123x";
+ final String source = "{\"key\":\"" + c + "\"}";
+ try {
+ JSONObject jo = new JSONObject(source);
+ fail("JSONException should be thrown");
+ } catch (JSONException ex) {
+ assertEquals("Illegal escape. \\u must be followed by a 4 digit hexadecimal number. " +
+ "\\123x is not valid. at 14 [character 15 line 1]", ex.getMessage());
+ }
+ }
+
+ @Test
+ public void jsonObjectParseIllegalEscapeAssertExceptionMessage(){
+ String c = "\\x";
+ final String source = "{\"key\":\"" + c + "\"}";
+ try {
+ JSONObject jo = new JSONObject(source);
+ fail("JSONException should be thrown");
+ } catch (JSONException ex) {
+ assertEquals("Illegal escape. Escape sequence " + c + " is not valid." +
+ " at 10 [character 11 line 1]", ex.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorTrailingCurlyBrace () {
try {
// does not end with '}'
String str = "{";
- assertNull("Expected an exception",new JSONObject(str));
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
+ assertNull("Expected an exception", new JSONObject(str));
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
"A JSONObject text must end with '}' at 1 [character 2 line 1]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorInitialCurlyBrace() {
+ try {
+ // does not start with '{'
+ String str = "abc";
+ assertNull("Expected an exception", new JSONObject(str));
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "A JSONObject text must begin with '{' at 1 [character 2 line 1]",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void parsingErrorNoColon() {
try {
// key with no ':'
String str = "{\"myKey\" = true}";
- assertNull("Expected an exception",new JSONObject(str));
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
+ assertNull("Expected an exception", new JSONObject(str));
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
"Expected a ':' after a key at 10 [character 11 line 1]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorNoCommaSeparator() {
try {
// entries with no ',' separator
String str = "{\"myKey\":true \"myOtherKey\":false}";
- assertNull("Expected an exception",new JSONObject(str));
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
+ assertNull("Expected an exception", new JSONObject(str));
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
"Expected a ',' or '}' at 15 [character 16 line 1]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorKeyIsNestedMap() {
try {
- // append to wrong key
+ // 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());
+ }
+ }
+
+ @Test
+ public void parsingErrorKeyIsNestedArrayWithMap() {
+ 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());
+ }
+ }
+
+ @Test
+ public void parsingErrorKeyContainsCurlyBrace() {
+ 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());
+ }
+ }
+
+ @Test
+ public void parsingErrorKeyContainsSquareBrace() {
+ 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());
+ }
+ }
+
+ @Test
+ public void parsingErrorKeyContainsBinaryZero() {
+ 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());
+ }
+ }
+
+ @Test
+ public void parsingErrorAppendToWrongValue() {
+ try {
+ // append to wrong value
String str = "{\"myKey\":true, \"myOtherKey\":false}";
JSONObject jsonObject = new JSONObject(str);
jsonObject.append("myKey", "hello");
fail("Expected an exception");
- } catch (JSONException e) {
+ } catch (JSONException e) {
assertEquals("Expecting an exception message",
"JSONObject[\"myKey\"] is not a JSONArray (null).",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorIncrementWrongValue() {
try {
- // increment wrong key
+ // increment wrong value
String str = "{\"myKey\":true, \"myOtherKey\":false}";
JSONObject jsonObject = new JSONObject(str);
jsonObject.increment("myKey");
fail("Expected an exception");
- } catch (JSONException e) {
+ } catch (JSONException e) {
assertEquals("Expecting an exception message",
"Unable to increment [\"myKey\"].",
e.getMessage());
}
+ }
+ @Test
+ public void parsingErrorInvalidKey() {
try {
// invalid key
String str = "{\"myKey\":true, \"myOtherKey\":false}";
JSONObject jsonObject = new JSONObject(str);
jsonObject.get(null);
fail("Expected an exception");
- } catch (JSONException e) {
+ } catch (JSONException e) {
assertEquals("Expecting an exception message",
"Null key.",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorNumberToString() {
try {
// invalid numberToString()
- JSONObject.numberToString((Number)null);
+ JSONObject.numberToString((Number) null);
fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
"Null pointer",
e.getMessage());
}
+ }
+ @Test
+ public void parsingErrorPutOnceDuplicateKey() {
try {
- // multiple putOnce key
+ // multiple putOnce key
JSONObject jsonObject = new JSONObject("{}");
jsonObject.putOnce("hello", "world");
jsonObject.putOnce("hello", "world!");
fail("Expected an exception");
- } catch (JSONException e) {
+ } catch (JSONException e) {
assertTrue("", true);
}
+ }
+
+ @Test
+ public void parsingErrorInvalidDouble() {
try {
- // test validity of invalid double
+ // test validity of invalid double
JSONObject.testValidity(Double.NaN);
fail("Expected an exception");
- } catch (JSONException e) {
+ } catch (JSONException e) {
assertTrue("", true);
}
+ }
+
+ @Test
+ public void parsingErrorInvalidFloat() {
try {
- // test validity of invalid float
+ // test validity of invalid float
JSONObject.testValidity(Float.NEGATIVE_INFINITY);
fail("Expected an exception");
- } catch (JSONException e) {
+ } catch (JSONException e) {
assertTrue("", true);
}
+ }
+
+ @Test
+ public void parsingErrorDuplicateKeyException() {
try {
// test exception message when including a duplicate key (level 0)
String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr03\":\"value-04\"\n"
- + "}";
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\":\"value-02\",\n"
+ + " \"attr03\":\"value-03\",\n"
+ + " \"attr03\":\"value-04\"\n"
+ + "}";
new JSONObject(str);
fail("Expected an exception");
} catch (JSONException e) {
@@ -2206,18 +2553,22 @@ public void jsonObjectParsingErrors() {
"Duplicate key \"attr03\" at 90 [character 13 line 5]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorNestedDuplicateKeyException() {
try {
// test exception message when including a duplicate key (level 0) holding an object
String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr03\": {"
- +" \"attr04-01\":\"value-04-01\",n"
- +" \"attr04-02\":\"value-04-02\",n"
- +" \"attr04-03\":\"value-04-03\"n"
- + " }\n"
- + "}";
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\":\"value-02\",\n"
+ + " \"attr03\":\"value-03\",\n"
+ + " \"attr03\": {"
+ + " \"attr04-01\":\"value-04-01\",n"
+ + " \"attr04-02\":\"value-04-02\",n"
+ + " \"attr04-03\":\"value-04-03\"n"
+ + " }\n"
+ + "}";
new JSONObject(str);
fail("Expected an exception");
} catch (JSONException e) {
@@ -2225,20 +2576,24 @@ public void jsonObjectParsingErrors() {
"Duplicate key \"attr03\" at 90 [character 13 line 5]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorNestedDuplicateKeyWithArrayException() {
try {
// test exception message when including a duplicate key (level 0) holding an array
String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr03\": [\n"
- +" {"
- +" \"attr04-01\":\"value-04-01\",n"
- +" \"attr04-02\":\"value-04-02\",n"
- +" \"attr04-03\":\"value-04-03\"n"
- +" }\n"
- + " ]\n"
- + "}";
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\":\"value-02\",\n"
+ + " \"attr03\":\"value-03\",\n"
+ + " \"attr03\": [\n"
+ + " {"
+ + " \"attr04-01\":\"value-04-01\",n"
+ + " \"attr04-02\":\"value-04-02\",n"
+ + " \"attr04-03\":\"value-04-03\"n"
+ + " }\n"
+ + " ]\n"
+ + "}";
new JSONObject(str);
fail("Expected an exception");
} catch (JSONException e) {
@@ -2246,19 +2601,23 @@ public void jsonObjectParsingErrors() {
"Duplicate key \"attr03\" at 90 [character 13 line 5]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorDuplicateKeyWithinNestedDictExceptionMessage() {
try {
// test exception message when including a duplicate key (level 1)
String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr04\": {\n"
- +" \"attr04-01\":\"value04-01\",\n"
- +" \"attr04-02\":\"value04-02\",\n"
- +" \"attr04-03\":\"value04-03\",\n"
- +" \"attr04-03\":\"value04-04\"\n"
- + " }\n"
- + "}";
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\":\"value-02\",\n"
+ + " \"attr03\":\"value-03\",\n"
+ + " \"attr04\": {\n"
+ + " \"attr04-01\":\"value04-01\",\n"
+ + " \"attr04-02\":\"value04-02\",\n"
+ + " \"attr04-03\":\"value04-03\",\n"
+ + " \"attr04-03\":\"value04-04\"\n"
+ + " }\n"
+ + "}";
new JSONObject(str);
fail("Expected an exception");
} catch (JSONException e) {
@@ -2266,23 +2625,28 @@ public void jsonObjectParsingErrors() {
"Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorDuplicateKeyDoubleNestedDictExceptionMessage() {
try {
- // test exception message when including a duplicate key (level 1) holding an object
+ // test exception message when including a duplicate key (level 1) holding an
+ // object
String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr04\": {\n"
- +" \"attr04-01\":\"value04-01\",\n"
- +" \"attr04-02\":\"value04-02\",\n"
- +" \"attr04-03\":\"value04-03\",\n"
- +" \"attr04-03\": {\n"
- +" \"attr04-04-01\":\"value04-04-01\",\n"
- +" \"attr04-04-02\":\"value04-04-02\",\n"
- +" \"attr04-04-03\":\"value04-04-03\",\n"
- +" }\n"
- +" }\n"
- + "}";
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\":\"value-02\",\n"
+ + " \"attr03\":\"value-03\",\n"
+ + " \"attr04\": {\n"
+ + " \"attr04-01\":\"value04-01\",\n"
+ + " \"attr04-02\":\"value04-02\",\n"
+ + " \"attr04-03\":\"value04-03\",\n"
+ + " \"attr04-03\": {\n"
+ + " \"attr04-04-01\":\"value04-04-01\",\n"
+ + " \"attr04-04-02\":\"value04-04-02\",\n"
+ + " \"attr04-04-03\":\"value04-04-03\",\n"
+ + " }\n"
+ + " }\n"
+ + "}";
new JSONObject(str);
fail("Expected an exception");
} catch (JSONException e) {
@@ -2290,25 +2654,30 @@ public void jsonObjectParsingErrors() {
"Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorDuplicateKeyNestedWithArrayExceptionMessage() {
try {
- // test exception message when including a duplicate key (level 1) holding an array
+ // test exception message when including a duplicate key (level 1) holding an
+ // array
String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr04\": {\n"
- +" \"attr04-01\":\"value04-01\",\n"
- +" \"attr04-02\":\"value04-02\",\n"
- +" \"attr04-03\":\"value04-03\",\n"
- +" \"attr04-03\": [\n"
- +" {\n"
- +" \"attr04-04-01\":\"value04-04-01\",\n"
- +" \"attr04-04-02\":\"value04-04-02\",\n"
- +" \"attr04-04-03\":\"value04-04-03\",\n"
- +" }\n"
- +" ]\n"
- +" }\n"
- + "}";
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\":\"value-02\",\n"
+ + " \"attr03\":\"value-03\",\n"
+ + " \"attr04\": {\n"
+ + " \"attr04-01\":\"value04-01\",\n"
+ + " \"attr04-02\":\"value04-02\",\n"
+ + " \"attr04-03\":\"value04-03\",\n"
+ + " \"attr04-03\": [\n"
+ + " {\n"
+ + " \"attr04-04-01\":\"value04-04-01\",\n"
+ + " \"attr04-04-02\":\"value04-04-02\",\n"
+ + " \"attr04-04-03\":\"value04-04-03\",\n"
+ + " }\n"
+ + " ]\n"
+ + " }\n"
+ + "}";
new JSONObject(str);
fail("Expected an exception");
} catch (JSONException e) {
@@ -2316,18 +2685,23 @@ public void jsonObjectParsingErrors() {
"Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorDuplicateKeyWithinArrayExceptionMessage() {
try {
- // test exception message when including a duplicate key in object (level 0) within an array
+ // test exception message when including a duplicate key in object (level 0)
+ // within an array
String str = "[\n"
- +" {\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\"\n"
- +" },\n"
- +" {\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr01\":\"value-02\"\n"
- +" }\n"
- + "]";
+ + " {\n"
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\":\"value-02\"\n"
+ + " },\n"
+ + " {\n"
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr01\":\"value-02\"\n"
+ + " }\n"
+ + "]";
new JSONArray(str);
fail("Expected an exception");
} catch (JSONException e) {
@@ -2335,24 +2709,29 @@ public void jsonObjectParsingErrors() {
"Duplicate key \"attr01\" at 124 [character 17 line 8]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorDuplicateKeyDoubleNestedWithinArrayExceptionMessage() {
try {
- // test exception message when including a duplicate key in object (level 1) within an array
+ // test exception message when including a duplicate key in object (level 1)
+ // within an array
String str = "[\n"
- +" {\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\": {\n"
- +" \"attr02-01\":\"value-02-01\",\n"
- +" \"attr02-02\":\"value-02-02\"\n"
- +" }\n"
- +" },\n"
- +" {\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\": {\n"
- +" \"attr02-01\":\"value-02-01\",\n"
- +" \"attr02-01\":\"value-02-02\"\n"
- +" }\n"
- +" }\n"
- + "]";
+ + " {\n"
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\": {\n"
+ + " \"attr02-01\":\"value-02-01\",\n"
+ + " \"attr02-02\":\"value-02-02\"\n"
+ + " }\n"
+ + " },\n"
+ + " {\n"
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\": {\n"
+ + " \"attr02-01\":\"value-02-01\",\n"
+ + " \"attr02-01\":\"value-02-02\"\n"
+ + " }\n"
+ + " }\n"
+ + "]";
new JSONArray(str);
fail("Expected an exception");
} catch (JSONException e) {
@@ -2374,6 +2753,7 @@ public void jsonObjectPutOnceNull() {
assertTrue("jsonObject should be empty", jsonObject.isEmpty());
jsonObject.putOnce(null, "");
assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -2391,24 +2771,37 @@ public void jsonObjectOptDefault() {
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);
}
/**
@@ -2427,24 +2820,37 @@ public void jsonObjectOptNoKey() {
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);
}
/**
@@ -2454,15 +2860,22 @@ public void jsonObjectOptNoKey() {
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);
}
/**
@@ -2478,25 +2891,38 @@ public void jsonObjectOptCoercion() {
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);
}
/**
@@ -2519,6 +2945,7 @@ public void jsonObjectOptBigDecimal() {
assertNull(jo.optBigDecimal("nullVal", null));
assertEquals(jo.optBigDecimal("float", null),jo.getBigDecimal("float"));
assertEquals(jo.optBigDecimal("double", null),jo.getBigDecimal("double"));
+ Util.checkJSONObjectMaps(jo);
}
/**
@@ -2539,6 +2966,7 @@ public void jsonObjectOptBigInteger() {
assertEquals(new BigInteger("1234"),jo.optBigInteger("bigInteger", null));
assertEquals(new BigInteger("1234"),jo.optBigInteger("bigDecimal", null));
assertNull(jo.optBigDecimal("nullVal", null));
+ Util.checkJSONObjectMaps(jo);
}
/**
@@ -2556,8 +2984,9 @@ public void jsonObjectputNull() {
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
+ )));
}
/**
@@ -2632,13 +3061,17 @@ public void write() throws IOException {
String str = "{\"key1\":\"value1\",\"key2\":[1,2,3]}";
String expectedStr = str;
JSONObject jsonObject = new JSONObject(str);
- try (StringWriter stringWriter = new StringWriter()) {
+ StringWriter stringWriter = new StringWriter();
+ try {
String actualStr = jsonObject.write(stringWriter).toString();
// key order may change. verify length and individual key content
assertEquals("length", expectedStr.length(), actualStr.length());
assertTrue("key1", actualStr.contains("\"key1\":\"value1\""));
assertTrue("key2", actualStr.contains("\"key2\":[1,2,3]"));
+ } finally {
+ stringWriter.close();
}
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -2651,29 +3084,40 @@ public void testJSONWriterException() {
jsonObject.put("someKey",new BrokenToString());
// test single element JSONObject
- try(StringWriter writer = new StringWriter();) {
+ StringWriter writer = new StringWriter();
+ try {
jsonObject.write(writer).toString();
fail("Expected an exception, got a String value");
} catch (JSONException e) {
assertEquals("Unable to write JSONObject value for key: someKey", e.getMessage());
} catch(Exception e) {
fail("Expected JSONException");
+ } finally {
+ try {
+ writer.close();
+ } catch (Exception e) {}
}
//test multiElement
jsonObject.put("somethingElse", "a value");
- try (StringWriter writer = new StringWriter()) {
+ writer = new StringWriter();
+ try {
jsonObject.write(writer).toString();
fail("Expected an exception, got a String value");
} catch (JSONException e) {
assertEquals("Unable to write JSONObject value for key: someKey", e.getMessage());
} catch(Exception e) {
fail("Expected JSONException");
+ } finally {
+ try {
+ writer.close();
+ } catch (Exception e) {}
}
// test a more complex object
- try (StringWriter writer = new StringWriter()) {
+ writer = new StringWriter();
+ try {
new JSONObject()
.put("somethingElse", "a value")
.put("someKey", new JSONArray()
@@ -2684,10 +3128,15 @@ public void testJSONWriterException() {
assertEquals("Unable to write JSONObject value for key: someKey", e.getMessage());
} catch(Exception e) {
fail("Expected JSONException");
+ } finally {
+ try {
+ writer.close();
+ } catch (Exception e) {}
}
// test a more slightly complex object
- try (StringWriter writer = new StringWriter()) {
+ writer = new StringWriter();
+ try {
new JSONObject()
.put("somethingElse", "a value")
.put("someKey", new JSONArray()
@@ -2700,8 +3149,12 @@ public void testJSONWriterException() {
assertEquals("Unable to write JSONObject value for key: someKey", e.getMessage());
} catch(Exception e) {
fail("Expected JSONException");
+ } finally {
+ try {
+ writer.close();
+ } catch (Exception e) {}
}
-
+ Util.checkJSONObjectMaps(jsonObject);
}
@@ -2739,15 +3192,21 @@ public void write3Param() throws IOException {
" ]\n" +
" }";
JSONObject jsonObject = new JSONObject(str0);
- try (StringWriter stringWriter = new StringWriter();) {
+ StringWriter stringWriter = new StringWriter();
+ try {
String actualStr = jsonObject.write(stringWriter,0,0).toString();
assertEquals("length", str0.length(), actualStr.length());
assertTrue("key1", actualStr.contains("\"key1\":\"value1\""));
assertTrue("key2", actualStr.contains("\"key2\":[1,false,3.14]"));
+ } finally {
+ try {
+ stringWriter.close();
+ } catch (Exception e) {}
}
- try (StringWriter stringWriter = new StringWriter();) {
+ stringWriter = new StringWriter();
+ try {
String actualStr = jsonObject.write(stringWriter,2,1).toString();
assertEquals("length", str2.length(), actualStr.length());
@@ -2758,7 +3217,12 @@ public void write3Param() throws IOException {
" 3.14\n" +
" ]")
);
+ } finally {
+ try {
+ stringWriter.close();
+ } catch (Exception e) {}
}
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -2801,6 +3265,7 @@ public void equals() {
JSONObject aJsonObject = new JSONObject(str);
assertTrue("Same JSONObject should be equal to itself",
aJsonObject.equals(aJsonObject));
+ Util.checkJSONObjectMaps(aJsonObject);
}
/**
@@ -2886,6 +3351,9 @@ public void jsonObjectNullOperations() {
"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)
@@ -2983,6 +3451,7 @@ public void toMap() {
// 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);
}
/**
@@ -3007,6 +3476,9 @@ public void testSingletonBean() {
// 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
+ )));
}
/**
@@ -3031,6 +3503,9 @@ public void testSingletonEnumBean() {
// 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
+ )));
}
/**
@@ -3046,6 +3521,7 @@ public void testGenericBean() {
assertEquals("Expected the getter to only be called once",
1, bean.genericGetCounter);
assertEquals(0, bean.genericSetCounter);
+ Util.checkJSONObjectMaps(jo);
}
/**
@@ -3061,6 +3537,7 @@ public void testGenericIntBean() {
assertEquals("Expected the getter to only be called once",
1, bean.genericGetCounter);
assertEquals(0, bean.genericSetCounter);
+ Util.checkJSONObjectMaps(jo);
}
/**
@@ -3079,12 +3556,14 @@ public void testWierdListBean() {
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 + "}]");
@@ -3096,6 +3575,8 @@ public void testObjectToBigDecimal() {
BigDecimal wantedValue = BigDecimal.valueOf(value);
assertEquals(current, wantedValue);
+ Util.checkJSONObjectMaps(jsonObject);
+ Util.checkJSONArrayMaps(array, jsonObject.getMapType());
}
/**
@@ -3109,6 +3590,7 @@ public void testExceptionalBean() {
1, jo.length());
assertTrue(jo.get("closeable") instanceof JSONObject);
assertTrue(jo.getJSONObject("closeable").has("string"));
+ Util.checkJSONObjectMaps(jo);
}
@Test(expected=NullPointerException.class)
@@ -3167,5 +3649,585 @@ public void testPutNullObject() {
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() {
+ JSONObject jsonObject = new JSONObject("{\"empty_json_array\": []}");
+ 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);
+ }
+
+ /**
+ * Tests if calling JSONObject clear() method actually makes the JSONObject empty
+ */
+ @Test(expected = JSONException.class)
+ public void jsonObjectClearMethodTest() {
+ //Adds random stuff to the JSONObject
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("key1", 123);
+ jsonObject.put("key2", "456");
+ jsonObject.put("key3", new JSONObject());
+ 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 issue743SerializationMapWith500Objects() {
+ // TODO: find out why 1000 objects no longer works
+ HashMap map = buildNestedMap(500);
+ JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withMaxNestingDepth(500);
+ 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");
+ }
+
+
+ @Test
+ public void testStrictModeJSONTokener_expectException(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
+ JSONTokener tokener = new JSONTokener("{\"key\":\"value\"}invalidCharacters", jsonParserConfiguration);
+
+ assertThrows(JSONException.class, () -> { new JSONObject(tokener); });
+ }
+
+ @Test
+ public void test_strictModeWithMisCasedBooleanOrNullValue(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
+ try{
+ new JSONObject("{\"a\":True}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+ try{
+ new JSONObject("{\"a\":TRUE}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+ try{
+ new JSONObject("{\"a\":nUlL}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+ }
+
+ @Test
+ public void test_strictModeWithInappropriateKey(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
+
+ // Parsing the following objects should fail
+ try{
+ new JSONObject("{true : 3}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+ try{
+ new JSONObject("{TRUE : 3}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+ try{
+ new JSONObject("{1 : 3}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+
+ }
+
+
+ /**
+ * 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;
+ }
+
+
+ /**
+ * Tests the behavior of the {@link JSONObject} when parsing a bean with null fields
+ * using a custom {@link JSONParserConfiguration} that enables the use of native nulls.
+ *
+ * This test ensures that uninitialized fields in the bean are serialized correctly
+ * into the resulting JSON object, and their keys are present in the JSON string output.
+ */
+ @Test
+ public void jsonObjectParseNullFieldsWithParserConfiguration() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+ RecursiveBean bean = new RecursiveBean(null);
+ JSONObject jsonObject = new JSONObject(bean, jsonParserConfiguration.withUseNativeNulls(true));
+ assertTrue("name key should be present", jsonObject.has("name"));
+ assertTrue("ref key should be present", jsonObject.has("ref"));
+ assertTrue("ref2 key should be present", jsonObject.has("ref2"));
+ }
+
+ /**
+ * Tests the behavior of the {@link JSONObject} when parsing a bean with null fields
+ * without using a custom {@link JSONParserConfiguration}.
+ *
+ * This test ensures that uninitialized fields in the bean are not serialized
+ * into the resulting JSON object, and the object remains empty.
+ */
+ @Test
+ public void jsonObjectParseNullFieldsWithoutParserConfiguration() {
+ RecursiveBean bean = new RecursiveBean(null);
+ JSONObject jsonObject = new JSONObject(bean);
+ assertTrue("JSONObject should be empty", jsonObject.isEmpty());
+ }
+
+
+ @Test
+ public void jsonObjectParseFromJson_0() {
+ JSONObject object = new JSONObject();
+ object.put("number", 12);
+ object.put("name", "Alex");
+ object.put("longNumber", 1500000000L);
+ CustomClass customClass = object.fromJson(CustomClass.class);
+ CustomClass compareClass = new CustomClass(12, "Alex", 1500000000L);
+ assertEquals(customClass, compareClass);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_1() {
+ JSONObject object = new JSONObject();
+
+ BigInteger largeInt = new BigInteger("123");
+ object.put("largeInt", largeInt.toString());
+ CustomClassA customClassA = object.fromJson(CustomClassA.class);
+ CustomClassA compareClassClassA = new CustomClassA(largeInt);
+ assertEquals(customClassA, compareClassClassA);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_2() {
+ JSONObject object = new JSONObject();
+ object.put("number", 12);
+
+ JSONObject classC = new JSONObject();
+ classC.put("stringName", "Alex");
+ classC.put("longNumber", 123456L);
+
+ object.put("classC", classC);
+
+ CustomClassB customClassB = object.fromJson(CustomClassB.class);
+ CustomClassC classCObject = new CustomClassC("Alex", 123456L);
+ CustomClassB compareClassB = new CustomClassB(12, classCObject);
+ assertEquals(customClassB, compareClassB);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_3() {
+ JSONObject object = new JSONObject();
+ JSONArray array = new JSONArray();
+ array.put("test1");
+ array.put("test2");
+ array.put("test3");
+ object.put("stringList", array);
+
+ CustomClassD customClassD = object.fromJson(CustomClassD.class);
+ CustomClassD compareClassD = new CustomClassD(Arrays.asList("test1", "test2", "test3"));
+ assertEquals(customClassD, compareClassD);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_4() {
+ JSONObject object = new JSONObject();
+ JSONArray array = new JSONArray();
+ array.put(new CustomClassC("test1", 1L).toJSON());
+ array.put(new CustomClassC("test2", 2L).toJSON());
+ object.put("listClassC", array);
+
+ CustomClassE customClassE = object.fromJson(CustomClassE.class);
+ CustomClassE compareClassE = new CustomClassE(java.util.Arrays.asList(
+ new CustomClassC("test1", 1L),
+ new CustomClassC("test2", 2L)));
+ assertEquals(customClassE, compareClassE);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_5() {
+ JSONObject object = new JSONObject();
+ JSONArray array = new JSONArray();
+ array.put(Arrays.asList("A", "B", "C"));
+ array.put(Arrays.asList("D", "E"));
+ object.put("listOfString", array);
+
+ CustomClassF customClassF = object.fromJson(CustomClassF.class);
+ List> listOfString = new ArrayList<>();
+ listOfString.add(Arrays.asList("A", "B", "C"));
+ listOfString.add(Arrays.asList("D", "E"));
+ CustomClassF compareClassF = new CustomClassF(listOfString);
+ assertEquals(customClassF, compareClassF);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_6() {
+ JSONObject object = new JSONObject();
+ Map dataList = new HashMap<>();
+ dataList.put("A", "Aa");
+ dataList.put("B", "Bb");
+ dataList.put("C", "Cc");
+ object.put("dataList", dataList);
+
+ CustomClassG customClassG = object.fromJson(CustomClassG.class);
+ CustomClassG compareClassG = new CustomClassG(dataList);
+ assertEquals(customClassG, compareClassG);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_7() {
+ JSONObject object = new JSONObject();
+ Map> dataList = new HashMap<>();
+ dataList.put("1", Arrays.asList(1, 2, 3, 4));
+ dataList.put("2", Arrays.asList(2, 3, 4, 5));
+ object.put("integerMap", dataList);
+
+ CustomClassH customClassH = object.fromJson(CustomClassH.class);
+ CustomClassH compareClassH = new CustomClassH(dataList);
+ assertEquals(customClassH.integerMap.toString(), compareClassH.integerMap.toString());
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_8() {
+ JSONObject object = new JSONObject();
+ Map> dataList = new HashMap<>();
+ dataList.put("1", Collections.singletonMap("1", 1));
+ dataList.put("2", Collections.singletonMap("2", 2));
+ object.put("integerMap", dataList);
+
+ CustomClassI customClassI = object.fromJson(CustomClassI.class);
+ CustomClassI compareClassI = new CustomClassI(dataList);
+ assertEquals(customClassI.integerMap.toString(), compareClassI.integerMap.toString());
+ }
}
diff --git a/src/test/java/org/json/junit/JSONParserConfigurationTest.java b/src/test/java/org/json/junit/JSONParserConfigurationTest.java
new file mode 100644
index 000000000..926c49f41
--- /dev/null
+++ b/src/test/java/org/json/junit/JSONParserConfigurationTest.java
@@ -0,0 +1,624 @@
+package org.json.junit;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONParserConfiguration;
+import org.json.JSONTokener;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class JSONParserConfigurationTest {
+ private static final String TEST_SOURCE = "{\"key\": \"value1\", \"key\": \"value2\"}";
+
+ @Test(expected = JSONException.class)
+ public void testThrowException() {
+ new JSONObject(TEST_SOURCE);
+ }
+
+ @Test
+ public void testOverwrite() {
+ JSONObject jsonObject = new JSONObject(TEST_SOURCE,
+ new JSONParserConfiguration().withOverwriteDuplicateKey(true));
+
+ assertEquals("duplicate key should be overwritten", "value2", jsonObject.getString("key"));
+ }
+
+ @Test
+ public void strictModeIsCloned(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true)
+ .withMaxNestingDepth(12);
+
+ assertTrue(jsonParserConfiguration.isStrictMode());
+ }
+
+ @Test
+ public void maxNestingDepthIsCloned(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withKeepStrings(true)
+ .withStrictMode(true);
+
+ assertTrue(jsonParserConfiguration.isKeepStrings());
+ }
+
+ @Test
+ public void useNativeNullsIsCloned() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withUseNativeNulls(true)
+ .withStrictMode(true);
+ assertTrue(jsonParserConfiguration.isUseNativeNulls());
+ }
+
+ @Test
+ public void verifyDuplicateKeyThenMaxDepth() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withOverwriteDuplicateKey(true)
+ .withMaxNestingDepth(42);
+
+ assertEquals(42, jsonParserConfiguration.getMaxNestingDepth());
+ assertTrue(jsonParserConfiguration.isOverwriteDuplicateKey());
+ }
+
+ @Test
+ public void verifyMaxDepthThenDuplicateKey() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withMaxNestingDepth(42)
+ .withOverwriteDuplicateKey(true);
+
+ assertTrue(jsonParserConfiguration.isOverwriteDuplicateKey());
+ assertEquals(42, jsonParserConfiguration.getMaxNestingDepth());
+ }
+
+ @Test
+ public void givenInvalidInput_testStrictModeTrue_shouldThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ List strictModeInputTestCases = getNonCompliantJSONArrayList();
+ // this is a lot easier to debug when things stop working
+ for (int i = 0; i < strictModeInputTestCases.size(); ++i) {
+ String testCase = strictModeInputTestCases.get(i);
+ try {
+ JSONArray jsonArray = new JSONArray(testCase, jsonParserConfiguration);
+ String s = jsonArray.toString();
+ String msg = "Expected an exception, but got: " + s + " Noncompliant Array index: " + i;
+ fail(msg);
+ } catch (Exception e) {
+ // its all good
+ }
+ }
+ }
+
+ @Test
+ public void givenInvalidInputObjects_testStrictModeTrue_shouldThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ List strictModeInputTestCases = getNonCompliantJSONObjectList();
+ // this is a lot easier to debug when things stop working
+ for (int i = 0; i < strictModeInputTestCases.size(); ++i) {
+ String testCase = strictModeInputTestCases.get(i);
+ try {
+ JSONObject jsonObject = new JSONObject(testCase, jsonParserConfiguration);
+ String s = jsonObject.toString();
+ String msg = "Expected an exception, but got: " + s + " Noncompliant Array index: " + i;
+ fail(msg);
+ } catch (Exception e) {
+ // its all good
+ }
+ }
+ }
+
+ @Test
+ public void givenEmptyArray_testStrictModeTrue_shouldNotThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "[]";
+ JSONArray jsonArray = new JSONArray(testCase, jsonParserConfiguration);
+ assertEquals(testCase, jsonArray.toString());
+ }
+
+ @Test
+ public void givenEmptyObject_testStrictModeTrue_shouldNotThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "{}";
+ JSONObject jsonObject = new JSONObject(testCase, jsonParserConfiguration);
+ assertEquals(testCase, jsonObject.toString());
+ }
+
+ @Test
+ public void givenValidNestedArray_testStrictModeTrue_shouldNotThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+
+ String testCase = "[[\"c\"], [10.2], [true, false, true]]";
+
+ JSONArray jsonArray = new JSONArray(testCase, jsonParserConfiguration);
+ JSONArray arrayShouldContainStringAt0 = jsonArray.getJSONArray(0);
+ JSONArray arrayShouldContainNumberAt0 = jsonArray.getJSONArray(1);
+ JSONArray arrayShouldContainBooleanAt0 = jsonArray.getJSONArray(2);
+
+ assertTrue(arrayShouldContainStringAt0.get(0) instanceof String);
+ assertTrue(arrayShouldContainNumberAt0.get(0) instanceof Number);
+ assertTrue(arrayShouldContainBooleanAt0.get(0) instanceof Boolean);
+ }
+
+ @Test
+ public void givenValidNestedObject_testStrictModeTrue_shouldNotThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+
+ String testCase = "{\"a0\":[\"c\"], \"a1\":[10.2], \"a2\":[true, false, true]}";
+
+ JSONObject jsonObject = new JSONObject(testCase, jsonParserConfiguration);
+ JSONArray arrayShouldContainStringAt0 = jsonObject.getJSONArray("a0");
+ JSONArray arrayShouldContainNumberAt0 = jsonObject.getJSONArray("a1");
+ JSONArray arrayShouldContainBooleanAt0 = jsonObject.getJSONArray("a2");
+
+ assertTrue(arrayShouldContainStringAt0.get(0) instanceof String);
+ assertTrue(arrayShouldContainNumberAt0.get(0) instanceof Number);
+ assertTrue(arrayShouldContainBooleanAt0.get(0) instanceof Boolean);
+ }
+
+ @Test
+ public void givenValidEmptyArrayInsideArray_testStrictModeTrue_shouldNotThrowJsonException(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "[[]]";
+ JSONArray jsonArray = new JSONArray(testCase, jsonParserConfiguration);
+ assertEquals(testCase, jsonArray.toString());
+ }
+
+ @Test
+ public void givenValidEmptyArrayInsideObject_testStrictModeTrue_shouldNotThrowJsonException(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "{\"a0\":[]}";
+ JSONObject jsonObject = new JSONObject(testCase, jsonParserConfiguration);
+ assertEquals(testCase, jsonObject.toString());
+ }
+
+ @Test
+ public void givenValidEmptyArrayInsideArray_testStrictModeFalse_shouldNotThrowJsonException(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(false);
+ String testCase = "[[]]";
+ JSONArray jsonArray = new JSONArray(testCase, jsonParserConfiguration);
+ assertEquals(testCase, jsonArray.toString());
+ }
+
+ @Test
+ public void givenValidEmptyArrayInsideObject_testStrictModeFalse_shouldNotThrowJsonException(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(false);
+ String testCase = "{\"a0\":[]}";
+ JSONObject jsonObject = new JSONObject(testCase, jsonParserConfiguration);
+ assertEquals(testCase, jsonObject.toString());
+ }
+
+ @Test
+ public void givenInvalidStringArray_testStrictModeTrue_shouldThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "[badString]";
+ JSONException je = assertThrows(JSONException.class, () -> new JSONArray(testCase, jsonParserConfiguration));
+ assertEquals("Strict mode error: Value 'badString' is not surrounded by quotes at 10 [character 11 line 1]",
+ je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidStringObject_testStrictModeTrue_shouldThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "{\"a0\":badString}";
+ JSONException je = assertThrows(JSONException.class, () -> new JSONObject(testCase, jsonParserConfiguration));
+ assertEquals("Strict mode error: Value 'badString' is not surrounded by quotes at 15 [character 16 line 1]",
+ je.getMessage());
+ }
+
+ @Test
+ public void allowNullArrayInStrictMode() {
+ String expected = "[null]";
+ JSONArray jsonArray = new JSONArray(expected, new JSONParserConfiguration().withStrictMode(true));
+ assertEquals(expected, jsonArray.toString());
+ }
+
+ @Test
+ public void allowNullObjectInStrictMode() {
+ String expected = "{\"a0\":null}";
+ JSONObject jsonObject = new JSONObject(expected, new JSONParserConfiguration().withStrictMode(true));
+ assertEquals(expected, jsonObject.toString());
+ }
+
+ @Test
+ public void shouldHandleNumericArray() {
+ String expected = "[10]";
+ JSONArray jsonArray = new JSONArray(expected, new JSONParserConfiguration().withStrictMode(true));
+ assertEquals(expected, jsonArray.toString());
+ }
+
+ @Test
+ public void shouldHandleNumericObject() {
+ String expected = "{\"a0\":10}";
+ JSONObject jsonObject = new JSONObject(expected, new JSONParserConfiguration().withStrictMode(true));
+ assertEquals(expected, jsonObject.toString());
+ }
+ @Test
+ public void givenCompliantJSONArrayFile_testStrictModeTrue_shouldNotThrowAnyException() throws IOException {
+ try (Stream lines = Files.lines(Paths.get("src/test/resources/compliantJsonArray.json"))) {
+ String compliantJsonArrayAsString = lines.collect(Collectors.joining());
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ new JSONArray(compliantJsonArrayAsString, jsonParserConfiguration);
+ }
+ }
+
+ @Test
+ public void givenCompliantJSONObjectFile_testStrictModeTrue_shouldNotThrowAnyException() throws IOException {
+ try (Stream lines = Files.lines(Paths.get("src/test/resources/compliantJsonObject.json"))) {
+ String compliantJsonObjectAsString = lines.collect(Collectors.joining());
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+
+ new JSONObject(compliantJsonObjectAsString, jsonParserConfiguration);
+ }
+ }
+
+ @Test
+ public void givenInvalidInputArrays_testStrictModeFalse_shouldNotThrowAnyException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(false);
+
+ List strictModeInputTestCases = getNonCompliantJSONArrayList();
+
+ // this is a lot easier to debug when things stop working
+ for (int i = 0; i < strictModeInputTestCases.size(); ++i) {
+ String testCase = strictModeInputTestCases.get(i);
+ try {
+ JSONArray jsonArray = new JSONArray(testCase, jsonParserConfiguration);
+ } catch (Exception e) {
+ System.out.println("Unexpected exception: " + e.getMessage() + " Noncompliant Array index: " + i);
+ fail(String.format("Noncompliant array index: %d", i));
+ }
+ }
+ }
+
+ @Test
+ public void givenInvalidInputObjects_testStrictModeFalse_shouldNotThrowAnyException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(false);
+
+ List strictModeInputTestCases = getNonCompliantJSONObjectList();
+
+ // this is a lot easier to debug when things stop working
+ for (int i = 0; i < strictModeInputTestCases.size(); ++i) {
+ String testCase = strictModeInputTestCases.get(i);
+ try {
+ JSONObject jsonObject = new JSONObject(testCase, jsonParserConfiguration);
+ } catch (Exception e) {
+ System.out.println("Unexpected exception: " + e.getMessage() + " Noncompliant Array index: " + i);
+ fail(String.format("Noncompliant array index: %d", i));
+ }
+ }
+ }
+
+ @Test
+ public void givenInvalidInputArray_testStrictModeTrue_shouldThrowInvalidCharacterErrorMessage() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "[1,2];[3,4]";
+ JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
+ JSONException.class, () -> new JSONArray(testCase, jsonParserConfiguration));
+ assertEquals("Strict mode error: Unparsed characters found at end of input text at 6 [character 7 line 1]",
+ je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputObject_testStrictModeTrue_shouldThrowInvalidCharacterErrorMessage() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "{\"a0\":[1,2];\"a1\":[3,4]}";
+ JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
+ JSONException.class, () -> new JSONObject(testCase, jsonParserConfiguration));
+ assertEquals("Strict mode error: Invalid character ';' found at 12 [character 13 line 1]", je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputArrayWithNumericStrings_testStrictModeTrue_shouldThrowInvalidCharacterErrorMessage() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "[\"1\",\"2\"];[3,4]";
+ JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
+ JSONException.class, () -> new JSONArray(testCase, jsonParserConfiguration));
+ assertEquals("Strict mode error: Unparsed characters found at end of input text at 10 [character 11 line 1]",
+ je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputObjectWithNumericStrings_testStrictModeTrue_shouldThrowInvalidCharacterErrorMessage() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "{\"a0\":[\"1\",\"2\"];\"a1\":[3,4]}";
+ JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
+ JSONException.class, () -> new JSONObject(testCase, jsonParserConfiguration));
+ assertEquals("Strict mode error: Invalid character ';' found at 16 [character 17 line 1]", je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputArray_testStrictModeTrue_shouldThrowValueNotSurroundedByQuotesErrorMessage() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "[{\"test\": implied}]";
+ JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
+ JSONException.class, () -> new JSONArray(testCase, jsonParserConfiguration));
+ assertEquals("Strict mode error: Value 'implied' is not surrounded by quotes at 17 [character 18 line 1]",
+ je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputObject_testStrictModeTrue_shouldThrowValueNotSurroundedByQuotesErrorMessage() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "{\"a0\":{\"test\": implied}]}";
+ JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
+ JSONException.class, () -> new JSONObject(testCase, jsonParserConfiguration));
+ assertEquals("Strict mode error: Value 'implied' is not surrounded by quotes at 22 [character 23 line 1]",
+ je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputArray_testStrictModeFalse_shouldNotThrowAnyException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(false);
+ String testCase = "[{\"test\": implied}]";
+ new JSONArray(testCase, jsonParserConfiguration);
+ }
+
+ @Test
+ public void givenInvalidInputObject_testStrictModeFalse_shouldNotThrowAnyException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(false);
+ String testCase = "{\"a0\":{\"test\": implied}}";
+ new JSONObject(testCase, jsonParserConfiguration);
+ }
+
+ @Test
+ public void givenNonCompliantQuotesArray_testStrictModeTrue_shouldThrowJsonExceptionWithConcreteErrorDescription() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+
+ String testCaseOne = "[\"abc', \"test\"]";
+ String testCaseTwo = "['abc\", \"test\"]";
+ String testCaseThree = "['abc']";
+ String testCaseFour = "[{'testField': \"testValue\"}]";
+
+ JSONException jeOne = assertThrows(JSONException.class,
+ () -> new JSONArray(testCaseOne, jsonParserConfiguration));
+ JSONException jeTwo = assertThrows(JSONException.class,
+ () -> new JSONArray(testCaseTwo, jsonParserConfiguration));
+ JSONException jeThree = assertThrows(JSONException.class,
+ () -> new JSONArray(testCaseThree, jsonParserConfiguration));
+ JSONException jeFour = assertThrows(JSONException.class,
+ () -> new JSONArray(testCaseFour, jsonParserConfiguration));
+
+ assertEquals(
+ "Expected a ',' or ']' at 10 [character 11 line 1]",
+ jeOne.getMessage());
+ assertEquals(
+ "Strict mode error: Single quoted strings are not allowed at 2 [character 3 line 1]",
+ jeTwo.getMessage());
+ assertEquals(
+ "Strict mode error: Single quoted strings are not allowed at 2 [character 3 line 1]",
+ jeThree.getMessage());
+ assertEquals(
+ "Strict mode error: Single quoted strings are not allowed at 3 [character 4 line 1]",
+ jeFour.getMessage());
+ }
+
+ @Test
+ public void givenNonCompliantQuotesObject_testStrictModeTrue_shouldThrowJsonExceptionWithConcreteErrorDescription() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+
+ String testCaseOne = "{\"abc': \"test\"}";
+ String testCaseTwo = "{'abc\": \"test\"}";
+ String testCaseThree = "{\"a\":'abc'}";
+ String testCaseFour = "{'testField': \"testValue\"}";
+
+ JSONException jeOne = assertThrows(JSONException.class,
+ () -> new JSONObject(testCaseOne, jsonParserConfiguration));
+ JSONException jeTwo = assertThrows(JSONException.class,
+ () -> new JSONObject(testCaseTwo, jsonParserConfiguration));
+ JSONException jeThree = assertThrows(JSONException.class,
+ () -> new JSONObject(testCaseThree, jsonParserConfiguration));
+ JSONException jeFour = assertThrows(JSONException.class,
+ () -> new JSONObject(testCaseFour, jsonParserConfiguration));
+
+ assertEquals(
+ "Expected a ':' after a key at 10 [character 11 line 1]",
+ jeOne.getMessage());
+ assertEquals(
+ "Strict mode error: Single quoted strings are not allowed at 2 [character 3 line 1]",
+ jeTwo.getMessage());
+ assertEquals(
+ "Strict mode error: Single quoted strings are not allowed at 6 [character 7 line 1]",
+ jeThree.getMessage());
+ assertEquals(
+ "Strict mode error: Single quoted strings are not allowed at 2 [character 3 line 1]",
+ jeFour.getMessage());
+ }
+
+ @Test
+ public void givenUnbalancedQuotesArray_testStrictModeFalse_shouldThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(false);
+
+ String testCaseOne = "[\"abc', \"test\"]";
+ String testCaseTwo = "['abc\", \"test\"]";
+
+ JSONException jeOne = assertThrows(JSONException.class,
+ () -> new JSONArray(testCaseOne, jsonParserConfiguration));
+ JSONException jeTwo = assertThrows(JSONException.class,
+ () -> new JSONArray(testCaseTwo, jsonParserConfiguration));
+
+ assertEquals("Expected a ',' or ']' at 10 [character 11 line 1]", jeOne.getMessage());
+ assertEquals("Unterminated string. Character with int code 0 is not allowed within a quoted string. at 15 [character 16 line 1]", jeTwo.getMessage());
+ }
+
+ @Test
+ public void givenUnbalancedQuotesObject_testStrictModeFalse_shouldThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(false);
+
+ String testCaseOne = "{\"abc': \"test\"}";
+ String testCaseTwo = "{'abc\": \"test\"}";
+
+ JSONException jeOne = assertThrows(JSONException.class,
+ () -> new JSONObject(testCaseOne, jsonParserConfiguration));
+ JSONException jeTwo = assertThrows(JSONException.class,
+ () -> new JSONObject(testCaseTwo, jsonParserConfiguration));
+
+ assertEquals("Expected a ':' after a key at 10 [character 11 line 1]", jeOne.getMessage());
+ assertEquals("Unterminated string. Character with int code 0 is not allowed within a quoted string. at 15 [character 16 line 1]", jeTwo.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputArray_testStrictModeTrue_shouldThrowKeyNotSurroundedByQuotesErrorMessage() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+
+ String testCase = "[{test: implied}]";
+ JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
+ JSONException.class, () -> new JSONArray(testCase, jsonParserConfiguration));
+
+ assertEquals("Strict mode error: Value 'test' is not surrounded by quotes at 6 [character 7 line 1]",
+ je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputObject_testStrictModeTrue_shouldThrowKeyNotSurroundedByQuotesErrorMessage() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+
+ String testCase = "{test: implied}";
+ JSONException je = assertThrows("expected non-compliant json but got instead: " + testCase,
+ JSONException.class, () -> new JSONObject(testCase, jsonParserConfiguration));
+
+ assertEquals("Strict mode error: Value 'test' is not surrounded by quotes at 5 [character 6 line 1]",
+ je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputObject_testStrictModeTrue_JSONObjectUsingJSONTokener_shouldThrowJSONException() {
+ JSONException exception = assertThrows(JSONException.class, () -> {
+ new JSONObject(new JSONTokener("{\"key\":\"value\"} invalid trailing text"), new JSONParserConfiguration().withStrictMode(true));
+ });
+
+ assertEquals("Strict mode error: Unparsed characters found at end of input text at 17 [character 18 line 1]", exception.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputObject_testStrictModeTrue_JSONObjectUsingString_shouldThrowJSONException() {
+ JSONException exception = assertThrows(JSONException.class, () -> {
+ new JSONObject("{\"key\":\"value\"} invalid trailing text", new JSONParserConfiguration().withStrictMode(true));
+ });
+ assertEquals("Strict mode error: Unparsed characters found at end of input text at 17 [character 18 line 1]", exception.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputObject_testStrictModeTrue_JSONArrayUsingJSONTokener_shouldThrowJSONException() {
+ JSONException exception = assertThrows(JSONException.class, () -> {
+ new JSONArray(new JSONTokener("[\"value\"] invalid trailing text"), new JSONParserConfiguration().withStrictMode(true));
+ });
+
+ assertEquals("Strict mode error: Unparsed characters found at end of input text at 11 [character 12 line 1]", exception.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputObject_testStrictModeTrue_JSONArrayUsingString_shouldThrowJSONException() {
+ JSONException exception = assertThrows(JSONException.class, () -> {
+ new JSONArray("[\"value\"] invalid trailing text", new JSONParserConfiguration().withStrictMode(true));
+ });
+ assertEquals("Strict mode error: Unparsed characters found at end of input text at 11 [character 12 line 1]", exception.getMessage());
+ }
+
+ /**
+ * This method contains short but focused use-case samples and is exclusively used to test strictMode unit tests in
+ * this class.
+ *
+ * @return List with JSON strings.
+ */
+ private List getNonCompliantJSONArrayList() {
+ return Arrays.asList(
+ "[1],",
+ "[1,]",
+ "[,]",
+ "[,,]",
+ "[[1],\"sa\",[2]]a",
+ "[1],\"dsa\": \"test\"",
+ "[[a]]",
+ "[]asdf",
+ "[]]",
+ "[]}",
+ "[][",
+ "[]{",
+ "[],",
+ "[]:",
+ "[],[",
+ "[],{",
+ "[1,2];[3,4]",
+ "[test]",
+ "[{'testSingleQuote': 'testSingleQuote'}]",
+ "[1, 2,3]:[4,5]",
+ "[{test: implied}]",
+ "[{\"test\": implied}]",
+ "[{\"number\":\"7990154836330\",\"color\":'c'},{\"number\":8784148854580,\"color\":RosyBrown},{\"number\":\"5875770107113\",\"color\":\"DarkSeaGreen\"}]",
+ "[{test: \"implied\"}]");
+ }
+
+ /**
+ * This method contains short but focused use-case samples and is exclusively used to test strictMode unit tests in
+ * this class.
+ *
+ * @return List with JSON strings.
+ */
+ private List getNonCompliantJSONObjectList() {
+ return Arrays.asList(
+ "{\"a\":1},",
+ "{\"a\":1,}",
+ "{\"a0\":[1],\"a1\":\"sa\",\"a2\":[2]}a",
+ "{\"a\":1},\"dsa\": \"test\"",
+ "{\"a\":[a]}",
+ "{}asdf",
+ "{}}",
+ "{}]",
+ "{}{",
+ "{}[",
+ "{},",
+ "{}:",
+ "{},{",
+ "{},[",
+ "{\"a0\":[1,2];\"a1\":[3,4]}",
+ "{\"a\":test}",
+ "{a:{'testSingleQuote': 'testSingleQuote'}}",
+ "{\"a0\":1, \"a1\":2,\"a2\":3}:{\"a3\":4,\"a4\":5}",
+ "{\"a\":{test: implied}}",
+ "{a:{\"test\": implied}}",
+ "{a:[{\"number\":\"7990154836330\",\"color\":'c'},{\"number\":8784148854580,\"color\":RosyBrown},{\"number\":\"5875770107113\",\"color\":\"DarkSeaGreen\"}]}",
+ "{a:{test: \"implied\"}}"
+ );
+ }
+
+}
diff --git a/src/test/java/org/json/junit/JSONPointerTest.java b/src/test/java/org/json/junit/JSONPointerTest.java
index e06851eb7..a420b297f 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 @@ of this software and associated documentation files (the "Software"), to deal
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 @@ private Object query(String pointer) {
@Test
public void emptyPointer() {
- assertSame(document, query(""));
+ assertTrue(new JSONObject(EXPECTED_COMPLETE_DOCUMENT).similar(query("")));
}
@SuppressWarnings("unused")
@@ -68,12 +52,12 @@ public void nullPointer() {
@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 void stringPropOfArrayFailure() {
@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 void toStringEscaping() {
.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,27 @@ public void optQueryFromJSONArrayUsingPointer() {
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 788d8ebb3..235df1806 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.*;
@@ -49,84 +29,114 @@ public void writeValues() throws Exception {
JSONArray jsonArray = new JSONArray();
jsonArray.put((Object)null);
- try (StringWriter writer = new StringWriter();) {
+ StringWriter writer = new StringWriter();
+ try {
String output = jsonArray.write(writer).toString();
assertTrue("String values should be equal", "[null]".equals(output));
jsonArray = new JSONArray();
jsonArray.put(JSONObject.NULL);
+ } finally {
+ writer.close();
}
- try (StringWriter writer = new StringWriter();) {
+ writer = new StringWriter();
+ try {
String output = jsonArray.write(writer).toString();
assertTrue("String values should be equal", "[null]".equals(output));
jsonArray = new JSONArray();
jsonArray.put(new JSONObject());
+ } finally {
+ writer.close();
}
- try (StringWriter writer = new StringWriter();) {
+ writer = new StringWriter();
+ try {
String output = jsonArray.write(writer).toString();
assertTrue("String values should be equal", "[{}]".equals(output));
jsonArray = new JSONArray();
jsonArray.put(new JSONArray());
+ } finally {
+ writer.close();
}
- try (StringWriter writer = new StringWriter();) {
+ writer = new StringWriter();
+ try {
String output = jsonArray.write(writer).toString();
assertTrue("String values should be equal", "[[]]".equals(output));
jsonArray = new JSONArray();
Map,?> singleMap = Collections.singletonMap("key1", "value1");
jsonArray.put((Object)singleMap);
+ } finally {
+ writer.close();
}
- try (StringWriter writer = new StringWriter();) {
+ writer = new StringWriter();
+ try {
String output = jsonArray.write(writer).toString();
assertTrue("String values should be equal", "[{\"key1\":\"value1\"}]".equals(output));
jsonArray = new JSONArray();
List> singleList = Collections.singletonList("entry1");
jsonArray.put((Object)singleList);
+ } finally {
+ writer.close();
}
- try (StringWriter writer = new StringWriter();) {
+ writer = new StringWriter();
+ try {
String output = jsonArray.write(writer).toString();
assertTrue("String values should be equal", "[[\"entry1\"]]".equals(output));
jsonArray = new JSONArray();
int[] intArray = new int[] { 1, 2, 3 };
jsonArray.put(intArray);
+ } finally {
+ writer.close();
}
- try (StringWriter writer = new StringWriter();) {
+ writer = new StringWriter();
+ try {
String output = jsonArray.write(writer).toString();
assertTrue("String values should be equal", "[[1,2,3]]".equals(output));
jsonArray = new JSONArray();
jsonArray.put(24);
+ } finally {
+ writer.close();
}
- try (StringWriter writer = new StringWriter();) {
+ writer = new StringWriter();
+ try {
String output = jsonArray.write(writer).toString();
assertTrue("String values should be equal", "[24]".equals(output));
jsonArray = new JSONArray();
jsonArray.put("string value");
+ } finally {
+ writer.close();
}
- try (StringWriter writer = new StringWriter();) {
+ writer = new StringWriter();
+ try {
String output = jsonArray.write(writer).toString();
assertTrue("String values should be equal", "[\"string value\"]".equals(output));
jsonArray = new JSONArray();
jsonArray.put(true);
+ } finally {
+ writer.close();
}
- try (StringWriter writer = new StringWriter();) {
+ writer = new StringWriter();
+ try {
String output = jsonArray.write(writer).toString();
assertTrue("String values should be equal", "[true]".equals(output));
+ } finally {
+ writer.close();
}
}
@@ -185,13 +195,15 @@ public void testJSONStringValue() throws Exception {
jsonArray.put(jsonString);
-
- try (StringWriter writer = new StringWriter();) {
+ StringWriter writer = new StringWriter();
+ try {
String output = jsonArray.write(writer).toString();
assertTrue("String values should be equal", "[\"the JSON string value\"]".equals(output));
output = JSONObject.valueToString(jsonString);
assertTrue("String values should be equal", "\"the JSON string value\"".equals(output));
+ } finally {
+ writer.close();
}
}
@@ -206,7 +218,8 @@ public void testJSONNullStringValue() throws Exception {
jsonArray.put(jsonString);
- try (StringWriter writer = new StringWriter();) {
+ StringWriter writer = new StringWriter();
+ try {
String output = jsonArray.write(writer).toString();
assertTrue("String values should be equal", "[\"the toString value\"]".equals(output));
@@ -219,6 +232,8 @@ public void testJSONNullStringValue() throws Exception {
assertTrue("Expected JSONException", e instanceof JSONException);
assertTrue("Exception message does not match", "Bad value from toJSONString: null".equals(e.getMessage()));
}
+ } finally {
+ writer.close();
}
}
@@ -234,13 +249,18 @@ public void testJSONStringExceptionValue() {
jsonArray.put(jsonString);
- try (StringWriter writer = new StringWriter();) {
+ StringWriter writer = new StringWriter();
+ try {
jsonArray.write(writer).toString();
fail("Expected an exception, got a String value");
} catch (JSONException e) {
assertEquals("Unable to write JSONArray value at index: 0", e.getMessage());
} catch(Exception e) {
fail("Expected JSONException");
+ } finally {
+ try {
+ writer.close();
+ } catch (Exception e){}
}
try {
@@ -264,12 +284,15 @@ public void testStringValue() throws Exception {
jsonArray.put(nonJsonString);
- try (StringWriter writer = new StringWriter();) {
+ StringWriter writer = new StringWriter();
+ try {
String output = jsonArray.write(writer).toString();
assertTrue("String values should be equal", "[\"the toString value for StringValue\"]".equals(output));
output = JSONObject.valueToString(nonJsonString);
assertTrue("String values should be equal", "\"the toString value for StringValue\"".equals(output));
+ } finally {
+ writer.close();
}
}
@@ -284,13 +307,31 @@ public void testNullStringValue() throws Exception {
jsonArray.put(nonJsonString);
-
- try (StringWriter writer = new StringWriter();) {
+ StringWriter writer = new StringWriter();
+ try {
String output = jsonArray.write(writer).toString();
assertTrue("String values should be equal", "[\"\"]".equals(output));
output = JSONObject.valueToString(nonJsonString);
assertTrue("String values should be equal", "\"\"".equals(output));
+ } finally {
+ writer.close();
+ }
+ }
+
+ @Test
+ public void testEnumJSONString() {
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("key", MyEnum.MY_ENUM);
+ assertEquals("{\"key\":\"myJsonString\"}", jsonObject.toString());
+ }
+
+ private enum MyEnum implements JSONString {
+ MY_ENUM;
+
+ @Override
+ public String toJSONString() {
+ return "\"myJsonString\"";
}
}
diff --git a/src/test/java/org/json/junit/JSONStringerTest.java b/src/test/java/org/json/junit/JSONStringerTest.java
index a99db3b8c..0ecb9d662 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 86a614c55..b0b45cb7c 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;
@@ -36,10 +16,7 @@ of this software and associated documentation files (the "Software"), to deal
import java.io.Reader;
import java.io.StringReader;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONTokener;
+import org.json.*;
import org.junit.Test;
/**
@@ -55,7 +32,8 @@ public class JSONTokenerTest {
*/
@Test
public void verifyBackFailureZeroIndex() throws IOException {
- try(Reader reader = new StringReader("some test string")) {
+ Reader reader = new StringReader("some test string");
+ try {
final JSONTokener tokener = new JSONTokener(reader);
try {
// this should fail since the index is 0;
@@ -67,6 +45,8 @@ public void verifyBackFailureZeroIndex() throws IOException {
fail("Unknown Exception type " + e.getClass().getCanonicalName()+" with message "+e.getMessage());
}
+ } finally {
+ reader.close();
}
}
/**
@@ -75,7 +55,8 @@ public void verifyBackFailureZeroIndex() throws IOException {
*/
@Test
public void verifyBackFailureDoubleBack() throws IOException {
- try(Reader reader = new StringReader("some test string")) {
+ Reader reader = new StringReader("some test string");
+ try {
final JSONTokener tokener = new JSONTokener(reader);
tokener.next();
tokener.back();
@@ -88,6 +69,8 @@ public void verifyBackFailureDoubleBack() throws IOException {
} catch (Exception e) {
fail("Unknown Exception type " + e.getClass().getCanonicalName()+" with message "+e.getMessage());
}
+ } finally {
+ reader.close();
}
}
@@ -112,7 +95,17 @@ public void testValid() {
checkValid(" [] ",JSONArray.class);
checkValid("[1,2]",JSONArray.class);
checkValid("\n\n[1,2]\n\n",JSONArray.class);
- checkValid("1 2", String.class);
+
+ // Test should fail if default strictMode is true, pass if false
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+ if (jsonParserConfiguration.isStrictMode()) {
+ try {
+ checkValid("1 2", String.class);
+ assertEquals("Expected to throw exception due to invalid string", true, false);
+ } catch (JSONException e) { }
+ } else {
+ checkValid("1 2", String.class);
+ }
}
@Test
@@ -164,7 +157,8 @@ private void checkError(String testStr) {
* @throws Exception
*/
private Object nextValue(String testStr) throws JSONException {
- try(StringReader sr = new StringReader(testStr);){
+ StringReader sr = new StringReader(testStr);
+ try {
JSONTokener tokener = new JSONTokener(sr);
Object result = tokener.nextValue();
@@ -179,6 +173,8 @@ private Object nextValue(String testStr) throws JSONException {
}
return result;
+ } finally {
+ sr.close();
}
}
@@ -196,7 +192,10 @@ public void testSkipToFailureWithBufferedReader() throws IOException {
for(int i=0;i 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 14c4ba048..ca1980c8a 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 @@ of this software and associated documentation files (the "Software"), to deal
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import static org.junit.Assert.*;
+
/**
* Tests for JSON-Java XML.java with XMLParserConfiguration.java
@@ -209,7 +188,7 @@ public void shouldHandleInvalidCDATABangInTag() {
"
";
try {
XMLParserConfiguration config =
- new XMLParserConfiguration("altContent");
+ new XMLParserConfiguration().withcDataTagName("altContent");
XML.toJSONObject(xmlStr, config);
fail("Expecting a JSONException");
} catch (JSONException e) {
@@ -291,16 +270,16 @@ public void shouldHandleSimpleXML() {
String expectedStr =
"{\"addresses\":{\"address\":{\"street\":\"[CDATA[Baker street 5]\","+
- "\"name\":\"Joe Tester\",\"NothingHere\":\"\",TrueValue:true,\n"+
+ "\"name\":\"Joe Tester\",\"NothingHere\":\"\",\"TrueValue\":true,\n"+
"\"FalseValue\":false,\"NullValue\":null,\"PositiveValue\":42,\n"+
- "\"NegativeValue\":-23,\"DoubleValue\":-23.45,\"Nan\":-23x.45,\n"+
+ "\"NegativeValue\":-23,\"DoubleValue\":-23.45,\"Nan\":\"-23x.45\",\n"+
"\"ArrayOfNum\":\"1, 2, 3, 4.1, 5.2\"\n"+
"},\"xsi:noNamespaceSchemaLocation\":"+
"\"test.xsd\",\"xmlns:xsi\":\"http://www.w3.org/2001/"+
"XMLSchema-instance\"}}";
XMLParserConfiguration config =
- new XMLParserConfiguration("altContent");
+ new XMLParserConfiguration().withcDataTagName("altContent");
compareStringToJSONObject(xmlStr, expectedStr, config);
compareReaderToJSONObject(xmlStr, expectedStr, config);
compareFileToJSONObject(xmlStr, expectedStr);
@@ -325,7 +304,7 @@ public void shouldHandleCommentsInXML() {
" \n"+
"
";
XMLParserConfiguration config =
- new XMLParserConfiguration("altContent");
+ new XMLParserConfiguration().withcDataTagName("altContent");
JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
String expectedStr = "{\"addresses\":{\"address\":{\"street\":\"Baker "+
"street 5\",\"name\":\"Joe Tester\",\"altContent\":\" this is -- "+
@@ -378,7 +357,7 @@ public void shouldHandleContentNoArraytoString() {
String expectedStr =
"{\"addresses\":{\"altContent\":\">\"}}";
JSONObject expectedJsonObject = new JSONObject(expectedStr);
- XMLParserConfiguration config = new XMLParserConfiguration("altContent");
+ XMLParserConfiguration config = new XMLParserConfiguration().withcDataTagName("altContent");
String finalStr = XML.toString(expectedJsonObject, null, config);
String expectedFinalStr = "> ";
assertTrue("Should handle expectedFinal: ["+expectedStr+"] final: ["+
@@ -395,7 +374,7 @@ public void shouldHandleContentArraytoString() {
String expectedStr =
"{\"addresses\":{\"altContent\":[1, 2, 3]}}";
JSONObject expectedJsonObject = new JSONObject(expectedStr);
- XMLParserConfiguration config = new XMLParserConfiguration("altContent");
+ XMLParserConfiguration config = new XMLParserConfiguration().withcDataTagName("altContent");
String finalStr = XML.toString(expectedJsonObject, null, config);
String expectedFinalStr = ""+
"1\n2\n3"+
@@ -575,6 +554,40 @@ public void shouldHandleNullNodeValue()
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.isKeepNumberAsString());
+ assertTrue(keepStrings.isKeepBooleanAsString());
+ assertFalse(keepStrings.isCloseEmptyTag());
+ assertTrue(keepStringsAndCloseEmptyTag.isKeepNumberAsString());
+ assertTrue(keepStringsAndCloseEmptyTag.isKeepBooleanAsString());
+ assertTrue(keepStringsAndCloseEmptyTag.isCloseEmptyTag());
+ assertFalse(keepDigits.isKeepNumberAsString());
+ assertFalse(keepDigits.isKeepBooleanAsString());
+ assertTrue(keepDigits.isCloseEmptyTag());
+ assertFalse(keepDigitsAndNoCloseEmptyTag.isKeepNumberAsString());
+ assertFalse(keepDigitsAndNoCloseEmptyTag.isKeepBooleanAsString());
+ assertFalse(keepDigitsAndNoCloseEmptyTag.isCloseEmptyTag());
+ }
+
/**
* Investigate exactly how the "content" keyword works
*/
@@ -595,7 +608,9 @@ public void contentOperations() {
// multiple consecutive standalone cdatas are accumulated into an array
xmlStr = " 0) then return]]> ";
jsonObject = XML.toJSONObject(xmlStr,
- new XMLParserConfiguration(true, "altContent"));
+ new XMLParserConfiguration()
+ .withKeepStrings(true)
+ .withcDataTagName("altContent"));
assertTrue("2. 3 items", 3 == jsonObject.length());
assertTrue("2. empty tag1", "".equals(jsonObject.get("tag1")));
assertTrue("2. empty tag2", "".equals(jsonObject.get("tag2")));
@@ -612,7 +627,9 @@ public void contentOperations() {
*/
xmlStr = "value 1 ";
jsonObject = XML.toJSONObject(xmlStr,
- new XMLParserConfiguration(true, "altContent"));
+ new XMLParserConfiguration()
+ .withKeepStrings(true)
+ .withcDataTagName("altContent"));
assertTrue("3. 2 items", 1 == jsonObject.length());
assertTrue("3. value tag1", "value 1".equals(jsonObject.get("tag1")));
@@ -623,7 +640,9 @@ public void contentOperations() {
*/
xmlStr = "value 1 2 true ";
jsonObject = XML.toJSONObject(xmlStr,
- new XMLParserConfiguration(true, "altContent"));
+ new XMLParserConfiguration()
+ .withKeepStrings(true)
+ .withcDataTagName("altContent"));
assertTrue("4. 1 item", 1 == jsonObject.length());
assertTrue("4. content array found", jsonObject.get("tag1") instanceof JSONArray);
jsonArray = jsonObject.getJSONArray("tag1");
@@ -639,7 +658,9 @@ public void contentOperations() {
*/
xmlStr = "val1 val2 ";
jsonObject = XML.toJSONObject(xmlStr,
- new XMLParserConfiguration(true, "altContent"));
+ new XMLParserConfiguration()
+ .withKeepStrings(true)
+ .withcDataTagName("altContent"));
assertTrue("5. 1 item", 1 == jsonObject.length());
assertTrue("5. jsonObject found", jsonObject.get("tag1")
instanceof JSONObject);
@@ -659,7 +680,9 @@ public void contentOperations() {
*/
xmlStr = "val1 ";
jsonObject = XML.toJSONObject(xmlStr,
- new XMLParserConfiguration(true, "altContent"));
+ new XMLParserConfiguration()
+ .withKeepStrings(true)
+ .withcDataTagName("altContent"));
assertTrue("6. 1 item", 1 == jsonObject.length());
assertTrue("6. jsonObject found", jsonObject.get("tag1") instanceof JSONObject);
jsonObject = jsonObject.getJSONObject("tag1");
@@ -674,7 +697,9 @@ public void contentOperations() {
*/
xmlStr = "val1 ";
jsonObject = XML.toJSONObject(xmlStr,
- new XMLParserConfiguration(true, "altContent"));
+ new XMLParserConfiguration()
+ .withKeepStrings(true)
+ .withcDataTagName("altContent"));
assertTrue("7. 1 item", 1 == jsonObject.length());
assertTrue("7. jsonArray found",
jsonObject.get("tag1") instanceof JSONArray);
@@ -713,7 +738,9 @@ public void contentOperations() {
"}";
jsonObject = new JSONObject(jsonStr);
xmlStr = XML.toString(jsonObject, null,
- new XMLParserConfiguration(true, "altContent"));
+ new XMLParserConfiguration()
+ .withKeepStrings(true)
+ .withcDataTagName("altContent"));
/*
* This is the created XML. Looks like content was mistaken for
* complex (child node + text) XML.
@@ -739,17 +766,78 @@ public void testToJSONArray_jsonOutput() {
final String originalXml = "01 1 00 0 True ";
final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0],\"title\":true}}");
final JSONObject actualJsonOutput = XML.toJSONObject(originalXml,
- new XMLParserConfiguration(false));
+ new XMLParserConfiguration().withKeepStrings(false));
+ Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);
+ }
+
+ /**
+ * JSON string lost leading zero and converted "True" to true.
+ */
+ @Test
+ public void testToJSONArray_jsonOutput_withKeepNumberAsString() {
+ final String originalXml = "01 1 00 0 null True ";
+ final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\",null],\"title\":true}}");
+ final JSONObject actualJsonOutput = XML.toJSONObject(originalXml,
+ new XMLParserConfiguration().withKeepNumberAsString(true));
Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);
}
+ /**
+ * JSON string lost leading zero and converted "True" to true.
+ */
+ @Test
+ public void testToJSONArray_jsonOutput_withKeepBooleanAsString() {
+ final String originalXml = "01 1 00 0 null True ";
+ final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0,null],\"title\":\"True\"}}");
+ final JSONObject actualJsonOutput = XML.toJSONObject(originalXml,
+ new XMLParserConfiguration().withKeepBooleanAsString(true));
+ Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);
+ }
+
+ /**
+ * null is "null" when keepStrings == true
+ */
+ @Test
+ public void testToJSONArray_jsonOutput_null_withKeepString() {
+ final String originalXml = "01 1 00 0 null ";
+ final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\"],\"title\":\"null\"}}");
+ final JSONObject actualJsonOutput = XML.toJSONObject(originalXml,
+ new XMLParserConfiguration().withKeepStrings(true));
+ Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);
+ }
+
+ /**
+ * Test keepStrings behavior when setting keepBooleanAsString, keepNumberAsString
+ */
+ @Test
+ public void test_keepStringBehavior() {
+ XMLParserConfiguration xpc = new XMLParserConfiguration().withKeepStrings(true);
+ assertEquals(xpc.isKeepStrings(), true);
+
+ xpc = xpc.withKeepBooleanAsString(true);
+ xpc = xpc.withKeepNumberAsString(false);
+ assertEquals(xpc.isKeepStrings(), false);
+
+ xpc = xpc.withKeepBooleanAsString(false);
+ xpc = xpc.withKeepNumberAsString(true);
+ assertEquals(xpc.isKeepStrings(), false);
+
+ xpc = xpc.withKeepBooleanAsString(true);
+ xpc = xpc.withKeepNumberAsString(true);
+ assertEquals(xpc.isKeepStrings(), true);
+
+ xpc = xpc.withKeepBooleanAsString(false);
+ xpc = xpc.withKeepNumberAsString(false);
+ assertEquals(xpc.isKeepStrings(), false);
+ }
+
/**
* JSON string cannot be reverted to original xml.
*/
@Test
public void testToJSONArray_reversibility() {
final String originalXml = "01 1 00 0 True ";
- XMLParserConfiguration config = new XMLParserConfiguration(false);
+ XMLParserConfiguration config = new XMLParserConfiguration().withKeepStrings(false);
final String revertedXml =
XML.toString(XML.toJSONObject(originalXml, config),
null, config);
@@ -765,7 +853,7 @@ public void testToJsonXML() {
final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\"],\"title\":\"True\"}}");
final JSONObject json = XML.toJSONObject(originalXml,
- new XMLParserConfiguration(true));
+ new XMLParserConfiguration().withKeepStrings(true));
Util.compareActualVsExpectedJsonObjects(json, expected);
final String reverseXml = XML.toString(json);
@@ -850,7 +938,9 @@ public void testConfig() {
// keep strings, use the altContent tag
XMLParserConfiguration config =
- new XMLParserConfiguration(true, "altContent");
+ new XMLParserConfiguration()
+ .withKeepStrings(true)
+ .withcDataTagName("altContent");
JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
// num is parsed as a string
assertEquals(jsonObject.getJSONObject("addresses").
@@ -875,7 +965,7 @@ public void testConfig() {
Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
// use alternate content name
- config = new XMLParserConfiguration("altContent");
+ config = new XMLParserConfiguration().withcDataTagName("altContent");
jsonObject = XML.toJSONObject(xmlStr, config);
// num is parsed as a number
assertEquals(jsonObject.getJSONObject("addresses").
@@ -887,7 +977,195 @@ public void testConfig() {
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,
@@ -918,11 +1196,16 @@ private void compareReaderToJSONObject(String xmlStr, String expectedStr,
* to support XML.toJSONObject(reader)
*/
JSONObject expectedJsonObject = new JSONObject(expectedStr);
- try(Reader reader = new StringReader(xmlStr);) {
+ Reader reader = new StringReader(xmlStr);
+ try {
JSONObject jsonObject = XML.toJSONObject(reader, config);
Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
- } catch (IOException e) {
- assertTrue("IO Reader error: " +e.getMessage(), false);
+ } catch (Exception e) {
+ assertTrue("Reader error: " +e.getMessage(), false);
+ } finally {
+ try {
+ reader.close();
+ } catch (Exception e) {}
}
}
@@ -944,15 +1227,22 @@ private void compareFileToJSONObject(String xmlStr, String expectedStr) {
try {
JSONObject expectedJsonObject = new JSONObject(expectedStr);
File tempFile = this.testFolder.newFile("fileToJSONObject.xml");
- try(FileWriter fileWriter = new FileWriter(tempFile);){
+ FileWriter fileWriter = new FileWriter(tempFile);
+ try {
fileWriter.write(xmlStr);
+ } finally {
+ fileWriter.close();
}
- try(Reader reader = new FileReader(tempFile);){
+
+ Reader reader = new FileReader(tempFile);
+ try {
JSONObject jsonObject = XML.toJSONObject(reader);
Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ } finally {
+ reader.close();
}
} catch (IOException e) {
- assertTrue("file writer error: " +e.getMessage(), false);
+ 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 d59496133..2fa5daeea 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;
@@ -34,14 +14,15 @@ of this software and associated documentation files (the "Software"), to deal
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
+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.XML;
-import org.json.XMLParserConfiguration;
+import org.json.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -59,6 +40,7 @@ public class XMLTest {
@Rule
public TemporaryFolder testFolder = new TemporaryFolder();
+
/**
* JSONObject from a null XML string.
* Expects a NullPointerException
@@ -285,9 +267,9 @@ public void shouldHandleSimpleXML() {
String expectedStr =
"{\"addresses\":{\"address\":{\"street\":\"[CDATA[Baker street 5]\","+
- "\"name\":\"Joe Tester\",\"NothingHere\":\"\",TrueValue:true,\n"+
+ "\"name\":\"Joe Tester\",\"NothingHere\":\"\",\"TrueValue\":true,\n"+
"\"FalseValue\":false,\"NullValue\":null,\"PositiveValue\":42,\n"+
- "\"NegativeValue\":-23,\"DoubleValue\":-23.45,\"Nan\":-23x.45,\n"+
+ "\"NegativeValue\":-23,\"DoubleValue\":-23.45,\"Nan\":\"-23x.45\",\n"+
"\"ArrayOfNum\":\"1, 2, 3, 4.1, 5.2\"\n"+
"},\"xsi:noNamespaceSchemaLocation\":"+
"\"test.xsd\",\"xmlns:xsi\":\"http://www.w3.org/2001/"+
@@ -784,15 +766,22 @@ private void compareFileToJSONObject(String xmlStr, String expectedStr) {
try {
JSONObject expectedJsonObject = new JSONObject(expectedStr);
File tempFile = this.testFolder.newFile("fileToJSONObject.xml");
- try(FileWriter fileWriter = new FileWriter(tempFile);){
+ FileWriter fileWriter = new FileWriter(tempFile);
+ try {
fileWriter.write(xmlStr);
+ } finally {
+ fileWriter.close();
}
- try(Reader reader = new FileReader(tempFile);){
+
+ Reader reader = new FileReader(tempFile);
+ try {
JSONObject jsonObject = XML.toJSONObject(reader);
Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ } finally {
+ reader.close();
}
} catch (IOException e) {
- fail("file writer error: " +e.getMessage());
+ fail("Error: " +e.getMessage());
}
}
@@ -883,7 +872,11 @@ public void testToJsonWithNullWhenNilConversionEnabled() {
final String originalXml = " ";
final String expectedJsonString = "{\"root\":{\"id\":null}}";
- final JSONObject json = XML.toJSONObject(originalXml, new XMLParserConfiguration(false, "content", true));
+ final JSONObject json = XML.toJSONObject(originalXml,
+ new XMLParserConfiguration()
+ .withKeepStrings(false)
+ .withcDataTagName("content")
+ .withConvertNilAttributeToNull(true));
assertEquals(expectedJsonString, json.toString());
}
@@ -898,4 +891,542 @@ public void testToJsonWithNullWhenNilConversionDisabled() {
final JSONObject json = XML.toJSONObject(originalXml, new XMLParserConfiguration());
assertEquals(expectedJsonString, json.toString());
}
-}
\ No newline at end of file
+
+ /**
+ * Tests to verify that supported escapes in XML are converted to actual values.
+ */
+ @Test
+ public void testIssue537CaseSensitiveHexEscapeMinimal(){
+ String xmlStr =
+ "\n"+
+ "Neutrophils.Hypersegmented | Bld-Ser-Plas ";
+ String expectedStr =
+ "{\"root\":\"Neutrophils.Hypersegmented | Bld-Ser-Plas\"}";
+ JSONObject xmlJSONObj = XML.toJSONObject(xmlStr, true);
+ JSONObject expected = new JSONObject(expectedStr);
+ Util.compareActualVsExpectedJsonObjects(xmlJSONObj, expected);
+ }
+
+ /**
+ * Tests to verify that supported escapes in XML are converted to actual values.
+ */
+ @Test
+ public void testIssue537CaseSensitiveHexEscapeFullFile(){
+ try {
+ InputStream xmlStream = null;
+ try {
+ xmlStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue537.xml");
+ Reader xmlReader = new InputStreamReader(xmlStream, Charset.forName("UTF-8"));
+ JSONObject actual = XML.toJSONObject(xmlReader, true);
+ InputStream jsonStream = null;
+ try {
+ jsonStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue537.json");
+ final JSONObject expected = new JSONObject(new JSONTokener(jsonStream));
+ Util.compareActualVsExpectedJsonObjects(actual,expected);
+ } finally {
+ if (jsonStream != null) {
+ jsonStream.close();
+ }
+ }
+ } finally {
+ if (xmlStream != null) {
+ xmlStream.close();
+ }
+ }
+ } catch (IOException e) {
+ fail("file writer error: " +e.getMessage());
+ }
+ }
+
+ /**
+ * Tests to verify that supported escapes in XML are converted to actual values.
+ */
+ @Test
+ public void testIssue537CaseSensitiveHexUnEscapeDirect(){
+ String origStr =
+ "Neutrophils.Hypersegmented | Bld-Ser-Plas";
+ String expectedStr =
+ "Neutrophils.Hypersegmented | Bld-Ser-Plas";
+ String actualStr = XML.unescape(origStr);
+
+ assertEquals("Case insensitive Entity unescape", expectedStr, actualStr);
+ }
+
+ /**
+ * test passes when xsi:type="java.lang.String" not converting to string
+ */
+ @Test
+ public void testToJsonWithTypeWhenTypeConversionDisabled() {
+ String originalXml = "1234 ";
+ String expectedJsonString = "{\"root\":{\"id\":{\"xsi:type\":\"string\",\"content\":1234}}}";
+ JSONObject expectedJson = new JSONObject(expectedJsonString);
+ JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration());
+ Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
+ }
+
+ /**
+ * test passes when xsi:type="java.lang.String" converting to String
+ */
+ @Test
+ public void testToJsonWithTypeWhenTypeConversionEnabled() {
+ String originalXml = "1234 "
+ + "1234 ";
+ String expectedJsonString = "{\"root\":{\"id2\":1234,\"id1\":\"1234\"}}";
+ JSONObject expectedJson = new JSONObject(expectedJsonString);
+ Map> xsiTypeMap = new HashMap>();
+ xsiTypeMap.put("string", new XMLXsiTypeConverter() {
+ @Override public String convert(final String value) {
+ return value;
+ }
+ });
+ xsiTypeMap.put("integer", new XMLXsiTypeConverter() {
+ @Override public Integer convert(final String value) {
+ return Integer.valueOf(value);
+ }
+ });
+ JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap));
+ Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
+ }
+
+ @Test
+ public void testToJsonWithXSITypeWhenTypeConversionEnabled() {
+ String originalXml = "12345 54321 ";
+ String expectedJsonString = "{\"root\":{\"asString\":\"12345\",\"asInt\":54321}}";
+ JSONObject expectedJson = new JSONObject(expectedJsonString);
+ Map> xsiTypeMap = new HashMap>();
+ xsiTypeMap.put("string", new XMLXsiTypeConverter() {
+ @Override public String convert(final String value) {
+ return value;
+ }
+ });
+ xsiTypeMap.put("integer", new XMLXsiTypeConverter() {
+ @Override public Integer convert(final String value) {
+ return Integer.valueOf(value);
+ }
+ });
+ JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap));
+ Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
+ }
+
+ @Test
+ public void testToJsonWithXSITypeWhenTypeConversionNotEnabledOnOne() {
+ String originalXml = "12345 54321 ";
+ String expectedJsonString = "{\"root\":{\"asString\":\"12345\",\"asInt\":54321}}";
+ JSONObject expectedJson = new JSONObject(expectedJsonString);
+ Map> xsiTypeMap = new HashMap>();
+ xsiTypeMap.put("string", new XMLXsiTypeConverter() {
+ @Override public String convert(final String value) {
+ return value;
+ }
+ });
+ JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap));
+ Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
+ }
+
+ @Test
+ public void testXSITypeMapNotModifiable() {
+ Map> xsiTypeMap = new HashMap>();
+ XMLParserConfiguration config = new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap);
+ xsiTypeMap.put("string", new XMLXsiTypeConverter() {
+ @Override public String convert(final String value) {
+ return value;
+ }
+ });
+ assertEquals("Config Conversion Map size is expected to be 0", 0, config.getXsiTypeMap().size());
+
+ try {
+ config.getXsiTypeMap().put("boolean", new XMLXsiTypeConverter() {
+ @Override public Boolean convert(final String value) {
+ return Boolean.valueOf(value);
+ }
+ });
+ 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/XMLTokenerTest.java b/src/test/java/org/json/junit/XMLTokenerTest.java
new file mode 100644
index 000000000..ca2f2075e
--- /dev/null
+++ b/src/test/java/org/json/junit/XMLTokenerTest.java
@@ -0,0 +1,81 @@
+package org.json.junit;
+
+import org.json.XMLTokener;
+import org.junit.Test;
+
+import java.io.StringReader;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for JSON-Java XMLTokener.java
+ */
+public class XMLTokenerTest {
+
+ /**
+ * Tests that nextCDATA() correctly extracts content from within a CDATA section.
+ */
+ @Test
+ public void testNextCDATA() {
+ String xml = "This is content ]]> after";
+ XMLTokener tokener = new XMLTokener(new StringReader(xml));
+ tokener.skipPast(" content ", cdata);
+ }
+
+ /**
+ * Tests that nextContent() returns plain text content before a tag.
+ */
+ @Test
+ public void testNextContentWithText() {
+ String xml = "Some content";
+ XMLTokener tokener = new XMLTokener(xml);
+ Object content = tokener.nextContent();
+ assertEquals("Some content", content);
+ }
+
+ /**
+ * Tests that nextContent() returns '<' character when starting with a tag.
+ */
+ @Test
+ public void testNextContentWithTag() {
+ String xml = "";
+ XMLTokener tokener = new XMLTokener(xml);
+ Object content = tokener.nextContent();
+ assertEquals('<', content);
+ }
+
+ /**
+ * Tests that nextEntity() resolves a known entity like & correctly.
+ */
+ @Test
+ public void testNextEntityKnown() {
+ XMLTokener tokener = new XMLTokener("amp;");
+ Object result = tokener.nextEntity('&');
+ assertEquals("&", result);
+ }
+
+ /**
+ * Tests that nextEntity() preserves unknown entities by returning them unchanged.
+ */
+ @Test
+ public void testNextEntityUnknown() {
+ XMLTokener tokener = new XMLTokener("unknown;");
+ tokener.next(); // skip 'u'
+ Object result = tokener.nextEntity('&');
+ assertEquals("&nknown;", result); // malformed start to simulate unknown
+ }
+
+ /**
+ * Tests skipPast() to ensure the cursor moves past the specified string.
+ */
+ @Test
+ public void testSkipPast() {
+ String xml = "Ignore this... endHere more text";
+ XMLTokener tokener = new XMLTokener(xml);
+ tokener.skipPast("endHere");
+ assertEquals(' ', tokener.next()); // should be the space after "endHere"
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/json/junit/data/CustomClass.java b/src/test/java/org/json/junit/data/CustomClass.java
new file mode 100644
index 000000000..9ae405597
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClass.java
@@ -0,0 +1,23 @@
+package org.json.junit.data;
+
+public class CustomClass {
+ public int number;
+ public String name;
+ public Long longNumber;
+
+ public CustomClass() {}
+ public CustomClass (int number, String name, Long longNumber) {
+ this.number = number;
+ this.name = name;
+ this.longNumber = longNumber;
+ }
+ @Override
+ public boolean equals(Object o) {
+ CustomClass customClass = (CustomClass) o;
+
+ return (this.number == customClass.number
+ && this.name.equals(customClass.name)
+ && this.longNumber.equals(customClass.longNumber));
+ }
+}
+
diff --git a/src/test/java/org/json/junit/data/CustomClassA.java b/src/test/java/org/json/junit/data/CustomClassA.java
new file mode 100644
index 000000000..08a99d333
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassA.java
@@ -0,0 +1,19 @@
+package org.json.junit.data;
+
+import java.math.BigInteger;
+
+public class CustomClassA {
+ public BigInteger largeInt;
+
+ public CustomClassA() {}
+ public CustomClassA(BigInteger largeInt) {
+ this.largeInt = largeInt;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ CustomClassA classA = (CustomClassA) o;
+ return this.largeInt.equals(classA.largeInt);
+ }
+}
+
diff --git a/src/test/java/org/json/junit/data/CustomClassB.java b/src/test/java/org/json/junit/data/CustomClassB.java
new file mode 100644
index 000000000..688997ec4
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassB.java
@@ -0,0 +1,20 @@
+package org.json.junit.data;
+
+public class CustomClassB {
+ public int number;
+ public CustomClassC classC;
+
+ public CustomClassB() {}
+ public CustomClassB(int number, CustomClassC classC) {
+ this.number = number;
+ this.classC = classC;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ CustomClassB classB = (CustomClassB) o;
+ return this.number == classB.number
+ && this.classC.equals(classB.classC);
+ }
+}
+
diff --git a/src/test/java/org/json/junit/data/CustomClassC.java b/src/test/java/org/json/junit/data/CustomClassC.java
new file mode 100644
index 000000000..9d20aa392
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassC.java
@@ -0,0 +1,34 @@
+package org.json.junit.data;
+
+import org.json.JSONObject;
+
+public class CustomClassC {
+ public String stringName;
+ public Long longNumber;
+
+ public CustomClassC() {}
+ public CustomClassC(String stringName, Long longNumber) {
+ this.stringName = stringName;
+ this.longNumber = longNumber;
+ }
+
+ public JSONObject toJSON() {
+ JSONObject object = new JSONObject();
+ object.put("stringName", this.stringName);
+ object.put("longNumber", this.longNumber);
+ return object;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ CustomClassC classC = (CustomClassC) o;
+ return this.stringName.equals(classC.stringName)
+ && this.longNumber.equals(classC.longNumber);
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(stringName, longNumber);
+ }
+}
+
diff --git a/src/test/java/org/json/junit/data/CustomClassD.java b/src/test/java/org/json/junit/data/CustomClassD.java
new file mode 100644
index 000000000..4a858058c
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassD.java
@@ -0,0 +1,19 @@
+package org.json.junit.data;
+
+import java.util.List;
+
+public class CustomClassD {
+ public List stringList;
+
+ public CustomClassD() {}
+ public CustomClassD(List stringList) {
+ this.stringList = stringList;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ CustomClassD classD = (CustomClassD) o;
+ return this.stringList.equals(classD.stringList);
+ }
+}
+
diff --git a/src/test/java/org/json/junit/data/CustomClassE.java b/src/test/java/org/json/junit/data/CustomClassE.java
new file mode 100644
index 000000000..807dc5540
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassE.java
@@ -0,0 +1,18 @@
+package org.json.junit.data;
+
+import java.util.List;
+
+public class CustomClassE {
+ public List listClassC;
+
+ public CustomClassE() {}
+ public CustomClassE(List listClassC) {
+ this.listClassC = listClassC;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ CustomClassE classE = (CustomClassE) o;
+ return this.listClassC.equals(classE.listClassC);
+ }
+}
diff --git a/src/test/java/org/json/junit/data/CustomClassF.java b/src/test/java/org/json/junit/data/CustomClassF.java
new file mode 100644
index 000000000..d85861036
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassF.java
@@ -0,0 +1,19 @@
+package org.json.junit.data;
+
+import java.util.List;
+
+public class CustomClassF {
+ public List> listOfString;
+
+ public CustomClassF() {}
+ public CustomClassF(List> listOfString) {
+ this.listOfString = listOfString;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ CustomClassF classF = (CustomClassF) o;
+ return this.listOfString.equals(classF.listOfString);
+ }
+}
+
diff --git a/src/test/java/org/json/junit/data/CustomClassG.java b/src/test/java/org/json/junit/data/CustomClassG.java
new file mode 100644
index 000000000..c8c9f5784
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassG.java
@@ -0,0 +1,18 @@
+package org.json.junit.data;
+
+import java.util.Map;
+
+public class CustomClassG {
+ public Map dataList;
+
+ public CustomClassG () {}
+ public CustomClassG (Map dataList) {
+ this.dataList = dataList;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ CustomClassG classG = (CustomClassG) object;
+ return this.dataList.equals(classG.dataList);
+ }
+}
diff --git a/src/test/java/org/json/junit/data/CustomClassH.java b/src/test/java/org/json/junit/data/CustomClassH.java
new file mode 100644
index 000000000..ce9b1af23
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassH.java
@@ -0,0 +1,22 @@
+package org.json.junit.data;
+
+import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
+
+public class CustomClassH {
+ public Map> integerMap;
+
+ public CustomClassH() {}
+ public CustomClassH(Map> integerMap) {
+ this.integerMap = integerMap;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ CustomClassH classH = (CustomClassH) object;
+ return this.integerMap.size() == classH.integerMap.size()
+ && this.integerMap.keySet().equals(classH.integerMap.keySet())
+ && new ArrayList<>(this.integerMap.values()).equals(new ArrayList<>(classH.integerMap.values()));
+ }
+}
diff --git a/src/test/java/org/json/junit/data/CustomClassI.java b/src/test/java/org/json/junit/data/CustomClassI.java
new file mode 100644
index 000000000..bd7c4ed89
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassI.java
@@ -0,0 +1,12 @@
+package org.json.junit.data;
+
+import java.util.Map;
+
+public class CustomClassI {
+ public Map> integerMap;
+
+ public CustomClassI() {}
+ public CustomClassI(Map> integerMap) {
+ this.integerMap = integerMap;
+ }
+}
diff --git a/src/test/java/org/json/junit/data/ExceptionalBean.java b/src/test/java/org/json/junit/data/ExceptionalBean.java
index 72d6c0cdb..91067b86b 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.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 da6370d48..dd46b88e6 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 @@
* @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/PersonRecord.java b/src/test/java/org/json/junit/data/PersonRecord.java
new file mode 100644
index 000000000..891f1bb9e
--- /dev/null
+++ b/src/test/java/org/json/junit/data/PersonRecord.java
@@ -0,0 +1,31 @@
+package org.json.junit.data;
+
+/**
+ * A test class that mimics Java record accessor patterns.
+ * Records use accessor methods without get/is prefixes (e.g., name() instead of getName()).
+ * This class simulates that behavior to test JSONObject's handling of such methods.
+ */
+public class PersonRecord {
+ private final String name;
+ private final int age;
+ private final boolean active;
+
+ public PersonRecord(String name, int age, boolean active) {
+ this.name = name;
+ this.age = age;
+ this.active = active;
+ }
+
+ // Record-style accessors (no "get" or "is" prefix)
+ public String name() {
+ return name;
+ }
+
+ public int age() {
+ return age;
+ }
+
+ public boolean active() {
+ return active;
+ }
+}
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 000000000..dad6e7a65
--- /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 000000000..10166481e
--- /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/resources/Issue537.json b/src/test/resources/Issue537.json
new file mode 100644
index 000000000..b3c82feb1
--- /dev/null
+++ b/src/test/resources/Issue537.json
@@ -0,0 +1,189 @@
+{
+ "clinical_study": {
+ "brief_summary": {
+ "textblock": "CLEAR SYNERGY is an international multi center 2x2 randomized placebo controlled trial of"
+ },
+ "brief_title": "CLEAR SYNERGY Neutrophil Substudy",
+ "overall_status": "Recruiting",
+ "eligibility": {
+ "study_pop": {
+ "textblock": "Patients who are randomized to the drug RCT portion of the CLEAR SYNERGY (OASIS 9) trial"
+ },
+ "minimum_age": "19 Years",
+ "sampling_method": "Non-Probability Sample",
+ "gender": "All",
+ "criteria": {
+ "textblock": "Inclusion Criteria:"
+ },
+ "healthy_volunteers": "No",
+ "maximum_age": "110 Years"
+ },
+ "number_of_groups": "2",
+ "source": "NYU Langone Health",
+ "location_countries": {
+ "country": "United States"
+ },
+ "study_design_info": {
+ "time_perspective": "Prospective",
+ "observational_model": "Other"
+ },
+ "last_update_submitted_qc": "September 10, 2019",
+ "intervention_browse": {
+ "mesh_term": "Colchicine"
+ },
+ "official_title": "Studies on the Effects of Colchicine on Neutrophil Biology in Acute Myocardial Infarction: A Substudy of the CLEAR SYNERGY (OASIS 9) Trial",
+ "primary_completion_date": {
+ "type": "Anticipated",
+ "content": "February 1, 2021"
+ },
+ "sponsors": {
+ "lead_sponsor": {
+ "agency_class": "Other",
+ "agency": "NYU Langone Health"
+ },
+ "collaborator": [
+ {
+ "agency_class": "Other",
+ "agency": "Population Health Research Institute"
+ },
+ {
+ "agency_class": "NIH",
+ "agency": "National Heart, Lung, and Blood Institute (NHLBI)"
+ }
+ ]
+ },
+ "overall_official": {
+ "role": "Principal Investigator",
+ "affiliation": "NYU School of Medicine",
+ "last_name": "Binita Shah, MD"
+ },
+ "overall_contact_backup": {
+ "last_name": "Binita Shah, MD"
+ },
+ "condition_browse": {
+ "mesh_term": [
+ "Myocardial Infarction",
+ "ST Elevation Myocardial Infarction",
+ "Infarction"
+ ]
+ },
+ "overall_contact": {
+ "phone": "646-501-9648",
+ "last_name": "Fatmira Curovic",
+ "email": "fatmira.curovic@nyumc.org"
+ },
+ "responsible_party": {
+ "responsible_party_type": "Principal Investigator",
+ "investigator_title": "Assistant Professor of Medicine",
+ "investigator_full_name": "Binita Shah",
+ "investigator_affiliation": "NYU Langone Health"
+ },
+ "study_first_submitted_qc": "March 12, 2019",
+ "start_date": {
+ "type": "Actual",
+ "content": "March 4, 2019"
+ },
+ "has_expanded_access": "No",
+ "study_first_posted": {
+ "type": "Actual",
+ "content": "March 14, 2019"
+ },
+ "arm_group": [
+ {
+ "arm_group_label": "Colchicine"
+ },
+ {
+ "arm_group_label": "Placebo"
+ }
+ ],
+ "primary_outcome": {
+ "measure": "soluble L-selectin",
+ "time_frame": "between baseline and 3 months",
+ "description": "Change in soluble L-selectin between baseline and 3 mo after STEMI in the placebo vs. colchicine groups."
+ },
+ "secondary_outcome": [
+ {
+ "measure": "Other soluble markers of neutrophil activity",
+ "time_frame": "between baseline and 3 months",
+ "description": "Other markers of neutrophil activity will be evaluated at baseline and 3 months after STEMI (myeloperoxidase, matrix metalloproteinase-9, neutrophil gelatinase-associated lipocalin, neutrophil elastase, intercellular/vascular cellular adhesion molecules)"
+ },
+ {
+ "measure": "Markers of systemic inflammation",
+ "time_frame": "between baseline and 3 months",
+ "description": "Markers of systemic inflammation will be evaluated at baseline and 3 months after STEMI (high sensitive CRP, IL-1β)"
+ },
+ {
+ "measure": "Neutrophil-driven responses that may further propagate injury",
+ "time_frame": "between baseline and 3 months",
+ "description": "Neutrophil-driven responses that may further propagate injury will be evaluated at baseline and 3 months after STEMI (neutrophil extracellular traps, neutrophil-derived microparticles)"
+ }
+ ],
+ "oversight_info": {
+ "is_fda_regulated_drug": "No",
+ "is_fda_regulated_device": "No",
+ "has_dmc": "No"
+ },
+ "last_update_posted": {
+ "type": "Actual",
+ "content": "September 12, 2019"
+ },
+ "id_info": {
+ "nct_id": "NCT03874338",
+ "org_study_id": "18-01323",
+ "secondary_id": "1R01HL146206"
+ },
+ "enrollment": {
+ "type": "Anticipated",
+ "content": "670"
+ },
+ "study_first_submitted": "March 12, 2019",
+ "condition": [
+ "Neutrophils.Hypersegmented | Bld-Ser-Plas",
+ "STEMI - ST Elevation Myocardial Infarction"
+ ],
+ "study_type": "Observational",
+ "required_header": {
+ "download_date": "ClinicalTrials.gov processed this data on July 19, 2020",
+ "link_text": "Link to the current ClinicalTrials.gov record.",
+ "url": "https://clinicaltrials.gov/show/NCT03874338"
+ },
+ "last_update_submitted": "September 10, 2019",
+ "completion_date": {
+ "type": "Anticipated",
+ "content": "February 1, 2022"
+ },
+ "location": {
+ "contact": {
+ "phone": "646-501-9648",
+ "last_name": "Fatmira Curovic",
+ "email": "fatmira.curovic@nyumc.org"
+ },
+ "facility": {
+ "address": {
+ "zip": "10016",
+ "country": "United States",
+ "city": "New York",
+ "state": "New York"
+ },
+ "name": "NYU School of Medicine"
+ },
+ "status": "Recruiting",
+ "contact_backup": {
+ "last_name": "Binita Shah, MD"
+ }
+ },
+ "intervention": {
+ "intervention_type": "Drug",
+ "arm_group_label": [
+ "Colchicine",
+ "Placebo"
+ ],
+ "description": "Participants in the main CLEAR SYNERGY trial are randomized to colchicine/spironolactone versus placebo in a 2x2 factorial design. The substudy is interested in the evaluation of biospecimens obtained from patients in the colchicine vs placebo group.",
+ "intervention_name": "Colchicine Pill"
+ },
+ "patient_data": {
+ "sharing_ipd": "No"
+ },
+ "verification_date": "September 2019"
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/Issue537.xml b/src/test/resources/Issue537.xml
new file mode 100644
index 000000000..bf78f3b39
--- /dev/null
+++ b/src/test/resources/Issue537.xml
@@ -0,0 +1,169 @@
+
+
+
+
+ ClinicalTrials.gov processed this data on July 19, 2020
+ Link to the current ClinicalTrials.gov record.
+ https://clinicaltrials.gov/show/NCT03874338
+
+
+ 18-01323
+ 1R01HL146206
+ NCT03874338
+
+ CLEAR SYNERGY Neutrophil Substudy
+ Studies on the Effects of Colchicine on Neutrophil Biology in Acute Myocardial Infarction: A Substudy of the CLEAR SYNERGY (OASIS 9) Trial
+
+
+ NYU Langone Health
+ Other
+
+
+ Population Health Research Institute
+ Other
+
+
+ National Heart, Lung, and Blood Institute (NHLBI)
+ NIH
+
+
+ NYU Langone Health
+
+ No
+ No
+ No
+
+
+
+ CLEAR SYNERGY is an international multi center 2x2 randomized placebo controlled trial of
+
+
+ Recruiting
+ March 4, 2019
+ February 1, 2022
+ February 1, 2021
+ Observational
+ No
+
+ Other
+ Prospective
+
+
+ soluble L-selectin
+ between baseline and 3 months
+ Change in soluble L-selectin between baseline and 3 mo after STEMI in the placebo vs. colchicine groups.
+
+
+ Other soluble markers of neutrophil activity
+ between baseline and 3 months
+ Other markers of neutrophil activity will be evaluated at baseline and 3 months after STEMI (myeloperoxidase, matrix metalloproteinase-9, neutrophil gelatinase-associated lipocalin, neutrophil elastase, intercellular/vascular cellular adhesion molecules)
+
+
+ Markers of systemic inflammation
+ between baseline and 3 months
+ Markers of systemic inflammation will be evaluated at baseline and 3 months after STEMI (high sensitive CRP, IL-1β)
+
+
+ Neutrophil-driven responses that may further propagate injury
+ between baseline and 3 months
+ Neutrophil-driven responses that may further propagate injury will be evaluated at baseline and 3 months after STEMI (neutrophil extracellular traps, neutrophil-derived microparticles)
+
+ 2
+ 670
+ Neutrophils.Hypersegmented | Bld-Ser-Plas
+ STEMI - ST Elevation Myocardial Infarction
+
+ Colchicine
+
+
+ Placebo
+
+
+ Drug
+ Colchicine Pill
+ Participants in the main CLEAR SYNERGY trial are randomized to colchicine/spironolactone versus placebo in a 2x2 factorial design. The substudy is interested in the evaluation of biospecimens obtained from patients in the colchicine vs placebo group.
+ Colchicine
+ Placebo
+
+
+
+
+ Patients who are randomized to the drug RCT portion of the CLEAR SYNERGY (OASIS 9) trial
+
+
+ Non-Probability Sample
+
+
+ Inclusion Criteria:
+
+
+ All
+ 19 Years
+ 110 Years
+ No
+
+
+ Binita Shah, MD
+ Principal Investigator
+ NYU School of Medicine
+
+
+ Fatmira Curovic
+ 646-501-9648
+ fatmira.curovic@nyumc.org
+
+
+ Binita Shah, MD
+
+
+
+ NYU School of Medicine
+
+ New York
+ New York
+ 10016
+ United States
+
+
+ Recruiting
+
+ Fatmira Curovic
+ 646-501-9648
+ fatmira.curovic@nyumc.org
+
+
+ Binita Shah, MD
+
+
+
+ United States
+
+ September 2019
+ March 12, 2019
+ March 12, 2019
+ March 14, 2019
+ September 10, 2019
+ September 10, 2019
+ September 12, 2019
+
+ Principal Investigator
+ NYU Langone Health
+ Binita Shah
+ Assistant Professor of Medicine
+
+
+
+ Myocardial Infarction
+ ST Elevation Myocardial Infarction
+ Infarction
+
+
+
+ Colchicine
+
+
+ No
+
+
+
diff --git a/src/test/resources/Issue593.json b/src/test/resources/Issue593.json
new file mode 100644
index 000000000..213625af2
--- /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 000000000..0c6c038b6
--- /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 000000000..513e1b460
--- /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 000000000..70344c145
--- /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":{}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
+}}}}}}}}}}}}
diff --git a/src/test/resources/compliantJsonArray.json b/src/test/resources/compliantJsonArray.json
new file mode 100644
index 000000000..d68c99588
--- /dev/null
+++ b/src/test/resources/compliantJsonArray.json
@@ -0,0 +1,317 @@
+[
+ {
+ "_id": "6606c27d2ab4a0102d49420a",
+ "index": 0,
+ "guid": "441331fb-84d1-4873-a649-3814621a0370",
+ "isActive": true,
+ "balance": "$2,691.63",
+ "picture": "http://example.abc/32x32",
+ "age": 26,
+ "eyeColor": "blue",
+ "name": "abc",
+ "gender": "female",
+ "company": "example",
+ "email": "abc@def.com",
+ "phone": "+1 (123) 456-7890",
+ "address": "123 Main St",
+ "about": "Laborum magna tempor officia irure cillum nulla incididunt Lorem dolor veniam elit cupidatat amet. Veniam veniam exercitation nulla consectetur officia esse ex sunt nulla nisi ea cillum nisi reprehenderit. Qui aliquip reprehenderit aliqua aliquip aliquip anim sit magna nostrud dolore veniam velit elit aliquip.\r\n",
+ "registered": "2016-07-22T03:18:11 -01:00",
+ "latitude": -21.544934,
+ "longitude": 72.765495,
+ "tags": [
+ "consectetur",
+ "minim",
+ "sunt",
+ "in",
+ "ut",
+ "velit",
+ "anim"
+ ],
+ "friends": [
+ {
+ "id": 0,
+ "name": "abc def"
+ },
+ {
+ "id": 1,
+ "name": "ghi jkl"
+ },
+ {
+ "id": 2,
+ "name": "mno pqr"
+ }
+ ],
+ "greeting": "Hello, abc! You have 10 unread messages.",
+ "favoriteFruit": "banana"
+ },
+ {
+ "_id": "6606c27d0a45df5121fb765f",
+ "index": 1,
+ "guid": "fd774715-de85-44b9-b498-c214d8f68d9f",
+ "isActive": true,
+ "balance": "$2,713.96",
+ "picture": "http://placehold.it/32x32",
+ "age": 27,
+ "eyeColor": "green",
+ "name": "def",
+ "gender": "female",
+ "company": "sample",
+ "email": "def@abc.com",
+ "phone": "+1 (123) 456-78910",
+ "address": "1234 Main St",
+ "about": "Ea id cupidatat eiusmod culpa. Nulla consequat esse elit enim et pariatur eiusmod ipsum. Consequat eu non reprehenderit in.\r\n",
+ "registered": "2015-04-06T07:54:22 -01:00",
+ "latitude": 83.512347,
+ "longitude": -9.368739,
+ "tags": [
+ "excepteur",
+ "non",
+ "nostrud",
+ "laboris",
+ "laboris",
+ "qui",
+ "aute"
+ ],
+ "friends": [
+ {
+ "id": 0,
+ "name": "sample example"
+ },
+ {
+ "id": 1,
+ "name": "test name"
+ },
+ {
+ "id": 2,
+ "name": "aaa aaaa"
+ }
+ ],
+ "greeting": "Hello, test! You have 7 unread messages.",
+ "favoriteFruit": "apple"
+ },
+ {
+ "_id": "6606c27dfb3a0e4e7e7183d3",
+ "index": 2,
+ "guid": "688b0c36-98e0-4ee7-86b8-863638d79b5f",
+ "isActive": false,
+ "balance": "$3,514.35",
+ "picture": "http://placehold.it/32x32",
+ "age": 32,
+ "eyeColor": "green",
+ "name": "test",
+ "gender": "female",
+ "company": "test",
+ "email": "test@test.com",
+ "phone": "+1 (123) 456-7890",
+ "address": "123 Main St",
+ "about": "Mollit officia adipisicing ex nisi non Lorem sunt quis est. Irure exercitation duis ipsum qui ullamco eu ea commodo occaecat minim proident. Incididunt nostrud ex cupidatat eiusmod mollit anim irure culpa. Labore voluptate voluptate labore nisi sit eu. Dolor sit proident velit dolor deserunt labore sit ipsum incididunt eiusmod reprehenderit voluptate. Duis anim velit officia laboris consequat officia dolor sint dolor nisi ex.\r\n",
+ "registered": "2021-11-02T12:50:05 -00:00",
+ "latitude": -82.969939,
+ "longitude": 86.415645,
+ "tags": [
+ "aliquip",
+ "et",
+ "est",
+ "nulla",
+ "nulla",
+ "tempor",
+ "adipisicing"
+ ],
+ "friends": [
+ {
+ "id": 0,
+ "name": "test"
+ },
+ {
+ "id": 1,
+ "name": "sample"
+ },
+ {
+ "id": 2,
+ "name": "example"
+ }
+ ],
+ "greeting": "Hello, test! You have 1 unread messages.",
+ "favoriteFruit": "strawberry"
+ },
+ {
+ "_id": "6606c27d204bc2327fc9ba23",
+ "index": 3,
+ "guid": "be970cba-306e-4cbd-be08-c265a43a61fa",
+ "isActive": true,
+ "balance": "$3,691.63",
+ "picture": "http://placehold.it/32x32",
+ "age": 35,
+ "eyeColor": "brown",
+ "name": "another test",
+ "gender": "male",
+ "company": "TEST",
+ "email": "anothertest@anothertest.com",
+ "phone": "+1 (321) 987-6543",
+ "address": "123 Example Main St",
+ "about": "Do proident consectetur minim quis. In adipisicing culpa Lorem fugiat cillum exercitation velit velit. Non voluptate laboris deserunt veniam et sint consectetur irure aliqua quis eiusmod consectetur elit id. Ex sint do anim Lorem excepteur eu nulla.\r\n",
+ "registered": "2020-06-25T04:55:25 -01:00",
+ "latitude": 63.614955,
+ "longitude": -109.299405,
+ "tags": [
+ "irure",
+ "esse",
+ "non",
+ "mollit",
+ "laborum",
+ "adipisicing",
+ "ad"
+ ],
+ "friends": [
+ {
+ "id": 0,
+ "name": "test"
+ },
+ {
+ "id": 1,
+ "name": "sample"
+ },
+ {
+ "id": 2,
+ "name": "example"
+ }
+ ],
+ "greeting": "Hello, another test! You have 5 unread messages.",
+ "favoriteFruit": "apple"
+ },
+ {
+ "_id": "6606c27df63eb5f390cb9989",
+ "index": 4,
+ "guid": "2c3e5115-758d-468e-99c5-c9afa26e1f9f",
+ "isActive": true,
+ "balance": "$1,047.20",
+ "picture": "http://test.it/32x32",
+ "age": 30,
+ "eyeColor": "green",
+ "name": "Test Name",
+ "gender": "female",
+ "company": "test",
+ "email": "testname@testname.com",
+ "phone": "+1 (999) 999-9999",
+ "address": "999 Test Main St",
+ "about": "Voluptate exercitation tempor consectetur velit magna ea occaecat cupidatat consectetur anim aute. Aliquip est aute ipsum laboris non irure qui consectetur tempor quis do ea Lorem. Cupidatat exercitation ad culpa aliqua amet commodo mollit reprehenderit exercitation adipisicing amet et laborum pariatur.\r\n",
+ "registered": "2023-01-19T02:43:18 -00:00",
+ "latitude": 14.15208,
+ "longitude": 170.411535,
+ "tags": [
+ "dolor",
+ "qui",
+ "cupidatat",
+ "aliqua",
+ "laboris",
+ "reprehenderit",
+ "sint"
+ ],
+ "friends": [
+ {
+ "id": 0,
+ "name": "test"
+ },
+ {
+ "id": 1,
+ "name": "sample"
+ },
+ {
+ "id": 2,
+ "name": "example"
+ }
+ ],
+ "greeting": "Hello, test! You have 6 unread messages.",
+ "favoriteFruit": "apple"
+ },
+ {
+ "_id": "6606c27d01d19fa29853d59c",
+ "index": 5,
+ "guid": "816cda74-5d4b-498f-9724-20f340d5f5bf",
+ "isActive": false,
+ "balance": "$2,628.74",
+ "picture": "http://testing.it/32x32",
+ "age": 28,
+ "eyeColor": "green",
+ "name": "Testing",
+ "gender": "female",
+ "company": "test",
+ "email": "testing@testing.com",
+ "phone": "+1 (888) 888-8888",
+ "address": "123 Main St",
+ "about": "Cupidatat non ut nulla qui excepteur in minim non et nulla fugiat. Dolor quis laborum occaecat veniam dolor ullamco deserunt amet veniam dolor quis proident tempor laboris. In cillum duis ut quis. Aliqua cupidatat magna proident velit tempor veniam et consequat laborum ex dolore qui. Incididunt deserunt magna minim Lorem consectetur.\r\n",
+ "registered": "2017-10-14T11:14:08 -01:00",
+ "latitude": -5.345728,
+ "longitude": -9.706491,
+ "tags": [
+ "officia",
+ "velit",
+ "laboris",
+ "qui",
+ "cupidatat",
+ "cupidatat",
+ "ad"
+ ],
+ "friends": [
+ {
+ "id": 0,
+ "name": "test"
+ },
+ {
+ "id": 1,
+ "name": "sample"
+ },
+ {
+ "id": 2,
+ "name": "example"
+ }
+ ],
+ "greeting": "Hello, testing! You have 2 unread messages.",
+ "favoriteFruit": "strawberry"
+ },
+ {
+ "_id": "6606c27d803003cede1d6deb",
+ "index": 6,
+ "guid": "4ee550bc-0920-4104-b3ce-ebf9db6a803f",
+ "isActive": true,
+ "balance": "$1,709.31",
+ "picture": "http://sample.it/32x32",
+ "age": 31,
+ "eyeColor": "blue",
+ "name": "Sample Name",
+ "gender": "female",
+ "company": "Sample",
+ "email": "sample@sample.com",
+ "phone": "+1 (777) 777-7777",
+ "address": "123 Main St",
+ "about": "Lorem ex proident ipsum ullamco velit sit nisi eiusmod cillum. Id tempor irure culpa nisi sit non qui veniam non ut. Aliquip reprehenderit excepteur mollit quis excepteur ex sit. Quis do eu veniam do ullamco occaecat eu cupidatat nisi laborum tempor minim fugiat pariatur. Ex in nulla ex velit.\r\n",
+ "registered": "2019-04-08T03:54:36 -01:00",
+ "latitude": -70.660321,
+ "longitude": 71.547525,
+ "tags": [
+ "consequat",
+ "veniam",
+ "pariatur",
+ "aliqua",
+ "cillum",
+ "eu",
+ "officia"
+ ],
+ "friends": [
+ {
+ "id": 0,
+ "name": "Test"
+ },
+ {
+ "id": 1,
+ "name": "Sample"
+ },
+ {
+ "id": 2,
+ "name": "Example"
+ }
+ ],
+ "greeting": "Hello, Sample! You have 6 unread messages.",
+ "favoriteFruit": "apple"
+ }
+]
diff --git a/src/test/resources/compliantJsonObject.json b/src/test/resources/compliantJsonObject.json
new file mode 100644
index 000000000..cb2918d37
--- /dev/null
+++ b/src/test/resources/compliantJsonObject.json
@@ -0,0 +1,3703 @@
+{
+ "a0": [
+ {
+ "id": 0,
+ "name": "Elijah",
+ "city": "Austin",
+ "age": 78,
+ "friends": [
+ {
+ "name": "Michelle",
+ "hobbies": [
+ "Watching Sports",
+ "Reading",
+ "Skiing & Snowboarding"
+ ]
+ },
+ {
+ "name": "Robert",
+ "hobbies": [
+ "Traveling",
+ "Video Games"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 1,
+ "name": "Noah",
+ "city": "Boston",
+ "age": 97,
+ "friends": [
+ {
+ "name": "Oliver",
+ "hobbies": [
+ "Watching Sports",
+ "Skiing & Snowboarding",
+ "Collecting"
+ ]
+ },
+ {
+ "name": "Olivia",
+ "hobbies": [
+ "Running",
+ "Music",
+ "Woodworking"
+ ]
+ },
+ {
+ "name": "Robert",
+ "hobbies": [
+ "Woodworking",
+ "Calligraphy",
+ "Genealogy"
+ ]
+ },
+ {
+ "name": "Ava",
+ "hobbies": [
+ "Walking",
+ "Church Activities"
+ ]
+ },
+ {
+ "name": "Michael",
+ "hobbies": [
+ "Music",
+ "Church Activities"
+ ]
+ },
+ {
+ "name": "Michael",
+ "hobbies": [
+ "Martial Arts",
+ "Painting",
+ "Jewelry Making"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 2,
+ "name": "Evy",
+ "city": "San Diego",
+ "age": 48,
+ "friends": [
+ {
+ "name": "Joe",
+ "hobbies": [
+ "Reading",
+ "Volunteer Work"
+ ]
+ },
+ {
+ "name": "Joe",
+ "hobbies": [
+ "Genealogy",
+ "Golf"
+ ]
+ },
+ {
+ "name": "Oliver",
+ "hobbies": [
+ "Collecting",
+ "Writing",
+ "Bicycling"
+ ]
+ },
+ {
+ "name": "Liam",
+ "hobbies": [
+ "Church Activities",
+ "Jewelry Making"
+ ]
+ },
+ {
+ "name": "Amelia",
+ "hobbies": [
+ "Calligraphy",
+ "Dancing"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 3,
+ "name": "Oliver",
+ "city": "St. Louis",
+ "age": 39,
+ "friends": [
+ {
+ "name": "Mateo",
+ "hobbies": [
+ "Watching Sports",
+ "Gardening"
+ ]
+ },
+ {
+ "name": "Nora",
+ "hobbies": [
+ "Traveling",
+ "Team Sports"
+ ]
+ },
+ {
+ "name": "Ava",
+ "hobbies": [
+ "Church Activities",
+ "Running"
+ ]
+ },
+ {
+ "name": "Amelia",
+ "hobbies": [
+ "Gardening",
+ "Board Games",
+ "Watching Sports"
+ ]
+ },
+ {
+ "name": "Leo",
+ "hobbies": [
+ "Martial Arts",
+ "Video Games",
+ "Reading"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 4,
+ "name": "Michael",
+ "city": "St. Louis",
+ "age": 95,
+ "friends": [
+ {
+ "name": "Mateo",
+ "hobbies": [
+ "Movie Watching",
+ "Collecting"
+ ]
+ },
+ {
+ "name": "Chris",
+ "hobbies": [
+ "Housework",
+ "Bicycling",
+ "Collecting"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 5,
+ "name": "Michael",
+ "city": "Portland",
+ "age": 19,
+ "friends": [
+ {
+ "name": "Jack",
+ "hobbies": [
+ "Painting",
+ "Television"
+ ]
+ },
+ {
+ "name": "Oliver",
+ "hobbies": [
+ "Walking",
+ "Watching Sports",
+ "Movie Watching"
+ ]
+ },
+ {
+ "name": "Charlotte",
+ "hobbies": [
+ "Podcasts",
+ "Jewelry Making"
+ ]
+ },
+ {
+ "name": "Elijah",
+ "hobbies": [
+ "Eating Out",
+ "Painting"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 6,
+ "name": "Lucas",
+ "city": "Austin",
+ "age": 76,
+ "friends": [
+ {
+ "name": "John",
+ "hobbies": [
+ "Genealogy",
+ "Cooking"
+ ]
+ },
+ {
+ "name": "John",
+ "hobbies": [
+ "Socializing",
+ "Yoga"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 7,
+ "name": "Michelle",
+ "city": "San Antonio",
+ "age": 25,
+ "friends": [
+ {
+ "name": "Jack",
+ "hobbies": [
+ "Music",
+ "Golf"
+ ]
+ },
+ {
+ "name": "Daniel",
+ "hobbies": [
+ "Socializing",
+ "Housework",
+ "Walking"
+ ]
+ },
+ {
+ "name": "Robert",
+ "hobbies": [
+ "Collecting",
+ "Walking"
+ ]
+ },
+ {
+ "name": "Nora",
+ "hobbies": [
+ "Painting",
+ "Church Activities"
+ ]
+ },
+ {
+ "name": "Mia",
+ "hobbies": [
+ "Running",
+ "Painting"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 8,
+ "name": "Emily",
+ "city": "Austin",
+ "age": 61,
+ "friends": [
+ {
+ "name": "Nora",
+ "hobbies": [
+ "Bicycling",
+ "Skiing & Snowboarding",
+ "Watching Sports"
+ ]
+ },
+ {
+ "name": "Ava",
+ "hobbies": [
+ "Writing",
+ "Reading",
+ "Collecting"
+ ]
+ },
+ {
+ "name": "Amelia",
+ "hobbies": [
+ "Eating Out",
+ "Watching Sports"
+ ]
+ },
+ {
+ "name": "Daniel",
+ "hobbies": [
+ "Skiing & Snowboarding",
+ "Martial Arts",
+ "Writing"
+ ]
+ },
+ {
+ "name": "Zoey",
+ "hobbies": [
+ "Board Games",
+ "Tennis"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 9,
+ "name": "Liam",
+ "city": "New Orleans",
+ "age": 33,
+ "friends": [
+ {
+ "name": "Chloe",
+ "hobbies": [
+ "Traveling",
+ "Bicycling",
+ "Shopping"
+ ]
+ },
+ {
+ "name": "Evy",
+ "hobbies": [
+ "Eating Out",
+ "Watching Sports"
+ ]
+ },
+ {
+ "name": "Grace",
+ "hobbies": [
+ "Jewelry Making",
+ "Yoga",
+ "Podcasts"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 10,
+ "name": "Levi",
+ "city": "New Orleans",
+ "age": 59,
+ "friends": [
+ {
+ "name": "Noah",
+ "hobbies": [
+ "Video Games",
+ "Fishing",
+ "Shopping"
+ ]
+ },
+ {
+ "name": "Sarah",
+ "hobbies": [
+ "Woodworking",
+ "Music",
+ "Reading"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 11,
+ "name": "Lucas",
+ "city": "Portland",
+ "age": 82,
+ "friends": [
+ {
+ "name": "Luke",
+ "hobbies": [
+ "Jewelry Making",
+ "Yoga"
+ ]
+ },
+ {
+ "name": "Noah",
+ "hobbies": [
+ "Fishing",
+ "Movie Watching"
+ ]
+ },
+ {
+ "name": "Evy",
+ "hobbies": [
+ "Gardening",
+ "Church Activities",
+ "Fishing"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 12,
+ "name": "Kevin",
+ "city": "Charleston",
+ "age": 82,
+ "friends": [
+ {
+ "name": "Oliver",
+ "hobbies": [
+ "Eating Out"
+ ]
+ },
+ {
+ "name": "Michael",
+ "hobbies": [
+ "Fishing",
+ "Writing"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 13,
+ "name": "Olivia",
+ "city": "San Antonio",
+ "age": 34,
+ "friends": [
+ {
+ "name": "Daniel",
+ "hobbies": [
+ "Yoga",
+ "Traveling",
+ "Movie Watching"
+ ]
+ },
+ {
+ "name": "Zoey",
+ "hobbies": [
+ "Team Sports",
+ "Writing"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 14,
+ "name": "Robert",
+ "city": "Los Angeles",
+ "age": 49,
+ "friends": [
+ {
+ "name": "Michelle",
+ "hobbies": [
+ "Yoga",
+ "Television"
+ ]
+ },
+ {
+ "name": "Daniel",
+ "hobbies": [
+ "Fishing",
+ "Martial Arts"
+ ]
+ },
+ {
+ "name": "Chloe",
+ "hobbies": [
+ "Church Activities",
+ "Television"
+ ]
+ },
+ {
+ "name": "Sarah",
+ "hobbies": [
+ "Movie Watching",
+ "Playing Cards"
+ ]
+ },
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Golf",
+ "Running",
+ "Cooking"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 15,
+ "name": "Grace",
+ "city": "Chicago",
+ "age": 98,
+ "friends": [
+ {
+ "name": "Joe",
+ "hobbies": [
+ "Traveling",
+ "Genealogy"
+ ]
+ },
+ {
+ "name": "Mateo",
+ "hobbies": [
+ "Golf",
+ "Podcasts"
+ ]
+ },
+ {
+ "name": "Mateo",
+ "hobbies": [
+ "Reading",
+ "Cooking"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 16,
+ "name": "Michael",
+ "city": "New Orleans",
+ "age": 78,
+ "friends": [
+ {
+ "name": "Amelia",
+ "hobbies": [
+ "Running",
+ "Housework",
+ "Gardening"
+ ]
+ },
+ {
+ "name": "Michael",
+ "hobbies": [
+ "Writing",
+ "Golf"
+ ]
+ },
+ {
+ "name": "Leo",
+ "hobbies": [
+ "Running",
+ "Church Activities"
+ ]
+ },
+ {
+ "name": "Elijah",
+ "hobbies": [
+ "Volunteer Work",
+ "Eating Out"
+ ]
+ },
+ {
+ "name": "Mateo",
+ "hobbies": [
+ "Socializing",
+ "Watching Sports",
+ "Collecting"
+ ]
+ },
+ {
+ "name": "Levi",
+ "hobbies": [
+ "Eating Out",
+ "Walking"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 17,
+ "name": "Mateo",
+ "city": "Palm Springs",
+ "age": 19,
+ "friends": [
+ {
+ "name": "Emily",
+ "hobbies": [
+ "Playing Cards",
+ "Walking"
+ ]
+ },
+ {
+ "name": "Sophie",
+ "hobbies": [
+ "Gardening",
+ "Board Games",
+ "Volunteer Work"
+ ]
+ },
+ {
+ "name": "Camila",
+ "hobbies": [
+ "Board Games",
+ "Dancing"
+ ]
+ },
+ {
+ "name": "John",
+ "hobbies": [
+ "Golf",
+ "Playing Cards",
+ "Music"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 18,
+ "name": "Levi",
+ "city": "Chicago",
+ "age": 38,
+ "friends": [
+ {
+ "name": "Emma",
+ "hobbies": [
+ "Tennis",
+ "Eating Out"
+ ]
+ },
+ {
+ "name": "Emma",
+ "hobbies": [
+ "Writing",
+ "Reading",
+ "Eating Out"
+ ]
+ },
+ {
+ "name": "Daniel",
+ "hobbies": [
+ "Collecting",
+ "Video Games"
+ ]
+ },
+ {
+ "name": "Evy",
+ "hobbies": [
+ "Shopping",
+ "Walking"
+ ]
+ },
+ {
+ "name": "Sophie",
+ "hobbies": [
+ "Dancing",
+ "Volunteer Work"
+ ]
+ },
+ {
+ "name": "Mia",
+ "hobbies": [
+ "Podcasts",
+ "Woodworking",
+ "Martial Arts"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 19,
+ "name": "Luke",
+ "city": "New York City",
+ "age": 49,
+ "friends": [
+ {
+ "name": "Leo",
+ "hobbies": [
+ "Writing",
+ "Playing Cards",
+ "Housework"
+ ]
+ },
+ {
+ "name": "Emma",
+ "hobbies": [
+ "Gardening",
+ "Running"
+ ]
+ },
+ {
+ "name": "Sarah",
+ "hobbies": [
+ "Golf",
+ "Music"
+ ]
+ },
+ {
+ "name": "Jack",
+ "hobbies": [
+ "Board Games",
+ "Socializing",
+ "Writing"
+ ]
+ },
+ {
+ "name": "Jack",
+ "hobbies": [
+ "Movie Watching",
+ "Writing",
+ "Fishing"
+ ]
+ },
+ {
+ "name": "Michael",
+ "hobbies": [
+ "Golf",
+ "Jewelry Making",
+ "Yoga"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 20,
+ "name": "Camila",
+ "city": "New Orleans",
+ "age": 69,
+ "friends": [
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Video Games",
+ "Collecting",
+ "Painting"
+ ]
+ },
+ {
+ "name": "Grace",
+ "hobbies": [
+ "Reading",
+ "Volunteer Work"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 21,
+ "name": "Amelia",
+ "city": "Charleston",
+ "age": 70,
+ "friends": [
+ {
+ "name": "John",
+ "hobbies": [
+ "Quilting",
+ "Volunteer Work"
+ ]
+ },
+ {
+ "name": "Leo",
+ "hobbies": [
+ "Painting",
+ "Podcasts"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 22,
+ "name": "Victoria",
+ "city": "Miami Beach",
+ "age": 50,
+ "friends": [
+ {
+ "name": "Mia",
+ "hobbies": [
+ "Cooking",
+ "Team Sports"
+ ]
+ },
+ {
+ "name": "Lucas",
+ "hobbies": [
+ "Team Sports",
+ "Genealogy"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 23,
+ "name": "Kevin",
+ "city": "Miami Beach",
+ "age": 93,
+ "friends": [
+ {
+ "name": "Jack",
+ "hobbies": [
+ "Bicycling",
+ "Fishing"
+ ]
+ },
+ {
+ "name": "Sophie",
+ "hobbies": [
+ "Martial Arts",
+ "Genealogy",
+ "Tennis"
+ ]
+ },
+ {
+ "name": "Chloe",
+ "hobbies": [
+ "Yoga"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 24,
+ "name": "Daniel",
+ "city": "Saint Augustine",
+ "age": 43,
+ "friends": [
+ {
+ "name": "Sarah",
+ "hobbies": [
+ "Calligraphy",
+ "Martial Arts",
+ "Music"
+ ]
+ },
+ {
+ "name": "Evy",
+ "hobbies": [
+ "Walking",
+ "Bicycling"
+ ]
+ },
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Collecting",
+ "Golf"
+ ]
+ },
+ {
+ "name": "Mia",
+ "hobbies": [
+ "Podcasts",
+ "Walking"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 25,
+ "name": "Olivia",
+ "city": "Austin",
+ "age": 46,
+ "friends": [
+ {
+ "name": "Mia",
+ "hobbies": [
+ "Podcasts",
+ "Housework"
+ ]
+ },
+ {
+ "name": "Robert",
+ "hobbies": [
+ "Golf",
+ "Volunteer Work"
+ ]
+ },
+ {
+ "name": "Michelle",
+ "hobbies": [
+ "Eating Out",
+ "Music"
+ ]
+ },
+ {
+ "name": "Elijah",
+ "hobbies": [
+ "Eating Out",
+ "Genealogy",
+ "Reading"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 26,
+ "name": "Michael",
+ "city": "Palm Springs",
+ "age": 62,
+ "friends": [
+ {
+ "name": "Sarah",
+ "hobbies": [
+ "Socializing",
+ "Playing Cards"
+ ]
+ },
+ {
+ "name": "Daniel",
+ "hobbies": [
+ "Playing Cards",
+ "Shopping",
+ "Collecting"
+ ]
+ },
+ {
+ "name": "Chloe",
+ "hobbies": [
+ "Music",
+ "Movie Watching"
+ ]
+ },
+ {
+ "name": "Zoey",
+ "hobbies": [
+ "Volunteer Work",
+ "Calligraphy",
+ "Jewelry Making"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 27,
+ "name": "Kevin",
+ "city": "San Antonio",
+ "age": 97,
+ "friends": [
+ {
+ "name": "Sarah",
+ "hobbies": [
+ "Television",
+ "Quilting",
+ "Team Sports"
+ ]
+ },
+ {
+ "name": "Oliver",
+ "hobbies": [
+ "Shopping",
+ "Martial Arts"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 28,
+ "name": "Oliver",
+ "city": "Honolulu",
+ "age": 79,
+ "friends": [
+ {
+ "name": "Charlotte",
+ "hobbies": [
+ "Housework",
+ "Jewelry Making"
+ ]
+ },
+ {
+ "name": "Isabella",
+ "hobbies": [
+ "Volunteer Work",
+ "Movie Watching"
+ ]
+ },
+ {
+ "name": "Ava",
+ "hobbies": [
+ "Traveling",
+ "Bicycling"
+ ]
+ },
+ {
+ "name": "Chris",
+ "hobbies": [
+ "Shopping",
+ "Church Activities",
+ "Dancing"
+ ]
+ },
+ {
+ "name": "Sarah",
+ "hobbies": [
+ "Reading",
+ "Movie Watching"
+ ]
+ },
+ {
+ "name": "Daniel",
+ "hobbies": [
+ "Socializing",
+ "Collecting",
+ "Cooking"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 29,
+ "name": "Levi",
+ "city": "Miami Beach",
+ "age": 46,
+ "friends": [
+ {
+ "name": "Ava",
+ "hobbies": [
+ "Housework",
+ "Video Games",
+ "Walking"
+ ]
+ },
+ {
+ "name": "Elijah",
+ "hobbies": [
+ "Golf",
+ "Volunteer Work",
+ "Painting"
+ ]
+ },
+ {
+ "name": "Lucas",
+ "hobbies": [
+ "Writing",
+ "Martial Arts",
+ "Television"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 30,
+ "name": "Michael",
+ "city": "Seattle",
+ "age": 18,
+ "friends": [
+ {
+ "name": "Oliver",
+ "hobbies": [
+ "Shopping",
+ "Woodworking"
+ ]
+ },
+ {
+ "name": "Emma",
+ "hobbies": [
+ "Yoga",
+ "Genealogy",
+ "Traveling"
+ ]
+ },
+ {
+ "name": "Evy",
+ "hobbies": [
+ "Eating Out",
+ "Church Activities",
+ "Calligraphy"
+ ]
+ },
+ {
+ "name": "Noah",
+ "hobbies": [
+ "Board Games",
+ "Television"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 31,
+ "name": "Isabella",
+ "city": "Savannah",
+ "age": 65,
+ "friends": [
+ {
+ "name": "Jack",
+ "hobbies": [
+ "Church Activities",
+ "Housework",
+ "Martial Arts"
+ ]
+ },
+ {
+ "name": "Oliver",
+ "hobbies": [
+ "Calligraphy",
+ "Cooking"
+ ]
+ },
+ {
+ "name": "Grace",
+ "hobbies": [
+ "Volunteer Work",
+ "Podcasts",
+ "Tennis"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 32,
+ "name": "Chris",
+ "city": "Las Vegas",
+ "age": 31,
+ "friends": [
+ {
+ "name": "Levi",
+ "hobbies": [
+ "Shopping",
+ "Fishing"
+ ]
+ },
+ {
+ "name": "Nora",
+ "hobbies": [
+ "Dancing",
+ "Quilting"
+ ]
+ },
+ {
+ "name": "Levi",
+ "hobbies": [
+ "Reading",
+ "Eating Out"
+ ]
+ },
+ {
+ "name": "Zoey",
+ "hobbies": [
+ "Traveling",
+ "Golf",
+ "Genealogy"
+ ]
+ },
+ {
+ "name": "Mia",
+ "hobbies": [
+ "Video Games",
+ "Shopping",
+ "Walking"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 33,
+ "name": "Kevin",
+ "city": "Portland",
+ "age": 51,
+ "friends": [
+ {
+ "name": "Olivia",
+ "hobbies": [
+ "Running",
+ "Calligraphy"
+ ]
+ },
+ {
+ "name": "Amelia",
+ "hobbies": [
+ "Tennis",
+ "Genealogy"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 34,
+ "name": "Sophie",
+ "city": "New York City",
+ "age": 25,
+ "friends": [
+ {
+ "name": "Levi",
+ "hobbies": [
+ "Video Games",
+ "Board Games",
+ "Podcasts"
+ ]
+ },
+ {
+ "name": "Joe",
+ "hobbies": [
+ "Calligraphy",
+ "Video Games",
+ "Martial Arts"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 35,
+ "name": "John",
+ "city": "Orlando",
+ "age": 67,
+ "friends": [
+ {
+ "name": "Nora",
+ "hobbies": [
+ "Podcasts",
+ "Skiing & Snowboarding",
+ "Quilting"
+ ]
+ },
+ {
+ "name": "Jack",
+ "hobbies": [
+ "Tennis",
+ "Socializing",
+ "Music"
+ ]
+ },
+ {
+ "name": "Camila",
+ "hobbies": [
+ "Walking",
+ "Church Activities",
+ "Quilting"
+ ]
+ },
+ {
+ "name": "Grace",
+ "hobbies": [
+ "Team Sports",
+ "Cooking"
+ ]
+ },
+ {
+ "name": "Isabella",
+ "hobbies": [
+ "Skiing & Snowboarding",
+ "Dancing",
+ "Painting"
+ ]
+ },
+ {
+ "name": "Ava",
+ "hobbies": [
+ "Tennis",
+ "Bicycling"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 36,
+ "name": "Emily",
+ "city": "New York City",
+ "age": 82,
+ "friends": [
+ {
+ "name": "Emma",
+ "hobbies": [
+ "Church Activities",
+ "Writing"
+ ]
+ },
+ {
+ "name": "Luke",
+ "hobbies": [
+ "Running",
+ "Calligraphy",
+ "Tennis"
+ ]
+ },
+ {
+ "name": "Robert",
+ "hobbies": [
+ "Dancing",
+ "Socializing"
+ ]
+ },
+ {
+ "name": "Amelia",
+ "hobbies": [
+ "Genealogy",
+ "Calligraphy",
+ "Tennis"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 37,
+ "name": "Amelia",
+ "city": "New Orleans",
+ "age": 28,
+ "friends": [
+ {
+ "name": "Victoria",
+ "hobbies": [
+ "Traveling",
+ "Skiing & Snowboarding"
+ ]
+ },
+ {
+ "name": "Luke",
+ "hobbies": [
+ "Martial Arts",
+ "Cooking",
+ "Dancing"
+ ]
+ },
+ {
+ "name": "Olivia",
+ "hobbies": [
+ "Bicycling",
+ "Walking",
+ "Podcasts"
+ ]
+ },
+ {
+ "name": "Elijah",
+ "hobbies": [
+ "Traveling",
+ "Volunteer Work",
+ "Collecting"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 38,
+ "name": "Victoria",
+ "city": "Austin",
+ "age": 71,
+ "friends": [
+ {
+ "name": "Noah",
+ "hobbies": [
+ "Yoga",
+ "Shopping"
+ ]
+ },
+ {
+ "name": "Evy",
+ "hobbies": [
+ "Eating Out",
+ "Writing",
+ "Shopping"
+ ]
+ },
+ {
+ "name": "Camila",
+ "hobbies": [
+ "Volunteer Work",
+ "Team Sports"
+ ]
+ },
+ {
+ "name": "Lucas",
+ "hobbies": [
+ "Volunteer Work",
+ "Board Games",
+ "Running"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 39,
+ "name": "Mia",
+ "city": "Honolulu",
+ "age": 63,
+ "friends": [
+ {
+ "name": "Sophie",
+ "hobbies": [
+ "Volunteer Work",
+ "Housework",
+ "Bicycling"
+ ]
+ },
+ {
+ "name": "Evy",
+ "hobbies": [
+ "Woodworking",
+ "Golf"
+ ]
+ },
+ {
+ "name": "Ava",
+ "hobbies": [
+ "Martial Arts",
+ "Skiing & Snowboarding",
+ "Dancing"
+ ]
+ },
+ {
+ "name": "Joe",
+ "hobbies": [
+ "Collecting",
+ "Watching Sports"
+ ]
+ },
+ {
+ "name": "Camila",
+ "hobbies": [
+ "Television",
+ "Socializing",
+ "Skiing & Snowboarding"
+ ]
+ },
+ {
+ "name": "Robert",
+ "hobbies": [
+ "Martial Arts",
+ "Woodworking",
+ "Reading"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 40,
+ "name": "Daniel",
+ "city": "Las Vegas",
+ "age": 50,
+ "friends": [
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Bicycling",
+ "Housework"
+ ]
+ },
+ {
+ "name": "Evy",
+ "hobbies": [
+ "Woodworking",
+ "Collecting"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 41,
+ "name": "Luke",
+ "city": "Nashville",
+ "age": 84,
+ "friends": [
+ {
+ "name": "Lucas",
+ "hobbies": [
+ "Fishing",
+ "Shopping"
+ ]
+ },
+ {
+ "name": "Victoria",
+ "hobbies": [
+ "Church Activities",
+ "Martial Arts",
+ "Television"
+ ]
+ },
+ {
+ "name": "Charlotte",
+ "hobbies": [
+ "Church Activities",
+ "Walking"
+ ]
+ },
+ {
+ "name": "Evy",
+ "hobbies": [
+ "Calligraphy",
+ "Writing"
+ ]
+ },
+ {
+ "name": "Liam",
+ "hobbies": [
+ "Movie Watching",
+ "Board Games"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 42,
+ "name": "Joe",
+ "city": "Orlando",
+ "age": 28,
+ "friends": [
+ {
+ "name": "Sophie",
+ "hobbies": [
+ "Board Games",
+ "Music"
+ ]
+ },
+ {
+ "name": "Camila",
+ "hobbies": [
+ "Woodworking",
+ "Yoga",
+ "Music"
+ ]
+ },
+ {
+ "name": "Jack",
+ "hobbies": [
+ "Team Sports",
+ "Bicycling"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 43,
+ "name": "Robert",
+ "city": "Boston",
+ "age": 89,
+ "friends": [
+ {
+ "name": "Mia",
+ "hobbies": [
+ "Team Sports",
+ "Church Activities",
+ "Martial Arts"
+ ]
+ },
+ {
+ "name": "Elijah",
+ "hobbies": [
+ "Housework",
+ "Collecting"
+ ]
+ },
+ {
+ "name": "Zoey",
+ "hobbies": [
+ "Watching Sports",
+ "Golf",
+ "Podcasts"
+ ]
+ },
+ {
+ "name": "Sarah",
+ "hobbies": [
+ "Volunteer Work",
+ "Team Sports"
+ ]
+ },
+ {
+ "name": "Chloe",
+ "hobbies": [
+ "Yoga",
+ "Walking"
+ ]
+ },
+ {
+ "name": "Mateo",
+ "hobbies": [
+ "Running",
+ "Painting",
+ "Television"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 44,
+ "name": "Mateo",
+ "city": "Palm Springs",
+ "age": 75,
+ "friends": [
+ {
+ "name": "Sarah",
+ "hobbies": [
+ "Socializing",
+ "Walking",
+ "Painting"
+ ]
+ },
+ {
+ "name": "Sophie",
+ "hobbies": [
+ "Skiing & Snowboarding",
+ "Bicycling",
+ "Eating Out"
+ ]
+ },
+ {
+ "name": "Zoey",
+ "hobbies": [
+ "Podcasts",
+ "Socializing",
+ "Calligraphy"
+ ]
+ },
+ {
+ "name": "Olivia",
+ "hobbies": [
+ "Dancing",
+ "Volunteer Work"
+ ]
+ },
+ {
+ "name": "Mia",
+ "hobbies": [
+ "Watching Sports",
+ "Yoga",
+ "Martial Arts"
+ ]
+ },
+ {
+ "name": "Mateo",
+ "hobbies": [
+ "Housework",
+ "Genealogy"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 45,
+ "name": "Michelle",
+ "city": "Portland",
+ "age": 64,
+ "friends": [
+ {
+ "name": "Ava",
+ "hobbies": [
+ "Watching Sports",
+ "Shopping"
+ ]
+ },
+ {
+ "name": "John",
+ "hobbies": [
+ "Martial Arts",
+ "Video Games",
+ "Fishing"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 46,
+ "name": "Emma",
+ "city": "Portland",
+ "age": 47,
+ "friends": [
+ {
+ "name": "Zoey",
+ "hobbies": [
+ "Yoga",
+ "Music",
+ "Bicycling"
+ ]
+ },
+ {
+ "name": "Olivia",
+ "hobbies": [
+ "Traveling",
+ "Movie Watching",
+ "Gardening"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 47,
+ "name": "Elijah",
+ "city": "Chicago",
+ "age": 96,
+ "friends": [
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Video Games",
+ "Watching Sports",
+ "Eating Out"
+ ]
+ },
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Housework",
+ "Tennis",
+ "Playing Cards"
+ ]
+ },
+ {
+ "name": "Liam",
+ "hobbies": [
+ "Cooking"
+ ]
+ },
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Genealogy",
+ "Housework"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 48,
+ "name": "Elijah",
+ "city": "Seattle",
+ "age": 30,
+ "friends": [
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Socializing",
+ "Eating Out"
+ ]
+ },
+ {
+ "name": "Olivia",
+ "hobbies": [
+ "Martial Arts",
+ "Golf",
+ "Cooking"
+ ]
+ },
+ {
+ "name": "Daniel",
+ "hobbies": [
+ "Gardening",
+ "Bicycling",
+ "Television"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 49,
+ "name": "Sophie",
+ "city": "Palm Springs",
+ "age": 84,
+ "friends": [
+ {
+ "name": "Victoria",
+ "hobbies": [
+ "Podcasts",
+ "Martial Arts"
+ ]
+ },
+ {
+ "name": "Nora",
+ "hobbies": [
+ "Volunteer Work",
+ "Bicycling",
+ "Reading"
+ ]
+ },
+ {
+ "name": "Noah",
+ "hobbies": [
+ "Television",
+ "Watching Sports",
+ "Traveling"
+ ]
+ },
+ {
+ "name": "Victoria",
+ "hobbies": [
+ "Bicycling",
+ "Woodworking",
+ "Tennis"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 50,
+ "name": "Sophie",
+ "city": "Chicago",
+ "age": 52,
+ "friends": [
+ {
+ "name": "Chris",
+ "hobbies": [
+ "Collecting",
+ "Dancing",
+ "Cooking"
+ ]
+ },
+ {
+ "name": "Emma",
+ "hobbies": [
+ "Watching Sports",
+ "Dancing",
+ "Tennis"
+ ]
+ },
+ {
+ "name": "Joe",
+ "hobbies": [
+ "Board Games",
+ "Skiing & Snowboarding"
+ ]
+ },
+ {
+ "name": "Jack",
+ "hobbies": [
+ "Calligraphy",
+ "Running"
+ ]
+ },
+ {
+ "name": "John",
+ "hobbies": [
+ "Quilting",
+ "Golf",
+ "Gardening"
+ ]
+ },
+ {
+ "name": "John",
+ "hobbies": [
+ "Watching Sports",
+ "Jewelry Making"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 51,
+ "name": "Nora",
+ "city": "Lahaina",
+ "age": 79,
+ "friends": [
+ {
+ "name": "Elijah",
+ "hobbies": [
+ "Skiing & Snowboarding",
+ "Martial Arts"
+ ]
+ },
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Volunteer Work",
+ "Running",
+ "Tennis"
+ ]
+ },
+ {
+ "name": "Michael",
+ "hobbies": [
+ "Skiing & Snowboarding",
+ "Quilting",
+ "Fishing"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 52,
+ "name": "Chris",
+ "city": "Miami Beach",
+ "age": 59,
+ "friends": [
+ {
+ "name": "Amelia",
+ "hobbies": [
+ "Video Games",
+ "Traveling",
+ "Cooking"
+ ]
+ },
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Shopping",
+ "Calligraphy"
+ ]
+ },
+ {
+ "name": "Luke",
+ "hobbies": [
+ "Playing Cards",
+ "Housework"
+ ]
+ },
+ {
+ "name": "Chloe",
+ "hobbies": [
+ "Painting",
+ "Housework",
+ "Shopping"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 53,
+ "name": "Kevin",
+ "city": "Boston",
+ "age": 88,
+ "friends": [
+ {
+ "name": "Zoey",
+ "hobbies": [
+ "Dancing"
+ ]
+ },
+ {
+ "name": "Liam",
+ "hobbies": [
+ "Traveling",
+ "Martial Arts"
+ ]
+ },
+ {
+ "name": "Robert",
+ "hobbies": [
+ "Woodworking",
+ "Collecting"
+ ]
+ },
+ {
+ "name": "Elijah",
+ "hobbies": [
+ "Collecting",
+ "Running"
+ ]
+ },
+ {
+ "name": "Daniel",
+ "hobbies": [
+ "Dancing",
+ "Traveling"
+ ]
+ },
+ {
+ "name": "Emily",
+ "hobbies": [
+ "Fishing",
+ "Quilting",
+ "Team Sports"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 54,
+ "name": "Grace",
+ "city": "Miami Beach",
+ "age": 62,
+ "friends": [
+ {
+ "name": "Joe",
+ "hobbies": [
+ "Church Activities",
+ "Music"
+ ]
+ },
+ {
+ "name": "Emily",
+ "hobbies": [
+ "Genealogy",
+ "Watching Sports",
+ "Woodworking"
+ ]
+ },
+ {
+ "name": "Chris",
+ "hobbies": [
+ "Team Sports",
+ "Skiing & Snowboarding"
+ ]
+ },
+ {
+ "name": "Grace",
+ "hobbies": [
+ "Yoga",
+ "Music",
+ "Running"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 55,
+ "name": "Chloe",
+ "city": "Lahaina",
+ "age": 97,
+ "friends": [
+ {
+ "name": "Emily",
+ "hobbies": [
+ "Genealogy",
+ "Team Sports",
+ "Skiing & Snowboarding"
+ ]
+ },
+ {
+ "name": "Joe",
+ "hobbies": [
+ "Movie Watching",
+ "Television"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 56,
+ "name": "Zoey",
+ "city": "Saint Augustine",
+ "age": 75,
+ "friends": [
+ {
+ "name": "Luke",
+ "hobbies": [
+ "Bicycling",
+ "Martial Arts"
+ ]
+ },
+ {
+ "name": "Emma",
+ "hobbies": [
+ "Music",
+ "Cooking"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 57,
+ "name": "Sophie",
+ "city": "Boston",
+ "age": 26,
+ "friends": [
+ {
+ "name": "Levi",
+ "hobbies": [
+ "Writing",
+ "Yoga",
+ "Movie Watching"
+ ]
+ },
+ {
+ "name": "Noah",
+ "hobbies": [
+ "Board Games",
+ "Martial Arts",
+ "Shopping"
+ ]
+ },
+ {
+ "name": "Camila",
+ "hobbies": [
+ "Jewelry Making",
+ "Skiing & Snowboarding",
+ "Fishing"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 58,
+ "name": "Emma",
+ "city": "Seattle",
+ "age": 40,
+ "friends": [
+ {
+ "name": "Victoria",
+ "hobbies": [
+ "Traveling",
+ "Bicycling"
+ ]
+ },
+ {
+ "name": "Michelle",
+ "hobbies": [
+ "Skiing & Snowboarding",
+ "Bicycling",
+ "Watching Sports"
+ ]
+ },
+ {
+ "name": "Michael",
+ "hobbies": [
+ "Board Games",
+ "Watching Sports"
+ ]
+ },
+ {
+ "name": "Daniel",
+ "hobbies": [
+ "Yoga",
+ "Shopping"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 59,
+ "name": "Luke",
+ "city": "San Diego",
+ "age": 44,
+ "friends": [
+ {
+ "name": "Mia",
+ "hobbies": [
+ "Calligraphy",
+ "Writing"
+ ]
+ },
+ {
+ "name": "Michelle",
+ "hobbies": [
+ "Podcasts",
+ "Movie Watching",
+ "Playing Cards"
+ ]
+ },
+ {
+ "name": "Charlotte",
+ "hobbies": [
+ "Bicycling",
+ "Golf",
+ "Walking"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 60,
+ "name": "Chloe",
+ "city": "Austin",
+ "age": 23,
+ "friends": [
+ {
+ "name": "Camila",
+ "hobbies": [
+ "Martial Arts",
+ "Golf"
+ ]
+ },
+ {
+ "name": "Sarah",
+ "hobbies": [
+ "Writing",
+ "Martial Arts",
+ "Jewelry Making"
+ ]
+ },
+ {
+ "name": "Amelia",
+ "hobbies": [
+ "Video Games",
+ "Bicycling",
+ "Eating Out"
+ ]
+ },
+ {
+ "name": "Sophie",
+ "hobbies": [
+ "Socializing",
+ "Collecting",
+ "Traveling"
+ ]
+ },
+ {
+ "name": "Olivia",
+ "hobbies": [
+ "Team Sports",
+ "Woodworking",
+ "Collecting"
+ ]
+ },
+ {
+ "name": "Elijah",
+ "hobbies": [
+ "Yoga",
+ "Music",
+ "Skiing & Snowboarding"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 61,
+ "name": "Nora",
+ "city": "Orlando",
+ "age": 83,
+ "friends": [
+ {
+ "name": "Zoey",
+ "hobbies": [
+ "Board Games",
+ "Woodworking"
+ ]
+ },
+ {
+ "name": "Emma",
+ "hobbies": [
+ "Board Games",
+ "Traveling"
+ ]
+ },
+ {
+ "name": "Nora",
+ "hobbies": [
+ "Bicycling",
+ "Golf"
+ ]
+ },
+ {
+ "name": "Leo",
+ "hobbies": [
+ "Church Activities",
+ "Golf",
+ "Socializing"
+ ]
+ },
+ {
+ "name": "Oliver",
+ "hobbies": [
+ "Running",
+ "Dancing"
+ ]
+ },
+ {
+ "name": "Amelia",
+ "hobbies": [
+ "Board Games",
+ "Volunteer Work"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 62,
+ "name": "Kevin",
+ "city": "Saint Augustine",
+ "age": 76,
+ "friends": [
+ {
+ "name": "Lucas",
+ "hobbies": [
+ "Playing Cards",
+ "Skiing & Snowboarding"
+ ]
+ },
+ {
+ "name": "Grace",
+ "hobbies": [
+ "Movie Watching",
+ "Calligraphy",
+ "Socializing"
+ ]
+ },
+ {
+ "name": "Ava",
+ "hobbies": [
+ "Podcasts",
+ "Yoga",
+ "Quilting"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 63,
+ "name": "Amelia",
+ "city": "Honolulu",
+ "age": 84,
+ "friends": [
+ {
+ "name": "Grace",
+ "hobbies": [
+ "Golf",
+ "Reading"
+ ]
+ },
+ {
+ "name": "Luke",
+ "hobbies": [
+ "Genealogy",
+ "Martial Arts"
+ ]
+ },
+ {
+ "name": "Lucas",
+ "hobbies": [
+ "Gardening",
+ "Music"
+ ]
+ },
+ {
+ "name": "Isabella",
+ "hobbies": [
+ "Board Games",
+ "Music"
+ ]
+ },
+ {
+ "name": "Noah",
+ "hobbies": [
+ "Cooking",
+ "Eating Out",
+ "Quilting"
+ ]
+ },
+ {
+ "name": "Levi",
+ "hobbies": [
+ "Movie Watching",
+ "Church Activities",
+ "Shopping"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 64,
+ "name": "Joe",
+ "city": "San Francisco",
+ "age": 37,
+ "friends": [
+ {
+ "name": "Michelle",
+ "hobbies": [
+ "Skiing & Snowboarding",
+ "Dancing"
+ ]
+ },
+ {
+ "name": "John",
+ "hobbies": [
+ "Running",
+ "Podcasts",
+ "Woodworking"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 65,
+ "name": "Chloe",
+ "city": "Palm Springs",
+ "age": 60,
+ "friends": [
+ {
+ "name": "Lucas",
+ "hobbies": [
+ "Movie Watching",
+ "Walking"
+ ]
+ },
+ {
+ "name": "Grace",
+ "hobbies": [
+ "Volunteer Work",
+ "Socializing"
+ ]
+ },
+ {
+ "name": "Daniel",
+ "hobbies": [
+ "Church Activities",
+ "Gardening"
+ ]
+ },
+ {
+ "name": "Michael",
+ "hobbies": [
+ "Walking",
+ "Team Sports",
+ "Martial Arts"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 66,
+ "name": "Leo",
+ "city": "New Orleans",
+ "age": 97,
+ "friends": [
+ {
+ "name": "Ava",
+ "hobbies": [
+ "Martial Arts",
+ "Woodworking",
+ "Quilting"
+ ]
+ },
+ {
+ "name": "Chloe",
+ "hobbies": [
+ "Fishing",
+ "Genealogy",
+ "Writing"
+ ]
+ },
+ {
+ "name": "Nora",
+ "hobbies": [
+ "Traveling",
+ "Woodworking"
+ ]
+ },
+ {
+ "name": "Victoria",
+ "hobbies": [
+ "Church Activities",
+ "Cooking"
+ ]
+ },
+ {
+ "name": "Camila",
+ "hobbies": [
+ "Video Games",
+ "Housework"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 67,
+ "name": "Robert",
+ "city": "Austin",
+ "age": 19,
+ "friends": [
+ {
+ "name": "Joe",
+ "hobbies": [
+ "Writing",
+ "Yoga"
+ ]
+ },
+ {
+ "name": "Emma",
+ "hobbies": [
+ "Writing",
+ "Socializing"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 68,
+ "name": "Robert",
+ "city": "Orlando",
+ "age": 65,
+ "friends": [
+ {
+ "name": "Mateo",
+ "hobbies": [
+ "Board Games",
+ "Cooking"
+ ]
+ },
+ {
+ "name": "Noah",
+ "hobbies": [
+ "Collecting",
+ "Housework",
+ "Skiing & Snowboarding"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 69,
+ "name": "Mateo",
+ "city": "New Orleans",
+ "age": 95,
+ "friends": [
+ {
+ "name": "Chloe",
+ "hobbies": [
+ "Painting",
+ "Eating Out",
+ "Walking"
+ ]
+ },
+ {
+ "name": "Nora",
+ "hobbies": [
+ "Bicycling",
+ "Jewelry Making",
+ "Woodworking"
+ ]
+ },
+ {
+ "name": "Jack",
+ "hobbies": [
+ "Cooking",
+ "Gardening"
+ ]
+ },
+ {
+ "name": "Liam",
+ "hobbies": [
+ "Reading",
+ "Collecting",
+ "Podcasts"
+ ]
+ },
+ {
+ "name": "Noah",
+ "hobbies": [
+ "Housework",
+ "Team Sports"
+ ]
+ },
+ {
+ "name": "Emily",
+ "hobbies": [
+ "Dancing",
+ "Yoga"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 70,
+ "name": "Jack",
+ "city": "Boston",
+ "age": 76,
+ "friends": [
+ {
+ "name": "Ava",
+ "hobbies": [
+ "Martial Arts",
+ "Volunteer Work",
+ "Team Sports"
+ ]
+ },
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Traveling",
+ "Bicycling"
+ ]
+ },
+ {
+ "name": "Lucas",
+ "hobbies": [
+ "Podcasts",
+ "Jewelry Making",
+ "Dancing"
+ ]
+ },
+ {
+ "name": "Michelle",
+ "hobbies": [
+ "Gardening",
+ "Shopping",
+ "Genealogy"
+ ]
+ },
+ {
+ "name": "Michelle",
+ "hobbies": [
+ "Writing",
+ "Tennis"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 71,
+ "name": "Liam",
+ "city": "Savannah",
+ "age": 37,
+ "friends": [
+ {
+ "name": "Michelle",
+ "hobbies": [
+ "Painting",
+ "Volunteer Work"
+ ]
+ },
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Dancing",
+ "Fishing"
+ ]
+ },
+ {
+ "name": "Olivia",
+ "hobbies": [
+ "Television",
+ "Running"
+ ]
+ },
+ {
+ "name": "Robert",
+ "hobbies": [
+ "Fishing",
+ "Eating Out"
+ ]
+ },
+ {
+ "name": "John",
+ "hobbies": [
+ "Skiing & Snowboarding"
+ ]
+ },
+ {
+ "name": "Chloe",
+ "hobbies": [
+ "Church Activities",
+ "Calligraphy",
+ "Writing"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 72,
+ "name": "Daniel",
+ "city": "Los Angeles",
+ "age": 63,
+ "friends": [
+ {
+ "name": "Evy",
+ "hobbies": [
+ "Television",
+ "Dancing"
+ ]
+ },
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Walking",
+ "Socializing",
+ "Writing"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 73,
+ "name": "Olivia",
+ "city": "Boston",
+ "age": 89,
+ "friends": [
+ {
+ "name": "Ava",
+ "hobbies": [
+ "Fishing",
+ "Playing Cards"
+ ]
+ },
+ {
+ "name": "Noah",
+ "hobbies": [
+ "Movie Watching",
+ "Board Games"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 74,
+ "name": "Amelia",
+ "city": "Orlando",
+ "age": 40,
+ "friends": [
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Golf",
+ "Reading",
+ "Shopping"
+ ]
+ },
+ {
+ "name": "Liam",
+ "hobbies": [
+ "Writing",
+ "Woodworking"
+ ]
+ },
+ {
+ "name": "Amelia",
+ "hobbies": [
+ "Movie Watching",
+ "Music"
+ ]
+ },
+ {
+ "name": "Grace",
+ "hobbies": [
+ "Jewelry Making",
+ "Bicycling"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 75,
+ "name": "Camila",
+ "city": "New Orleans",
+ "age": 65,
+ "friends": [
+ {
+ "name": "Mateo",
+ "hobbies": [
+ "Yoga",
+ "Reading",
+ "Team Sports"
+ ]
+ },
+ {
+ "name": "Mateo",
+ "hobbies": [
+ "Board Games",
+ "Shopping"
+ ]
+ },
+ {
+ "name": "Leo",
+ "hobbies": [
+ "Woodworking",
+ "Martial Arts"
+ ]
+ },
+ {
+ "name": "Jack",
+ "hobbies": [
+ "Television",
+ "Calligraphy",
+ "Playing Cards"
+ ]
+ },
+ {
+ "name": "Liam",
+ "hobbies": [
+ "Fishing",
+ "Martial Arts"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 76,
+ "name": "Jack",
+ "city": "Orlando",
+ "age": 42,
+ "friends": [
+ {
+ "name": "Daniel",
+ "hobbies": [
+ "Podcasts",
+ "Collecting"
+ ]
+ },
+ {
+ "name": "Robert",
+ "hobbies": [
+ "Running",
+ "Shopping",
+ "Quilting"
+ ]
+ },
+ {
+ "name": "Chris",
+ "hobbies": [
+ "Martial Arts",
+ "Golf",
+ "Quilting"
+ ]
+ },
+ {
+ "name": "Amelia",
+ "hobbies": [
+ "Eating Out",
+ "Bicycling",
+ "Golf"
+ ]
+ },
+ {
+ "name": "Olivia",
+ "hobbies": [
+ "Skiing & Snowboarding",
+ "Church Activities"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 77,
+ "name": "Leo",
+ "city": "Lahaina",
+ "age": 46,
+ "friends": [
+ {
+ "name": "Robert",
+ "hobbies": [
+ "Traveling",
+ "Watching Sports"
+ ]
+ },
+ {
+ "name": "Daniel",
+ "hobbies": [
+ "Video Games",
+ "Music"
+ ]
+ },
+ {
+ "name": "Michael",
+ "hobbies": [
+ "Video Games",
+ "Gardening"
+ ]
+ },
+ {
+ "name": "Emma",
+ "hobbies": [
+ "Painting",
+ "Television"
+ ]
+ },
+ {
+ "name": "Sarah",
+ "hobbies": [
+ "Dancing",
+ "Tennis"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 78,
+ "name": "Kevin",
+ "city": "San Antonio",
+ "age": 19,
+ "friends": [
+ {
+ "name": "Leo",
+ "hobbies": [
+ "Traveling",
+ "Television"
+ ]
+ },
+ {
+ "name": "Victoria",
+ "hobbies": [
+ "Fishing",
+ "Collecting",
+ "Gardening"
+ ]
+ },
+ {
+ "name": "Mia",
+ "hobbies": [
+ "Skiing & Snowboarding",
+ "Watching Sports",
+ "Martial Arts"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 79,
+ "name": "Leo",
+ "city": "Sedona",
+ "age": 56,
+ "friends": [
+ {
+ "name": "Mateo",
+ "hobbies": [
+ "Board Games",
+ "Reading"
+ ]
+ },
+ {
+ "name": "Grace",
+ "hobbies": [
+ "Reading",
+ "Fishing",
+ "Woodworking"
+ ]
+ },
+ {
+ "name": "Mia",
+ "hobbies": [
+ "Gardening",
+ "Woodworking"
+ ]
+ },
+ {
+ "name": "Noah",
+ "hobbies": [
+ "Video Games",
+ "Television",
+ "Eating Out"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 80,
+ "name": "Charlotte",
+ "city": "Orlando",
+ "age": 73,
+ "friends": [
+ {
+ "name": "Camila",
+ "hobbies": [
+ "Golf",
+ "Collecting"
+ ]
+ },
+ {
+ "name": "Evy",
+ "hobbies": [
+ "Shopping",
+ "Yoga",
+ "Genealogy"
+ ]
+ },
+ {
+ "name": "Charlotte",
+ "hobbies": [
+ "Yoga",
+ "Volunteer Work"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 81,
+ "name": "Robert",
+ "city": "Chicago",
+ "age": 52,
+ "friends": [
+ {
+ "name": "Noah",
+ "hobbies": [
+ "Church Activities",
+ "Woodworking",
+ "Traveling"
+ ]
+ },
+ {
+ "name": "Liam",
+ "hobbies": [
+ "Board Games",
+ "Socializing"
+ ]
+ },
+ {
+ "name": "Liam",
+ "hobbies": [
+ "Housework",
+ "Music",
+ "Calligraphy"
+ ]
+ },
+ {
+ "name": "Zoey",
+ "hobbies": [
+ "Shopping",
+ "Fishing",
+ "Walking"
+ ]
+ },
+ {
+ "name": "Grace",
+ "hobbies": [
+ "Dancing",
+ "Yoga"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 82,
+ "name": "Kevin",
+ "city": "Palm Springs",
+ "age": 75,
+ "friends": [
+ {
+ "name": "Oliver",
+ "hobbies": [
+ "Running",
+ "Traveling"
+ ]
+ },
+ {
+ "name": "Luke",
+ "hobbies": [
+ "Socializing",
+ "Martial Arts",
+ "Running"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 83,
+ "name": "Evy",
+ "city": "Palm Springs",
+ "age": 51,
+ "friends": [
+ {
+ "name": "Michael",
+ "hobbies": [
+ "Writing",
+ "Podcasts"
+ ]
+ },
+ {
+ "name": "Noah",
+ "hobbies": [
+ "Yoga",
+ "Quilting",
+ "Fishing"
+ ]
+ },
+ {
+ "name": "Sophie",
+ "hobbies": [
+ "Painting"
+ ]
+ },
+ {
+ "name": "Olivia",
+ "hobbies": [
+ "Martial Arts",
+ "Shopping",
+ "Podcasts"
+ ]
+ },
+ {
+ "name": "Camila",
+ "hobbies": [
+ "Reading",
+ "Collecting"
+ ]
+ },
+ {
+ "name": "Noah",
+ "hobbies": [
+ "Socializing",
+ "Housework"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 84,
+ "name": "Daniel",
+ "city": "Saint Augustine",
+ "age": 57,
+ "friends": [
+ {
+ "name": "Emily",
+ "hobbies": [
+ "Walking",
+ "Painting",
+ "Reading"
+ ]
+ },
+ {
+ "name": "Oliver",
+ "hobbies": [
+ "Team Sports",
+ "Board Games"
+ ]
+ },
+ {
+ "name": "Levi",
+ "hobbies": [
+ "Jewelry Making",
+ "Eating Out",
+ "Volunteer Work"
+ ]
+ },
+ {
+ "name": "Zoey",
+ "hobbies": [
+ "Movie Watching",
+ "Video Games"
+ ]
+ },
+ {
+ "name": "Sophie",
+ "hobbies": [
+ "Watching Sports",
+ "Walking",
+ "Martial Arts"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 85,
+ "name": "Olivia",
+ "city": "Charleston",
+ "age": 63,
+ "friends": [
+ {
+ "name": "Oliver",
+ "hobbies": [
+ "Reading",
+ "Playing Cards"
+ ]
+ },
+ {
+ "name": "Mia",
+ "hobbies": [
+ "Running",
+ "Shopping"
+ ]
+ },
+ {
+ "name": "John",
+ "hobbies": [
+ "Writing",
+ "Walking",
+ "Tennis"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 86,
+ "name": "Amelia",
+ "city": "Seattle",
+ "age": 96,
+ "friends": [
+ {
+ "name": "Daniel",
+ "hobbies": [
+ "Dancing",
+ "Eating Out"
+ ]
+ },
+ {
+ "name": "Levi",
+ "hobbies": [
+ "Bicycling",
+ "Dancing"
+ ]
+ },
+ {
+ "name": "Daniel",
+ "hobbies": [
+ "Writing",
+ "Shopping",
+ "Tennis"
+ ]
+ },
+ {
+ "name": "Michelle",
+ "hobbies": [
+ "Board Games",
+ "Walking",
+ "Housework"
+ ]
+ },
+ {
+ "name": "Chloe",
+ "hobbies": [
+ "Genealogy",
+ "Dancing",
+ "Podcasts"
+ ]
+ },
+ {
+ "name": "Joe",
+ "hobbies": [
+ "Movie Watching",
+ "Cooking",
+ "Housework"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 87,
+ "name": "Luke",
+ "city": "Seattle",
+ "age": 26,
+ "friends": [
+ {
+ "name": "Isabella",
+ "hobbies": [
+ "Traveling",
+ "Walking",
+ "Team Sports"
+ ]
+ },
+ {
+ "name": "Amelia",
+ "hobbies": [
+ "Writing",
+ "Housework",
+ "Volunteer Work"
+ ]
+ },
+ {
+ "name": "Elijah",
+ "hobbies": [
+ "Golf",
+ "Yoga"
+ ]
+ },
+ {
+ "name": "Levi",
+ "hobbies": [
+ "Volunteer Work",
+ "Eating Out"
+ ]
+ },
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Yoga",
+ "Genealogy",
+ "Volunteer Work"
+ ]
+ },
+ {
+ "name": "Levi",
+ "hobbies": [
+ "Tennis",
+ "Television"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 88,
+ "name": "Chris",
+ "city": "Nashville",
+ "age": 34,
+ "friends": [
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Podcasts",
+ "Team Sports",
+ "Traveling"
+ ]
+ },
+ {
+ "name": "Elijah",
+ "hobbies": [
+ "Television",
+ "Woodworking",
+ "Cooking"
+ ]
+ },
+ {
+ "name": "Evy",
+ "hobbies": [
+ "Podcasts",
+ "Genealogy",
+ "Calligraphy"
+ ]
+ },
+ {
+ "name": "Victoria",
+ "hobbies": [
+ "Fishing",
+ "Church Activities",
+ "Collecting"
+ ]
+ },
+ {
+ "name": "Camila",
+ "hobbies": [
+ "Television",
+ "Writing"
+ ]
+ },
+ {
+ "name": "Michelle",
+ "hobbies": [
+ "Yoga",
+ "Running"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 89,
+ "name": "Michelle",
+ "city": "Honolulu",
+ "age": 85,
+ "friends": [
+ {
+ "name": "Isabella",
+ "hobbies": [
+ "Calligraphy",
+ "Gardening"
+ ]
+ },
+ {
+ "name": "Chloe",
+ "hobbies": [
+ "Shopping",
+ "Playing Cards",
+ "Tennis"
+ ]
+ },
+ {
+ "name": "Evy",
+ "hobbies": [
+ "Watching Sports",
+ "Cooking",
+ "Golf"
+ ]
+ },
+ {
+ "name": "Elijah",
+ "hobbies": [
+ "Writing",
+ "Tennis",
+ "Playing Cards"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 90,
+ "name": "Lucas",
+ "city": "Los Angeles",
+ "age": 78,
+ "friends": [
+ {
+ "name": "Emma",
+ "hobbies": [
+ "Woodworking",
+ "Painting",
+ "Television"
+ ]
+ },
+ {
+ "name": "Lucas",
+ "hobbies": [
+ "Bicycling",
+ "Volunteer Work"
+ ]
+ },
+ {
+ "name": "Grace",
+ "hobbies": [
+ "Dancing",
+ "Running"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 91,
+ "name": "Sophie",
+ "city": "St. Louis",
+ "age": 86,
+ "friends": [
+ {
+ "name": "Joe",
+ "hobbies": [
+ "Socializing",
+ "Music"
+ ]
+ },
+ {
+ "name": "Zoey",
+ "hobbies": [
+ "Running",
+ "Playing Cards"
+ ]
+ },
+ {
+ "name": "Elijah",
+ "hobbies": [
+ "Dancing"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 92,
+ "name": "Victoria",
+ "city": "Saint Augustine",
+ "age": 33,
+ "friends": [
+ {
+ "name": "Leo",
+ "hobbies": [
+ "Socializing",
+ "Fishing"
+ ]
+ },
+ {
+ "name": "Emily",
+ "hobbies": [
+ "Video Games",
+ "Watching Sports"
+ ]
+ },
+ {
+ "name": "Luke",
+ "hobbies": [
+ "Martial Arts"
+ ]
+ },
+ {
+ "name": "Oliver",
+ "hobbies": [
+ "Traveling",
+ "Quilting",
+ "Television"
+ ]
+ },
+ {
+ "name": "Charlotte",
+ "hobbies": [
+ "Gardening",
+ "Cooking",
+ "Housework"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 93,
+ "name": "Michael",
+ "city": "New Orleans",
+ "age": 82,
+ "friends": [
+ {
+ "name": "Jack",
+ "hobbies": [
+ "Bicycling",
+ "Board Games",
+ "Movie Watching"
+ ]
+ },
+ {
+ "name": "Liam",
+ "hobbies": [
+ "Painting",
+ "Writing",
+ "Bicycling"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 94,
+ "name": "Michael",
+ "city": "Seattle",
+ "age": 49,
+ "friends": [
+ {
+ "name": "John",
+ "hobbies": [
+ "Collecting",
+ "Playing Cards",
+ "Cooking"
+ ]
+ },
+ {
+ "name": "Sarah",
+ "hobbies": [
+ "Fishing",
+ "Walking",
+ "Movie Watching"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 95,
+ "name": "Victoria",
+ "city": "Branson",
+ "age": 48,
+ "friends": [
+ {
+ "name": "Amelia",
+ "hobbies": [
+ "Painting",
+ "Volunteer Work",
+ "Socializing"
+ ]
+ },
+ {
+ "name": "Evy",
+ "hobbies": [
+ "Skiing & Snowboarding",
+ "Volunteer Work"
+ ]
+ },
+ {
+ "name": "Amelia",
+ "hobbies": [
+ "Genealogy",
+ "Reading",
+ "Yoga"
+ ]
+ },
+ {
+ "name": "Sophie",
+ "hobbies": [
+ "Movie Watching",
+ "Golf",
+ "Television"
+ ]
+ },
+ {
+ "name": "Charlotte",
+ "hobbies": [
+ "Jewelry Making",
+ "Quilting",
+ "Playing Cards"
+ ]
+ },
+ {
+ "name": "Jack",
+ "hobbies": [
+ "Playing Cards",
+ "Golf"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 96,
+ "name": "Grace",
+ "city": "Seattle",
+ "age": 89,
+ "friends": [
+ {
+ "name": "Chris",
+ "hobbies": [
+ "Board Games",
+ "Golf",
+ "Playing Cards"
+ ]
+ },
+ {
+ "name": "Emily",
+ "hobbies": [
+ "Video Games",
+ "Golf"
+ ]
+ },
+ {
+ "name": "Victoria",
+ "hobbies": [
+ "Housework",
+ "Collecting",
+ "Woodworking"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 97,
+ "name": "Liam",
+ "city": "Nashville",
+ "age": 64,
+ "friends": [
+ {
+ "name": "Kevin",
+ "hobbies": [
+ "Collecting"
+ ]
+ },
+ {
+ "name": "Amelia",
+ "hobbies": [
+ "Golf",
+ "Playing Cards",
+ "Cooking"
+ ]
+ },
+ {
+ "name": "Charlotte",
+ "hobbies": [
+ "Reading",
+ "Board Games",
+ "Genealogy"
+ ]
+ },
+ {
+ "name": "Leo",
+ "hobbies": [
+ "Video Games",
+ "Writing"
+ ]
+ },
+ {
+ "name": "Nora",
+ "hobbies": [
+ "Jewelry Making",
+ "Volunteer Work"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 98,
+ "name": "Mia",
+ "city": "Miami Beach",
+ "age": 77,
+ "friends": [
+ {
+ "name": "Emma",
+ "hobbies": [
+ "Podcasts",
+ "Movie Watching"
+ ]
+ },
+ {
+ "name": "Oliver",
+ "hobbies": [
+ "Playing Cards",
+ "Fishing",
+ "Eating Out"
+ ]
+ },
+ {
+ "name": "Emma",
+ "hobbies": [
+ "Collecting",
+ "Yoga"
+ ]
+ },
+ {
+ "name": "Michael",
+ "hobbies": [
+ "Bicycling",
+ "Team Sports"
+ ]
+ },
+ {
+ "name": "Ava",
+ "hobbies": [
+ "Watching Sports",
+ "Jewelry Making"
+ ]
+ },
+ {
+ "name": "Joe",
+ "hobbies": [
+ "Video Games",
+ "Woodworking",
+ "Music"
+ ]
+ }
+ ]
+ },
+ {
+ "id": 99,
+ "name": "Mateo",
+ "city": "Branson",
+ "age": 66,
+ "friends": [
+ {
+ "name": "Isabella",
+ "hobbies": [
+ "Television",
+ "Skiing & Snowboarding"
+ ]
+ },
+ {
+ "name": "Noah",
+ "hobbies": [
+ "Housework",
+ "Running",
+ "Podcasts"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file