{
}
/**
- * Uses a uaer initialized JSONPointer and tries to
- * match it to an item whithin this JSONArray. For example, given a
+ * Uses a user initialized JSONPointer and tries to
+ * match it to an item within this JSONArray. For example, given a
* JSONArray initialized with this document:
*
* [
@@ -1275,7 +1301,7 @@ public class JSONArray implements Iterable {
Object valueThis = this.myArrayList.get(i);
Object valueOther = ((JSONArray)other).myArrayList.get(i);
if(valueThis == valueOther) {
- return true;
+ continue;
}
if(valueThis == null) {
return false;
@@ -1308,7 +1334,7 @@ public class JSONArray implements Iterable {
* If any of the names are null.
*/
public JSONObject toJSONObject(JSONArray names) throws JSONException {
- if (names == null || names.length() == 0 || this.length() == 0) {
+ if (names == null || names.isEmpty() || this.isEmpty()) {
return null;
}
JSONObject jo = new JSONObject(names.length());
@@ -1342,7 +1368,7 @@ public class JSONArray implements Iterable {
/**
* Make a pretty-printed JSON text of this JSONArray.
*
- * If indentFactor > 0
and the {@link JSONArray} has only
+ *
If
{@code indentFactor > 0} and the {@link JSONArray} has only
* one element, then the array will be output on a single line:
* {@code [1]}
*
@@ -1364,7 +1390,7 @@ public class JSONArray implements Iterable {
* object, beginning with [
(left
* bracket) and ending with ]
* (right bracket) .
- * @throws JSONException
+ * @throws JSONException if a called function fails
*/
public String toString(int indentFactor) throws JSONException {
StringWriter sw = new StringWriter();
@@ -1379,9 +1405,9 @@ public class JSONArray implements Iterable {
*
* Warning: This method assumes that the data structure is acyclical.
*
- *
+ * @param writer the writer object
* @return The writer.
- * @throws JSONException
+ * @throws JSONException if a called function fails
*/
public Writer write(Writer writer) throws JSONException {
return this.write(writer, 0, 0);
@@ -1390,7 +1416,7 @@ public class JSONArray implements Iterable {
/**
* Write the contents of the JSONArray as JSON text to a writer.
*
- * If indentFactor > 0
and the {@link JSONArray} has only
+ *
If
{@code indentFactor > 0} and the {@link JSONArray} has only
* one element, then the array will be output on a single line:
* {@code [1]}
*
@@ -1413,12 +1439,12 @@ public class JSONArray implements Iterable {
* @param indent
* The indentation of the top level.
* @return The writer.
- * @throws JSONException
+ * @throws JSONException if a called function fails or unable to write
*/
public Writer write(Writer writer, int indentFactor, int indent)
throws JSONException {
try {
- boolean commanate = false;
+ boolean needsComma = false;
int length = this.length();
writer.write('[');
@@ -1430,23 +1456,23 @@ public class JSONArray implements Iterable {
throw new JSONException("Unable to write JSONArray value at index: 0", e);
}
} else if (length != 0) {
- final int newindent = indent + indentFactor;
+ final int newIndent = indent + indentFactor;
for (int i = 0; i < length; i += 1) {
- if (commanate) {
+ if (needsComma) {
writer.write(',');
}
if (indentFactor > 0) {
writer.write('\n');
}
- JSONObject.indent(writer, newindent);
+ JSONObject.indent(writer, newIndent);
try {
JSONObject.writeValue(writer, this.myArrayList.get(i),
- indentFactor, newindent);
+ indentFactor, newIndent);
} catch (Exception e) {
throw new JSONException("Unable to write JSONArray value at index: " + i, e);
}
- commanate = true;
+ needsComma = true;
}
if (indentFactor > 0) {
writer.write('\n');
@@ -1463,7 +1489,7 @@ public class JSONArray implements Iterable {
/**
* Returns a java.util.List containing all of the elements in this array.
* If an element in the array is a JSONArray or JSONObject it will also
- * be converted.
+ * be converted to a List and a Map respectively.
*
* Warning: This method assumes that the data structure is acyclical.
*
@@ -1484,4 +1510,86 @@ public class JSONArray implements Iterable {
}
return results;
}
+
+ /**
+ * Check if JSONArray is empty.
+ *
+ * @return true if JSONArray is empty, otherwise false.
+ */
+ public boolean isEmpty() {
+ return this.myArrayList.isEmpty();
+ }
+
+
+ /**
+ * Add a collection's elements to the JSONArray.
+ *
+ * @param collection
+ * A Collection.
+ */
+ private void addAll(Collection> collection) {
+ this.myArrayList.ensureCapacity(this.myArrayList.size() + collection.size());
+ for (Object o: collection){
+ this.myArrayList.add(JSONObject.wrap(o));
+ }
+ }
+
+ /**
+ * Add an array's elements to the JSONArray.
+ *
+ * @param array
+ * Array. If the parameter passed is null, or not an array, an
+ * exception will be thrown.
+ *
+ * @throws JSONException
+ * If not an array or if an array value is non-finite number.
+ * @throws NullPointerException
+ * Thrown if the array parameter is null.
+ */
+ private void addAll(Object array) throws JSONException {
+ if (array.getClass().isArray()) {
+ int length = Array.getLength(array);
+ this.myArrayList.ensureCapacity(this.myArrayList.size() + length);
+ for (int i = 0; i < length; i += 1) {
+ this.put(JSONObject.wrap(Array.get(array, i)));
+ }
+ } else {
+ throw new JSONException(
+ "JSONArray initial value should be a string or collection or array.");
+ }
+ }
+
+ /**
+ * Create a new JSONException in a common format for incorrect conversions.
+ * @param idx index of the item
+ * @param valueType the type of value being coerced to
+ * @param cause optional cause of the coercion failure
+ * @return JSONException that can be thrown.
+ */
+ private static JSONException wrongValueFormatException(
+ int idx,
+ String valueType,
+ Throwable cause) {
+ return new JSONException(
+ "JSONArray[" + idx + "] is not a " + valueType + "."
+ , cause);
+ }
+
+ /**
+ * Create a new JSONException in a common format for incorrect conversions.
+ * @param idx index of the item
+ * @param valueType the type of value being coerced to
+ * @param cause optional cause of the coercion failure
+ * @return JSONException that can be thrown.
+ */
+ private static JSONException wrongValueFormatException(
+ int idx,
+ String valueType,
+ Object value,
+ Throwable cause) {
+ return new JSONException(
+ "JSONArray[" + idx + "] is not a " + valueType + " (" + value + ")."
+ , cause);
+ }
+
}
diff --git a/JSONException.java b/src/main/java/org/json/JSONException.java
similarity index 50%
rename from JSONException.java
rename to src/main/java/org/json/JSONException.java
index 72542df..ab7ff77 100644
--- a/JSONException.java
+++ b/src/main/java/org/json/JSONException.java
@@ -1,5 +1,29 @@
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.
+ */
+
/**
* The JSONException is thrown by the JSON.org classes when things are amiss.
*
diff --git a/JSONML.java b/src/main/java/org/json/JSONML.java
similarity index 95%
rename from JSONML.java
rename to src/main/java/org/json/JSONML.java
index be16693..aafdf72 100644
--- a/JSONML.java
+++ b/src/main/java/org/json/JSONML.java
@@ -1,7 +1,5 @@
package org.json;
-import java.util.Map.Entry;
-
/*
Copyright (c) 2008 JSON.org
@@ -43,7 +41,7 @@ public class JSONML {
* if we are at the outermost level.
* @param keepStrings Don't type-convert text nodes and attribute values
* @return A JSONArray if the value is the outermost tag, otherwise null.
- * @throws JSONException
+ * @throws JSONException if a parsing error occurs
*/
private static Object parse(
XMLTokener x,
@@ -240,7 +238,7 @@ public class JSONML {
* attributes, then the second element will be JSONObject containing the
* name/value pairs. If the tag contains children, then strings and
* JSONArrays will represent the child tags.
- * Comments, prologs, DTDs, and <[ [ ]]>
are ignored.
+ * Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored.
* @param string The source string.
* @return A JSONArray containing the structured data from the XML string.
* @throws JSONException Thrown on error converting to a JSONArray
@@ -260,7 +258,7 @@ public class JSONML {
* As opposed to toJSONArray this method does not attempt to convert
* any text node or attribute value to any type
* but just leaves it as a string.
- * Comments, prologs, DTDs, and <[ [ ]]>
are ignored.
+ * Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored.
* @param string The source string.
* @param keepStrings If true, then values will not be coerced into boolean
* or numeric values and will instead be left as strings
@@ -282,7 +280,7 @@ public class JSONML {
* As opposed to toJSONArray this method does not attempt to convert
* any text node or attribute value to any type
* but just leaves it as a string.
- * Comments, prologs, DTDs, and <[ [ ]]>
are ignored.
+ * Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored.
* @param x An XMLTokener.
* @param keepStrings If true, then values will not be coerced into boolean
* or numeric values and will instead be left as strings
@@ -301,7 +299,7 @@ public class JSONML {
* attributes, then the second element will be JSONObject containing the
* name/value pairs. If the tag contains children, then strings and
* JSONArrays will represent the child content and tags.
- * Comments, prologs, DTDs, and <[ [ ]]>
are ignored.
+ * Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored.
* @param x An XMLTokener.
* @return A JSONArray containing the structured data from the XML string.
* @throws JSONException Thrown on error converting to a JSONArray
@@ -319,7 +317,7 @@ public class JSONML {
* contains children, the object will have a "childNodes" property which
* will be an array of strings and JsonML JSONObjects.
- * Comments, prologs, DTDs, and <[ [ ]]>
are ignored.
+ * Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored.
* @param string The XML source text.
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException Thrown on error converting to a JSONObject
@@ -337,7 +335,7 @@ public class JSONML {
* contains children, the object will have a "childNodes" property which
* will be an array of strings and JsonML JSONObjects.
- * Comments, prologs, DTDs, and <[ [ ]]>
are ignored.
+ * Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored.
* @param string The XML source text.
* @param keepStrings If true, then values will not be coerced into boolean
* or numeric values and will instead be left as strings
@@ -357,7 +355,7 @@ public class JSONML {
* contains children, the object will have a "childNodes" property which
* will be an array of strings and JsonML JSONObjects.
- * Comments, prologs, DTDs, and <[ [ ]]>
are ignored.
+ * Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored.
* @param x An XMLTokener of the XML source text.
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException Thrown on error converting to a JSONObject
@@ -375,7 +373,7 @@ public class JSONML {
* contains children, the object will have a "childNodes" property which
* will be an array of strings and JsonML JSONObjects.
- * Comments, prologs, DTDs, and <[ [ ]]>
are ignored.
+ * Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored.
* @param x An XMLTokener of the XML source text.
* @param keepStrings If true, then values will not be coerced into boolean
* or numeric values and will instead be left as strings
@@ -416,10 +414,10 @@ public class JSONML {
// Emit the attributes
- for (final Entry entry : jo.entrySet()) {
- final String key = entry.getKey();
+ // Don't use the new entrySet API to maintain Android support
+ for (final String key : jo.keySet()) {
+ final Object value = jo.opt(key);
XML.noSpace(key);
- final Object value = entry.getValue();
if (value != null) {
sb.append(' ');
sb.append(XML.escape(key));
@@ -495,11 +493,11 @@ public class JSONML {
//Emit the attributes
- for (final Entry entry : jo.entrySet()) {
- final String key = entry.getKey();
+ // Don't use the new entrySet API to maintain Android support
+ for (final String key : jo.keySet()) {
if (!"tagName".equals(key) && !"childNodes".equals(key)) {
XML.noSpace(key);
- value = entry.getValue();
+ value = jo.opt(key);
if (value != null) {
sb.append(' ');
sb.append(XML.escape(key));
diff --git a/JSONObject.java b/src/main/java/org/json/JSONObject.java
similarity index 76%
rename from JSONObject.java
rename to src/main/java/org/json/JSONObject.java
index 1b7b0a1..f718c06 100644
--- a/JSONObject.java
+++ b/src/main/java/org/json/JSONObject.java
@@ -29,6 +29,7 @@ import java.io.Closeable;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -44,6 +45,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.ResourceBundle;
import java.util.Set;
+import java.util.regex.Pattern;
/**
* A JSONObject is an unordered collection of name/value pairs. Its external
@@ -149,6 +151,12 @@ public class JSONObject {
return "null";
}
}
+
+ /**
+ * Regular Expression Pattern that matches JSON Numbers. This is primarily used for
+ * output to guarantee that we are always writing valid JSON.
+ */
+ static final Pattern NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?");
/**
* The map where the JSONObject's properties are kept.
@@ -186,7 +194,7 @@ public class JSONObject {
* @param names
* An array of strings.
*/
- public JSONObject(JSONObject jo, String[] names) {
+ public JSONObject(JSONObject jo, String ... names) {
this(names.length);
for (int i = 0; i < names.length; i += 1) {
try {
@@ -271,6 +279,10 @@ public class JSONObject {
* @param m
* A map object that can be used to initialize the contents of
* the JSONObject.
+ * @throws JSONException
+ * If a value in the map is non-finite number.
+ * @throws NullPointerException
+ * If a key in the map is null
*/
public JSONObject(Map, ?> m) {
if (m == null) {
@@ -278,6 +290,9 @@ public class JSONObject {
} else {
this.map = new HashMap(m.size());
for (final Entry, ?> e : m.entrySet()) {
+ if(e.getKey() == null) {
+ throw new NullPointerException("Null key.");
+ }
final Object value = e.getValue();
if (value != null) {
this.map.put(String.valueOf(e.getKey()), wrap(value));
@@ -298,13 +313,47 @@ public class JSONObject {
* prefix. If the second remaining character is not upper case, then the
* first character is converted to lower case.
*
+ * Methods that are static
, return void
,
+ * have parameters, or are "bridge" methods, are ignored.
+ *
* For example, if an object has a method named "getName"
, and
* if the result of calling object.getName()
is
* "Larry Fine"
, then the JSONObject will contain
* "name": "Larry Fine"
.
*
- * Methods that return void
as well as static
- * methods are ignored.
+ * The {@link JSONPropertyName} annotation can be used on a bean getter to
+ * override key name used in the JSONObject. For example, using the object
+ * above with the getName
method, if we annotated it with:
+ *
+ * @JSONPropertyName("FullName")
+ * public String getName() { return this.name; }
+ *
+ * The resulting JSON object would contain "FullName": "Larry Fine"
+ *
+ * Similarly, the {@link JSONPropertyName} annotation can be used on non-
+ * get
and is
methods. We can also override key
+ * name used in the JSONObject as seen below even though the field would normally
+ * be ignored:
+ *
+ * @JSONPropertyName("FullName")
+ * public String fullName() { return this.name; }
+ *
+ * The resulting JSON object would contain "FullName": "Larry Fine"
+ *
+ * The {@link JSONPropertyIgnore} annotation can be used to force the bean property
+ * to not be serialized into JSON. If both {@link JSONPropertyIgnore} and
+ * {@link JSONPropertyName} are defined on the same method, a depth comparison is
+ * performed and the one closest to the concrete class being serialized is used.
+ * If both annotations are at the same level, then the {@link JSONPropertyIgnore}
+ * annotation takes precedent and the field is not serialized.
+ * For example, the following declaration would prevent the getName
+ * method from being serialized:
+ *
+ * @JSONPropertyName("FullName")
+ * @JSONPropertyIgnore
+ * public String getName() { return this.name; }
+ *
+ *
*
* @param bean
* An object that has getter methods that should be used to make
@@ -329,7 +378,7 @@ public class JSONObject {
* An array of strings, the names of the fields to be obtained
* from the object.
*/
- public JSONObject(Object object, String names[]) {
+ public JSONObject(Object object, String ... names) {
this(names.length);
Class> c = object.getClass();
for (int i = 0; i < names.length; i += 1) {
@@ -428,7 +477,9 @@ public class JSONObject {
* An object to be accumulated under the key.
* @return this.
* @throws JSONException
- * If the value is an invalid number or if the key is null.
+ * If the value is non-finite number.
+ * @throws NullPointerException
+ * If the key is null
.
*/
public JSONObject accumulate(String key, Object value) throws JSONException {
testValidity(value);
@@ -457,8 +508,10 @@ public class JSONObject {
* An object to be accumulated under the key.
* @return this.
* @throws JSONException
- * If the key is null or if the current value associated with
+ * If the value is non-finite number or if the current value associated with
* the key is not a JSONArray.
+ * @throws NullPointerException
+ * If the key is null
.
*/
public JSONObject append(String key, Object value) throws JSONException {
testValidity(value);
@@ -468,8 +521,7 @@ public class JSONObject {
} else if (object instanceof JSONArray) {
this.put(key, ((JSONArray) object).put(value));
} else {
- throw new JSONException("JSONObject[" + key
- + "] is not a JSONArray.");
+ throw wrongValueFormatException(key, "JSONArray", null, null);
}
return this;
}
@@ -523,26 +575,26 @@ public class JSONObject {
}
/**
- * Get the enum value associated with a key.
- *
- * @param clazz
- * The type of enum to retrieve.
- * @param key
- * A key string.
- * @return The enum value associated with the key
- * @throws JSONException
- * if the key is not found or if the value cannot be converted
- * to an enum.
- */
+ * Get the enum value associated with a key.
+ *
+ * @param
+ * Enum Type
+ * @param clazz
+ * The type of enum to retrieve.
+ * @param key
+ * A key string.
+ * @return The enum value associated with the key
+ * @throws JSONException
+ * if the key is not found or if the value cannot be converted
+ * to an enum.
+ */
public > E getEnum(Class clazz, String key) throws JSONException {
E val = optEnum(clazz, key);
if(val==null) {
// JSONException should really take a throwable argument.
// If it did, I would re-implement this with the Enum.valueOf
// method and place any thrown exception in the JSONException
- throw new JSONException("JSONObject[" + quote(key)
- + "] is not an enum of type " + quote(clazz.getSimpleName())
- + ".");
+ throw wrongValueFormatException(key, "enum of type " + quote(clazz.getSimpleName()), null);
}
return val;
}
@@ -568,8 +620,7 @@ public class JSONObject {
.equalsIgnoreCase("true"))) {
return true;
}
- throw new JSONException("JSONObject[" + quote(key)
- + "] is not a Boolean.");
+ throw wrongValueFormatException(key, "Boolean", null);
}
/**
@@ -584,16 +635,18 @@ public class JSONObject {
*/
public BigInteger getBigInteger(String key) throws JSONException {
Object object = this.get(key);
- try {
- return new BigInteger(object.toString());
- } catch (Exception e) {
- throw new JSONException("JSONObject[" + quote(key)
- + "] could not be converted to BigInteger.", e);
+ BigInteger ret = objectToBigInteger(object, null);
+ if (ret != null) {
+ return ret;
}
+ throw wrongValueFormatException(key, "BigInteger", object, null);
}
/**
- * Get the BigDecimal value associated with a key.
+ * Get the BigDecimal value associated with a key. If the value is float or
+ * double, the the {@link BigDecimal#BigDecimal(double)} constructor will
+ * be used. See notes on the constructor for conversion issues that may
+ * arise.
*
* @param key
* A key string.
@@ -604,15 +657,11 @@ public class JSONObject {
*/
public BigDecimal getBigDecimal(String key) throws JSONException {
Object object = this.get(key);
- if (object instanceof BigDecimal) {
- return (BigDecimal)object;
- }
- try {
- return new BigDecimal(object.toString());
- } catch (Exception e) {
- throw new JSONException("JSONObject[" + quote(key)
- + "] could not be converted to BigDecimal.", e);
+ BigDecimal ret = objectToBigDecimal(object, null);
+ if (ret != null) {
+ return ret;
}
+ throw wrongValueFormatException(key, "BigDecimal", object, null);
}
/**
@@ -626,13 +675,14 @@ public class JSONObject {
* object and cannot be converted to a number.
*/
public double getDouble(String key) throws JSONException {
- Object object = this.get(key);
+ final Object object = this.get(key);
+ if(object instanceof Number) {
+ return ((Number)object).doubleValue();
+ }
try {
- return object instanceof Number ? ((Number) object).doubleValue()
- : Double.parseDouble(object.toString());
+ return Double.parseDouble(object.toString());
} catch (Exception e) {
- throw new JSONException("JSONObject[" + quote(key)
- + "] is not a number.", e);
+ throw wrongValueFormatException(key, "double", e);
}
}
@@ -647,13 +697,14 @@ public class JSONObject {
* object and cannot be converted to a number.
*/
public float getFloat(String key) throws JSONException {
- Object object = this.get(key);
+ final Object object = this.get(key);
+ if(object instanceof Number) {
+ return ((Number)object).floatValue();
+ }
try {
- return object instanceof Number ? ((Number) object).floatValue()
- : Float.parseFloat(object.toString());
+ return Float.parseFloat(object.toString());
} catch (Exception e) {
- throw new JSONException("JSONObject[" + quote(key)
- + "] is not a number.", e);
+ throw wrongValueFormatException(key, "float", e);
}
}
@@ -675,8 +726,7 @@ public class JSONObject {
}
return stringToNumber(object.toString());
} catch (Exception e) {
- throw new JSONException("JSONObject[" + quote(key)
- + "] is not a number.", e);
+ throw wrongValueFormatException(key, "number", e);
}
}
@@ -691,13 +741,14 @@ public class JSONObject {
* to an integer.
*/
public int getInt(String key) throws JSONException {
- Object object = this.get(key);
+ final Object object = this.get(key);
+ if(object instanceof Number) {
+ return ((Number)object).intValue();
+ }
try {
- return object instanceof Number ? ((Number) object).intValue()
- : Integer.parseInt((String) object);
+ return Integer.parseInt(object.toString());
} catch (Exception e) {
- throw new JSONException("JSONObject[" + quote(key)
- + "] is not an int.", e);
+ throw wrongValueFormatException(key, "int", e);
}
}
@@ -715,8 +766,7 @@ public class JSONObject {
if (object instanceof JSONArray) {
return (JSONArray) object;
}
- throw new JSONException("JSONObject[" + quote(key)
- + "] is not a JSONArray.");
+ throw wrongValueFormatException(key, "JSONArray", null);
}
/**
@@ -733,8 +783,7 @@ public class JSONObject {
if (object instanceof JSONObject) {
return (JSONObject) object;
}
- throw new JSONException("JSONObject[" + quote(key)
- + "] is not a JSONObject.");
+ throw wrongValueFormatException(key, "JSONObject", null);
}
/**
@@ -748,32 +797,36 @@ public class JSONObject {
* to a long.
*/
public long getLong(String key) throws JSONException {
- Object object = this.get(key);
+ final Object object = this.get(key);
+ if(object instanceof Number) {
+ return ((Number)object).longValue();
+ }
try {
- return object instanceof Number ? ((Number) object).longValue()
- : Long.parseLong((String) object);
+ return Long.parseLong(object.toString());
} catch (Exception e) {
- throw new JSONException("JSONObject[" + quote(key)
- + "] is not a long.", e);
+ throw wrongValueFormatException(key, "long", e);
}
}
/**
* Get an array of field names from a JSONObject.
*
+ * @param jo
+ * JSON object
* @return An array of field names, or null if there are no names.
*/
public static String[] getNames(JSONObject jo) {
- int length = jo.length();
- if (length == 0) {
+ if (jo.isEmpty()) {
return null;
}
- return jo.keySet().toArray(new String[length]);
+ return jo.keySet().toArray(new String[jo.length()]);
}
/**
- * Get an array of field names from an Object.
+ * Get an array of public field names from an Object.
*
+ * @param object
+ * object to read
* @return An array of field names, or null if there are no names.
*/
public static String[] getNames(Object object) {
@@ -807,7 +860,7 @@ public class JSONObject {
if (object instanceof String) {
return (String) object;
}
- throw new JSONException("JSONObject[" + quote(key) + "] not a string.");
+ throw wrongValueFormatException(key, "string", null);
}
/**
@@ -823,8 +876,11 @@ public class JSONObject {
/**
* Increment a property of a JSONObject. If there is no such property,
- * create one with a value of 1. If there is such a property, and if it is
- * an Integer, Long, Double, or Float, then add one to it.
+ * create one with a value of 1 (Integer). If there is such a property, and if it is
+ * an Integer, Long, Double, Float, BigInteger, or BigDecimal then add one to it.
+ * No overflow bounds checking is performed, so callers should initialize the key
+ * prior to this call with an appropriate type that can handle the maximum expected
+ * value.
*
* @param key
* A key string.
@@ -837,18 +893,18 @@ public class JSONObject {
Object value = this.opt(key);
if (value == null) {
this.put(key, 1);
- } else if (value instanceof BigInteger) {
- this.put(key, ((BigInteger)value).add(BigInteger.ONE));
- } else if (value instanceof BigDecimal) {
- this.put(key, ((BigDecimal)value).add(BigDecimal.ONE));
} else if (value instanceof Integer) {
this.put(key, ((Integer) value).intValue() + 1);
} else if (value instanceof Long) {
this.put(key, ((Long) value).longValue() + 1L);
- } else if (value instanceof Double) {
- this.put(key, ((Double) value).doubleValue() + 1.0d);
+ } else if (value instanceof BigInteger) {
+ this.put(key, ((BigInteger)value).add(BigInteger.ONE));
} else if (value instanceof Float) {
this.put(key, ((Float) value).floatValue() + 1.0f);
+ } else if (value instanceof Double) {
+ this.put(key, ((Double) value).doubleValue() + 1.0d);
+ } else if (value instanceof BigDecimal) {
+ this.put(key, ((BigDecimal)value).add(BigDecimal.ONE));
} else {
throw new JSONException("Unable to increment [" + quote(key) + "].");
}
@@ -856,13 +912,13 @@ public class JSONObject {
}
/**
- * Determine if the value associated with the key is null or if there is no
+ * Determine if the value associated with the key is null
or if there is no
* value.
*
* @param key
* A key string.
* @return true if there is no value associated with the key or if the value
- * is the JSONObject.NULL object.
+ * is the JSONObject.NULL object.
*/
public boolean isNull(String key) {
return JSONObject.NULL.equals(this.opt(key));
@@ -917,12 +973,21 @@ public class JSONObject {
return this.map.size();
}
+ /**
+ * Check if JSONObject is empty.
+ *
+ * @return true if JSONObject is empty, otherwise false.
+ */
+ public boolean isEmpty() {
+ return this.map.isEmpty();
+ }
+
/**
* Produce a JSONArray containing the names of the elements of this
* JSONObject.
*
* @return A JSONArray containing the key strings, or null if the JSONObject
- * is empty.
+ * is empty.
*/
public JSONArray names() {
if(this.map.isEmpty()) {
@@ -975,6 +1040,8 @@ public class JSONObject {
/**
* Get the enum value associated with a key.
*
+ * @param
+ * Enum Type
* @param clazz
* The type of enum to retrieve.
* @param key
@@ -988,6 +1055,8 @@ public class JSONObject {
/**
* Get the enum value associated with a key.
*
+ * @param
+ * Enum Type
* @param clazz
* The type of enum to retrieve.
* @param key
@@ -1059,7 +1128,10 @@ public class JSONObject {
/**
* Get an optional BigDecimal associated with a key, or the defaultValue if
* there is no such key or if its value is not a number. If the value is a
- * string, an attempt will be made to evaluate it as a number.
+ * string, an attempt will be made to evaluate it as a number. If the value
+ * is float or double, then the {@link BigDecimal#BigDecimal(double)}
+ * constructor will be used. See notes on the constructor for conversion
+ * issues that may arise.
*
* @param key
* A key string.
@@ -1069,6 +1141,16 @@ public class JSONObject {
*/
public BigDecimal optBigDecimal(String key, BigDecimal defaultValue) {
Object val = this.opt(key);
+ return objectToBigDecimal(val, defaultValue);
+ }
+
+ /**
+ * @param val value to convert
+ * @param defaultValue default value to return is the conversion doesn't work or is null.
+ * @return BigDecimal conversion of the original value, or the defaultValue if unable
+ * to convert.
+ */
+ static BigDecimal objectToBigDecimal(Object val, BigDecimal defaultValue) {
if (NULL.equals(val)) {
return defaultValue;
}
@@ -1079,6 +1161,10 @@ public class JSONObject {
return new BigDecimal((BigInteger) val);
}
if (val instanceof Double || val instanceof Float){
+ final double d = ((Number) val).doubleValue();
+ if(Double.isNaN(d)) {
+ return defaultValue;
+ }
return new BigDecimal(((Number) val).doubleValue());
}
if (val instanceof Long || val instanceof Integer
@@ -1106,6 +1192,16 @@ public class JSONObject {
*/
public BigInteger optBigInteger(String key, BigInteger defaultValue) {
Object val = this.opt(key);
+ return objectToBigInteger(val, defaultValue);
+ }
+
+ /**
+ * @param val value to convert
+ * @param defaultValue default value to return is the conversion doesn't work or is null.
+ * @return BigInteger conversion of the original value, or the defaultValue if unable
+ * to convert.
+ */
+ static BigInteger objectToBigInteger(Object val, BigInteger defaultValue) {
if (NULL.equals(val)) {
return defaultValue;
}
@@ -1116,7 +1212,11 @@ public class JSONObject {
return ((BigDecimal) val).toBigInteger();
}
if (val instanceof Double || val instanceof Float){
- return new BigDecimal(((Number) val).doubleValue()).toBigInteger();
+ final double d = ((Number) val).doubleValue();
+ if(Double.isNaN(d)) {
+ return defaultValue;
+ }
+ return new BigDecimal(d).toBigInteger();
}
if (val instanceof Long || val instanceof Integer
|| val instanceof Short || val instanceof Byte){
@@ -1164,21 +1264,15 @@ public class JSONObject {
* @return An object which is the value.
*/
public double optDouble(String key, double defaultValue) {
- Object val = this.opt(key);
- if (NULL.equals(val)) {
+ Number val = this.optNumber(key);
+ if (val == null) {
return defaultValue;
}
- if (val instanceof Number){
- return ((Number) val).doubleValue();
- }
- if (val instanceof String) {
- try {
- return Double.parseDouble((String) val);
- } catch (Exception e) {
- return defaultValue;
- }
- }
- return defaultValue;
+ final double doubleValue = val.doubleValue();
+ // if (Double.isNaN(doubleValue) || Double.isInfinite(doubleValue)) {
+ // return defaultValue;
+ // }
+ return doubleValue;
}
/**
@@ -1206,21 +1300,15 @@ public class JSONObject {
* @return The value.
*/
public float optFloat(String key, float defaultValue) {
- Object val = this.opt(key);
- if (JSONObject.NULL.equals(val)) {
+ Number val = this.optNumber(key);
+ if (val == null) {
return defaultValue;
}
- if (val instanceof Number){
- return ((Number) val).floatValue();
- }
- if (val instanceof String) {
- try {
- return Float.parseFloat((String) val);
- } catch (Exception e) {
- return defaultValue;
- }
- }
- return defaultValue;
+ final float floatValue = val.floatValue();
+ // if (Float.isNaN(floatValue) || Float.isInfinite(floatValue)) {
+ // return defaultValue;
+ // }
+ return floatValue;
}
/**
@@ -1248,22 +1336,11 @@ public class JSONObject {
* @return An object which is the value.
*/
public int optInt(String key, int defaultValue) {
- Object val = this.opt(key);
- if (NULL.equals(val)) {
+ final Number val = this.optNumber(key, null);
+ if (val == null) {
return defaultValue;
}
- if (val instanceof Number){
- return ((Number) val).intValue();
- }
-
- if (val instanceof String) {
- try {
- return new BigDecimal((String) val).intValue();
- } catch (Exception e) {
- return defaultValue;
- }
- }
- return defaultValue;
+ return val.intValue();
}
/**
@@ -1317,22 +1394,12 @@ public class JSONObject {
* @return An object which is the value.
*/
public long optLong(String key, long defaultValue) {
- Object val = this.opt(key);
- if (NULL.equals(val)) {
+ final Number val = this.optNumber(key, null);
+ if (val == null) {
return defaultValue;
}
- if (val instanceof Number){
- return ((Number) val).longValue();
- }
- if (val instanceof String) {
- try {
- return new BigDecimal((String) val).longValue();
- } catch (Exception e) {
- return defaultValue;
- }
- }
- return defaultValue;
+ return val.longValue();
}
/**
@@ -1370,14 +1437,11 @@ public class JSONObject {
return (Number) val;
}
- if (val instanceof String) {
- try {
- return stringToNumber((String) val);
- } catch (Exception e) {
- return defaultValue;
- }
+ try {
+ return stringToNumber(val.toString());
+ } catch (Exception e) {
+ return defaultValue;
}
- return defaultValue;
}
/**
@@ -1409,8 +1473,8 @@ public class JSONObject {
}
/**
- * Populates the internal map of the JSONObject with the bean properties.
- * The bean can not be recursive.
+ * Populates the internal map of the JSONObject with the bean properties. The
+ * bean can not be recursive.
*
* @see JSONObject#JSONObject(Object)
*
@@ -1420,49 +1484,31 @@ public class JSONObject {
private void populateMap(Object bean) {
Class> klass = bean.getClass();
-// If klass is a System class then set includeSuperClass to false.
+ // If klass is a System class then set includeSuperClass to false.
boolean includeSuperClass = klass.getClassLoader() != null;
- Method[] methods = includeSuperClass ? klass.getMethods() : klass
- .getDeclaredMethods();
+ Method[] methods = includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods();
for (final Method method : methods) {
final int modifiers = method.getModifiers();
if (Modifier.isPublic(modifiers)
&& !Modifier.isStatic(modifiers)
&& method.getParameterTypes().length == 0
&& !method.isBridge()
- && method.getReturnType() != Void.TYPE ) {
- final String name = method.getName();
- String key;
- if (name.startsWith("get")) {
- if ("getClass".equals(name) || "getDeclaringClass".equals(name)) {
- continue;
- }
- key = name.substring(3);
- } else if (name.startsWith("is")) {
- key = name.substring(2);
- } else {
- continue;
- }
- if (key.length() > 0
- && Character.isUpperCase(key.charAt(0))) {
- if (key.length() == 1) {
- key = key.toLowerCase(Locale.ROOT);
- } else if (!Character.isUpperCase(key.charAt(1))) {
- key = key.substring(0, 1).toLowerCase(Locale.ROOT)
- + key.substring(1);
- }
-
+ && method.getReturnType() != Void.TYPE
+ && isValidMethodName(method.getName())) {
+ final String key = getKeyNameFromMethod(method);
+ if (key != null && !key.isEmpty()) {
try {
final Object result = method.invoke(bean);
if (result != null) {
this.map.put(key, wrap(result));
// we don't use the result anywhere outside of wrap
- // if it's a resource we should be sure to close it after calling toString
- if(result instanceof Closeable) {
+ // if it's a resource we should be sure to close it
+ // after calling toString
+ if (result instanceof Closeable) {
try {
- ((Closeable)result).close();
+ ((Closeable) result).close();
} catch (IOException ignore) {
}
}
@@ -1476,6 +1522,162 @@ public class JSONObject {
}
}
+ private static boolean isValidMethodName(String name) {
+ return !"getClass".equals(name) && !"getDeclaringClass".equals(name);
+ }
+
+ private static String getKeyNameFromMethod(Method method) {
+ final int ignoreDepth = getAnnotationDepth(method, JSONPropertyIgnore.class);
+ if (ignoreDepth > 0) {
+ final int forcedNameDepth = getAnnotationDepth(method, JSONPropertyName.class);
+ if (forcedNameDepth < 0 || ignoreDepth <= forcedNameDepth) {
+ // the hierarchy asked to ignore, and the nearest name override
+ // was higher or non-existent
+ return null;
+ }
+ }
+ JSONPropertyName annotation = getAnnotation(method, JSONPropertyName.class);
+ if (annotation != null && annotation.value() != null && !annotation.value().isEmpty()) {
+ return annotation.value();
+ }
+ String key;
+ final String name = method.getName();
+ if (name.startsWith("get") && name.length() > 3) {
+ key = name.substring(3);
+ } else if (name.startsWith("is") && name.length() > 2) {
+ key = name.substring(2);
+ } else {
+ return null;
+ }
+ // if the first letter in the key is not uppercase, then skip.
+ // This is to maintain backwards compatibility before PR406
+ // (https://github.com/stleary/JSON-java/pull/406/)
+ if (Character.isLowerCase(key.charAt(0))) {
+ return null;
+ }
+ if (key.length() == 1) {
+ key = key.toLowerCase(Locale.ROOT);
+ } else if (!Character.isUpperCase(key.charAt(1))) {
+ key = key.substring(0, 1).toLowerCase(Locale.ROOT) + key.substring(1);
+ }
+ return key;
+ }
+
+ /**
+ * Searches the class hierarchy to see if the method or it's super
+ * implementations and interfaces has the annotation.
+ *
+ * @param
+ * type of the annotation
+ *
+ * @param m
+ * method to check
+ * @param annotationClass
+ * annotation to look for
+ * @return the {@link Annotation} if the annotation exists on the current method
+ * or one of it's super class definitions
+ */
+ private static A getAnnotation(final Method m, final Class annotationClass) {
+ // if we have invalid data the result is null
+ if (m == null || annotationClass == null) {
+ return null;
+ }
+
+ if (m.isAnnotationPresent(annotationClass)) {
+ return m.getAnnotation(annotationClass);
+ }
+
+ // if we've already reached the Object class, return null;
+ Class> c = m.getDeclaringClass();
+ if (c.getSuperclass() == null) {
+ return null;
+ }
+
+ // check directly implemented interfaces for the method being checked
+ for (Class> i : c.getInterfaces()) {
+ try {
+ Method im = i.getMethod(m.getName(), m.getParameterTypes());
+ return getAnnotation(im, annotationClass);
+ } catch (final SecurityException ex) {
+ continue;
+ } catch (final NoSuchMethodException ex) {
+ continue;
+ }
+ }
+
+ try {
+ return getAnnotation(
+ c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()),
+ annotationClass);
+ } catch (final SecurityException ex) {
+ return null;
+ } catch (final NoSuchMethodException ex) {
+ return null;
+ }
+ }
+
+ /**
+ * Searches the class hierarchy to see if the method or it's super
+ * implementations and interfaces has the annotation. Returns the depth of the
+ * annotation in the hierarchy.
+ *
+ * @param
+ * type of the annotation
+ *
+ * @param m
+ * method to check
+ * @param annotationClass
+ * annotation to look for
+ * @return Depth of the annotation or -1 if the annotation is not on the method.
+ */
+ private static int getAnnotationDepth(final Method m, final Class extends Annotation> annotationClass) {
+ // if we have invalid data the result is -1
+ if (m == null || annotationClass == null) {
+ return -1;
+ }
+
+ if (m.isAnnotationPresent(annotationClass)) {
+ return 1;
+ }
+
+ // if we've already reached the Object class, return -1;
+ Class> c = m.getDeclaringClass();
+ if (c.getSuperclass() == null) {
+ return -1;
+ }
+
+ // check directly implemented interfaces for the method being checked
+ for (Class> i : c.getInterfaces()) {
+ try {
+ Method im = i.getMethod(m.getName(), m.getParameterTypes());
+ int d = getAnnotationDepth(im, annotationClass);
+ if (d > 0) {
+ // since the annotation was on the interface, add 1
+ return d + 1;
+ }
+ } catch (final SecurityException ex) {
+ continue;
+ } catch (final NoSuchMethodException ex) {
+ continue;
+ }
+ }
+
+ try {
+ int d = getAnnotationDepth(
+ c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()),
+ annotationClass);
+ if (d > 0) {
+ // since the annotation was on the superclass, add 1
+ return d + 1;
+ }
+ return -1;
+ } catch (final SecurityException ex) {
+ return -1;
+ } catch (final NoSuchMethodException ex) {
+ return -1;
+ }
+ }
+
/**
* Put a key/boolean pair in the JSONObject.
*
@@ -1485,11 +1687,12 @@ public class JSONObject {
* A boolean which is the value.
* @return this.
* @throws JSONException
- * If the key is null.
+ * If the value is non-finite number.
+ * @throws NullPointerException
+ * If the key is null
.
*/
public JSONObject put(String key, boolean value) throws JSONException {
- this.put(key, value ? Boolean.TRUE : Boolean.FALSE);
- return this;
+ return this.put(key, value ? Boolean.TRUE : Boolean.FALSE);
}
/**
@@ -1502,10 +1705,12 @@ public class JSONObject {
* A Collection value.
* @return this.
* @throws JSONException
+ * If the value is non-finite number.
+ * @throws NullPointerException
+ * If the key is null
.
*/
public JSONObject put(String key, Collection> value) throws JSONException {
- this.put(key, new JSONArray(value));
- return this;
+ return this.put(key, new JSONArray(value));
}
/**
@@ -1517,11 +1722,12 @@ public class JSONObject {
* A double which is the value.
* @return this.
* @throws JSONException
- * If the key is null or if the number is invalid.
+ * If the value is non-finite number.
+ * @throws NullPointerException
+ * If the key is null
.
*/
public JSONObject put(String key, double value) throws JSONException {
- this.put(key, Double.valueOf(value));
- return this;
+ return this.put(key, Double.valueOf(value));
}
/**
@@ -1533,11 +1739,12 @@ public class JSONObject {
* A float which is the value.
* @return this.
* @throws JSONException
- * If the key is null or if the number is invalid.
+ * If the value is non-finite number.
+ * @throws NullPointerException
+ * If the key is null
.
*/
public JSONObject put(String key, float value) throws JSONException {
- this.put(key, Float.valueOf(value));
- return this;
+ return this.put(key, Float.valueOf(value));
}
/**
@@ -1549,11 +1756,12 @@ public class JSONObject {
* An int which is the value.
* @return this.
* @throws JSONException
- * If the key is null.
+ * If the value is non-finite number.
+ * @throws NullPointerException
+ * If the key is null
.
*/
public JSONObject put(String key, int value) throws JSONException {
- this.put(key, Integer.valueOf(value));
- return this;
+ return this.put(key, Integer.valueOf(value));
}
/**
@@ -1565,11 +1773,12 @@ public class JSONObject {
* A long which is the value.
* @return this.
* @throws JSONException
- * If the key is null.
+ * If the value is non-finite number.
+ * @throws NullPointerException
+ * If the key is null
.
*/
public JSONObject put(String key, long value) throws JSONException {
- this.put(key, Long.valueOf(value));
- return this;
+ return this.put(key, Long.valueOf(value));
}
/**
@@ -1582,14 +1791,16 @@ public class JSONObject {
* A Map value.
* @return this.
* @throws JSONException
+ * If the value is non-finite number.
+ * @throws NullPointerException
+ * If the key is null
.
*/
public JSONObject put(String key, Map, ?> value) throws JSONException {
- this.put(key, new JSONObject(value));
- return this;
+ return this.put(key, new JSONObject(value));
}
/**
- * Put a key/value pair in the JSONObject. If the value is null, then the
+ * Put a key/value pair in the JSONObject. If the value is null
, then the
* key will be removed from the JSONObject if it is present.
*
* @param key
@@ -1600,7 +1811,9 @@ public class JSONObject {
* String, or the JSONObject.NULL object.
* @return this.
* @throws JSONException
- * If the value is non-finite number or if the key is null.
+ * If the value is non-finite number.
+ * @throws NullPointerException
+ * If the key is null
.
*/
public JSONObject put(String key, Object value) throws JSONException {
if (key == null) {
@@ -1620,8 +1833,10 @@ public class JSONObject {
* are both non-null, and only if there is not already a member with that
* name.
*
- * @param key string
- * @param value object
+ * @param key
+ * key to insert into
+ * @param value
+ * value to insert
* @return this.
* @throws JSONException
* if the key is a duplicate
@@ -1631,7 +1846,7 @@ public class JSONObject {
if (this.opt(key) != null) {
throw new JSONException("Duplicate key \"" + key + "\"");
}
- this.put(key, value);
+ return this.put(key, value);
}
return this;
}
@@ -1652,7 +1867,7 @@ public class JSONObject {
*/
public JSONObject putOpt(String key, Object value) throws JSONException {
if (key != null && value != null) {
- this.put(key, value);
+ return this.put(key, value);
}
return this;
}
@@ -1732,9 +1947,10 @@ public class JSONObject {
/**
* Produce a string in double quotes with backslash sequences in all the
- * right places. A backslash will be inserted within , producing <\/,
- * allowing JSON text to be delivered in HTML. In JSON text, a string cannot
- * contain a control character or an unescaped quote or backslash.
+ * right places. A backslash will be inserted within </, producing
+ * <\/, allowing JSON text to be delivered in HTML. In JSON text, a
+ * string cannot contain a control character or an unescaped quote or
+ * backslash.
*
* @param string
* A String
@@ -1753,7 +1969,7 @@ public class JSONObject {
}
public static Writer quote(String string, Writer w) throws IOException {
- if (string == null || string.length() == 0) {
+ if (string == null || string.isEmpty()) {
w.write("\"\"");
return w;
}
@@ -1844,7 +2060,7 @@ public class JSONObject {
Object valueThis = entry.getValue();
Object valueOther = ((JSONObject)other).get(name);
if(valueThis == valueOther) {
- return true;
+ continue;
}
if(valueThis == null) {
return false;
@@ -1893,48 +2109,54 @@ public class JSONObject {
if ((initial >= '0' && initial <= '9') || initial == '-') {
// decimal representation
if (isDecimalNotation(val)) {
- // quick dirty way to see if we need a BigDecimal instead of a Double
- // this only handles some cases of overflow or underflow
- if (val.length()>14) {
- return new BigDecimal(val);
+ // Use a BigDecimal all the time so we keep the original
+ // representation. BigDecimal doesn't support -0.0, ensure we
+ // keep that by forcing a decimal.
+ try {
+ BigDecimal bd = new BigDecimal(val);
+ if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) {
+ return Double.valueOf(-0.0);
+ }
+ return bd;
+ } catch (NumberFormatException retryAsDouble) {
+ // this is to support "Hex Floats" like this: 0x1.0P-1074
+ try {
+ Double d = Double.valueOf(val);
+ if(d.isNaN() || d.isInfinite()) {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ return d;
+ } catch (NumberFormatException ignore) {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
}
- final Double d = Double.valueOf(val);
- if (d.isInfinite() || d.isNaN()) {
- // if we can't parse it as a double, go up to BigDecimal
- // this is probably due to underflow like 4.32e-678
- // or overflow like 4.65e5324. The size of the string is small
- // but can't be held in a Double.
- return new BigDecimal(val);
+ }
+ // block items like 00 01 etc. Java number parsers treat these as Octal.
+ if(initial == '0' && val.length() > 1) {
+ char at1 = val.charAt(1);
+ if(at1 >= '0' && at1 <= '9') {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ } else if (initial == '-' && val.length() > 2) {
+ char at1 = val.charAt(1);
+ char at2 = val.charAt(2);
+ if(at1 == '0' && at2 >= '0' && at2 <= '9') {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
}
- return d;
}
// integer representation.
// This will narrow any values to the smallest reasonable Object representation
// (Integer, Long, or BigInteger)
- // string version
- // The compare string length method reduces GC,
- // but leads to smaller integers being placed in larger wrappers even though not
- // needed. i.e. 1,000,000,000 -> Long even though it's an Integer
- // 1,000,000,000,000,000,000 -> BigInteger even though it's a Long
- //if(val.length()<=9){
- // return Integer.valueOf(val);
- //}
- //if(val.length()<=18){
- // return Long.valueOf(val);
- //}
- //return new BigInteger(val);
-
- // BigInteger version: We use a similar bitLenth compare as
+ // BigInteger down conversion: We use a similar bitLenth compare as
// BigInteger#intValueExact uses. Increases GC, but objects hold
// only what they need. i.e. Less runtime overhead if the value is
- // long lived. Which is the better tradeoff? This is closer to what's
- // in stringToValue.
+ // long lived.
BigInteger bi = new BigInteger(val);
- if(bi.bitLength()<=31){
+ if(bi.bitLength() <= 31){
return Integer.valueOf(bi.intValue());
}
- if(bi.bitLength()<=63){
+ if(bi.bitLength() <= 63){
return Long.valueOf(bi.longValue());
}
return bi;
@@ -1947,20 +2169,26 @@ public class JSONObject {
* can't be converted, return the string.
*
* @param string
- * A String.
+ * A String. can not be null.
* @return A simple JSON value.
+ * @throws NullPointerException
+ * Thrown if the string is null.
*/
+ // Changes to this method must be copied to the corresponding method in
+ // the XML class to keep full support for Android
public static Object stringToValue(String string) {
- if (string.equals("")) {
+ if ("".equals(string)) {
return string;
}
- if (string.equalsIgnoreCase("true")) {
+
+ // check JSON key words true/false/null
+ if ("true".equalsIgnoreCase(string)) {
return Boolean.TRUE;
}
- if (string.equalsIgnoreCase("false")) {
+ if ("false".equalsIgnoreCase(string)) {
return Boolean.FALSE;
}
- if (string.equalsIgnoreCase("null")) {
+ if ("null".equalsIgnoreCase(string)) {
return JSONObject.NULL;
}
@@ -1972,22 +2200,7 @@ public class JSONObject {
char initial = string.charAt(0);
if ((initial >= '0' && initial <= '9') || initial == '-') {
try {
- // if we want full Big Number support this block can be replaced with:
- // return stringToNumber(string);
- if (isDecimalNotation(string)) {
- Double d = Double.valueOf(string);
- if (!d.isInfinite() && !d.isNaN()) {
- return d;
- }
- } else {
- Long myLong = Long.valueOf(string);
- if (string.equals(myLong.toString())) {
- if (myLong.longValue() == myLong.intValue()) {
- return Integer.valueOf(myLong.intValue());
- }
- return myLong;
- }
- }
+ return stringToNumber(string);
} catch (Exception ignore) {
}
}
@@ -2030,7 +2243,7 @@ public class JSONObject {
* If any of the values are non-finite numbers.
*/
public JSONArray toJSONArray(JSONArray names) throws JSONException {
- if (names == null || names.length() == 0) {
+ if (names == null || names.isEmpty()) {
return null;
}
JSONArray ja = new JSONArray();
@@ -2065,16 +2278,16 @@ public class JSONObject {
/**
* Make a pretty-printed JSON text of this JSONObject.
*
- * If indentFactor > 0
and the {@link JSONObject}
+ *
If
{@code indentFactor > 0} and the {@link JSONObject}
* has only one key, then the object will be output on a single line:
* {@code {"key": 1}}
*
* If an object has 2 or more keys, then it will be output across
- * multiple lines: {
+ * multiple lines: {@code {
* "key1": 1,
* "key2": "value 2",
* "key3": 3
- * }
+ * }}
*
* Warning: This method assumes that the data structure is acyclical.
*
@@ -2120,59 +2333,15 @@ public class JSONObject {
* If the value is or contains an invalid number.
*/
public static String valueToString(Object value) throws JSONException {
- if (value == null || value.equals(null)) {
- return "null";
- }
- if (value instanceof JSONString) {
- Object object;
- try {
- object = ((JSONString) value).toJSONString();
- } catch (Exception e) {
- throw new JSONException(e);
- }
- if (object instanceof String) {
- return (String) object;
- }
- throw new JSONException("Bad value from toJSONString: " + object);
- }
- if (value instanceof Number) {
- // not all Numbers may match actual JSON Numbers. i.e. Fractions or Complex
- final String numberAsString = numberToString((Number) value);
- try {
- // Use the BigDecimal constructor for it's parser to validate the format.
- @SuppressWarnings("unused")
- BigDecimal unused = new BigDecimal(numberAsString);
- // Close enough to a JSON number that we will return it unquoted
- return numberAsString;
- } catch (NumberFormatException ex){
- // The Number value is not a valid JSON number.
- // Instead we will quote it as a string
- return quote(numberAsString);
- }
- }
- if (value instanceof Boolean || value instanceof JSONObject
- || value instanceof JSONArray) {
- return value.toString();
- }
- if (value instanceof Map) {
- Map, ?> map = (Map, ?>) value;
- return new JSONObject(map).toString();
- }
- if (value instanceof Collection) {
- Collection> coll = (Collection>) value;
- return new JSONArray(coll).toString();
- }
- if (value.getClass().isArray()) {
- return new JSONArray(value).toString();
- }
- if(value instanceof Enum>){
- return quote(((Enum>)value).name());
- }
- return quote(value.toString());
+ // moves the implementation to JSONWriter as:
+ // 1. It makes more sense to be part of the writer class
+ // 2. For Android support this method is not available. By implementing it in the Writer
+ // Android users can use the writer with the built in Android JSONObject implementation.
+ return JSONWriter.valueToString(value);
}
/**
- * Wrap an object, if necessary. If the object is null, return the NULL
+ * Wrap an object, if necessary. If the object is null
, return the NULL
* object. If it is an array or collection, wrap it in a JSONArray. If it is
* a map, wrap it in a JSONObject. If it is a standard property (Double,
* String, et al) then it is already wrapped. Otherwise, if it comes from
@@ -2230,9 +2399,9 @@ public class JSONObject {
*
* Warning: This method assumes that the data structure is acyclical.
*
- *
+ * @param writer the writer object
* @return The writer.
- * @throws JSONException
+ * @throws JSONException if a called function has an error
*/
public Writer write(Writer writer) throws JSONException {
return this.write(writer, 0, 0);
@@ -2253,13 +2422,9 @@ public class JSONObject {
} else if (value instanceof Number) {
// not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary
final String numberAsString = numberToString((Number) value);
- try {
- // Use the BigDecimal constructor for it's parser to validate the format.
- @SuppressWarnings("unused")
- BigDecimal testNum = new BigDecimal(numberAsString);
- // Close enough to a JSON number that we will use it unquoted
+ if(NUMBER_PATTERN.matcher(numberAsString).matches()) {
writer.write(numberAsString);
- } catch (NumberFormatException ex){
+ } else {
// The Number value is not a valid JSON number.
// Instead we will quote it as a string
quote(numberAsString, writer);
@@ -2295,16 +2460,16 @@ public class JSONObject {
/**
* Write the contents of the JSONObject as JSON text to a writer.
*
- *
If indentFactor > 0
and the {@link JSONObject}
+ *
If
{@code indentFactor > 0} and the {@link JSONObject}
* has only one key, then the object will be output on a single line:
* {@code {"key": 1}}
*
* If an object has 2 or more keys, then it will be output across
- * multiple lines: {
+ * multiple lines: {@code {
* "key1": 1,
* "key2": "value 2",
* "key3": 3
- * }
+ * }}
*
* Warning: This method assumes that the data structure is acyclical.
*
@@ -2316,12 +2481,13 @@ public class JSONObject {
* @param indent
* The indentation of the top level.
* @return The writer.
- * @throws JSONException
+ * @throws JSONException if a called function has an error or a write error
+ * occurs
*/
public Writer write(Writer writer, int indentFactor, int indent)
throws JSONException {
try {
- boolean commanate = false;
+ boolean needsComma = false;
final int length = this.length();
writer.write('{');
@@ -2339,15 +2505,15 @@ public class JSONObject {
throw new JSONException("Unable to write JSONObject value for key: " + key, e);
}
} else if (length != 0) {
- final int newindent = indent + indentFactor;
+ final int newIndent = indent + indentFactor;
for (final Entry entry : this.entrySet()) {
- if (commanate) {
+ if (needsComma) {
writer.write(',');
}
if (indentFactor > 0) {
writer.write('\n');
}
- indent(writer, newindent);
+ indent(writer, newIndent);
final String key = entry.getKey();
writer.write(quote(key));
writer.write(':');
@@ -2355,11 +2521,11 @@ public class JSONObject {
writer.write(' ');
}
try {
- writeValue(writer, entry.getValue(), indentFactor, newindent);
+ writeValue(writer, entry.getValue(), indentFactor, newIndent);
} catch (Exception e) {
throw new JSONException("Unable to write JSONObject value for key: " + key, e);
}
- commanate = true;
+ needsComma = true;
}
if (indentFactor > 0) {
writer.write('\n');
@@ -2399,4 +2565,37 @@ public class JSONObject {
}
return results;
}
+
+ /**
+ * Create a new JSONException in a common format for incorrect conversions.
+ * @param key name of the key
+ * @param valueType the type of value being coerced to
+ * @param cause optional cause of the coercion failure
+ * @return JSONException that can be thrown.
+ */
+ private static JSONException wrongValueFormatException(
+ String key,
+ String valueType,
+ Throwable cause) {
+ return new JSONException(
+ "JSONObject[" + quote(key) + "] is not a " + valueType + "."
+ , cause);
+ }
+
+ /**
+ * Create a new JSONException in a common format for incorrect conversions.
+ * @param key name of the key
+ * @param valueType the type of value being coerced to
+ * @param cause optional cause of the coercion failure
+ * @return JSONException that can be thrown.
+ */
+ private static JSONException wrongValueFormatException(
+ String key,
+ String valueType,
+ Object value,
+ Throwable cause) {
+ return new JSONException(
+ "JSONObject[" + quote(key) + "] is not a " + valueType + " (" + value + ")."
+ , cause);
+ }
}
diff --git a/JSONPointer.java b/src/main/java/org/json/JSONPointer.java
similarity index 83%
rename from JSONPointer.java
rename to src/main/java/org/json/JSONPointer.java
index 8142f9a..e8a0b78 100644
--- a/JSONPointer.java
+++ b/src/main/java/org/json/JSONPointer.java
@@ -5,7 +5,9 @@ import static java.lang.String.format;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/*
Copyright (c) 2002 JSON.org
@@ -66,6 +68,7 @@ public class JSONPointer {
/**
* Creates a {@code JSONPointer} instance using the tokens previously set using the
* {@link #append(String)} method calls.
+ * @return a JSONPointer object
*/
public JSONPointer build() {
return new JSONPointer(this.refTokens);
@@ -156,16 +159,35 @@ public class JSONPointer {
throw new IllegalArgumentException("a JSON pointer should start with '/' or '#/'");
}
this.refTokens = new ArrayList();
- for (String token : refs.split("/")) {
- this.refTokens.add(unescape(token));
- }
+ int slashIdx = -1;
+ int prevSlashIdx = 0;
+ do {
+ prevSlashIdx = slashIdx + 1;
+ slashIdx = refs.indexOf('/', prevSlashIdx);
+ if(prevSlashIdx == slashIdx || prevSlashIdx == refs.length()) {
+ // found 2 slashes in a row ( obj//next )
+ // or single slash at the end of a string ( obj/test/ )
+ this.refTokens.add("");
+ } else if (slashIdx >= 0) {
+ final String token = refs.substring(prevSlashIdx, slashIdx);
+ this.refTokens.add(unescape(token));
+ } else {
+ // last item after separator, or no separator at all.
+ final String token = refs.substring(prevSlashIdx);
+ this.refTokens.add(unescape(token));
+ }
+ } while (slashIdx >= 0);
+ // using split does not take into account consecutive separators or "ending nulls"
+ //for (String token : refs.split("/")) {
+ // this.refTokens.add(unescape(token));
+ //}
}
public JSONPointer(List refTokens) {
this.refTokens = new ArrayList(refTokens);
}
- private String unescape(String token) {
+ private static String unescape(String token) {
return token.replace("~1", "/").replace("~0", "~")
.replace("\\\"", "\"")
.replace("\\\\", "\\");
@@ -181,7 +203,7 @@ public class JSONPointer {
* @return the result of the evaluation
* @throws JSONPointerException if an error occurs during evaluation
*/
- public Object queryFrom(Object document) {
+ public Object queryFrom(Object document) throws JSONPointerException {
if (this.refTokens.isEmpty()) {
return document;
}
@@ -205,18 +227,21 @@ public class JSONPointer {
* @param current the JSONArray to be evaluated
* @param indexToken the array index in string form
* @return the matched object. If no matching item is found a
- * JSONPointerException is thrown
+ * @throws JSONPointerException is thrown if the index is out of bounds
*/
- @SuppressWarnings("boxing")
- private Object readByIndexToken(Object current, String indexToken) {
+ private static Object readByIndexToken(Object current, String indexToken) throws JSONPointerException {
try {
int index = Integer.parseInt(indexToken);
JSONArray currentArr = (JSONArray) current;
if (index >= currentArr.length()) {
- throw new JSONPointerException(format("index %d is out of bounds - the array has %d elements", index,
- currentArr.length()));
+ throw new JSONPointerException(format("index %s is out of bounds - the array has %d elements", indexToken,
+ Integer.valueOf(currentArr.length())));
}
- return currentArr.get(index);
+ try {
+ return currentArr.get(index);
+ } catch (JSONException e) {
+ throw new JSONPointerException("Error reading value at index position " + index, e);
+ }
} catch (NumberFormatException e) {
throw new JSONPointerException(format("%s is not an array index", indexToken), e);
}
@@ -243,7 +268,7 @@ public class JSONPointer {
* @param token the JSONPointer segment value to be escaped
* @return the escaped value for the token
*/
- private String escape(String token) {
+ private static String escape(String token) {
return token.replace("~", "~0")
.replace("/", "~1")
.replace("\\", "\\\\")
@@ -253,6 +278,7 @@ public class JSONPointer {
/**
* Returns a string representing the JSONPointer path value using URI
* fragment identifier representation
+ * @return a uri fragment string
*/
public String toURIFragment() {
try {
diff --git a/JSONPointerException.java b/src/main/java/org/json/JSONPointerException.java
similarity index 100%
rename from JSONPointerException.java
rename to src/main/java/org/json/JSONPointerException.java
diff --git a/src/main/java/org/json/JSONPropertyIgnore.java b/src/main/java/org/json/JSONPropertyIgnore.java
new file mode 100644
index 0000000..682de74
--- /dev/null
+++ b/src/main/java/org/json/JSONPropertyIgnore.java
@@ -0,0 +1,43 @@
+package org.json;
+
+/*
+Copyright (c) 2018 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Documented
+@Retention(RUNTIME)
+@Target({METHOD})
+/**
+ * Use this annotation on a getter method to override the Bean name
+ * parser for Bean -> JSONObject mapping. If this annotation is
+ * present at any level in the class hierarchy, then the method will
+ * not be serialized from the bean into the JSONObject.
+ */
+public @interface JSONPropertyIgnore { }
diff --git a/src/main/java/org/json/JSONPropertyName.java b/src/main/java/org/json/JSONPropertyName.java
new file mode 100644
index 0000000..a1bcd58
--- /dev/null
+++ b/src/main/java/org/json/JSONPropertyName.java
@@ -0,0 +1,47 @@
+package org.json;
+
+/*
+Copyright (c) 2018 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Documented
+@Retention(RUNTIME)
+@Target({METHOD})
+/**
+ * Use this annotation on a getter method to override the Bean name
+ * parser for Bean -> JSONObject mapping. A value set to empty string ""
+ * will have the Bean parser fall back to the default field name processing.
+ */
+public @interface JSONPropertyName {
+ /**
+ * @return The name of the property as to be used in the JSON Object.
+ */
+ String value();
+}
diff --git a/src/main/java/org/json/JSONString.java b/src/main/java/org/json/JSONString.java
new file mode 100644
index 0000000..bcd9a81
--- /dev/null
+++ b/src/main/java/org/json/JSONString.java
@@ -0,0 +1,43 @@
+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.
+ */
+
+/**
+ * The JSONString
interface allows a toJSONString()
+ * method so that a class can change the behavior of
+ * JSONObject.toString()
, JSONArray.toString()
,
+ * and JSONWriter.value(
Object)
. The
+ * toJSONString
method will be used instead of the default behavior
+ * of using the Object's toString()
method and quoting the result.
+ */
+public interface JSONString {
+ /**
+ * The toJSONString
method allows a class to produce its own JSON
+ * serialization.
+ *
+ * @return A strictly syntactically correct JSON text.
+ */
+ public String toJSONString();
+}
diff --git a/JSONStringer.java b/src/main/java/org/json/JSONStringer.java
similarity index 97%
rename from JSONStringer.java
rename to src/main/java/org/json/JSONStringer.java
index 6e05d22..bb9e7a4 100644
--- a/JSONStringer.java
+++ b/src/main/java/org/json/JSONStringer.java
@@ -1,79 +1,79 @@
-package org.json;
-
-/*
-Copyright (c) 2006 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-*/
-
-import java.io.StringWriter;
-
-/**
- * JSONStringer provides a quick and convenient way of producing JSON text.
- * The texts produced strictly conform to JSON syntax rules. No whitespace is
- * added, so the results are ready for transmission or storage. Each instance of
- * JSONStringer can produce one JSON text.
- *
- * A JSONStringer instance provides a value
method for appending
- * values to the
- * text, and a key
- * method for adding keys before values in objects. There are array
- * and endArray
methods that make and bound array values, and
- * object
and endObject
methods which make and bound
- * object values. All of these methods return the JSONWriter instance,
- * permitting cascade style. For example,
- * myString = new JSONStringer()
- * .object()
- * .key("JSON")
- * .value("Hello, World!")
- * .endObject()
- * .toString(); which produces the string
- * {"JSON":"Hello, World!"}
- *
- * The first method called must be array
or object
.
- * There are no methods for adding commas or colons. JSONStringer adds them for
- * you. Objects and arrays can be nested up to 20 levels deep.
- *
- * This can sometimes be easier than using a JSONObject to build a string.
- * @author JSON.org
- * @version 2015-12-09
- */
-public class JSONStringer extends JSONWriter {
- /**
- * Make a fresh JSONStringer. It can be used to build one JSON text.
- */
- public JSONStringer() {
- super(new StringWriter());
- }
-
- /**
- * Return the JSON text. This method is used to obtain the product of the
- * JSONStringer instance. It will return null
if there was a
- * problem in the construction of the JSON text (such as the calls to
- * array
were not properly balanced with calls to
- * endArray
).
- * @return The JSON text.
- */
- @Override
- public String toString() {
- return this.mode == 'd' ? this.writer.toString() : null;
- }
-}
+package org.json;
+
+/*
+Copyright (c) 2006 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import java.io.StringWriter;
+
+/**
+ * JSONStringer provides a quick and convenient way of producing JSON text.
+ * The texts produced strictly conform to JSON syntax rules. No whitespace is
+ * added, so the results are ready for transmission or storage. Each instance of
+ * JSONStringer can produce one JSON text.
+ *
+ * A JSONStringer instance provides a value
method for appending
+ * values to the
+ * text, and a key
+ * method for adding keys before values in objects. There are array
+ * and endArray
methods that make and bound array values, and
+ * object
and endObject
methods which make and bound
+ * object values. All of these methods return the JSONWriter instance,
+ * permitting cascade style. For example,
+ * myString = new JSONStringer()
+ * .object()
+ * .key("JSON")
+ * .value("Hello, World!")
+ * .endObject()
+ * .toString(); which produces the string
+ * {"JSON":"Hello, World!"}
+ *
+ * The first method called must be array
or object
.
+ * There are no methods for adding commas or colons. JSONStringer adds them for
+ * you. Objects and arrays can be nested up to 20 levels deep.
+ *
+ * This can sometimes be easier than using a JSONObject to build a string.
+ * @author JSON.org
+ * @version 2015-12-09
+ */
+public class JSONStringer extends JSONWriter {
+ /**
+ * Make a fresh JSONStringer. It can be used to build one JSON text.
+ */
+ public JSONStringer() {
+ super(new StringWriter());
+ }
+
+ /**
+ * Return the JSON text. This method is used to obtain the product of the
+ * JSONStringer instance. It will return null
if there was a
+ * problem in the construction of the JSON text (such as the calls to
+ * array
were not properly balanced with calls to
+ * endArray
).
+ * @return The JSON text.
+ */
+ @Override
+ public String toString() {
+ return this.mode == 'd' ? this.writer.toString() : null;
+ }
+}
diff --git a/JSONTokener.java b/src/main/java/org/json/JSONTokener.java
similarity index 99%
rename from JSONTokener.java
rename to src/main/java/org/json/JSONTokener.java
index 36bce45..e6821de 100644
--- a/JSONTokener.java
+++ b/src/main/java/org/json/JSONTokener.java
@@ -448,7 +448,9 @@ public class JSONTokener {
sb.append(c);
c = this.next();
}
- this.back();
+ if (!this.eof) {
+ this.back();
+ }
string = sb.toString().trim();
if ("".equals(string)) {
diff --git a/JSONWriter.java b/src/main/java/org/json/JSONWriter.java
similarity index 69%
rename from JSONWriter.java
rename to src/main/java/org/json/JSONWriter.java
index 549f93e..dafb1b2 100644
--- a/JSONWriter.java
+++ b/src/main/java/org/json/JSONWriter.java
@@ -1,326 +1,414 @@
-package org.json;
-
-import java.io.IOException;
-
-/*
-Copyright (c) 2006 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-*/
-
-/**
- * JSONWriter provides a quick and convenient way of producing JSON text.
- * The texts produced strictly conform to JSON syntax rules. No whitespace is
- * added, so the results are ready for transmission or storage. Each instance of
- * JSONWriter can produce one JSON text.
- *
- * A JSONWriter instance provides a value
method for appending
- * values to the
- * text, and a key
- * method for adding keys before values in objects. There are array
- * and endArray
methods that make and bound array values, and
- * object
and endObject
methods which make and bound
- * object values. All of these methods return the JSONWriter instance,
- * permitting a cascade style. For example,
- * new JSONWriter(myWriter)
- * .object()
- * .key("JSON")
- * .value("Hello, World!")
- * .endObject(); which writes
- * {"JSON":"Hello, World!"}
- *
- * The first method called must be array
or object
.
- * There are no methods for adding commas or colons. JSONWriter adds them for
- * you. Objects and arrays can be nested up to 200 levels deep.
- *
- * This can sometimes be easier than using a JSONObject to build a string.
- * @author JSON.org
- * @version 2016-08-08
- */
-public class JSONWriter {
- private static final int maxdepth = 200;
-
- /**
- * The comma flag determines if a comma should be output before the next
- * value.
- */
- private boolean comma;
-
- /**
- * The current mode. Values:
- * 'a' (array),
- * 'd' (done),
- * 'i' (initial),
- * 'k' (key),
- * 'o' (object).
- */
- protected char mode;
-
- /**
- * The object/array stack.
- */
- private final JSONObject stack[];
-
- /**
- * The stack top index. A value of 0 indicates that the stack is empty.
- */
- private int top;
-
- /**
- * The writer that will receive the output.
- */
- protected Appendable writer;
-
- /**
- * Make a fresh JSONWriter. It can be used to build one JSON text.
- */
- public JSONWriter(Appendable w) {
- this.comma = false;
- this.mode = 'i';
- this.stack = new JSONObject[maxdepth];
- this.top = 0;
- this.writer = w;
- }
-
- /**
- * Append a value.
- * @param string A string value.
- * @return this
- * @throws JSONException If the value is out of sequence.
- */
- private JSONWriter append(String string) throws JSONException {
- if (string == null) {
- throw new JSONException("Null pointer");
- }
- if (this.mode == 'o' || this.mode == 'a') {
- try {
- if (this.comma && this.mode == 'a') {
- this.writer.append(',');
- }
- this.writer.append(string);
- } catch (IOException e) {
- throw new JSONException(e);
- }
- if (this.mode == 'o') {
- this.mode = 'k';
- }
- this.comma = true;
- return this;
- }
- throw new JSONException("Value out of sequence.");
- }
-
- /**
- * Begin appending a new array. All values until the balancing
- * endArray
will be appended to this array. The
- * endArray
method must be called to mark the array's end.
- * @return this
- * @throws JSONException If the nesting is too deep, or if the object is
- * started in the wrong place (for example as a key or after the end of the
- * outermost array or object).
- */
- public JSONWriter array() throws JSONException {
- if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') {
- this.push(null);
- this.append("[");
- this.comma = false;
- return this;
- }
- throw new JSONException("Misplaced array.");
- }
-
- /**
- * End something.
- * @param m Mode
- * @param c Closing character
- * @return this
- * @throws JSONException If unbalanced.
- */
- private JSONWriter end(char m, char c) throws JSONException {
- if (this.mode != m) {
- throw new JSONException(m == 'a'
- ? "Misplaced endArray."
- : "Misplaced endObject.");
- }
- this.pop(m);
- try {
- this.writer.append(c);
- } catch (IOException e) {
- throw new JSONException(e);
- }
- this.comma = true;
- return this;
- }
-
- /**
- * End an array. This method most be called to balance calls to
- * array
.
- * @return this
- * @throws JSONException If incorrectly nested.
- */
- public JSONWriter endArray() throws JSONException {
- return this.end('a', ']');
- }
-
- /**
- * End an object. This method most be called to balance calls to
- * object
.
- * @return this
- * @throws JSONException If incorrectly nested.
- */
- public JSONWriter endObject() throws JSONException {
- return this.end('k', '}');
- }
-
- /**
- * Append a key. The key will be associated with the next value. In an
- * object, every value must be preceded by a key.
- * @param string A key string.
- * @return this
- * @throws JSONException If the key is out of place. For example, keys
- * do not belong in arrays or if the key is null.
- */
- public JSONWriter key(String string) throws JSONException {
- if (string == null) {
- throw new JSONException("Null key.");
- }
- if (this.mode == 'k') {
- try {
- this.stack[this.top - 1].putOnce(string, Boolean.TRUE);
- if (this.comma) {
- this.writer.append(',');
- }
- this.writer.append(JSONObject.quote(string));
- this.writer.append(':');
- this.comma = false;
- this.mode = 'o';
- return this;
- } catch (IOException e) {
- throw new JSONException(e);
- }
- }
- throw new JSONException("Misplaced key.");
- }
-
-
- /**
- * Begin appending a new object. All keys and values until the balancing
- * endObject
will be appended to this object. The
- * endObject
method must be called to mark the object's end.
- * @return this
- * @throws JSONException If the nesting is too deep, or if the object is
- * started in the wrong place (for example as a key or after the end of the
- * outermost array or object).
- */
- public JSONWriter object() throws JSONException {
- if (this.mode == 'i') {
- this.mode = 'o';
- }
- if (this.mode == 'o' || this.mode == 'a') {
- this.append("{");
- this.push(new JSONObject());
- this.comma = false;
- return this;
- }
- throw new JSONException("Misplaced object.");
-
- }
-
-
- /**
- * Pop an array or object scope.
- * @param c The scope to close.
- * @throws JSONException If nesting is wrong.
- */
- private void pop(char c) throws JSONException {
- if (this.top <= 0) {
- throw new JSONException("Nesting error.");
- }
- char m = this.stack[this.top - 1] == null ? 'a' : 'k';
- if (m != c) {
- throw new JSONException("Nesting error.");
- }
- this.top -= 1;
- this.mode = this.top == 0
- ? 'd'
- : this.stack[this.top - 1] == null
- ? 'a'
- : 'k';
- }
-
- /**
- * Push an array or object scope.
- * @param jo The scope to open.
- * @throws JSONException If nesting is too deep.
- */
- private void push(JSONObject jo) throws JSONException {
- if (this.top >= maxdepth) {
- throw new JSONException("Nesting too deep.");
- }
- this.stack[this.top] = jo;
- this.mode = jo == null ? 'a' : 'k';
- this.top += 1;
- }
-
-
- /**
- * Append either the value true
or the value
- * false
.
- * @param b A boolean.
- * @return this
- * @throws JSONException
- */
- public JSONWriter value(boolean b) throws JSONException {
- return this.append(b ? "true" : "false");
- }
-
- /**
- * Append a double value.
- * @param d A double.
- * @return this
- * @throws JSONException If the number is not finite.
- */
- public JSONWriter value(double d) throws JSONException {
- return this.value(new Double(d));
- }
-
- /**
- * Append a long value.
- * @param l A long.
- * @return this
- * @throws JSONException
- */
- public JSONWriter value(long l) throws JSONException {
- return this.append(Long.toString(l));
- }
-
-
- /**
- * Append an object value.
- * @param object The object to append. It can be null, or a Boolean, Number,
- * String, JSONObject, or JSONArray, or an object that implements JSONString.
- * @return this
- * @throws JSONException If the value is out of sequence.
- */
- public JSONWriter value(Object object) throws JSONException {
- return this.append(JSONObject.valueToString(object));
- }
-}
+package org.json;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/*
+Copyright (c) 2006 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/**
+ * JSONWriter provides a quick and convenient way of producing JSON text.
+ * The texts produced strictly conform to JSON syntax rules. No whitespace is
+ * added, so the results are ready for transmission or storage. Each instance of
+ * JSONWriter can produce one JSON text.
+ *
+ * A JSONWriter instance provides a value
method for appending
+ * values to the
+ * text, and a key
+ * method for adding keys before values in objects. There are array
+ * and endArray
methods that make and bound array values, and
+ * object
and endObject
methods which make and bound
+ * object values. All of these methods return the JSONWriter instance,
+ * permitting a cascade style. For example,
+ * new JSONWriter(myWriter)
+ * .object()
+ * .key("JSON")
+ * .value("Hello, World!")
+ * .endObject(); which writes
+ * {"JSON":"Hello, World!"}
+ *
+ * The first method called must be array
or object
.
+ * There are no methods for adding commas or colons. JSONWriter adds them for
+ * you. Objects and arrays can be nested up to 200 levels deep.
+ *
+ * This can sometimes be easier than using a JSONObject to build a string.
+ * @author JSON.org
+ * @version 2016-08-08
+ */
+public class JSONWriter {
+ private static final int maxdepth = 200;
+
+ /**
+ * The comma flag determines if a comma should be output before the next
+ * value.
+ */
+ private boolean comma;
+
+ /**
+ * The current mode. Values:
+ * 'a' (array),
+ * 'd' (done),
+ * 'i' (initial),
+ * 'k' (key),
+ * 'o' (object).
+ */
+ protected char mode;
+
+ /**
+ * The object/array stack.
+ */
+ private final JSONObject stack[];
+
+ /**
+ * The stack top index. A value of 0 indicates that the stack is empty.
+ */
+ private int top;
+
+ /**
+ * The writer that will receive the output.
+ */
+ protected Appendable writer;
+
+ /**
+ * Make a fresh JSONWriter. It can be used to build one JSON text.
+ * @param w an appendable object
+ */
+ public JSONWriter(Appendable w) {
+ this.comma = false;
+ this.mode = 'i';
+ this.stack = new JSONObject[maxdepth];
+ this.top = 0;
+ this.writer = w;
+ }
+
+ /**
+ * Append a value.
+ * @param string A string value.
+ * @return this
+ * @throws JSONException If the value is out of sequence.
+ */
+ private JSONWriter append(String string) throws JSONException {
+ if (string == null) {
+ throw new JSONException("Null pointer");
+ }
+ if (this.mode == 'o' || this.mode == 'a') {
+ try {
+ if (this.comma && this.mode == 'a') {
+ this.writer.append(',');
+ }
+ this.writer.append(string);
+ } catch (IOException e) {
+ // Android as of API 25 does not support this exception constructor
+ // however we won't worry about it. If an exception is happening here
+ // it will just throw a "Method not found" exception instead.
+ throw new JSONException(e);
+ }
+ if (this.mode == 'o') {
+ this.mode = 'k';
+ }
+ this.comma = true;
+ return this;
+ }
+ throw new JSONException("Value out of sequence.");
+ }
+
+ /**
+ * Begin appending a new array. All values until the balancing
+ * endArray
will be appended to this array. The
+ * endArray
method must be called to mark the array's end.
+ * @return this
+ * @throws JSONException If the nesting is too deep, or if the object is
+ * started in the wrong place (for example as a key or after the end of the
+ * outermost array or object).
+ */
+ public JSONWriter array() throws JSONException {
+ if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') {
+ this.push(null);
+ this.append("[");
+ this.comma = false;
+ return this;
+ }
+ throw new JSONException("Misplaced array.");
+ }
+
+ /**
+ * End something.
+ * @param m Mode
+ * @param c Closing character
+ * @return this
+ * @throws JSONException If unbalanced.
+ */
+ private JSONWriter end(char m, char c) throws JSONException {
+ if (this.mode != m) {
+ throw new JSONException(m == 'a'
+ ? "Misplaced endArray."
+ : "Misplaced endObject.");
+ }
+ this.pop(m);
+ try {
+ this.writer.append(c);
+ } catch (IOException e) {
+ // Android as of API 25 does not support this exception constructor
+ // however we won't worry about it. If an exception is happening here
+ // it will just throw a "Method not found" exception instead.
+ throw new JSONException(e);
+ }
+ this.comma = true;
+ return this;
+ }
+
+ /**
+ * End an array. This method most be called to balance calls to
+ * array
.
+ * @return this
+ * @throws JSONException If incorrectly nested.
+ */
+ public JSONWriter endArray() throws JSONException {
+ return this.end('a', ']');
+ }
+
+ /**
+ * End an object. This method most be called to balance calls to
+ * object
.
+ * @return this
+ * @throws JSONException If incorrectly nested.
+ */
+ public JSONWriter endObject() throws JSONException {
+ return this.end('k', '}');
+ }
+
+ /**
+ * Append a key. The key will be associated with the next value. In an
+ * object, every value must be preceded by a key.
+ * @param string A key string.
+ * @return this
+ * @throws JSONException If the key is out of place. For example, keys
+ * do not belong in arrays or if the key is null.
+ */
+ public JSONWriter key(String string) throws JSONException {
+ if (string == null) {
+ throw new JSONException("Null key.");
+ }
+ if (this.mode == 'k') {
+ try {
+ JSONObject topObject = this.stack[this.top - 1];
+ // don't use the built in putOnce method to maintain Android support
+ if(topObject.has(string)) {
+ throw new JSONException("Duplicate key \"" + string + "\"");
+ }
+ topObject.put(string, true);
+ if (this.comma) {
+ this.writer.append(',');
+ }
+ this.writer.append(JSONObject.quote(string));
+ this.writer.append(':');
+ this.comma = false;
+ this.mode = 'o';
+ return this;
+ } catch (IOException e) {
+ // Android as of API 25 does not support this exception constructor
+ // however we won't worry about it. If an exception is happening here
+ // it will just throw a "Method not found" exception instead.
+ throw new JSONException(e);
+ }
+ }
+ throw new JSONException("Misplaced key.");
+ }
+
+
+ /**
+ * Begin appending a new object. All keys and values until the balancing
+ * endObject
will be appended to this object. The
+ * endObject
method must be called to mark the object's end.
+ * @return this
+ * @throws JSONException If the nesting is too deep, or if the object is
+ * started in the wrong place (for example as a key or after the end of the
+ * outermost array or object).
+ */
+ public JSONWriter object() throws JSONException {
+ if (this.mode == 'i') {
+ this.mode = 'o';
+ }
+ if (this.mode == 'o' || this.mode == 'a') {
+ this.append("{");
+ this.push(new JSONObject());
+ this.comma = false;
+ return this;
+ }
+ throw new JSONException("Misplaced object.");
+
+ }
+
+
+ /**
+ * Pop an array or object scope.
+ * @param c The scope to close.
+ * @throws JSONException If nesting is wrong.
+ */
+ private void pop(char c) throws JSONException {
+ if (this.top <= 0) {
+ throw new JSONException("Nesting error.");
+ }
+ char m = this.stack[this.top - 1] == null ? 'a' : 'k';
+ if (m != c) {
+ throw new JSONException("Nesting error.");
+ }
+ this.top -= 1;
+ this.mode = this.top == 0
+ ? 'd'
+ : this.stack[this.top - 1] == null
+ ? 'a'
+ : 'k';
+ }
+
+ /**
+ * Push an array or object scope.
+ * @param jo The scope to open.
+ * @throws JSONException If nesting is too deep.
+ */
+ private void push(JSONObject jo) throws JSONException {
+ if (this.top >= maxdepth) {
+ throw new JSONException("Nesting too deep.");
+ }
+ this.stack[this.top] = jo;
+ this.mode = jo == null ? 'a' : 'k';
+ this.top += 1;
+ }
+
+ /**
+ * Make a JSON text of an Object value. If the object has an
+ * value.toJSONString() method, then that method will be used to produce the
+ * JSON text. The method is required to produce a strictly conforming text.
+ * If the object does not contain a toJSONString method (which is the most
+ * common case), then a text will be produced by other means. If the value
+ * is an array or Collection, then a JSONArray will be made from it and its
+ * toJSONString method will be called. If the value is a MAP, then a
+ * JSONObject will be made from it and its toJSONString method will be
+ * called. Otherwise, the value's toString method will be called, and the
+ * result will be quoted.
+ *
+ *
+ * Warning: This method assumes that the data structure is acyclical.
+ *
+ * @param value
+ * The value to be serialized.
+ * @return a printable, displayable, transmittable representation of the
+ * object, beginning with {
(left
+ * brace) and ending with }
(right
+ * brace) .
+ * @throws JSONException
+ * If the value is or contains an invalid number.
+ */
+ public static String valueToString(Object value) throws JSONException {
+ if (value == null || value.equals(null)) {
+ return "null";
+ }
+ if (value instanceof JSONString) {
+ String object;
+ try {
+ object = ((JSONString) value).toJSONString();
+ } catch (Exception e) {
+ throw new JSONException(e);
+ }
+ if (object != null) {
+ return object;
+ }
+ throw new JSONException("Bad value from toJSONString: " + object);
+ }
+ if (value instanceof Number) {
+ // not all Numbers may match actual JSON Numbers. i.e. Fractions or Complex
+ final String numberAsString = JSONObject.numberToString((Number) value);
+ if(JSONObject.NUMBER_PATTERN.matcher(numberAsString).matches()) {
+ // Close enough to a JSON number that we will return it unquoted
+ return numberAsString;
+ }
+ // The Number value is not a valid JSON number.
+ // Instead we will quote it as a string
+ return JSONObject.quote(numberAsString);
+ }
+ if (value instanceof Boolean || value instanceof JSONObject
+ || value instanceof JSONArray) {
+ return value.toString();
+ }
+ if (value instanceof Map) {
+ Map, ?> map = (Map, ?>) value;
+ return new JSONObject(map).toString();
+ }
+ if (value instanceof Collection) {
+ Collection> coll = (Collection>) value;
+ return new JSONArray(coll).toString();
+ }
+ if (value.getClass().isArray()) {
+ return new JSONArray(value).toString();
+ }
+ if(value instanceof Enum>){
+ return JSONObject.quote(((Enum>)value).name());
+ }
+ return JSONObject.quote(value.toString());
+ }
+
+ /**
+ * Append either the value true
or the value
+ * false
.
+ * @param b A boolean.
+ * @return this
+ * @throws JSONException if a called function has an error
+ */
+ public JSONWriter value(boolean b) throws JSONException {
+ return this.append(b ? "true" : "false");
+ }
+
+ /**
+ * Append a double value.
+ * @param d A double.
+ * @return this
+ * @throws JSONException If the number is not finite.
+ */
+ public JSONWriter value(double d) throws JSONException {
+ return this.value(Double.valueOf(d));
+ }
+
+ /**
+ * Append a long value.
+ * @param l A long.
+ * @return this
+ * @throws JSONException if a called function has an error
+ */
+ public JSONWriter value(long l) throws JSONException {
+ return this.append(Long.toString(l));
+ }
+
+
+ /**
+ * Append an object value.
+ * @param object The object to append. It can be null, or a Boolean, Number,
+ * String, JSONObject, or JSONArray, or an object that implements JSONString.
+ * @return this
+ * @throws JSONException If the value is out of sequence.
+ */
+ public JSONWriter value(Object object) throws JSONException {
+ return this.append(valueToString(object));
+ }
+}
diff --git a/Property.java b/src/main/java/org/json/Property.java
similarity index 81%
rename from Property.java
rename to src/main/java/org/json/Property.java
index 51b97ed..7caeebb 100644
--- a/Property.java
+++ b/src/main/java/org/json/Property.java
@@ -25,7 +25,6 @@ SOFTWARE.
*/
import java.util.Enumeration;
-import java.util.Map.Entry;
import java.util.Properties;
/**
@@ -38,10 +37,12 @@ public class 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
* @return JSONObject
- * @throws JSONException
+ * @throws JSONException if a called function has an error
*/
public static JSONObject toJSONObject(java.util.Properties properties) throws JSONException {
- JSONObject jo = new JSONObject(properties == null ? 0 : properties.size());
+ // can't use the new constructor for Android support
+ // JSONObject jo = new JSONObject(properties == null ? 0 : properties.size());
+ JSONObject jo = new JSONObject();
if (properties != null && !properties.isEmpty()) {
Enumeration> enumProperties = properties.propertyNames();
while(enumProperties.hasMoreElements()) {
@@ -56,15 +57,16 @@ public class Property {
* Converts the JSONObject into a property file object.
* @param jo JSONObject
* @return java.util.Properties
- * @throws JSONException
+ * @throws JSONException if a called function has an error
*/
public static Properties toProperties(JSONObject jo) throws JSONException {
Properties properties = new Properties();
if (jo != null) {
- for (final Entry entry : jo.entrySet()) {
- Object value = entry.getValue();
+ // Don't use the new entrySet API to maintain Android support
+ for (final String key : jo.keySet()) {
+ Object value = jo.opt(key);
if (!JSONObject.NULL.equals(value)) {
- properties.put(entry.getKey(), value.toString());
+ properties.put(key, value.toString());
}
}
}
diff --git a/XML.java b/src/main/java/org/json/XML.java
similarity index 56%
rename from XML.java
rename to src/main/java/org/json/XML.java
index faa5b65..c09fb03 100644
--- a/XML.java
+++ b/src/main/java/org/json/XML.java
@@ -24,18 +24,22 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
+import java.io.Reader;
+import java.io.StringReader;
+import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.Iterator;
-import java.util.Map.Entry;
/**
* This provides static methods to convert an XML text into a JSONObject, and to
* covert a JSONObject into an XML text.
- *
+ *
* @author JSON.org
* @version 2016-08-10
*/
@SuppressWarnings("boxing")
public class XML {
+
/** The Character '&'. */
public static final Character AMP = '&';
@@ -48,7 +52,7 @@ public class XML {
/** The Character '='. */
public static final Character EQ = '=';
- /** The Character '>'. */
+ /** The Character {@code '>'. } */
public static final Character GT = '>';
/** The Character '<'. */
@@ -62,7 +66,12 @@ public class XML {
/** The Character '/'. */
public static final Character SLASH = '/';
-
+
+ /**
+ * Null attribute name
+ */
+ public static final String NULL_ATTR = "xsi:nil";
+
/**
* Creates an iterator for navigating Code Points in a string instead of
* characters. Once Java7 support is dropped, this can be replaced with
@@ -70,7 +79,7 @@ public class XML {
* string.codePoints()
*
* which is available in Java8 and above.
- *
+ *
* @see http://stackoverflow.com/a/21791059/6030888
*/
@@ -105,15 +114,15 @@ public class XML {
/**
* Replace special characters with XML escapes:
- *
- *
- * & (ampersand) is replaced by &
- * < (less than) is replaced by <
- * > (greater than) is replaced by >
- * " (double quote) is replaced by "
- * ' (single quote / apostrophe) is replaced by '
- *
- *
+ *
+ * {@code
+ * & (ampersand) is replaced by &
+ * < (less than) is replaced by <
+ * > (greater than) is replaced by >
+ * " (double quote) is replaced by "
+ * ' (single quote / apostrophe) is replaced by '
+ * }
+ *
* @param string
* The string to be escaped.
* @return The escaped string.
@@ -149,17 +158,17 @@ public class XML {
}
return sb.toString();
}
-
+
/**
* @param cp code point to test
* @return true if the code point is not valid for an XML
*/
private static boolean mustEscape(int cp) {
/* Valid range from https://www.w3.org/TR/REC-xml/#charsets
- *
- * #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
- *
- * any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.
+ *
+ * #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
+ *
+ * any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.
*/
// isISOControl is true when (cp >= 0 && cp <= 0x1F) || (cp >= 0x7F && cp <= 0x9F)
// all ISO control characters are out of range except tabs and new lines
@@ -178,7 +187,7 @@ public class XML {
/**
* Removes XML escapes from the string.
- *
+ *
* @param string
* string to remove escapes from
* @return string with converted entities
@@ -210,7 +219,7 @@ public class XML {
/**
* Throw an exception if the string contains whitespace. Whitespace is not
* allowed in tagNames and attributes.
- *
+ *
* @param string
* A string.
* @throws JSONException Thrown if the string contains whitespace or is empty.
@@ -230,7 +239,7 @@ public class XML {
/**
* Scan the content following the named tag, attaching it to the context.
- *
+ *
* @param x
* The XMLTokener containing the source string.
* @param context
@@ -240,11 +249,11 @@ public class XML {
* @return true if the close tag is processed.
* @throws JSONException
*/
- private static boolean parse(XMLTokener x, JSONObject context, String name, boolean keepStrings)
+ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config)
throws JSONException {
char c;
int i;
- JSONObject jsonobject = null;
+ JSONObject jsonObject = null;
String string;
String tagName;
Object token;
@@ -277,7 +286,7 @@ public class XML {
if (x.next() == '[') {
string = x.nextCDATA();
if (string.length() > 0) {
- context.accumulate("content", string);
+ context.accumulate(config.cDataTagName, string);
}
return false;
}
@@ -325,7 +334,8 @@ public class XML {
} else {
tagName = (String) token;
token = null;
- jsonobject = new JSONObject();
+ jsonObject = new JSONObject();
+ boolean nilAttributeFound = false;
for (;;) {
if (token == null) {
token = x.nextToken();
@@ -339,11 +349,20 @@ public class XML {
if (!(token instanceof String)) {
throw x.syntaxError("Missing value");
}
- jsonobject.accumulate(string,
- keepStrings ? ((String)token) : stringToValue((String) token));
+
+ if (config.convertNilAttributeToNull
+ && NULL_ATTR.equals(string)
+ && Boolean.parseBoolean((String) token)) {
+ nilAttributeFound = true;
+ } else if (!nilAttributeFound) {
+ jsonObject.accumulate(string,
+ config.keepStrings
+ ? ((String) token)
+ : stringToValue((String) token));
+ }
token = null;
} else {
- jsonobject.accumulate(string, "");
+ jsonObject.accumulate(string, "");
}
@@ -352,8 +371,10 @@ public class XML {
if (x.nextToken() != GT) {
throw x.syntaxError("Misshaped tag");
}
- if (jsonobject.length() > 0) {
- context.accumulate(tagName, jsonobject);
+ if (nilAttributeFound) {
+ context.accumulate(tagName, JSONObject.NULL);
+ } else if (jsonObject.length() > 0) {
+ context.accumulate(tagName, jsonObject);
} else {
context.accumulate(tagName, "");
}
@@ -371,21 +392,20 @@ public class XML {
} else if (token instanceof String) {
string = (String) token;
if (string.length() > 0) {
- jsonobject.accumulate("content",
- keepStrings ? string : stringToValue(string));
+ jsonObject.accumulate(config.cDataTagName,
+ config.keepStrings ? string : stringToValue(string));
}
} else if (token == LT) {
// Nested element
- if (parse(x, jsonobject, tagName,keepStrings)) {
- if (jsonobject.length() == 0) {
+ if (parse(x, jsonObject, tagName, config)) {
+ if (jsonObject.length() == 0) {
context.accumulate(tagName, "");
- } else if (jsonobject.length() == 1
- && jsonobject.opt("content") != null) {
- context.accumulate(tagName,
- jsonobject.opt("content"));
+ } else if (jsonObject.length() == 1
+ && jsonObject.opt(config.cDataTagName) != null) {
+ context.accumulate(tagName, jsonObject.opt(config.cDataTagName));
} else {
- context.accumulate(tagName, jsonobject);
+ context.accumulate(tagName, jsonObject);
}
return false;
}
@@ -397,17 +417,118 @@ public class XML {
}
}
}
-
+
/**
- * This method is the same as {@link JSONObject#stringToValue(String)}
- * except that this also tries to unescape String values.
- *
+ * 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) {
- return JSONObject.stringToValue(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;
}
+
+ /**
+ * direct copy of {@link JSONObject#stringToNumber(String)} to maintain Android support.
+ */
+ private static Number stringToNumber(final String val) throws NumberFormatException {
+ char initial = val.charAt(0);
+ if ((initial >= '0' && initial <= '9') || initial == '-') {
+ // decimal representation
+ if (isDecimalNotation(val)) {
+ // Use a BigDecimal all the time so we keep the original
+ // representation. BigDecimal doesn't support -0.0, ensure we
+ // keep that by forcing a decimal.
+ try {
+ BigDecimal bd = new BigDecimal(val);
+ if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) {
+ return Double.valueOf(-0.0);
+ }
+ return bd;
+ } catch (NumberFormatException retryAsDouble) {
+ // this is to support "Hex Floats" like this: 0x1.0P-1074
+ try {
+ Double d = Double.valueOf(val);
+ if(d.isNaN() || d.isInfinite()) {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ return d;
+ } catch (NumberFormatException ignore) {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ }
+ }
+ // block items like 00 01 etc. Java number parsers treat these as Octal.
+ if(initial == '0' && val.length() > 1) {
+ char at1 = val.charAt(1);
+ if(at1 >= '0' && at1 <= '9') {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ } else if (initial == '-' && val.length() > 2) {
+ char at1 = val.charAt(1);
+ char at2 = val.charAt(2);
+ if(at1 == '0' && at2 >= '0' && at2 <= '9') {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ }
+ // integer representation.
+ // This will narrow any values to the smallest reasonable Object representation
+ // (Integer, Long, or BigInteger)
+
+ // BigInteger down conversion: We use a similar bitLenth compare as
+ // BigInteger#intValueExact uses. Increases GC, but objects hold
+ // only what they need. i.e. Less runtime overhead if the value is
+ // long lived.
+ BigInteger bi = new BigInteger(val);
+ if(bi.bitLength() <= 31){
+ return Integer.valueOf(bi.intValue());
+ }
+ if(bi.bitLength() <= 63){
+ return Long.valueOf(bi.longValue());
+ }
+ return bi;
+ }
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+
+ /**
+ * direct copy of {@link JSONObject#isDecimalNotation(String)} to maintain Android support.
+ */
+ private static boolean isDecimalNotation(final String val) {
+ return val.indexOf('.') > -1 || val.indexOf('e') > -1
+ || val.indexOf('E') > -1 || "-0".equals(val);
+ }
+
/**
* Convert a well-formed (but not necessarily valid) XML string into a
@@ -417,18 +538,98 @@ public class XML {
* name/value pairs and arrays of values. JSON does not does not like to
* distinguish between elements and attributes. Sequences of similar
* elements are represented as JSONArrays. Content text may be placed in a
- * "content" member. Comments, prologs, DTDs, and <[ [ ]]>
+ * "content" member. Comments, prologs, DTDs, and {@code
+ * <[ [ ]]>}
* are ignored.
- *
+ *
* @param string
* The source string.
* @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) throws JSONException {
- return toJSONObject(string, false);
+ return toJSONObject(string, 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.
+ *
+ * @param reader The XML source reader.
+ * @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) throws JSONException {
+ 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 values 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.
+ *
+ * @param reader The XML source reader.
+ * @param keepStrings If true, then values will not be coerced into boolean
+ * or 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 keepStrings) throws JSONException {
+ if(keepStrings) {
+ return toJSONObject(reader, XMLParserConfiguration.KEEP_STRINGS);
+ }
+ 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 values 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.
+ *
+ * @param reader The XML source reader.
+ * @param config Configuration options for the parser
+ * @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, XMLParserConfiguration config) throws JSONException {
+ JSONObject jo = new JSONObject();
+ XMLTokener x = new XMLTokener(reader);
+ while (x.more()) {
+ x.skipPast("<");
+ if(x.more()) {
+ parse(x, jo, null, config);
+ }
+ }
+ return jo;
+ }
/**
* Convert a well-formed (but not necessarily valid) XML string into a
@@ -438,12 +639,13 @@ public class XML {
* name/value pairs and arrays of values. JSON does not does not like to
* distinguish between elements and attributes. Sequences of similar
* elements are represented as JSONArrays. Content text may be placed in a
- * "content" member. Comments, prologs, DTDs, and <[ [ ]]>
+ * "content" member. Comments, prologs, DTDs, and {@code
+ * <[ [ ]]>}
* are ignored.
- *
+ *
* All values 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.
- *
+ *
* @param string
* The source string.
* @param keepStrings If true, then values will not be coerced into boolean
@@ -452,28 +654,49 @@ public class XML {
* @throws JSONException Thrown if there is an errors while parsing the string
*/
public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException {
- JSONObject jo = new JSONObject();
- XMLTokener x = new XMLTokener(string);
- while (x.more() && x.skipPast("<")) {
- parse(x, jo, null, keepStrings);
- }
- return jo;
+ return toJSONObject(new StringReader(string), keepStrings);
}
+
+ /**
+ * 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.
+ *
+ * All values 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.
+ *
+ * @param string
+ * The source string.
+ * @param config Configuration options for the parser.
+ * @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, XMLParserConfiguration config) throws JSONException {
+ return toJSONObject(new StringReader(string), config);
+ }
+
/**
* Convert a JSONObject into a well-formed, element-normal XML string.
- *
+ *
* @param object
* A JSONObject.
* @return A string.
* @throws JSONException Thrown if there is an error parsing the string
*/
public static String toString(Object object) throws JSONException {
- return toString(object, null);
+ return toString(object, null, XMLParserConfiguration.ORIGINAL);
}
/**
* Convert a JSONObject into a well-formed, element-normal XML string.
- *
+ *
* @param object
* A JSONObject.
* @param tagName
@@ -481,7 +704,23 @@ public class XML {
* @return A string.
* @throws JSONException Thrown if there is an error parsing the string
*/
- public static String toString(final Object object, final String tagName)
+ public static String toString(final Object object, final String tagName) {
+ return toString(object, tagName, XMLParserConfiguration.ORIGINAL);
+ }
+
+ /**
+ * Convert a JSONObject into a well-formed, 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.
+ * @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)
throws JSONException {
StringBuilder sb = new StringBuilder();
JSONArray ja;
@@ -498,10 +737,10 @@ public class XML {
}
// Loop thru the keys.
+ // don't use the new entrySet accessor to maintain Android Support
jo = (JSONObject) object;
- for (final Entry entry : jo.entrySet()) {
- final String key = entry.getKey();
- Object value = entry.getValue();
+ for (final String key : jo.keySet()) {
+ Object value = jo.opt(key);
if (value == null) {
value = "";
} else if (value.getClass().isArray()) {
@@ -509,16 +748,17 @@ public class XML {
}
// Emit content in body
- if ("content".equals(key)) {
+ if (key.equals(config.cDataTagName)) {
if (value instanceof JSONArray) {
ja = (JSONArray) value;
- int i = 0;
- for (Object val : ja) {
+ int jaLength = ja.length();
+ // don't use the new iterator API to maintain support for Android
+ for (int i = 0; i < jaLength; i++) {
if (i > 0) {
sb.append('\n');
}
+ Object val = ja.opt(i);
sb.append(escape(val.toString()));
- i++;
}
} else {
sb.append(escape(value.toString()));
@@ -528,17 +768,20 @@ public class XML {
} else if (value instanceof JSONArray) {
ja = (JSONArray) value;
- for (Object val : ja) {
+ int jaLength = ja.length();
+ // don't use the new iterator API to maintain support for Android
+ for (int i = 0; i < jaLength; i++) {
+ Object val = ja.opt(i);
if (val instanceof JSONArray) {
sb.append('<');
sb.append(key);
sb.append('>');
- sb.append(toString(val));
+ sb.append(toString(val, null, config));
sb.append("");
sb.append(key);
sb.append('>');
} else {
- sb.append(toString(val, key));
+ sb.append(toString(val, key, config));
}
}
} else if ("".equals(value)) {
@@ -549,12 +792,12 @@ public class XML {
// Emit a new tag
} else {
- sb.append(toString(value, key));
+ sb.append(toString(value, key, config));
}
}
if (tagName != null) {
- // Emit the close tag
+ // Emit the close tag
sb.append("");
sb.append(tagName);
sb.append('>');
@@ -569,11 +812,14 @@ public class XML {
} else {
ja = (JSONArray) object;
}
- for (Object val : ja) {
+ int jaLength = ja.length();
+ // don't use the new iterator API to maintain support for Android
+ for (int i = 0; i < jaLength; i++) {
+ Object val = ja.opt(i);
// 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));
+ sb.append(toString(val, tagName == null ? "array" : tagName, config));
}
return sb.toString();
}
diff --git a/src/main/java/org/json/XMLParserConfiguration.java b/src/main/java/org/json/XMLParserConfiguration.java
new file mode 100644
index 0000000..c0186f7
--- /dev/null
+++ b/src/main/java/org/json/XMLParserConfiguration.java
@@ -0,0 +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.
+*/
+
+/**
+ * Configuration object for the XML parser.
+ * @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);
+ /**
+ * 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)
+ */
+ public final boolean keepStrings;
+ /**
+ * The name of the key in a JSON Object that indicates a CDATA section. Historically this has
+ * been the value "content" but can be changed. Use null
to indicate no CDATA
+ * processing.
+ */
+ public final 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)
+ */
+ public final boolean convertNilAttributeToNull;
+
+ /**
+ * Default parser configuration. Does not keep strings, and the CDATA Tag Name is "content".
+ */
+ public XMLParserConfiguration () {
+ this(false, "content", false);
+ }
+
+ /**
+ * 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.
+ */
+ public XMLParserConfiguration (final boolean keepStrings) {
+ this(keepStrings, "content", false);
+ }
+
+ /**
+ * 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
+ * to use that value as the JSONObject key name to process as CDATA.
+ */
+ public XMLParserConfiguration (final String cDataTagName) {
+ this(false, cDataTagName, false);
+ }
+
+ /**
+ * 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
+ * to use that value as the JSONObject key name to process as CDATA.
+ */
+ public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName) {
+ this.keepStrings = keepStrings;
+ this.cDataTagName = cDataTagName;
+ this.convertNilAttributeToNull = false;
+ }
+
+ /**
+ * 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}.
+ */
+ public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, final boolean convertNilAttributeToNull) {
+ this.keepStrings = keepStrings;
+ this.cDataTagName = cDataTagName;
+ this.convertNilAttributeToNull = convertNilAttributeToNull;
+ }
+}
diff --git a/XMLTokener.java b/src/main/java/org/json/XMLTokener.java
similarity index 89%
rename from XMLTokener.java
rename to src/main/java/org/json/XMLTokener.java
index fb54da3..0ecdb4f 100644
--- a/XMLTokener.java
+++ b/src/main/java/org/json/XMLTokener.java
@@ -24,6 +24,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
+import java.io.Reader;
+
/**
* The XMLTokener extends the JSONTokener to provide additional methods
* for the parsing of XML texts.
@@ -47,6 +49,14 @@ public class XMLTokener extends JSONTokener {
entity.put("quot", XML.QUOT);
}
+ /**
+ * Construct an XMLTokener from a Reader.
+ * @param r A source reader.
+ */
+ public XMLTokener(Reader r) {
+ super(r);
+ }
+
/**
* Construct an XMLTokener from a string.
* @param s A source string.
@@ -80,12 +90,13 @@ public class XMLTokener extends JSONTokener {
/**
* Get the next XML outer token, trimming whitespace. There are two kinds
- * of tokens: the '<' character which begins a markup tag, and the content
+ * of tokens: the {@code '<' } character which begins a markup
+ * tag, and the content
* text between markup tags.
*
- * @return A string, or a '<' Character, or null if there is no more
- * source text.
- * @throws JSONException
+ * @return A string, or a {@code '<' } Character, or null if
+ * there is no more source text.
+ * @throws JSONException if a called function has an error
*/
public Object nextContent() throws JSONException {
char c;
@@ -119,13 +130,15 @@ public class XMLTokener extends JSONTokener {
/**
+ * {@code
* Return the next entity. These entities are translated to Characters:
- * & ' > < "
.
+ * & ' > < ".
+ * }
* @param ampersand An ampersand character.
* @return A Character or an entity String if the entity is not recognized.
* @throws JSONException If missing ';' in XML entity.
*/
- public Object nextEntity(char ampersand) throws JSONException {
+ public Object nextEntity(@SuppressWarnings("unused") char ampersand) throws JSONException {
StringBuilder sb = new StringBuilder();
for (;;) {
char c = next();
@@ -142,7 +155,7 @@ public class XMLTokener extends JSONTokener {
}
/**
- * Unescapes an XML entity encoding;
+ * Unescape an XML entity encoding;
* @param e entity (only the actual entity value, not the preceding & or ending ;
* @return
*/
@@ -173,11 +186,14 @@ public class XMLTokener extends JSONTokener {
/**
+ * {@code
* Returns the next XML meta token. This is used for skipping over
* and ...?> structures.
- * @return Syntax characters (< > / = ! ?
) are returned as
+ * }
+ * @return {@code Syntax characters (< > / = ! ?) are returned as
* Character, and strings and names are returned as Boolean. We don't care
* what the values actually are.
+ * }
* @throws JSONException If a string is not properly closed or if the XML
* is badly structured.
*/
@@ -222,6 +238,7 @@ public class XMLTokener extends JSONTokener {
}
switch (c) {
case 0:
+ throw syntaxError("Unterminated string");
case '<':
case '>':
case '/':
@@ -239,10 +256,12 @@ public class XMLTokener extends JSONTokener {
/**
+ * {@code
* Get the next XML Token. These tokens are found inside of angle
- * brackets. It may be one of these characters: / > = ! ?
or it
+ * brackets. It may be one of these characters: / > = ! ? or it
* may be a string wrapped in single quotes or double quotes, or it may be a
* name.
+ * }
* @return a String or a Character.
* @throws JSONException If the XML is not well formed.
*/
@@ -326,9 +345,11 @@ public class XMLTokener extends JSONTokener {
* Skip characters until past the requested string.
* If it is not found, we are left at the end of the source with a result of false.
* @param to A string to skip past.
- * @throws JSONException
*/
- public boolean skipPast(String to) throws JSONException {
+ // The Android implementation of JSONTokener has a public method of public void skipPast(String to)
+ // even though ours does not have that method, to have API compatibility, our method in the subclass
+ // should match.
+ public void skipPast(String to) {
boolean b;
char c;
int i;
@@ -345,7 +366,7 @@ public class XMLTokener extends JSONTokener {
for (i = 0; i < length; i += 1) {
c = next();
if (c == 0) {
- return false;
+ return;
}
circle[i] = c;
}
@@ -372,14 +393,14 @@ public class XMLTokener extends JSONTokener {
/* If we exit the loop with b intact, then victory is ours. */
if (b) {
- return true;
+ return;
}
/* Get the next character. If there isn't one, then defeat is ours. */
c = next();
if (c == 0) {
- return false;
+ return;
}
/*
* Shove the character in the circle buffer and advance the
diff --git a/src/test/java/org/json/junit/CDLTest.java b/src/test/java/org/json/junit/CDLTest.java
new file mode 100644
index 0000000..48586b7
--- /dev/null
+++ b/src/test/java/org/json/junit/CDLTest.java
@@ -0,0 +1,324 @@
+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.
+*/
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONArray;
+import org.json.CDL;
+
+/**
+ * Tests for CDL.java.
+ * CDL provides an application level API, but it is not used by the
+ * reference app. To test it, strings will be converted to JSON-Java classes
+ * and then converted back.
+ */
+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"
+ );
+
+ /**
+ * CDL.toJSONArray() adds all values as strings, with no filtering or
+ * conversions. For testing, this means that the expected JSONObject
+ * 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}]");
+
+ /**
+ * Attempts to create a JSONArray from a null string.
+ * Expect a NullPointerException.
+ */
+ @Test(expected=NullPointerException.class)
+ public void exceptionOnNullString() {
+ String nullStr = null;
+ CDL.toJSONArray(nullStr);
+ }
+
+ /**
+ * Attempts to create a JSONArray from a string with unbalanced quotes
+ * in column title line. Expects a JSONException.
+ */
+ @Test
+ public void unbalancedQuoteInName() {
+ String badLine = "Col1, \"Col2\nVal1, Val2";
+ try {
+ CDL.toJSONArray(badLine);
+ fail("Expecting an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Missing close quote '\"'. at 12 [character 0 line 2]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Attempts to create a JSONArray from a string with unbalanced quotes
+ * in value line. Expects a JSONException.
+ */
+ @Test
+ public void unbalancedQuoteInValue() {
+ String badLine = "Col1, Col2\n\"Val1, Val2";
+ try {
+ CDL.toJSONArray(badLine);
+ fail("Expecting an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Missing close quote '\"'. at 22 [character 11 line 2]",
+ e.getMessage());
+
+ }
+ }
+
+ /**
+ * Attempts to create a JSONArray from a string with null char
+ * in column title line. Expects a JSONException.
+ */
+ @Test
+ public void nullInName() {
+ String badLine = "C\0ol1, Col2\nVal1, Val2";
+ try {
+ CDL.toJSONArray(badLine);
+ fail("Expecting an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Bad character 'o' (111). at 2 [character 3 line 1]",
+ e.getMessage());
+
+ }
+ }
+
+ /**
+ * Attempt to create a JSONArray with unbalanced quotes and a properly escaped doubled quote.
+ * Expects a JSONException.
+ */
+ @Test
+ public void unbalancedEscapedQuote(){
+ String badLine = "Col1, Col2\n\"Val1, \"\"Val2\"\"";
+ try {
+ CDL.toJSONArray(badLine);
+ fail("Expecting an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Missing close quote '\"'. at 26 [character 15 line 2]",
+ e.getMessage());
+
+ }
+ }
+
+ /**
+ * Assert that there is no error for a single escaped quote within a properly embedded quote.
+ */
+ @Test
+ public void singleEscapedQuote(){
+ String singleEscape = "Col1, Col2\nVal1, \"\"\"Val2\"";
+ JSONArray jsonArray = CDL.toJSONArray(singleEscape);
+
+ String cdlStr = CDL.toString(jsonArray);
+ assertTrue(cdlStr.contains("Col1"));
+ assertTrue(cdlStr.contains("Col2"));
+ assertTrue(cdlStr.contains("Val1"));
+ assertTrue(cdlStr.contains("\"Val2"));
+ }
+
+ /**
+ * Assert that there is no error for a single escaped quote within a properly
+ * embedded quote when not the last value.
+ */
+ @Test
+ public void singleEscapedQuoteMiddleString(){
+ String singleEscape = "Col1, Col2\nVal1, \"\"\"Val2\"\nVal 3,Val 4";
+ JSONArray jsonArray = CDL.toJSONArray(singleEscape);
+
+ String cdlStr = CDL.toString(jsonArray);
+ assertTrue(cdlStr.contains("Col1"));
+ assertTrue(cdlStr.contains("Col2"));
+ assertTrue(cdlStr.contains("Val1"));
+ assertTrue(cdlStr.contains("\"Val2"));
+ }
+
+ /**
+ * Attempt to create a JSONArray with an escape quote and no enclosing quotes.
+ * Expects a JSONException.
+ */
+ @Test
+ public void badEscapedQuote(){
+ String badLine = "Col1, Col2\nVal1, \"\"Val2";
+
+ try {
+ CDL.toJSONArray(badLine);
+ fail("Expecting an exception");
+ } catch (JSONException e) {
+ System.out.println("Message" + e.getMessage());
+ assertEquals("Expecting an exception message",
+ "Bad character 'V' (86). at 20 [character 9 line 2]",
+ e.getMessage());
+
+ }
+
+ }
+
+ /**
+ * call toString with a null array
+ */
+ @Test(expected=NullPointerException.class)
+ public void nullJSONArrayToString() {
+ CDL.toString((JSONArray)null);
+ }
+
+ /**
+ * Create a JSONArray from an empty string
+ */
+ @Test
+ public void emptyString() {
+ String emptyStr = "";
+ JSONArray jsonArray = CDL.toJSONArray(emptyStr);
+ assertTrue("CDL should return null when the input string is empty",
+ jsonArray == null);
+ }
+
+ /**
+ * Create a JSONArray with only 1 row
+ */
+ @Test
+ public void onlyColumnNames() {
+ String columnNameStr = "col1, col2, col3";
+ JSONArray jsonArray = CDL.toJSONArray(columnNameStr);
+ assertNull("CDL should return null when only 1 row is given",
+ jsonArray);
+ }
+
+ /**
+ * Create a JSONArray from string containing only whitespace and commas
+ */
+ @Test
+ public void emptyLinesToJSONArray() {
+ String str = " , , , \n , , , ";
+ JSONArray jsonArray = CDL.toJSONArray(str);
+ assertNull("JSONArray should be null for no content",
+ jsonArray);
+ }
+
+ /**
+ * call toString with a null array
+ */
+ @Test
+ public void emptyJSONArrayToString() {
+ JSONArray jsonArray = new JSONArray();
+ String str = CDL.toString(jsonArray);
+ assertNull("CDL should return null for toString(null)",
+ str);
+ }
+
+ /**
+ * call toString with a null arrays for names and values
+ */
+ @Test
+ public void nullJSONArraysToString() {
+ String str = CDL.toString(null, null);
+ assertNull("CDL should return null for toString(null)",
+ str);
+ }
+
+ /**
+ * Given a JSONArray that was not built by CDL, some chars may be
+ * found that would otherwise be filtered out by CDL.
+ */
+ @Test
+ public void checkSpecialChars() {
+ JSONArray jsonArray = new JSONArray();
+ JSONObject jsonObject = new JSONObject();
+ jsonArray.put(jsonObject);
+ // \r will be filtered from name
+ 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);
+ String cdlStr = CDL.toString(jsonArray);
+ jsonObject = jsonArray.getJSONObject(0);
+ assertTrue(cdlStr.contains("\"Col 1\""));
+ assertTrue(cdlStr.contains("Col 2"));
+ assertTrue(cdlStr.contains("V1"));
+ assertTrue(cdlStr.contains("\"V2\""));
+ }
+
+ /**
+ * Create a JSONArray from a string of lines
+ */
+ @Test
+ public void textToJSONArray() {
+ JSONArray jsonArray = CDL.toJSONArray(this.lines);
+ JSONArray expectedJsonArray = new JSONArray(this.expectedLines);
+ Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
+ }
+
+ /**
+ * Create a JSONArray from a JSONArray of titles and a
+ * string of value lines
+ */
+ @Test
+ public void jsonArrayToJSONArray() {
+ 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}]");
+ Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
+ }
+
+ /**
+ * Create a JSONArray from a string of lines,
+ * then convert to string and then back to JSONArray
+ */
+ @Test
+ public void textToJSONArrayAndBackToString() {
+ JSONArray jsonArray = CDL.toJSONArray(this.lines);
+ String jsonStr = CDL.toString(jsonArray);
+ JSONArray finalJsonArray = CDL.toJSONArray(jsonStr);
+ JSONArray expectedJsonArray = new JSONArray(this.expectedLines);
+ Util.compareActualVsExpectedJsonArrays(finalJsonArray, expectedJsonArray);
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/json/junit/CookieListTest.java b/src/test/java/org/json/junit/CookieListTest.java
new file mode 100644
index 0000000..c3f647f
--- /dev/null
+++ b/src/test/java/org/json/junit/CookieListTest.java
@@ -0,0 +1,210 @@
+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.
+*/
+
+import static org.junit.Assert.*;
+
+import java.util.*;
+
+import org.json.*;
+import org.junit.Test;
+
+import com.jayway.jsonpath.*;
+
+/**
+ * HTTP cookie specification RFC6265: http://tools.ietf.org/html/rfc6265
+ *
+ * A cookie list is a JSONObject whose members are presumed to be cookie
+ * name/value pairs. Entries are unescaped while being added, and escaped in
+ * the toString() output.
+ * Unescaping means to convert %hh hex strings to the ascii equivalent
+ * and converting '+' to ' '.
+ * Escaping converts '+', '%', '=', ';' and ascii control chars to %hh hex strings.
+ *
+ * CookieList should not be considered as just a list of Cookie objects:
+ * - CookieList stores a cookie name/value pair as a single entry; Cookie stores
+ * it as 2 entries (key="name" and key="value").
+ * - CookieList requires multiple name/value pairs as input; Cookie allows the
+ * 'secure' name with no associated value
+ * - CookieList has no special handling for attribute name/value pairs.
+ */
+public class CookieListTest {
+
+ /**
+ * Attempts to create a CookieList from a null string.
+ * Expects a NullPointerException.
+ */
+ @Test(expected=NullPointerException.class)
+ public void nullCookieListException() {
+ String cookieStr = null;
+ CookieList.toJSONObject(cookieStr);
+ }
+
+ /**
+ * Attempts to create a CookieList from a malformed string.
+ * Expects a JSONException.
+ */
+ @Test
+ public void malFormedCookieListException() {
+ String cookieStr = "thisCookieHasNoEqualsChar";
+ try {
+ CookieList.toJSONObject(cookieStr);
+ fail("should throw an exception");
+ } catch (JSONException e) {
+ /**
+ * Not sure of the missing char, but full string compare fails
+ */
+ assertEquals("Expecting an exception message",
+ "Expected '=' and instead saw '' at 25 [character 26 line 1]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Creates a CookieList from an empty string.
+ */
+ @Test
+ public void emptyStringCookieList() {
+ String cookieStr = "";
+ JSONObject jsonObject = CookieList.toJSONObject(cookieStr);
+ assertTrue(jsonObject.isEmpty());
+ }
+
+ /**
+ * CookieList with the simplest cookie - a name/value pair with no delimiter.
+ */
+ @Test
+ public void simpleCookieList() {
+ String cookieStr = "SID=31d4d96e407aad42";
+ JSONObject jsonObject = CookieList.toJSONObject(cookieStr);
+ // validate JSON content
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("Expected 1 top level item", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 1);
+ assertTrue("expected 31d4d96e407aad42", "31d4d96e407aad42".equals(jsonObject.query("/SID")));
+ }
+
+ /**
+ * CookieList with a single a cookie which has a name/value pair and delimiter.
+ */
+ @Test
+ public void simpleCookieListWithDelimiter() {
+ String cookieStr = "SID=31d4d96e407aad42;";
+ JSONObject jsonObject = CookieList.toJSONObject(cookieStr);
+ // validate JSON content
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("Expected 1 top level item", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 1);
+ assertTrue("expected 31d4d96e407aad42", "31d4d96e407aad42".equals(jsonObject.query("/SID")));
+ }
+
+ /**
+ * CookieList with multiple cookies consisting of name/value pairs
+ * with delimiters.
+ */
+ @Test
+ public void multiPartCookieList() {
+ String cookieStr =
+ "name1=myCookieValue1; "+
+ " name2=myCookieValue2;"+
+ "name3=myCookieValue3;"+
+ " name4=myCookieValue4; "+
+ "name5=myCookieValue5;"+
+ " name6=myCookieValue6;";
+ JSONObject jsonObject = CookieList.toJSONObject(cookieStr);
+ // validate JSON content
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("Expected 6 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 6);
+ assertTrue("expected myCookieValue1", "myCookieValue1".equals(jsonObject.query("/name1")));
+ assertTrue("expected myCookieValue2", "myCookieValue2".equals(jsonObject.query("/name2")));
+ assertTrue("expected myCookieValue3", "myCookieValue3".equals(jsonObject.query("/name3")));
+ assertTrue("expected myCookieValue4", "myCookieValue4".equals(jsonObject.query("/name4")));
+ assertTrue("expected myCookieValue5", "myCookieValue5".equals(jsonObject.query("/name5")));
+ assertTrue("expected myCookieValue6", "myCookieValue6".equals(jsonObject.query("/name6")));
+ }
+
+ /**
+ * CookieList from a JSONObject with valid key and null value
+ */
+ @Test
+ public void convertCookieListWithNullValueToString() {
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("key", JSONObject.NULL);
+ String cookieToStr = CookieList.toString(jsonObject);
+ assertTrue("toString() should be empty", "".equals(cookieToStr));
+ }
+
+ /**
+ * CookieList with multiple entries converted to a JSON document.
+ */
+ @Test
+ public void convertCookieListToString() {
+ String cookieStr =
+ "name1=myCookieValue1; "+
+ " name2=myCookieValue2;"+
+ "name3=myCookieValue3;"+
+ " name4=myCookieValue4; "+
+ "name5=myCookieValue5;"+
+ " name6=myCookieValue6;";
+ JSONObject jsonObject = CookieList.toJSONObject(cookieStr);
+ // exercise CookieList.toString()
+ String cookieListString = CookieList.toString(jsonObject);
+ // have to convert it back for validation
+ jsonObject = CookieList.toJSONObject(cookieListString);
+
+ // validate JSON content
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("Expected 6 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 6);
+ assertTrue("expected myCookieValue1", "myCookieValue1".equals(jsonObject.query("/name1")));
+ assertTrue("expected myCookieValue2", "myCookieValue2".equals(jsonObject.query("/name2")));
+ assertTrue("expected myCookieValue3", "myCookieValue3".equals(jsonObject.query("/name3")));
+ assertTrue("expected myCookieValue4", "myCookieValue4".equals(jsonObject.query("/name4")));
+ assertTrue("expected myCookieValue5", "myCookieValue5".equals(jsonObject.query("/name5")));
+ assertTrue("expected myCookieValue6", "myCookieValue6".equals(jsonObject.query("/name6")));
+ }
+
+ /**
+ * CookieList with multiple entries and some '+' chars and URL-encoded
+ * values converted to a JSON document.
+ */
+ @Test
+ public void convertEncodedCookieListToString() {
+ String cookieStr =
+ "name1=myCookieValue1; "+
+ " name2=my+Cookie+Value+2;"+
+ "name3=my%2BCookie%26Value%3B3%3D;"+
+ " name4=my%25CookieValue4; "+
+ "name5=myCookieValue5;"+
+ " name6=myCookieValue6;";
+ JSONObject jsonObject = CookieList.toJSONObject(cookieStr);
+ // validate JSON content
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("Expected 6 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 6);
+ assertTrue("expected myCookieValue1", "myCookieValue1".equals(jsonObject.query("/name1")));
+ assertTrue("expected my Cookie Value 2", "my Cookie Value 2".equals(jsonObject.query("/name2")));
+ assertTrue("expected my+Cookie&Value;3=", "my+Cookie&Value;3=".equals(jsonObject.query("/name3")));
+ assertTrue("expected my%CookieValue4", "my%CookieValue4".equals(jsonObject.query("/name4")));
+ assertTrue("expected my%CookieValue5", "myCookieValue5".equals(jsonObject.query("/name5")));
+ assertTrue("expected myCookieValue6", "myCookieValue6".equals(jsonObject.query("/name6")));
+ }
+}
diff --git a/src/test/java/org/json/junit/CookieTest.java b/src/test/java/org/json/junit/CookieTest.java
new file mode 100644
index 0000000..7e7b62b
--- /dev/null
+++ b/src/test/java/org/json/junit/CookieTest.java
@@ -0,0 +1,262 @@
+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.
+*/
+
+import static org.junit.Assert.*;
+
+import org.json.*;
+import org.junit.Test;
+
+
+/**
+ * HTTP cookie specification: RFC6265
+ *
+ * At its most basic, a cookie is a name=value pair. The value may be subdivided
+ * into other cookies, but that is not tested here. The cookie may also include
+ * certain named attributes, delimited by semicolons.
+ *
+ * The Cookie.toString() method emits certain attributes if present: expires,
+ * domain, path, secure. All but secure are name-value pairs. Other attributes
+ * are not included in the toString() output.
+ *
+ * A JSON-Java encoded cookie escapes '+', '%', '=', ';' with %hh values.
+ */
+public class CookieTest {
+
+ /**
+ * Attempts to create a JSONObject from a null string.
+ * Expects a NullPointerException.
+ */
+ @Test(expected=NullPointerException.class)
+ public void nullCookieException() {
+ String cookieStr = null;
+ Cookie.toJSONObject(cookieStr);
+ }
+
+ /**
+ * Attempts to create a JSONObject from a cookie string with
+ * no '=' char.
+ * Expects a JSONException.
+ */
+ @Test
+ public void malFormedNameValueException() {
+ String cookieStr = "thisCookieHasNoEqualsChar";
+ try {
+ Cookie.toJSONObject(cookieStr);
+ fail("Expecting an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Expected '=' and instead saw '' at 25 [character 26 line 1]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Attempts to create a JSONObject from a cookie string
+ * with embedded ';' char.
+ * Expects a JSONException.
+ */
+ @Test
+ public void booleanAttribute() {
+ String cookieStr = "this=Cookie;myAttribute";
+ JSONObject jo = Cookie.toJSONObject(cookieStr);
+ assertTrue("has key 'name'", jo.has("name"));
+ assertTrue("has key 'value'", jo.has("value"));
+ assertTrue("has key 'myAttribute'", jo.has("myattribute"));
+ }
+
+ /**
+ * Attempts to create a JSONObject from an empty cookie string.
+ * Note: Cookie throws an exception, but CookieList does not.
+ * Expects a JSONException
+ */
+ @Test
+ public void emptyStringCookieException() {
+ String cookieStr = "";
+ try {
+ Cookie.toJSONObject(cookieStr);
+ fail("Expecting an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Cookies must have a 'name'",
+ e.getMessage());
+ }
+ }
+ /**
+ *
+ * Attempts to create a JSONObject from an cookie string where the name is blank.
+ * Note: Cookie throws an exception, but CookieList does not.
+ * Expects a JSONException
+ */
+ @Test
+ public void emptyNameCookieException() {
+ String cookieStr = " = value ";
+ try {
+ Cookie.toJSONObject(cookieStr);
+ fail("Expecting an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Cookies must have a 'name'",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Cookie from a simple name/value pair with no delimiter
+ */
+ @Test
+ public void simpleCookie() {
+ String cookieStr = "SID=31d4d96e407aad42";
+ String expectedCookieStr = "{\"name\":\"SID\",\"value\":\"31d4d96e407aad42\"}";
+ JSONObject jsonObject = Cookie.toJSONObject(cookieStr);
+ JSONObject expectedJsonObject = new JSONObject(expectedCookieStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Store a cookie with all of the supported attributes in a
+ * JSONObject. The secure attribute, which has no value, is treated
+ * as a boolean.
+ */
+ @Test
+ public void multiPartCookie() {
+ String cookieStr =
+ "PH=deleted; "+
+ " expires=Wed, 19-Mar-2014 17:53:53 GMT;"+
+ "path=/; "+
+ " domain=.yahoo.com;"+
+ "secure";
+ String expectedCookieStr =
+ "{"+
+ "\"name\":\"PH\","+
+ "\"value\":\"deleted\","+
+ "\"path\":\"/\","+
+ "\"expires\":\"Wed, 19-Mar-2014 17:53:53 GMT\","+
+ "\"domain\":\".yahoo.com\","+
+ "\"secure\":true"+
+ "}";
+ JSONObject jsonObject = Cookie.toJSONObject(cookieStr);
+ JSONObject expectedJsonObject = new JSONObject(expectedCookieStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Cookie.toString() will emit the non-standard "thiswont=beIncluded"
+ * attribute, and the attribute is still stored in the JSONObject.
+ * This test confirms both behaviors.
+ */
+ @Test
+ public void convertCookieToString() {
+ String cookieStr =
+ "PH=deleted; "+
+ " expires=Wed, 19-Mar-2014 17:53:53 GMT;"+
+ "path=/; "+
+ " domain=.yahoo.com;"+
+ "thisWont=beIncluded;"+
+ "secure";
+ String expectedCookieStr =
+ "{\"thiswont\":\"beIncluded\","+
+ "\"path\":\"/\","+
+ "\"expires\":\"Wed, 19-Mar-2014 17:53:53 GMT\","+
+ "\"domain\":\".yahoo.com\","+
+ "\"name\":\"PH\","+
+ "\"secure\":true,"+
+ "\"value\":\"deleted\"}";
+ // Add the nonstandard attribute to the expected cookie string
+ String expectedDirectCompareCookieStr = expectedCookieStr;
+ // convert all strings into JSONObjects
+ JSONObject jsonObject = Cookie.toJSONObject(cookieStr);
+ JSONObject expectedJsonObject = new JSONObject(expectedCookieStr);
+ JSONObject expectedDirectCompareJsonObject =
+ new JSONObject(expectedDirectCompareCookieStr);
+ // emit the string
+ String cookieToStr = Cookie.toString(jsonObject);
+ // create a final JSONObject from the string
+ JSONObject finalJsonObject = Cookie.toJSONObject(cookieToStr);
+ // JSONObject should contain the nonstandard string
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedDirectCompareJsonObject);
+ // JSONObject -> string -> JSONObject should not contain the nonstandard string
+ Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject);
+ }
+
+ /**
+ * A string may be URL-encoded when converting to JSONObject.
+ * If found, '+' is converted to ' ', and %hh hex strings are converted
+ * to their ascii char equivalents. This test confirms the decoding
+ * behavior.
+ */
+ @Test
+ public void convertEncodedCookieToString() {
+ String cookieStr =
+ "PH=deleted; "+
+ " expires=Wed,+19-Mar-2014+17:53:53+GMT;"+
+ "path=/%2Bthis/is%26/a/spec%3Bsegment%3D; "+
+ " domain=.yahoo.com;"+
+ "secure";
+ String expectedCookieStr =
+ "{\"path\":\"/+this/is&/a/spec;segment=\","+
+ "\"expires\":\"Wed, 19-Mar-2014 17:53:53 GMT\","+
+ "\"domain\":\".yahoo.com\","+
+ "\"name\":\"PH\","+
+ "\"secure\":true,"+
+ "\"value\":\"deleted\"}";
+ JSONObject jsonObject = Cookie.toJSONObject(cookieStr);
+ JSONObject expectedJsonObject = new JSONObject(expectedCookieStr);
+ String cookieToStr = Cookie.toString(jsonObject);
+ JSONObject finalJsonObject = Cookie.toJSONObject(cookieToStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject);
+ }
+
+ /**
+ * A public API method performs a URL encoding for selected chars
+ * in a string. Control chars, '+', '%', '=', ';' are all encoded
+ * as %hh hex strings. The string is also trimmed.
+ * This test confirms that behavior.
+ */
+ @Test
+ public void escapeString() {
+ String str = " +%\r\n\t\b%=;;; ";
+ String expectedStr = "%2b%25%0d%0a%09%08%25%3d%3b%3b%3b";
+ String actualStr = Cookie.escape(str);
+ assertTrue("expect escape() to encode correctly. Actual: " +actualStr+
+ " expected: " +expectedStr, expectedStr.equals(actualStr));
+ }
+
+ /**
+ * A public API method performs URL decoding for strings.
+ * '+' is converted to space and %hh hex strings are converted to
+ * their ascii equivalent values. The string is not trimmed.
+ * This test confirms that behavior.
+ */
+ @Test
+ public void unescapeString() {
+ String str = " +%2b%25%0d%0a%09%08%25%3d%3b%3b%3b+ ";
+ String expectedStr = " +%\r\n\t\b%=;;; ";
+ String actualStr = Cookie.unescape(str);
+ assertTrue("expect unescape() to decode correctly. Actual: " +actualStr+
+ " expected: " +expectedStr, expectedStr.equals(actualStr));
+ }
+}
diff --git a/src/test/java/org/json/junit/EnumTest.java b/src/test/java/org/json/junit/EnumTest.java
new file mode 100644
index 0000000..ed2c87a
--- /dev/null
+++ b/src/test/java/org/json/junit/EnumTest.java
@@ -0,0 +1,453 @@
+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.
+*/
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.json.junit.data.MyEnum;
+import org.json.junit.data.MyEnumClass;
+import org.json.junit.data.MyEnumField;
+import org.junit.Test;
+
+import com.jayway.jsonpath.Configuration;
+import com.jayway.jsonpath.JsonPath;
+
+/**
+ * Enums are not explicitly supported in JSON-Java. But because enums act like
+ * classes, all required behavior is already be present in some form.
+ * These tests explore how enum serialization works with JSON-Java.
+ */
+public class EnumTest {
+
+ /**
+ * To serialize an enum by its getters, use the JSONObject Object constructor.
+ * The JSONObject ctor handles enum like any other bean. A JSONobject
+ * is created whose entries are the getter name/value pairs.
+ */
+ @Test
+ public void jsonObjectFromEnum() {
+ // If there are no getters then the object is empty.
+ MyEnum myEnum = MyEnum.VAL2;
+ JSONObject jsonObject = new JSONObject(myEnum);
+ assertTrue("simple enum has no getters", jsonObject.isEmpty());
+
+ // enum with a getters should create a non-empty object
+ MyEnumField myEnumField = MyEnumField.VAL2;
+ jsonObject = new JSONObject(myEnumField);
+
+ // validate JSON content
+ Object doc = Configuration.defaultConfiguration().jsonProvider()
+ .parse(jsonObject.toString());
+ assertTrue("expecting 2 items in top level object", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 2);
+ assertTrue("expecting val 2", "val 2".equals(jsonObject.query("/value")));
+ assertTrue("expecting 2", Integer.valueOf(2).equals(jsonObject.query("/intVal")));
+
+ /**
+ * class which contains enum instances. Each enum should be stored
+ * in its own JSONObject
+ */
+ MyEnumClass myEnumClass = new MyEnumClass();
+ myEnumClass.setMyEnum(MyEnum.VAL1);
+ myEnumClass.setMyEnumField(MyEnumField.VAL3);
+ jsonObject = new JSONObject(myEnumClass);
+
+ // validate JSON content
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 2 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 2);
+ assertTrue("expected 2 myEnumField items", "VAL3".equals((JsonPath.read(doc, "$.myEnumField"))));
+ assertTrue("expected 0 myEnum items", "VAL1".equals((JsonPath.read(doc, "$.myEnum"))));
+
+ assertTrue("expecting MyEnumField.VAL3", MyEnumField.VAL3.equals(jsonObject.query("/myEnumField")));
+ assertTrue("expecting MyEnum.VAL1", MyEnum.VAL1.equals(jsonObject.query("/myEnum")));
+ }
+
+ /**
+ * To serialize an enum by its set of allowed values, use getNames()
+ * and the the JSONObject Object with names constructor.
+ */
+ @Test
+ public void jsonObjectFromEnumWithNames() {
+ String [] names;
+ JSONObject jsonObject;
+
+ MyEnum myEnum = MyEnum.VAL1;
+ names = JSONObject.getNames(myEnum);
+ // The values will be MyEnum fields
+ jsonObject = new JSONObject(myEnum, names);
+
+ // validate JSON object
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 3 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 3);
+ assertTrue("expected VAL1", MyEnum.VAL1.equals(jsonObject.query("/VAL1")));
+ assertTrue("expected VAL2", MyEnum.VAL2.equals(jsonObject.query("/VAL2")));
+ assertTrue("expected VAL3", MyEnum.VAL3.equals(jsonObject.query("/VAL3")));
+
+ MyEnumField myEnumField = MyEnumField.VAL3;
+ names = JSONObject.getNames(myEnumField);
+ // The values will be MyEnmField fields
+ jsonObject = new JSONObject(myEnumField, names);
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 3 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 3);
+ assertTrue("expected VAL1", MyEnumField.VAL1.equals(jsonObject.query("/VAL1")));
+ assertTrue("expected VAL2", MyEnumField.VAL2.equals(jsonObject.query("/VAL2")));
+ assertTrue("expected VAL3", MyEnumField.VAL3.equals(jsonObject.query("/VAL3")));
+ }
+
+ /**
+ * Verify that enums are handled consistently between JSONArray and JSONObject
+ */
+ @Test
+ public void verifyEnumConsistency(){
+ JSONObject jo = new JSONObject();
+
+ jo.put("value", MyEnumField.VAL2);
+ String expected="{\"value\":\"VAL2\"}";
+ String actual = jo.toString();
+ assertTrue("Expected "+expected+" but actual was "+actual, expected.equals(actual));
+
+ jo.accumulate("value", MyEnumField.VAL1);
+ expected="{\"value\":[\"VAL2\",\"VAL1\"]}";
+ actual = jo.toString();
+ assertTrue("Expected "+expected+" but actual was "+actual, expected.equals(actual));
+
+ jo.remove("value");
+ jo.append("value", MyEnumField.VAL1);
+ expected="{\"value\":[\"VAL1\"]}";
+ actual = jo.toString();
+ assertTrue("Expected "+expected+" but actual was "+actual, expected.equals(actual));
+
+ jo.put("value", EnumSet.of(MyEnumField.VAL2));
+ expected="{\"value\":[\"VAL2\"]}";
+ actual = jo.toString();
+ assertTrue("Expected "+expected+" but actual was "+actual, expected.equals(actual));
+
+ JSONArray ja = new JSONArray();
+ ja.put(MyEnumField.VAL2);
+ jo.put("value", ja);
+ actual = jo.toString();
+ assertTrue("Expected "+expected+" but actual was "+actual, expected.equals(actual));
+
+ jo.put("value", new MyEnumField[]{MyEnumField.VAL2});
+ actual = jo.toString();
+ assertTrue("Expected "+expected+" but actual was "+actual, expected.equals(actual));
+
+ }
+
+ /**
+ * To serialize by assigned value, use the put() methods. The value
+ * will be stored as a enum type.
+ */
+ @Test
+ public void enumPut() {
+ JSONObject jsonObject = new JSONObject();
+ MyEnum myEnum = MyEnum.VAL2;
+ jsonObject.put("myEnum", myEnum);
+ MyEnumField myEnumField = MyEnumField.VAL1;
+ jsonObject.putOnce("myEnumField", myEnumField);
+
+ // validate JSON content
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 2 top level objects", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 2);
+ assertTrue("expected VAL2", MyEnum.VAL2.equals(jsonObject.query("/myEnum")));
+ assertTrue("expected VAL1", MyEnumField.VAL1.equals(jsonObject.query("/myEnumField")));
+
+ JSONArray jsonArray = new JSONArray();
+ jsonArray.put(myEnum);
+ jsonArray.put(1, myEnumField);
+
+ // validate JSON content
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
+ assertTrue("expected 2 top level objects", ((List>)(JsonPath.read(doc, "$"))).size() == 2);
+ assertTrue("expected VAL2", MyEnum.VAL2.equals(jsonArray.query("/0")));
+ assertTrue("expected VAL1", MyEnumField.VAL1.equals(jsonArray.query("/1")));
+
+ /**
+ * Leaving these tests because they exercise get, opt, and remove
+ */
+ assertTrue("expecting myEnum value", MyEnum.VAL2.equals(jsonArray.get(0)));
+ assertTrue("expecting myEnumField value", MyEnumField.VAL1.equals(jsonArray.opt(1)));
+ assertTrue("expecting myEnumField value", MyEnumField.VAL1.equals(jsonArray.remove(1)));
+ }
+
+ /**
+ * The default action of valueToString() is to call object.toString().
+ * For enums, this means the assigned value will be returned as a string.
+ */
+ @Test
+ public void enumValueToString() {
+ String expectedStr1 = "\"VAL1\"";
+ String expectedStr2 = "\"VAL1\"";
+ MyEnum myEnum = MyEnum.VAL1;
+ MyEnumField myEnumField = MyEnumField.VAL1;
+ MyEnumClass myEnumClass = new MyEnumClass();
+
+ String str1 = JSONObject.valueToString(myEnum);
+ assertTrue("actual myEnum: "+str1+" expected: "+expectedStr1,
+ str1.equals(expectedStr1));
+ String str2 = JSONObject.valueToString(myEnumField);
+ assertTrue("actual myEnumField: "+str2+" expected: "+expectedStr2,
+ str2.equals(expectedStr2));
+
+ /**
+ * However, an enum within another class will not be rendered
+ * unless that class overrides default toString()
+ */
+ String expectedStr3 = "\"org.json.junit.data.MyEnumClass@";
+ myEnumClass.setMyEnum(MyEnum.VAL1);
+ myEnumClass.setMyEnumField(MyEnumField.VAL1);
+ String str3 = JSONObject.valueToString(myEnumClass);
+ assertTrue("actual myEnumClass: "+str3+" expected: "+expectedStr3,
+ str3.startsWith(expectedStr3));
+ }
+
+ /**
+ * In whatever form the enum was added to the JSONObject or JSONArray,
+ * json[Object|Array].toString should serialize it in a reasonable way.
+ */
+ @Test
+ public void enumToString() {
+ MyEnum myEnum = MyEnum.VAL2;
+ JSONObject jsonObject = new JSONObject(myEnum);
+ String expectedStr = "{}";
+ assertTrue("myEnum toString() should be empty", expectedStr.equals(jsonObject.toString()));
+
+ MyEnumField myEnumField = MyEnumField.VAL2;
+ jsonObject = new JSONObject(myEnumField);
+
+ // validate JSON content
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 2 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 2);
+ assertTrue("expected val 2", "val 2".equals(jsonObject.query("/value")));
+ assertTrue("expected 2", Integer.valueOf(2).equals(jsonObject.query("/intVal")));
+
+ MyEnumClass myEnumClass = new MyEnumClass();
+ myEnumClass.setMyEnum(MyEnum.VAL1);
+ myEnumClass.setMyEnumField(MyEnumField.VAL3);
+ jsonObject = new JSONObject(myEnumClass);
+
+ // validate JSON content
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 2 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 2);
+ assertTrue("expected VAL3", "VAL3".equals((JsonPath.read(doc, "$.myEnumField"))));
+ assertTrue("expected VAL1", "VAL1".equals((JsonPath.read(doc, "$.myEnum"))));
+
+ String [] names = JSONObject.getNames(myEnum);
+ jsonObject = new JSONObject(myEnum, names);
+
+ // validate JSON content
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 3 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 3);
+ assertTrue("expected VAL1", MyEnum.VAL1.equals(jsonObject.query("/VAL1")));
+ assertTrue("expected VAL2", MyEnum.VAL2.equals(jsonObject.query("/VAL2")));
+ assertTrue("expected VAL3", MyEnum.VAL3.equals(jsonObject.query("/VAL3")));
+
+ names = JSONObject.getNames(myEnumField);
+ jsonObject = new JSONObject(myEnumField, names);
+
+ // validate JSON content
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 3 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 3);
+ assertTrue("expected VAL1", MyEnumField.VAL1.equals(jsonObject.query("/VAL1")));
+ assertTrue("expected VAL2", MyEnumField.VAL2.equals(jsonObject.query("/VAL2")));
+ assertTrue("expected VAL3", MyEnumField.VAL3.equals(jsonObject.query("/VAL3")));
+
+ expectedStr = "{\"myEnum\":\"VAL2\", \"myEnumField\":\"VAL2\"}";
+ jsonObject = new JSONObject();
+ jsonObject.putOpt("myEnum", myEnum);
+ jsonObject.putOnce("myEnumField", myEnumField);
+
+ // validate JSON content
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 2 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 2);
+ assertTrue("expected VAL2", MyEnum.VAL2.equals(jsonObject.query("/myEnum")));
+ assertTrue("expected VAL2", MyEnumField.VAL2.equals(jsonObject.query("/myEnumField")));
+
+ JSONArray jsonArray = new JSONArray();
+ jsonArray.put(myEnum);
+ jsonArray.put(1, myEnumField);
+
+ // validate JSON content
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
+ assertTrue("expected 2 top level items", ((List>)(JsonPath.read(doc, "$"))).size() == 2);
+ assertTrue("expected VAL2", MyEnum.VAL2.equals(jsonArray.query("/0")));
+ assertTrue("expected VAL2", MyEnumField.VAL2.equals(jsonArray.query("/1")));
+ }
+
+ /**
+ * Wrap should handle enums exactly as a value type like Integer, Boolean, or String.
+ */
+ @Test
+ public void wrap() {
+ assertTrue("simple enum has no getters", JSONObject.wrap(MyEnum.VAL2) instanceof MyEnum);
+
+ MyEnumField myEnumField = MyEnumField.VAL2;
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("enum",myEnumField);
+
+ // validate JSON content
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 1 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 1);
+ assertTrue("expected VAL2", MyEnumField.VAL2.equals(jsonObject.query("/enum")));
+
+ MyEnumClass myEnumClass = new MyEnumClass();
+ myEnumClass.setMyEnum(MyEnum.VAL1);
+ myEnumClass.setMyEnumField(MyEnumField.VAL3);
+ jsonObject = (JSONObject)JSONObject.wrap(myEnumClass);
+
+ // validate JSON content
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 2 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 2);
+ assertTrue("expected VAL3", "VAL3".equals((JsonPath.read(doc, "$.myEnumField"))));
+ assertTrue("expected VAL1", "VAL1".equals((JsonPath.read(doc, "$.myEnum"))));
+
+ assertTrue("expecting MyEnumField.VAL3", MyEnumField.VAL3.equals(jsonObject.query("/myEnumField")));
+ assertTrue("expecting MyEnum.VAL1", MyEnum.VAL1.equals(jsonObject.query("/myEnum")));
+ }
+
+ /**
+ * It was determined that some API methods should be added to
+ * support enums:
+ * JSONObject.getEnum(class, key)
+ * JSONObject.optEnum(class, key)
+ * JSONObject.optEnum(class, key, default)
+ * JSONArray.getEnum(class, index)
+ * JSONArray.optEnum(class, index)
+ * JSONArray.optEnum(class, index, default)
+ *
+ * Exercise these enum API methods on JSONObject and JSONArray
+ */
+ @Test
+ public void enumAPI() {
+ MyEnumClass myEnumClass = new MyEnumClass();
+ myEnumClass.setMyEnum(MyEnum.VAL1);
+ MyEnumField myEnumField = MyEnumField.VAL2;
+
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("strKey", "value");
+ jsonObject.put("strKey2", "VAL1");
+ jsonObject.put("enumKey", myEnumField);
+ jsonObject.put("enumClassKey", myEnumClass);
+
+ // get a plain old enum
+ MyEnumField actualEnum = jsonObject.getEnum(MyEnumField.class, "enumKey");
+ assertTrue("get myEnumField", actualEnum == MyEnumField.VAL2);
+
+ // try to get the wrong value
+ try {
+ actualEnum = jsonObject.getEnum(MyEnumField.class, "strKey");
+ assertTrue("should throw an exception for wrong key", false);
+ } catch (Exception ignored) {}
+
+ // get a class that contains an enum
+ MyEnumClass actualEnumClass = (MyEnumClass)jsonObject.get("enumClassKey");
+ assertTrue("get enum", actualEnumClass.getMyEnum() == MyEnum.VAL1);
+
+ // opt a plain old enum
+ actualEnum = jsonObject.optEnum(MyEnumField.class, "enumKey");
+ assertTrue("opt myEnumField", actualEnum == MyEnumField.VAL2);
+
+ // opt the wrong value
+ actualEnum = jsonObject.optEnum(MyEnumField.class, "strKey");
+ assertTrue("opt null", actualEnum == null);
+
+ // opt a class that contains an enum
+ actualEnumClass = (MyEnumClass)jsonObject.opt("enumClassKey");
+ assertTrue("get enum", actualEnumClass.getMyEnum() == MyEnum.VAL1);
+
+ // opt with default a plain old enum
+ actualEnum = jsonObject.optEnum(MyEnumField.class, "enumKey", null);
+ assertTrue("opt myEnumField", actualEnum == MyEnumField.VAL2);
+
+ // opt with default the wrong value
+ actualEnum = jsonObject.optEnum(MyEnumField.class, "strKey", null);
+ assertNull("opt null", actualEnum);
+
+ // opt with default the string value
+ actualEnum = jsonObject.optEnum(MyEnumField.class, "strKey2", null);
+ assertEquals(MyEnumField.VAL1, actualEnum);
+
+ // opt with default an index that does not exist
+ actualEnum = jsonObject.optEnum(MyEnumField.class, "noKey", null);
+ assertNull("opt null", actualEnum);
+
+ assertNull("Expected Null when the enum class is null",
+ jsonObject.optEnum(null, "enumKey"));
+
+ /**
+ * Exercise the proposed enum API methods on JSONArray
+ */
+ JSONArray jsonArray = new JSONArray();
+ jsonArray.put("value");
+ jsonArray.put(myEnumField);
+ jsonArray.put(myEnumClass);
+
+ // get a plain old enum
+ actualEnum = jsonArray.getEnum(MyEnumField.class, 1);
+ assertTrue("get myEnumField", actualEnum == MyEnumField.VAL2);
+
+ // try to get the wrong value
+ try {
+ actualEnum = jsonArray.getEnum(MyEnumField.class, 0);
+ assertTrue("should throw an exception for wrong index", false);
+ } catch (Exception ignored) {}
+
+ // get a class that contains an enum
+ actualEnumClass = (MyEnumClass)jsonArray.get(2);
+ assertTrue("get enum", actualEnumClass.getMyEnum() == MyEnum.VAL1);
+
+ // opt a plain old enum
+ actualEnum = jsonArray.optEnum(MyEnumField.class, 1);
+ assertTrue("opt myEnumField", actualEnum == MyEnumField.VAL2);
+
+ // opt the wrong value
+ actualEnum = jsonArray.optEnum(MyEnumField.class, 0);
+ assertTrue("opt null", actualEnum == null);
+
+ // opt a class that contains an enum
+ actualEnumClass = (MyEnumClass)jsonArray.opt(2);
+ assertTrue("get enum", actualEnumClass.getMyEnum() == MyEnum.VAL1);
+
+ // opt with default a plain old enum
+ actualEnum = jsonArray.optEnum(MyEnumField.class, 1, null);
+ assertTrue("opt myEnumField", actualEnum == MyEnumField.VAL2);
+
+ // opt with default the wrong value
+ actualEnum = jsonArray.optEnum(MyEnumField.class, 0, null);
+ assertTrue("opt null", actualEnum == null);
+
+ // opt with default an index that does not exist
+ actualEnum = jsonArray.optEnum(MyEnumField.class, 3, null);
+ assertTrue("opt null", actualEnum == null);
+
+ }
+}
diff --git a/src/test/java/org/json/junit/HTTPTest.java b/src/test/java/org/json/junit/HTTPTest.java
new file mode 100644
index 0000000..8182b60
--- /dev/null
+++ b/src/test/java/org/json/junit/HTTPTest.java
@@ -0,0 +1,220 @@
+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.
+*/
+
+import static org.junit.Assert.*;
+
+import org.json.*;
+import org.junit.Test;
+
+
+/**
+ * Unit tests for JSON-Java HTTP.java. See RFC7230.
+ */
+public class HTTPTest {
+
+ /**
+ * Attempt to call HTTP.toJSONObject() with a null string
+ * Expects a NUllPointerException.
+ */
+ @Test(expected=NullPointerException.class)
+ public void nullHTTPException() {
+ String httpStr = null;
+ HTTP.toJSONObject(httpStr);
+ }
+
+ /**
+ * Attempt to call HTTP.toJSONObject() with a string containing
+ * an empty object. Expects a JSONException.
+ */
+ @Test
+ public void notEnoughHTTPException() {
+ String httpStr = "{}";
+ JSONObject jsonObject = new JSONObject(httpStr);
+ try {
+ HTTP.toString(jsonObject);
+ assertTrue("Expected to throw exception", false);
+ } catch (JSONException e) {
+ assertTrue("Expecting an exception message",
+ "Not enough material for an HTTP header.".equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Calling HTTP.toJSONObject() with an empty string will result in a
+ * populated JSONObject with keys but no values for Request-URI, Method,
+ * and HTTP-Version.
+ */
+ @Test
+ public void emptyStringHTTPRequest() {
+ String httpStr = "";
+ String expectedHTTPStr = "{\"Request-URI\":\"\",\"Method\":\"\",\"HTTP-Version\":\"\"}";
+ JSONObject jsonObject = HTTP.toJSONObject(httpStr);
+ JSONObject expectedJsonObject = new JSONObject(expectedHTTPStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Call HTTP.toJSONObject() with a Request-URI, Method,
+ * and HTTP-Version.
+ */
+ @Test
+ public void simpleHTTPRequest() {
+ String httpStr = "GET /hello.txt HTTP/1.1";
+ String expectedHTTPStr =
+ "{\"Request-URI\":\"/hello.txt\",\"Method\":\"GET\",\"HTTP-Version\":\"HTTP/1.1\"}";
+ JSONObject jsonObject = HTTP.toJSONObject(httpStr);
+ JSONObject expectedJsonObject = new JSONObject(expectedHTTPStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Call HTTP.toJSONObject() with a response string containing a
+ * HTTP-Version, Status-Code, and Reason.
+ */
+ @Test
+ public void simpleHTTPResponse() {
+ String httpStr = "HTTP/1.1 200 OK";
+ String expectedHTTPStr =
+ "{\"HTTP-Version\":\"HTTP/1.1\",\"Status-Code\":\"200\",\"Reason-Phrase\":\"OK\"}";
+ JSONObject jsonObject = HTTP.toJSONObject(httpStr);
+ JSONObject expectedJsonObject = new JSONObject(expectedHTTPStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Call HTTP.toJSONObject() with a full request string including
+ * request headers.
+ */
+ @Test
+ public void extendedHTTPRequest() {
+ String httpStr =
+ "POST /enlighten/calais.asmx HTTP/1.1\n"+
+ "Host: api.opencalais.com\n"+
+ "Content-Type: text/xml; charset=utf-8\n"+
+ "Content-Length: 100\n"+
+ "SOAPAction: \"http://clearforest.com/Enlighten\"";
+ String expectedHTTPStr =
+ "{"+
+ "\"Request-URI\":\"/enlighten/calais.asmx\","+
+ "\"Host\":\"api.opencalais.com\","+
+ "\"Method\":\"POST\","+
+ "\"HTTP-Version\":\"HTTP/1.1\","+
+ "\"Content-Length\":\"100\","+
+ "\"Content-Type\":\"text/xml; charset=utf-8\"}";
+ JSONObject jsonObject = HTTP.toJSONObject(httpStr);
+ JSONObject expectedJsonObject = new JSONObject(expectedHTTPStr);
+ /**
+ * Not too easy for JSONObject to parse a string with embedded quotes.
+ * For the sake of the test, add it here.
+ */
+ expectedJsonObject.put("SOAPAction","\"http://clearforest.com/Enlighten\"");
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Call HTTP.toJSONObject() with a full response string including
+ * response headers.
+ */
+ @Test
+ public void extendedHTTPResponse() {
+ String httpStr =
+ "HTTP/1.1 200 OK\n"+
+ "Content-Type: text/xml; charset=utf-8\n"+
+ "Content-Length: 100\n";
+ String expectedHTTPStr =
+ "{\"HTTP-Version\":\"HTTP/1.1\","+
+ "\"Status-Code\":\"200\","+
+ "\"Content-Length\":\"100\","+
+ "\"Reason-Phrase\":\"OK\","+
+ "\"Content-Type\":\"text/xml; charset=utf-8\"}";
+ JSONObject jsonObject = HTTP.toJSONObject(httpStr);
+ JSONObject expectedJsonObject = new JSONObject(expectedHTTPStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Call HTTP.toJSONObject() with a full POST request string including
+ * response headers, then convert it back into an HTTP string.
+ */
+ @Test
+ public void convertHTTPRequestToString() {
+ String httpStr =
+ "POST /enlighten/calais.asmx HTTP/1.1\n"+
+ "Host: api.opencalais.com\n"+
+ "Content-Type: text/xml; charset=utf-8\n"+
+ "Content-Length: 100";
+ String expectedHTTPStr =
+ "{"+
+ "\"Request-URI\":\"/enlighten/calais.asmx\","+
+ "\"Host\":\"api.opencalais.com\","+
+ "\"Method\":\"POST\","+
+ "\"HTTP-Version\":\"HTTP/1.1\","+
+ "\"Content-Length\":\"100\","+
+ "\"Content-Type\":\"text/xml; charset=utf-8\"}";
+ JSONObject jsonObject = HTTP.toJSONObject(httpStr);
+ JSONObject expectedJsonObject = new JSONObject(expectedHTTPStr);
+ String httpToStr = HTTP.toString(jsonObject);
+ /**
+ * JSONObject objects to crlfs and any trailing chars.
+ * For the sake of the test, simplify the resulting string
+ */
+ httpToStr = httpToStr.replaceAll("("+HTTP.CRLF+HTTP.CRLF+")", "");
+ httpToStr = httpToStr.replaceAll(HTTP.CRLF, "\n");
+ JSONObject finalJsonObject = HTTP.toJSONObject(httpToStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Call HTTP.toJSONObject() with a full response string including
+ * response headers, then convert it back into an HTTP string.
+ */
+ @Test
+ public void convertHTTPResponseToString() {
+ String httpStr =
+ "HTTP/1.1 200 OK\n"+
+ "Content-Type: text/xml; charset=utf-8\n"+
+ "Content-Length: 100\n";
+ String expectedHTTPStr =
+ "{\"HTTP-Version\":\"HTTP/1.1\","+
+ "\"Status-Code\":\"200\","+
+ "\"Content-Length\":\"100\","+
+ "\"Reason-Phrase\":\"OK\","+
+ "\"Content-Type\":\"text/xml; charset=utf-8\"}";
+ JSONObject jsonObject = HTTP.toJSONObject(httpStr);
+ JSONObject expectedJsonObject = new JSONObject(expectedHTTPStr);
+ String httpToStr = HTTP.toString(jsonObject);
+ /**
+ * JSONObject objects to crlfs and any trailing chars.
+ * For the sake of the test, simplify the resulting string
+ */
+ httpToStr = httpToStr.replaceAll("("+HTTP.CRLF+HTTP.CRLF+")", "");
+ httpToStr = httpToStr.replaceAll(HTTP.CRLF, "\n");
+ JSONObject finalJsonObject = HTTP.toJSONObject(httpToStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject);
+ }
+}
diff --git a/src/test/java/org/json/junit/JSONArrayTest.java b/src/test/java/org/json/junit/JSONArrayTest.java
new file mode 100644
index 0000000..f11b953
--- /dev/null
+++ b/src/test/java/org/json/junit/JSONArrayTest.java
@@ -0,0 +1,1181 @@
+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.
+*/
+
+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.assertTrue;
+
+import java.io.IOException;
+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.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONPointerException;
+import org.junit.Test;
+
+import com.jayway.jsonpath.Configuration;
+import com.jayway.jsonpath.JsonPath;
+
+
+/**
+ * Tests for JSON-Java JSONArray.java
+ */
+public class JSONArrayTest {
+ private final String arrayStr =
+ "["+
+ "true,"+
+ "false,"+
+ "\"true\","+
+ "\"false\","+
+ "\"hello\","+
+ "23.45e-4,"+
+ "\"23.45\","+
+ "42,"+
+ "\"43\","+
+ "["+
+ "\"world\""+
+ "],"+
+ "{"+
+ "\"key1\":\"value1\","+
+ "\"key2\":\"value2\","+
+ "\"key3\":\"value3\","+
+ "\"key4\":\"value4\""+
+ "},"+
+ "0,"+
+ "\"-1\""+
+ "]";
+
+ /**
+ * Tests that the similar method is working as expected.
+ */
+ @Test
+ public void verifySimilar() {
+ final String string1 = "HasSameRef";
+ JSONArray obj1 = new JSONArray()
+ .put("abc")
+ .put(string1)
+ .put(2);
+
+ JSONArray obj2 = new JSONArray()
+ .put("abc")
+ .put(string1)
+ .put(3);
+
+ JSONArray obj3 = new JSONArray()
+ .put("abc")
+ .put(new String(string1))
+ .put(2);
+
+ assertFalse("Should eval to false", obj1.similar(obj2));
+
+ assertTrue("Should eval to true", obj1.similar(obj3));
+ }
+
+ /**
+ * Attempt to create a JSONArray with a null string.
+ * Expects a NullPointerException.
+ */
+ @Test(expected=NullPointerException.class)
+ public void nullException() {
+ String str = null;
+ assertNull("Should throw an exception", new JSONArray(str));
+ }
+
+ /**
+ * Attempt to create a JSONArray with an empty string.
+ * Expects a JSONException.
+ */
+ @Test
+ public void emptStr() {
+ String str = "";
+ try {
+ assertNull("Should throw an exception", new JSONArray(str));
+ } catch (JSONException e) {
+ assertEquals("Expected an exception message",
+ "A JSONArray text must start with '[' at 0 [character 1 line 1]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Attempt to create a JSONArray with an unclosed array.
+ * Expects an exception
+ */
+ @Test
+ public void unclosedArray() {
+ try {
+ assertNull("Should throw an exception", new JSONArray("["));
+ } catch (JSONException e) {
+ assertEquals("Expected an exception message",
+ "Expected a ',' or ']' at 1 [character 2 line 1]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Attempt to create a JSONArray with an unclosed array.
+ * Expects an exception
+ */
+ @Test
+ public void unclosedArray2() {
+ try {
+ assertNull("Should throw an exception", new JSONArray("[\"test\""));
+ } catch (JSONException e) {
+ assertEquals("Expected an exception message",
+ "Expected a ',' or ']' at 7 [character 8 line 1]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Attempt to create a JSONArray with an unclosed array.
+ * Expects an exception
+ */
+ @Test
+ public void unclosedArray3() {
+ try {
+ assertNull("Should throw an exception", new JSONArray("[\"test\","));
+ } catch (JSONException e) {
+ assertEquals("Expected an exception message",
+ "Expected a ',' or ']' at 8 [character 9 line 1]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Attempt to create a JSONArray with a string as object that is
+ * not a JSON array doc.
+ * Expects a JSONException.
+ */
+ @Test
+ public void badObject() {
+ String str = "abc";
+ try {
+ assertNull("Should throw an exception", new JSONArray((Object)str));
+ } catch (JSONException e) {
+ assertTrue("Expected an exception message",
+ "JSONArray initial value should be a string or collection or array.".
+ equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Verifies that the constructor has backwards compatibility with RAW types pre-java5.
+ */
+ @Test
+ public void verifyConstructor() {
+
+ final JSONArray expected = new JSONArray("[10]");
+
+ @SuppressWarnings("rawtypes")
+ Collection myRawC = Collections.singleton(Integer.valueOf(10));
+ JSONArray jaRaw = new JSONArray(myRawC);
+
+ Collection myCInt = Collections.singleton(Integer.valueOf(10));
+ JSONArray jaInt = new JSONArray(myCInt);
+
+ Collection myCObj = Collections.singleton((Object) Integer
+ .valueOf(10));
+ JSONArray jaObj = new JSONArray(myCObj);
+
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaRaw));
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaInt));
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaObj));
+ }
+
+ /**
+ * 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);
+
+ for (int i = 0; i < myList.size(); i++) {
+ assertEquals("collection elements should be equal",
+ myList.get(i),
+ jsonArray.getString(myInts.length + i));
+ }
+ }
+
+ /**
+ * Verifies that the put Collection has backwards compatibility with RAW types pre-java5.
+ */
+ @Test
+ public void verifyPutCollection() {
+
+ final JSONArray expected = new JSONArray("[[10]]");
+
+ @SuppressWarnings("rawtypes")
+ Collection myRawC = Collections.singleton(Integer.valueOf(10));
+ JSONArray jaRaw = new JSONArray();
+ jaRaw.put(myRawC);
+
+ Collection myCObj = Collections.singleton((Object) Integer
+ .valueOf(10));
+ JSONArray jaObj = new JSONArray();
+ jaObj.put(myCObj);
+
+ Collection myCInt = Collections.singleton(Integer.valueOf(10));
+ JSONArray jaInt = new JSONArray();
+ jaInt.put(myCInt);
+
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaRaw));
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaObj));
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaInt));
+ }
+
+
+ /**
+ * Verifies that the put Map has backwards compatibility with RAW types pre-java5.
+ */
+ @Test
+ public void verifyPutMap() {
+
+ final JSONArray expected = new JSONArray("[{\"myKey\":10}]");
+
+ @SuppressWarnings("rawtypes")
+ Map myRawC = Collections.singletonMap("myKey", Integer.valueOf(10));
+ JSONArray jaRaw = new JSONArray();
+ jaRaw.put(myRawC);
+
+ Map myCStrObj = Collections.singletonMap("myKey",
+ (Object) Integer.valueOf(10));
+ JSONArray jaStrObj = new JSONArray();
+ jaStrObj.put(myCStrObj);
+
+ Map myCStrInt = Collections.singletonMap("myKey",
+ Integer.valueOf(10));
+ JSONArray jaStrInt = new JSONArray();
+ jaStrInt.put(myCStrInt);
+
+ Map, ?> myCObjObj = Collections.singletonMap((Object) "myKey",
+ (Object) Integer.valueOf(10));
+ JSONArray jaObjObj = new JSONArray();
+ jaObjObj.put(myCObjObj);
+
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaRaw));
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaStrObj));
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaStrInt));
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaObjObj));
+ }
+
+ /**
+ * Create a JSONArray doc with a variety of different elements.
+ * Confirm that the values can be accessed via the get[type]() API methods
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void getArrayValues() {
+ JSONArray jsonArray = new JSONArray(this.arrayStr);
+ // booleans
+ assertTrue("Array true",
+ true == jsonArray.getBoolean(0));
+ assertTrue("Array false",
+ false == jsonArray.getBoolean(1));
+ assertTrue("Array string true",
+ true == jsonArray.getBoolean(2));
+ assertTrue("Array string false",
+ false == jsonArray.getBoolean(3));
+ // strings
+ assertTrue("Array value string",
+ "hello".equals(jsonArray.getString(4)));
+ // doubles
+ assertTrue("Array double",
+ new Double(23.45e-4).equals(jsonArray.getDouble(5)));
+ assertTrue("Array string double",
+ new Double(23.45).equals(jsonArray.getDouble(6)));
+ // ints
+ assertTrue("Array value int",
+ new Integer(42).equals(jsonArray.getInt(7)));
+ assertTrue("Array value string int",
+ new Integer(43).equals(jsonArray.getInt(8)));
+ // nested objects
+ JSONArray nestedJsonArray = jsonArray.getJSONArray(9);
+ assertTrue("Array value JSONArray", nestedJsonArray != null);
+ JSONObject nestedJsonObject = jsonArray.getJSONObject(10);
+ assertTrue("Array value JSONObject", nestedJsonObject != null);
+ // longs
+ assertTrue("Array value long",
+ new Long(0).equals(jsonArray.getLong(11)));
+ assertTrue("Array value string long",
+ new Long(-1).equals(jsonArray.getLong(12)));
+
+ assertTrue("Array value null", jsonArray.isNull(-1));
+ }
+
+ /**
+ * Create a JSONArray doc with a variety of different elements.
+ * Confirm that attempting to get the wrong types via the get[type]()
+ * API methods result in JSONExceptions
+ */
+ @Test
+ public void failedGetArrayValues() {
+ JSONArray jsonArray = new JSONArray(this.arrayStr);
+ try {
+ jsonArray.getBoolean(4);
+ assertTrue("expected getBoolean to fail", false);
+ } catch (JSONException e) {
+ assertEquals("Expected an exception message",
+ "JSONArray[4] is not a boolean.",e.getMessage());
+ }
+ try {
+ jsonArray.get(-1);
+ assertTrue("expected get to fail", false);
+ } catch (JSONException e) {
+ assertEquals("Expected an exception message",
+ "JSONArray[-1] not found.",e.getMessage());
+ }
+ try {
+ jsonArray.getDouble(4);
+ assertTrue("expected getDouble to fail", false);
+ } catch (JSONException e) {
+ assertEquals("Expected an exception message",
+ "JSONArray[4] is not a double.",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());
+ }
+ 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());
+ }
+ 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());
+ }
+ 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());
+ }
+ 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());
+ }
+ }
+
+ /**
+ * Exercise JSONArray.join() by converting a JSONArray into a
+ * comma-separated string. Since this is very nearly a JSON document,
+ * array braces are added to the beginning and end prior to validation.
+ */
+ @Test
+ public void join() {
+ JSONArray jsonArray = new JSONArray(this.arrayStr);
+ String joinStr = jsonArray.join(",");
+
+ // validate JSON
+ /**
+ * Don't need to remake the JSONArray to perform the parsing
+ */
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse("["+joinStr+"]");
+ assertTrue("expected 13 items in top level object", ((List>)(JsonPath.read(doc, "$"))).size() == 13);
+ assertTrue("expected true", Boolean.TRUE.equals(jsonArray.query("/0")));
+ assertTrue("expected false", Boolean.FALSE.equals(jsonArray.query("/1")));
+ assertTrue("expected \"true\"", "true".equals(jsonArray.query("/2")));
+ assertTrue("expected \"false\"", "false".equals(jsonArray.query("/3")));
+ assertTrue("expected hello", "hello".equals(jsonArray.query("/4")));
+ assertTrue("expected 0.002345", BigDecimal.valueOf(0.002345).equals(jsonArray.query("/5")));
+ assertTrue("expected \"23.45\"", "23.45".equals(jsonArray.query("/6")));
+ assertTrue("expected 42", Integer.valueOf(42).equals(jsonArray.query("/7")));
+ assertTrue("expected \"43\"", "43".equals(jsonArray.query("/8")));
+ assertTrue("expected 1 item in [9]", ((List>)(JsonPath.read(doc, "$[9]"))).size() == 1);
+ assertTrue("expected world", "world".equals(jsonArray.query("/9/0")));
+ assertTrue("expected 4 items in [10]", ((Map,?>)(JsonPath.read(doc, "$[10]"))).size() == 4);
+ assertTrue("expected value1", "value1".equals(jsonArray.query("/10/key1")));
+ assertTrue("expected value2", "value2".equals(jsonArray.query("/10/key2")));
+ assertTrue("expected value3", "value3".equals(jsonArray.query("/10/key3")));
+ 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")));
+ }
+
+ /**
+ * Confirm the JSONArray.length() method
+ */
+ @Test
+ public void length() {
+ assertTrue("expected empty JSONArray length 0",
+ new JSONArray().length() == 0);
+ JSONArray jsonArray = new JSONArray(this.arrayStr);
+ 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);
+ }
+
+ /**
+ * Create a JSONArray doc with a variety of different elements.
+ * Confirm that the values can be accessed via the opt[type](index)
+ * and opt[type](index, default) API methods.
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void opt() {
+ JSONArray jsonArray = new JSONArray(this.arrayStr);
+ assertTrue("Array opt value true",
+ Boolean.TRUE == jsonArray.opt(0));
+ assertTrue("Array opt value out of range",
+ null == jsonArray.opt(-1));
+
+ assertTrue("Array opt value out of range",
+ null == jsonArray.opt(jsonArray.length()));
+
+ assertTrue("Array opt boolean",
+ Boolean.TRUE == jsonArray.optBoolean(0));
+ assertTrue("Array opt boolean default",
+ Boolean.FALSE == jsonArray.optBoolean(-1, Boolean.FALSE));
+ assertTrue("Array opt boolean implicit default",
+ Boolean.FALSE == jsonArray.optBoolean(-1));
+
+ assertTrue("Array opt double",
+ new Double(23.45e-4).equals(jsonArray.optDouble(5)));
+ assertTrue("Array opt double default",
+ new Double(1).equals(jsonArray.optDouble(0, 1)));
+ assertTrue("Array opt double default implicit",
+ new Double(jsonArray.optDouble(99)).isNaN());
+
+ assertTrue("Array opt float",
+ new Float(23.45e-4).equals(jsonArray.optFloat(5)));
+ assertTrue("Array opt float default",
+ new Float(1).equals(jsonArray.optFloat(0, 1)));
+ assertTrue("Array opt float default implicit",
+ new Float(jsonArray.optFloat(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)));
+ assertTrue("Array opt Number default implicit",
+ new Double(jsonArray.optNumber(99,Double.NaN).doubleValue()).isNaN());
+
+ assertTrue("Array opt int",
+ new Integer(42).equals(jsonArray.optInt(7)));
+ assertTrue("Array opt int default",
+ new Integer(-1).equals(jsonArray.optInt(0, -1)));
+ assertTrue("Array opt int default implicit",
+ 0 == jsonArray.optInt(0));
+
+ JSONArray nestedJsonArray = jsonArray.optJSONArray(9);
+ assertTrue("Array opt JSONArray", nestedJsonArray != null);
+ assertTrue("Array opt JSONArray default",
+ null == jsonArray.optJSONArray(99));
+
+ JSONObject nestedJsonObject = jsonArray.optJSONObject(10);
+ assertTrue("Array opt JSONObject", nestedJsonObject != null);
+ assertTrue("Array opt JSONObject default",
+ null == jsonArray.optJSONObject(99));
+
+ assertTrue("Array opt long",
+ 0 == jsonArray.optLong(11));
+ assertTrue("Array opt long default",
+ -2 == jsonArray.optLong(-1, -2));
+ assertTrue("Array opt long default implicit",
+ 0 == jsonArray.optLong(-1));
+
+ assertTrue("Array opt string",
+ "hello".equals(jsonArray.optString(4)));
+ assertTrue("Array opt string default implicit",
+ "".equals(jsonArray.optString(-1)));
+ }
+
+ /**
+ * Verifies that the opt methods properly convert string values.
+ */
+ @Test
+ public void optStringConversion(){
+ JSONArray ja = new JSONArray("[\"123\",\"true\",\"false\"]");
+ assertTrue("unexpected optBoolean value",ja.optBoolean(1,false)==true);
+ assertTrue("unexpected optBoolean value",ja.optBoolean(2,true)==false);
+ assertTrue("unexpected optInt value",ja.optInt(0,0)==123);
+ assertTrue("unexpected optLong value",ja.optLong(0,0)==123);
+ assertTrue("unexpected optDouble value",ja.optDouble(0,0.0)==123.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); }
+
+ /**
+ * Exercise the JSONArray.put(value) method with various parameters
+ * and confirm the resulting JSONArray.
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void put() {
+ JSONArray jsonArray = new JSONArray();
+
+ // index 0
+ jsonArray.put(true);
+ // 1
+ jsonArray.put(false);
+
+ String jsonArrayStr =
+ "["+
+ "hello,"+
+ "world"+
+ "]";
+ // 2
+ jsonArray.put(new JSONArray(jsonArrayStr));
+
+ // 3
+ jsonArray.put(2.5);
+ // 4
+ jsonArray.put(1);
+ // 5
+ jsonArray.put(45L);
+
+ // 6
+ jsonArray.put("objectPut");
+
+ String jsonObjectStr =
+ "{"+
+ "\"key10\":\"val10\","+
+ "\"key20\":\"val20\","+
+ "\"key30\":\"val30\""+
+ "}";
+ JSONObject jsonObject = new JSONObject(jsonObjectStr);
+ // 7
+ jsonArray.put(jsonObject);
+
+ Map map = new HashMap();
+ map.put("k1", "v1");
+ // 8
+ jsonArray.put(map);
+
+ Collection collection = new ArrayList();
+ collection.add(1);
+ collection.add(2);
+ // 9
+ jsonArray.put(collection);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
+ assertTrue("expected 10 top level items", ((List>)(JsonPath.read(doc, "$"))).size() == 10);
+ assertTrue("expected true", Boolean.TRUE.equals(jsonArray.query("/0")));
+ assertTrue("expected false", Boolean.FALSE.equals(jsonArray.query("/1")));
+ assertTrue("expected 2 items in [2]", ((List>)(JsonPath.read(doc, "$[2]"))).size() == 2);
+ assertTrue("expected hello", "hello".equals(jsonArray.query("/2/0")));
+ assertTrue("expected world", "world".equals(jsonArray.query("/2/1")));
+ assertTrue("expected 2.5", Double.valueOf(2.5).equals(jsonArray.query("/3")));
+ assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/4")));
+ assertTrue("expected 45", Long.valueOf(45).equals(jsonArray.query("/5")));
+ assertTrue("expected objectPut", "objectPut".equals(jsonArray.query("/6")));
+ assertTrue("expected 3 items in [7]", ((Map,?>)(JsonPath.read(doc, "$[7]"))).size() == 3);
+ assertTrue("expected val10", "val10".equals(jsonArray.query("/7/key10")));
+ assertTrue("expected val20", "val20".equals(jsonArray.query("/7/key20")));
+ assertTrue("expected val30", "val30".equals(jsonArray.query("/7/key30")));
+ assertTrue("expected 1 item in [8]", ((Map,?>)(JsonPath.read(doc, "$[8]"))).size() == 1);
+ assertTrue("expected v1", "v1".equals(jsonArray.query("/8/k1")));
+ 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")));
+ }
+
+ /**
+ * Exercise the JSONArray.put(index, value) method with various parameters
+ * and confirm the resulting JSONArray.
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void putIndex() {
+ JSONArray jsonArray = new JSONArray();
+
+ // 1
+ jsonArray.put(1, false);
+ // index 0
+ jsonArray.put(0, true);
+
+ String jsonArrayStr =
+ "["+
+ "hello,"+
+ "world"+
+ "]";
+ // 2
+ jsonArray.put(2, new JSONArray(jsonArrayStr));
+
+ // 5
+ jsonArray.put(5, 45L);
+ // 4
+ jsonArray.put(4, 1);
+ // 3
+ jsonArray.put(3, 2.5);
+
+ // 6
+ jsonArray.put(6, "objectPut");
+
+ // 7 will be null
+
+ String jsonObjectStr =
+ "{"+
+ "\"key10\":\"val10\","+
+ "\"key20\":\"val20\","+
+ "\"key30\":\"val30\""+
+ "}";
+ JSONObject jsonObject = new JSONObject(jsonObjectStr);
+ jsonArray.put(8, jsonObject);
+ Collection collection = new ArrayList();
+ collection.add(1);
+ collection.add(2);
+ jsonArray.put(9,collection);
+
+ Map map = new HashMap();
+ map.put("k1", "v1");
+ jsonArray.put(10, map);
+ try {
+ jsonArray.put(-1, "abc");
+ assertTrue("put index < 0 should have thrown exception", false);
+ } catch(Exception ignored) {}
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
+ assertTrue("expected 11 top level items", ((List>)(JsonPath.read(doc, "$"))).size() == 11);
+ assertTrue("expected true", Boolean.TRUE.equals(jsonArray.query("/0")));
+ assertTrue("expected false", Boolean.FALSE.equals(jsonArray.query("/1")));
+ assertTrue("expected 2 items in [2]", ((List>)(JsonPath.read(doc, "$[2]"))).size() == 2);
+ assertTrue("expected hello", "hello".equals(jsonArray.query("/2/0")));
+ assertTrue("expected world", "world".equals(jsonArray.query("/2/1")));
+ assertTrue("expected 2.5", Double.valueOf(2.5).equals(jsonArray.query("/3")));
+ assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/4")));
+ assertTrue("expected 45", Long.valueOf(45).equals(jsonArray.query("/5")));
+ assertTrue("expected objectPut", "objectPut".equals(jsonArray.query("/6")));
+ assertTrue("expected null", JSONObject.NULL.equals(jsonArray.query("/7")));
+ assertTrue("expected 3 items in [8]", ((Map,?>)(JsonPath.read(doc, "$[8]"))).size() == 3);
+ assertTrue("expected val10", "val10".equals(jsonArray.query("/8/key10")));
+ assertTrue("expected val20", "val20".equals(jsonArray.query("/8/key20")));
+ assertTrue("expected val30", "val30".equals(jsonArray.query("/8/key30")));
+ 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")));
+ assertTrue("expected 1 item in [10]", ((Map,?>)(JsonPath.read(doc, "$[10]"))).size() == 1);
+ assertTrue("expected v1", "v1".equals(jsonArray.query("/10/k1")));
+ }
+
+ /**
+ * Exercise the JSONArray.remove(index) method
+ * and confirm the resulting JSONArray.
+ */
+ @Test
+ public void remove() {
+ String arrayStr1 =
+ "["+
+ "1"+
+ "]";
+ JSONArray jsonArray = new JSONArray(arrayStr1);
+ jsonArray.remove(0);
+ assertTrue("array should be empty", null == jsonArray.remove(5));
+ assertTrue("jsonArray should be empty", jsonArray.isEmpty());
+ }
+
+ /**
+ * Exercise the JSONArray.similar() method with various parameters
+ * and confirm the results when not similar.
+ */
+ @Test
+ public void notSimilar() {
+ String arrayStr1 =
+ "["+
+ "1"+
+ "]";
+ JSONArray jsonArray = new JSONArray(arrayStr1);
+ JSONArray otherJsonArray = new JSONArray();
+ assertTrue("arrays lengths differ", !jsonArray.similar(otherJsonArray));
+
+ JSONObject jsonObject = new JSONObject("{\"k1\":\"v1\"}");
+ JSONObject otherJsonObject = new JSONObject();
+ jsonArray = new JSONArray();
+ jsonArray.put(jsonObject);
+ otherJsonArray = new JSONArray();
+ otherJsonArray.put(otherJsonObject);
+ assertTrue("arrays JSONObjects differ", !jsonArray.similar(otherJsonArray));
+
+ JSONArray nestedJsonArray = new JSONArray("[1, 2]");
+ JSONArray otherNestedJsonArray = new JSONArray();
+ jsonArray = new JSONArray();
+ jsonArray.put(nestedJsonArray);
+ otherJsonArray = new JSONArray();
+ otherJsonArray.put(otherNestedJsonArray);
+ assertTrue("arrays nested JSONArrays differ",
+ !jsonArray.similar(otherJsonArray));
+
+ jsonArray = new JSONArray();
+ jsonArray.put("hello");
+ otherJsonArray = new JSONArray();
+ otherJsonArray.put("world");
+ assertTrue("arrays values differ",
+ !jsonArray.similar(otherJsonArray));
+ }
+
+ /**
+ * Exercise JSONArray toString() method with various indent levels.
+ */
+ @Test
+ public void jsonArrayToStringIndent() {
+ String jsonArray0Str =
+ "[" +
+ "[1,2," +
+ "{\"key3\":true}" +
+ "]," +
+ "{\"key1\":\"val1\",\"key2\":" +
+ "{\"key2\":\"val2\"}" +
+ "}," +
+ "[" +
+ "[1,2.1]" +
+ "," +
+ "[null]" +
+ "]" +
+ "]";
+
+ String jsonArray1Strs [] =
+ {
+ "[",
+ " [",
+ " 1,",
+ " 2,",
+ " {\"key3\": true}",
+ " ],",
+ " {",
+ " \"key1\": \"val1\",",
+ " \"key2\": {\"key2\": \"val2\"}",
+ " },",
+ " [",
+ " [",
+ " 1,",
+ " 2.1",
+ " ],",
+ " [null]",
+ " ]",
+ "]"
+ };
+ String jsonArray4Strs [] =
+ {
+ "[",
+ " [",
+ " 1,",
+ " 2,",
+ " {\"key3\": true}",
+ " ],",
+ " {",
+ " \"key1\": \"val1\",",
+ " \"key2\": {\"key2\": \"val2\"}",
+ " },",
+ " [",
+ " [",
+ " 1,",
+ " 2.1",
+ " ],",
+ " [null]",
+ " ]",
+ "]"
+ };
+ JSONArray jsonArray = new JSONArray(jsonArray0Str);
+ String [] actualStrArray = jsonArray.toString().split("\\r?\\n");
+ assertEquals("Expected 1 line", 1, actualStrArray.length);
+ actualStrArray = jsonArray.toString(0).split("\\r?\\n");
+ assertEquals("Expected 1 line", 1, actualStrArray.length);
+
+ actualStrArray = jsonArray.toString(1).split("\\r?\\n");
+ assertEquals("Expected lines", jsonArray1Strs.length, actualStrArray.length);
+ List list = Arrays.asList(actualStrArray);
+ for (String s : jsonArray1Strs) {
+ list.contains(s);
+ }
+
+ actualStrArray = jsonArray.toString(4).split("\\r?\\n");
+ assertEquals("Expected lines", jsonArray1Strs.length, actualStrArray.length);
+ list = Arrays.asList(actualStrArray);
+ for (String s : jsonArray4Strs) {
+ list.contains(s);
+ }
+ }
+
+ /**
+ * Convert an empty JSONArray to JSONObject
+ */
+ @Test
+ public void toJSONObject() {
+ JSONArray names = new JSONArray();
+ JSONArray jsonArray = new JSONArray();
+ assertTrue("toJSONObject should return null",
+ null == jsonArray.toJSONObject(names));
+ }
+
+ /**
+ * Confirm the creation of a JSONArray from an array of ints
+ */
+ @Test
+ public void objectArrayVsIsArray() {
+ int[] myInts = { 1, 2, 3, 4, 5, 6, 7 };
+ Object myObject = myInts;
+ JSONArray jsonArray = new JSONArray(myObject);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
+ assertTrue("expected 7 top level items", ((List>)(JsonPath.read(doc, "$"))).size() == 7);
+ assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0")));
+ assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1")));
+ assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2")));
+ assertTrue("expected 4", Integer.valueOf(4).equals(jsonArray.query("/3")));
+ 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")));
+ }
+
+ /**
+ * Exercise the JSONArray iterator.
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void iteratorTest() {
+ JSONArray jsonArray = new JSONArray(this.arrayStr);
+ Iterator it = jsonArray.iterator();
+ assertTrue("Array true",
+ Boolean.TRUE.equals(it.next()));
+ assertTrue("Array false",
+ Boolean.FALSE.equals(it.next()));
+ assertTrue("Array string true",
+ "true".equals(it.next()));
+ assertTrue("Array string false",
+ "false".equals(it.next()));
+ assertTrue("Array string",
+ "hello".equals(it.next()));
+
+ 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())));
+
+ assertTrue("Array value int",
+ new Integer(42).equals(it.next()));
+ assertTrue("Array value string int",
+ new Integer(43).equals(Integer.parseInt((String)it.next())));
+
+ JSONArray nestedJsonArray = (JSONArray)it.next();
+ assertTrue("Array value JSONArray", nestedJsonArray != null);
+
+ JSONObject nestedJsonObject = (JSONObject)it.next();
+ assertTrue("Array value JSONObject", nestedJsonObject != null);
+
+ assertTrue("Array value long",
+ new Long(0).equals(((Number) it.next()).longValue()));
+ assertTrue("Array value string long",
+ new Long(-1).equals(Long.parseLong((String) it.next())));
+ assertTrue("should be at end of array", !it.hasNext());
+ }
+
+ @Test(expected = JSONPointerException.class)
+ public void queryWithNoResult() {
+ new JSONArray().query("/a/b");
+ }
+
+ @Test
+ public void optQueryWithNoResult() {
+ assertNull(new JSONArray().optQuery("/a/b"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void optQueryWithSyntaxError() {
+ new JSONArray().optQuery("invalid");
+ }
+
+
+ /**
+ * Exercise the JSONArray write() method
+ */
+ @Test
+ 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();) {
+ 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")
+ );
+ }
+ }
+
+ /**
+ * Exercise the JSONArray write() method using Appendable.
+ */
+/*
+ @Test
+ public void writeAppendable() {
+ String str = "[\"value1\",\"value2\",{\"key1\":1,\"key2\":2,\"key3\":3}]";
+ JSONArray jsonArray = new JSONArray(str);
+ String expectedStr = str;
+ StringBuilder stringBuilder = new StringBuilder();
+ Appendable appendable = jsonArray.write(stringBuilder);
+ String actualStr = appendable.toString();
+ assertTrue("write() expected " + expectedStr +
+ " but found " + actualStr,
+ expectedStr.equals(actualStr));
+ }
+*/
+
+ /**
+ * Exercise the JSONArray write(Writer, int, int) method
+ */
+ @Test
+ 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();) {
+ String actualStr = jsonArray.write(stringWriter, 0, 0).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\":false")
+ && actualStr.contains("\"key3\":3.14")
+ );
+ }
+
+ try (StringWriter stringWriter = new StringWriter();) {
+ String actualStr = jsonArray.write(stringWriter, 2, 1).toString();
+ JSONArray finalArray = new JSONArray(actualStr);
+ Util.compareActualVsExpectedJsonArrays(jsonArray, finalArray);
+ assertTrue("write() expected " + expectedStr +
+ " but found " + actualStr,
+ actualStr.startsWith("[\n" +
+ " \"value1\",\n" +
+ " \"value2\",\n" +
+ " {")
+ && actualStr.contains("\"key1\": 1")
+ && actualStr.contains("\"key2\": false")
+ && actualStr.contains("\"key3\": 3.14")
+ );
+ }
+ }
+
+ /**
+ * Exercise the JSONArray write(Appendable, int, int) method
+ */
+/*
+ @Test
+ public void write3ParamAppendable() {
+ String str0 = "[\"value1\",\"value2\",{\"key1\":1,\"key2\":false,\"key3\":3.14}]";
+ String str2 =
+ "[\n" +
+ " \"value1\",\n" +
+ " \"value2\",\n" +
+ " {\n" +
+ " \"key1\": 1,\n" +
+ " \"key2\": false,\n" +
+ " \"key3\": 3.14\n" +
+ " }\n" +
+ " ]";
+ JSONArray jsonArray = new JSONArray(str0);
+ String expectedStr = str0;
+ StringBuilder stringBuilder = new StringBuilder();
+ Appendable appendable = jsonArray.write(stringBuilder, 0, 0);
+ String actualStr = appendable.toString();
+ assertEquals(expectedStr, actualStr);
+
+ expectedStr = str2;
+ stringBuilder = new StringBuilder();
+ appendable = jsonArray.write(stringBuilder, 2, 1);
+ actualStr = appendable.toString();
+ assertEquals(expectedStr, actualStr);
+ }
+*/
+
+ /**
+ * Exercise JSONArray toString() method with various indent levels.
+ */
+ @Test
+ public void toList() {
+ String jsonArrayStr =
+ "[" +
+ "[1,2," +
+ "{\"key3\":true}" +
+ "]," +
+ "{\"key1\":\"val1\",\"key2\":" +
+ "{\"key2\":null}," +
+ "\"key3\":42,\"key4\":[]" +
+ "}," +
+ "[" +
+ "[\"value1\",2.1]" +
+ "," +
+ "[null]" +
+ "]" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonArrayStr);
+ List> list = jsonArray.toList();
+
+ assertTrue("List should not be null", list != null);
+ assertTrue("List should have 3 elements", list.size() == 3);
+
+ List> val1List = (List>) list.get(0);
+ assertTrue("val1 should not be null", val1List != null);
+ assertTrue("val1 should have 3 elements", val1List.size() == 3);
+
+ assertTrue("val1 value 1 should be 1", val1List.get(0).equals(Integer.valueOf(1)));
+ assertTrue("val1 value 2 should be 2", val1List.get(1).equals(Integer.valueOf(2)));
+
+ Map,?> key1Value3Map = (Map,?>)val1List.get(2);
+ assertTrue("Map should not be null", key1Value3Map != null);
+ assertTrue("Map should have 1 element", key1Value3Map.size() == 1);
+ assertTrue("Map key3 should be true", key1Value3Map.get("key3").equals(Boolean.TRUE));
+
+ Map,?> val2Map = (Map,?>) list.get(1);
+ assertTrue("val2 should not be null", val2Map != null);
+ assertTrue("val2 should have 4 elements", val2Map.size() == 4);
+ assertTrue("val2 map key 1 should be val1", val2Map.get("key1").equals("val1"));
+ assertTrue("val2 map key 3 should be 42", val2Map.get("key3").equals(Integer.valueOf(42)));
+
+ Map,?> val2Key2Map = (Map,?>)val2Map.get("key2");
+ assertTrue("val2 map key 2 should not be null", val2Key2Map != null);
+ assertTrue("val2 map key 2 should have an entry", val2Key2Map.containsKey("key2"));
+ assertTrue("val2 map key 2 value should be null", val2Key2Map.get("key2") == null);
+
+ List> val2Key4List = (List>)val2Map.get("key4");
+ assertTrue("val2 map key 4 should not be null", val2Key4List != null);
+ assertTrue("val2 map key 4 should be empty", val2Key4List.isEmpty());
+
+ List> val3List = (List>) list.get(2);
+ assertTrue("val3 should not be null", val3List != null);
+ assertTrue("val3 should have 2 elements", val3List.size() == 2);
+
+ List> val3Val1List = (List>)val3List.get(0);
+ assertTrue("val3 list val 1 should not be null", val3Val1List != null);
+ assertTrue("val3 list val 1 should have 2 elements", val3Val1List.size() == 2);
+ assertTrue("val3 list val 1 list element 1 should be value1", val3Val1List.get(0).equals("value1"));
+ assertTrue("val3 list val 1 list element 2 should be 2.1", val3Val1List.get(1).equals(new BigDecimal("2.1")));
+
+ List> val3Val2List = (List>)val3List.get(1);
+ assertTrue("val3 list val 2 should not be null", val3Val2List != null);
+ assertTrue("val3 list val 2 should have 1 element", val3Val2List.size() == 1);
+ assertTrue("val3 list val 2 list element 1 should be null", val3Val2List.get(0) == null);
+
+ // assert that toList() is a deep copy
+ jsonArray.getJSONObject(1).put("key1", "still val1");
+ assertTrue("val2 map key 1 should be val1", val2Map.get("key1").equals("val1"));
+
+ // 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);
+ }
+
+ /**
+ * Create a JSONArray with specified initial capacity.
+ * Expects an exception if the initial capacity is specified as a negative integer
+ */
+ @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());
+ try {
+ assertNotNull("Should throw an exception", new JSONArray(-1));
+ } catch (JSONException e) {
+ assertEquals("Expected an exception message",
+ "JSONArray initial capacity cannot be negative.",
+ e.getMessage());
+ }
+ }
+}
diff --git a/src/test/java/org/json/junit/JSONMLTest.java b/src/test/java/org/json/junit/JSONMLTest.java
new file mode 100644
index 0000000..8f3de42
--- /dev/null
+++ b/src/test/java/org/json/junit/JSONMLTest.java
@@ -0,0 +1,856 @@
+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.
+*/
+
+import static org.junit.Assert.*;
+
+import org.json.*;
+import org.junit.Test;
+
+/**
+ * Tests for org.json.JSONML.java
+ *
+ * Certain inputs are expected to result in exceptions. These tests are
+ * executed first. JSONML provides an API to:
+ * Convert an XML string into a JSONArray or a JSONObject.
+ * Convert a JSONArray or JSONObject into an XML string.
+ * Both fromstring and tostring operations operations should be symmetrical
+ * 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 {
+
+ /**
+ * Attempts to transform a null XML string to JSON.
+ * Expects a NullPointerException
+ */
+ @Test(expected=NullPointerException.class)
+ public void nullXMLException() {
+ String xmlStr = null;
+ JSONML.toJSONArray(xmlStr);
+ }
+
+ /**
+ * Attempts to transform an empty string to JSON.
+ * Expects a JSONException
+ */
+ @Test
+ public void emptyXMLException() {
+ String xmlStr = "";
+ try {
+ JSONML.toJSONArray(xmlStr);
+ fail("Expecting an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Bad XML at 0 [character 1 line 1]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Attempts to call JSONML.toString() with a null JSONArray.
+ * Expects a NullPointerException.
+ */
+ @Test(expected=NullPointerException.class)
+ public void nullJSONXMLException() {
+ /**
+ * Tries to convert a null JSONArray to XML.
+ */
+ JSONArray jsonArray= null;
+ JSONML.toString(jsonArray);
+ }
+
+ /**
+ * Attempts to call JSONML.toString() with a null JSONArray.
+ * Expects a JSONException.
+ */
+ @Test
+ public void emptyJSONXMLException() {
+ /**
+ * Tries to convert an empty JSONArray to XML.
+ */
+ JSONArray jsonArray = new JSONArray();
+ try {
+ JSONML.toString(jsonArray);
+ assertTrue("Expecting an exception", false);
+ } catch (JSONException e) {
+ assertTrue("Expecting an exception message",
+ "JSONArray[0] not found.".
+ equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Attempts to transform an non-XML string to JSON.
+ * Expects a JSONException
+ */
+ @Test
+ public void nonXMLException() {
+ /**
+ * Attempts to transform a nonXML string to JSON
+ */
+ String xmlStr = "{ \"this is\": \"not xml\"}";
+ try {
+ JSONML.toJSONArray(xmlStr);
+ fail("Expecting an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Bad XML at 23 [character 24 line 1]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Attempts to transform a JSON document with XML content that
+ * does not follow JSONML conventions (element name is not first value
+ * in a nested JSONArray) to a JSONArray then back to string.
+ * Expects a JSONException
+ */
+ @Test
+ public void emptyTagException() {
+ /**
+ * jsonArrayStr is used to build a JSONArray which is then
+ * turned into XML. For this transformation, all arrays represent
+ * elements and the first array entry is the name of the element.
+ * In this case, one of the arrays does not have a name
+ */
+ String jsonArrayStr =
+ "[\"addresses\","+
+ "{\"xsi:noNamespaceSchemaLocation\":\"test.xsd\","+
+ "\"xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"},"+
+ // this array has no name
+ "["+
+ "[\"name\"],"+
+ "[\"nocontent\"],"+
+ "\">\""+
+ "]"+
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonArrayStr);
+ try {
+ JSONML.toString(jsonArray);
+ assertTrue("Expecting an exception", false);
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONArray[0] is not a String.",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Attempts to transform a JSON document with XML content that
+ * does not follow JSONML conventions (element tag has an embedded space)
+ * to a JSONArray then back to string. Expects a JSONException
+ */
+ @Test
+ public void spaceInTagException() {
+ /**
+ * jsonArrayStr is used to build a JSONArray which is then
+ * turned into XML. For this transformation, all arrays represent
+ * elements and the first array entry is the name of the element.
+ * In this case, one of the element names has an embedded space,
+ * which is not allowed.
+ */
+ String jsonArrayStr =
+ "[\"addresses\","+
+ "{\"xsi:noNamespaceSchemaLocation\":\"test.xsd\","+
+ "\"xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"},"+
+ // this array has an invalid name
+ "[\"addr esses\","+
+ "[\"name\"],"+
+ "[\"nocontent\"],"+
+ "\">\""+
+ "]"+
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonArrayStr);
+ try {
+ JSONML.toString(jsonArray);
+ assertTrue("Expecting an exception", false);
+ } catch (JSONException e) {
+ assertTrue("Expecting an exception message",
+ "'addr esses' contains a space character.".
+ equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Attempts to transform a malformed XML document
+ * (element tag has a frontslash) to a JSONArray.\
+ * Expects a JSONException
+ */
+ @Test
+ public void invalidSlashInTagException() {
+ /**
+ * xmlStr contains XML text which is transformed into a JSONArray.
+ * In this case, the XML is invalid because the 'name' element
+ * contains an invalid frontslash.
+ */
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " \n"+
+ " abc street \n"+
+ " \n"+
+ " ";
+ try {
+ JSONML.toJSONArray(xmlStr);
+ fail("Expecting an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Misshaped tag at 176 [character 14 line 4]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Malformed XML text (invalid tagname) is transformed into a JSONArray.
+ * Expects a JSONException.
+ */
+ @Test
+ public void invalidBangInTagException() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " \n"+
+ " \n"+
+ " \n"+
+ " ";
+ try {
+ JSONML.toJSONArray(xmlStr);
+ fail("Expecting an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Misshaped meta tag at 215 [character 12 line 7]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Malformed XML text (invalid tagname, no close bracket) is transformed\
+ * into a JSONArray. Expects a JSONException.
+ */
+ @Test
+ public void invalidBangNoCloseInTagException() {
+ /**
+ * xmlStr contains XML text which is transformed into a JSONArray.
+ * In this case, the XML is invalid because an element
+ * starts with '!' and has no closing tag
+ */
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " \n"+
+ " \n"+
+ " ";
+ try {
+ JSONML.toJSONArray(xmlStr);
+ fail("Expecting an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Misshaped meta tag at 214 [character 12 line 7]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Malformed XML text (tagname with no close bracket) is transformed\
+ * into a JSONArray. Expects a JSONException.
+ */
+ @Test
+ public void noCloseStartTagException() {
+ /**
+ * xmlStr contains XML text which is transformed into a JSONArray.
+ * In this case, the XML is invalid because an element
+ * has no closing '>'.
+ */
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " \n"+
+ " \n"+
+ " ";
+ try {
+ JSONML.toJSONArray(xmlStr);
+ fail("Expecting an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Misplaced '<' at 194 [character 5 line 6]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Malformed XML text (endtag with no name) is transformed\
+ * into a JSONArray. Expects a JSONException.
+ */
+ @Test
+ public void noCloseEndTagException() {
+ /**
+ * xmlStr contains XML text which is transformed into a JSONArray.
+ * In this case, the XML is invalid because an element
+ * has no name after the closing tag ''.
+ */
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " \n"+
+ " \n"+
+ " >\n"+
+ " ";
+ try {
+ JSONML.toJSONArray(xmlStr);
+ assertTrue("Expecting an exception", false);
+ } catch (JSONException e) {
+ assertTrue("Expecting an exception message",
+ "Expected a closing name instead of '>'.".
+ equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Malformed XML text (endtag with no close bracket) is transformed\
+ * into a JSONArray. Expects a JSONException.
+ */
+ @Test
+ public void noCloseEndBraceException() {
+ /**
+ * xmlStr contains XML text which is transformed into a JSONArray.
+ * In this case, the XML is invalid because an element
+ * has '>' after the closing tag '' and name.
+ */
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " \n"+
+ " \n"+
+ " ";
+ try {
+ JSONML.toJSONArray(xmlStr);
+ fail("Expecting an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Misplaced '<' at 206 [character 1 line 7]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Malformed XML text (incomplete CDATA string) is transformed\
+ * into a JSONArray. Expects a JSONException.
+ */
+ @Test
+ 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.
+ */
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " Joe Tester \n"+
+ " \n"+
+ " \n"+
+ " ";
+ try {
+ JSONML.toJSONArray(xmlStr);
+ fail("Expecting an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Expected 'CDATA[' at 204 [character 11 line 5]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * 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
+ * confirm the contents.
+ */
+ @Test
+ public void toJSONArray() {
+ /**
+ * xmlStr contains XML text which is transformed into a JSONArray.
+ * Each element becomes a JSONArray:
+ * 1st entry = elementname
+ * 2nd entry = attributes object (if present)
+ * 3rd entry = content (if present)
+ * 4th entry = child element JSONArrays (if present)
+ * The result is compared against an expected JSONArray.
+ * The transformed JSONArray is then transformed back into a string
+ * which is used to create a final JSONArray, which is also compared
+ * against the expected JSONArray.
+ */
+ String xmlStr =
+ "\n"+
+ "\n"+
+ "\n"+
+ "myName \n"+
+ " >\n"+
+ " \n"+
+ " ";
+ String expectedStr =
+ "[\"addresses\","+
+ "{\"xsi:noNamespaceSchemaLocation\":\"test.xsd\","+
+ "\"xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"},"+
+ "[\"address\","+
+ "{\"attr1\":\"attrValue1\",\"attr2\":\"attrValue2\",\"attr3\":\"attrValue3\"},"+
+ "[\"name\", {\"nameType\":\"mine\"},\"myName\"],"+
+ "[\"nocontent\"],"+
+ "\">\""+
+ "]"+
+ "]";
+ JSONArray jsonArray = JSONML.toJSONArray(xmlStr);
+ JSONArray expectedJsonArray = new JSONArray(expectedStr);
+ String xmlToStr = JSONML.toString(jsonArray);
+ JSONArray finalJsonArray = JSONML.toJSONArray(xmlToStr);
+ Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
+ Util.compareActualVsExpectedJsonArrays(finalJsonArray, expectedJsonArray);
+ }
+
+ /**
+ * 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
+ * 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.
+ *
+ * This test gives a comprehensive example of how the JSONML
+ * transformations work.
+ */
+ @Test
+ 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
+ * 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 =
+ "\n"+
+ "\n"+
+ "\n"+
+ "Joe Tester \n"+
+ " \n"+
+ " \n"+
+ "true \n"+
+ "false \n"+
+ "null \n"+
+ "42 \n"+
+ "-23 \n"+
+ "-23.45 \n"+
+ "-23x.45 \n"+
+ "\n"+
+ "1 \n"+
+ "2 \n"+
+ "abc \n"+
+ "3 \n"+
+ "4.1 \n"+
+ "5.2 \n"+
+ " \n"+
+ " \n"+
+ " ";
+
+ String expectedJSONObjectStr =
+ "{"+
+ "\"xsi:noNamespaceSchemaLocation\":\"test.xsd\","+
+ "\"childNodes\":["+
+ "{"+
+ "\"childNodes\":["+
+ "{"+
+ "\"childNodes\":[\"Joe Tester\"],"+
+ "\"nameType\":\"my name\","+
+ "\"tagName\":\"name\""+
+ "},"+
+ "{"+
+ "\"childNodes\":[\"Baker street 5\"],"+
+ "\"tagName\":\"street\""+
+ "},"+
+ "{"+
+ "\"tagName\":\"NothingHere\","+
+ "\"except\":\"an attribute\""+
+ "},"+
+ "{"+
+ "\"childNodes\":[true],"+
+ "\"tagName\":\"TrueValue\""+
+ "},"+
+ "{"+
+ "\"childNodes\":[false],"+
+ "\"tagName\":\"FalseValue\""+
+ "},"+
+ "{"+
+ "\"childNodes\":[null],"+
+ "\"tagName\":\"NullValue\""+
+ "},"+
+ "{"+
+ "\"childNodes\":[42],"+
+ "\"tagName\":\"PositiveValue\""+
+ "},"+
+ "{"+
+ "\"childNodes\":[-23],"+
+ "\"tagName\":\"NegativeValue\""+
+ "},"+
+ "{"+
+ "\"childNodes\":[-23.45],"+
+ "\"tagName\":\"DoubleValue\""+
+ "},"+
+ "{"+
+ "\"childNodes\":[\"-23x.45\"],"+
+ "\"tagName\":\"Nan\""+
+ "},"+
+ "{"+
+ "\"childNodes\":["+
+ "{"+
+ "\"childNodes\":[1],"+
+ "\"tagName\":\"value\""+
+ "},"+
+ "{"+
+ "\"childNodes\":[2],"+
+ "\"tagName\":\"value\""+
+ "},"+
+ "{"+
+ "\"childNodes\":["+
+ "{"+
+ "\"childNodes\":[\"abc\"],"+
+ "\"svAttr\":\"svValue\","+
+ "\"tagName\":\"subValue\""+
+ "}"+
+ "],"+
+ "\"tagName\":\"value\""+
+ "},"+
+ "{"+
+ "\"childNodes\":[3],"+
+ "\"tagName\":\"value\""+
+ "},"+
+ "{"+
+ "\"childNodes\":[4.1],"+
+ "\"tagName\":\"value\""+
+ "},"+
+ "{"+
+ "\"childNodes\":[5.2],"+
+ "\"tagName\":\"value\""+
+ "}"+
+ "],"+
+ "\"tagName\":\"ArrayOfNum\""+
+ "}"+
+ "],"+
+ "\"addrType\":\"my address\","+
+ "\"tagName\":\"address\""+
+ "}"+
+ "],"+
+ "\"xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\","+
+ "\"tagName\":\"addresses\""+
+ "}";
+
+ String expectedJSONArrayStr =
+ "["+
+ "\"addresses\","+
+ "{"+
+ "\"xsi:noNamespaceSchemaLocation\":\"test.xsd\","+
+ "\"xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\""+
+ "},"+
+ "["+
+ "\"address\","+
+ "{"+
+ "\"addrType\":\"my address\""+
+ "},"+
+ "["+
+ "\"name\","+
+ "{"+
+ "\"nameType\":\"my name\""+
+ "},"+
+ "\"Joe Tester\""+
+ "],"+
+ "[\"street\",\"Baker street 5\"],"+
+ "["+
+ "\"NothingHere\","+
+ "{\"except\":\"an attribute\"}"+
+ "],"+
+ "[\"TrueValue\",true],"+
+ "[\"FalseValue\",false],"+
+ "[\"NullValue\",null],"+
+ "[\"PositiveValue\",42],"+
+ "[\"NegativeValue\",-23],"+
+ "[\"DoubleValue\",-23.45],"+
+ "[\"Nan\",\"-23x.45\"],"+
+ "["+
+ "\"ArrayOfNum\","+
+ "[\"value\",1],"+
+ "[\"value\",2],"+
+ "[\"value\","+
+ "["+
+ "\"subValue\","+
+ "{\"svAttr\":\"svValue\"},"+
+ "\"abc\""+
+ "],"+
+ "],"+
+ "[\"value\",3],"+
+ "[\"value\",4.1],"+
+ "[\"value\",5.2]"+
+ "]"+
+ "]"+
+ "]";
+
+ // make a JSONObject and make sure it looks as expected
+ JSONObject jsonObject = JSONML.toJSONObject(xmlStr);
+ JSONObject expectedJsonObject = new JSONObject(expectedJSONObjectStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+
+ // restore the XML, then make another JSONObject and make sure it
+ // looks as expected
+ String jsonObjectXmlToStr = JSONML.toString(jsonObject);
+ JSONObject finalJsonObject = JSONML.toJSONObject(jsonObjectXmlToStr);
+ Util.compareActualVsExpectedJsonObjects(finalJsonObject, expectedJsonObject);
+
+ // 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);
+ JSONArray finalJsonArray = JSONML.toJSONArray(jsonArrayXmlToStr);
+ Util.compareActualVsExpectedJsonArrays(finalJsonArray, expectedJsonArray);
+
+ // lastly, confirm the restored JSONObject XML and JSONArray XML look
+ // reasonably similar
+ JSONObject jsonObjectFromObject = JSONML.toJSONObject(jsonObjectXmlToStr);
+ JSONObject jsonObjectFromArray = JSONML.toJSONObject(jsonArrayXmlToStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObjectFromObject, jsonObjectFromArray);
+ }
+
+ /**
+ * 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.
+ *
+ * This test shows how XML comments are handled.
+ */
+ @Test
+ public void commentsInXML() {
+
+ String xmlStr =
+ "\n"+
+ "\n"+
+ "\n"+
+ "\n"+
+ "\n"+
+ "Joe Tester \n"+
+ "\n"+
+ "Baker street 5 \n"+
+ " \n"+
+ " ";
+ String expectedStr =
+ "[\"addresses\","+
+ "[\"address\","+
+ "[\"name\",\"Joe Tester\"],"+
+ "[\"street\",\"Baker street 5\"]"+
+ "]"+
+ "]";
+ JSONArray jsonArray = JSONML.toJSONArray(xmlStr);
+ JSONArray expectedJsonArray = new JSONArray(expectedStr);
+ String xmlToStr = JSONML.toString(jsonArray);
+ JSONArray finalJsonArray = JSONML.toJSONArray(xmlToStr);
+ Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
+ Util.compareActualVsExpectedJsonArrays(finalJsonArray, expectedJsonArray);
+ }
+
+ /**
+ * JSON string with lost leading zero and converted "True" to true. See test
+ * result in comment below.
+ */
+ @Test
+ public void testToJSONArray_jsonOutput() {
+ final String originalXml = "01 1 00 0 True ";
+ final String expectedJsonString = "[\"root\",[\"id\",\"01\"],[\"id\",1],[\"id\",\"00\"],[\"id\",0],[\"item\",{\"id\":\"01\"}],[\"title\",true]]";
+ final JSONArray actualJsonOutput = JSONML.toJSONArray(originalXml, false);
+ assertEquals(expectedJsonString, actualJsonOutput.toString());
+ }
+
+ /**
+ * JSON string cannot be reverted to original xml when type guessing is used.
+ */
+ @Test
+ public void testToJSONArray_reversibility() {
+ final String originalXml = "01 1 00 0 True ";
+ final String revertedXml = JSONML.toString(JSONML.toJSONArray(originalXml, false));
+ assertNotEquals(revertedXml, originalXml);
+ }
+
+ /**
+ * JSON string cannot be reverted to original xml when type guessing is used.
+ * When we force all the values as string, the original text comes back.
+ */
+ @Test
+ public void testToJSONArray_reversibility2() {
+ final String originalXml = "01 1 00 0 True ";
+ 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);
+ }
+
+ /**
+ * JSON can be reverted to original xml.
+ */
+ @Test
+ public void testToJSONArray_reversibility3() {
+ final String originalXml = "400
402
";
+ final JSONArray jsonArray = JSONML.toJSONArray(originalXml, false);
+ final String revertedXml = JSONML.toString(jsonArray);
+ assertEquals(revertedXml, originalXml);
+ }
+
+ /**
+ * JSON string cannot be reverted to original xml. See test result in
+ * comment below.
+ */
+ @Test
+ public void testToJSONObject_reversibility() {
+ final String originalXml = "400
402
";
+ final JSONObject originalObject=JSONML.toJSONObject(originalXml,false);
+ final String originalJson = originalObject.toString();
+ 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);
+ }
+
+// 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.
+//
+// /**
+// * Test texts taken from jsonml.org. Currently our implementation FAILS this conversion but shouldn't.
+// * Technically JsonML should be able to transform any valid xhtml document, but ours only supports
+// * standard XML entities, not HTML entities.
+// */
+// @Test
+// public void testAttributeConversionReversabilityHTML() {
+// final String originalXml = "
#5D28D1 Example text here #AF44EF 127310656 #AAD034 ©
";
+// 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.
+// */
+// @Test
+// public void testAttributeConversionReversabilityXML() {
+// final String originalXml = "#5D28D1 Example text here #AF44EF 127310656 #AAD034 &> <
";
+// 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
+// assertEquals(originalXml, reverseXml);
+//
+// assertEquals(expectedJsonString, actualJsonString);
+// }
+
+ @Test (timeout = 6000)
+ public void testIssue484InfinteLoop1() {
+ try {
+ JSONML.toJSONObject("??*^M??|?CglR^F??`??>?w??PIlr^E??D^X^]?$?-^R?o??O?*??{OD?^FY??`2a????NM?b^Tq?:O?>S$^K?J?^FB.gUK?m^H??zE??^??!v]?^A???^[^A??^U?c??????h???s???g^Z???`?q^Dbi??:^QZl?)?}1^??k?0??:$V?$?Ovs(}J??^V????2;^QgQ?^_^A?^D?^U?Tg?K?`?h%c?hmGA??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?)(JsonPath.read(doc, "$"))).size() == 4);
+ assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObjectByName.query("/falseKey")));
+ 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")));
+ }
+
+ /**
+ * JSONObjects can be built from a Map.
+ * In this test the map is null.
+ * the JSONObject(JsonTokener) ctor is not tested directly since it already
+ * has full coverage from other tests.
+ */
+ @Test
+ public void jsonObjectByNullMap() {
+ Map map = null;
+ JSONObject jsonObject = new JSONObject(map);
+ assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ }
+
+ /**
+ * JSONObjects can be built from a Map.
+ * In this test all of the map entries are valid JSON types.
+ */
+ @Test
+ public void jsonObjectByMap() {
+ Map map = new HashMap();
+ map.put("trueKey", new Boolean(true));
+ map.put("falseKey", new Boolean(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));
+ JSONObject jsonObject = new JSONObject(map);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 6 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 6);
+ assertTrue("expected \"trueKey\":true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
+ assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
+ 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")));
+ }
+
+ /**
+ * Verifies that the constructor has backwards compatability with RAW types pre-java5.
+ */
+ @Test
+ public void verifyConstructor() {
+
+ final JSONObject expected = new JSONObject("{\"myKey\":10}");
+
+ @SuppressWarnings("rawtypes")
+ Map myRawC = Collections.singletonMap("myKey", Integer.valueOf(10));
+ JSONObject jaRaw = new JSONObject(myRawC);
+
+ Map myCStrObj = Collections.singletonMap("myKey",
+ (Object) Integer.valueOf(10));
+ JSONObject jaStrObj = new JSONObject(myCStrObj);
+
+ Map myCStrInt = Collections.singletonMap("myKey",
+ Integer.valueOf(10));
+ JSONObject jaStrInt = new JSONObject(myCStrInt);
+
+ Map, ?> myCObjObj = Collections.singletonMap((Object) "myKey",
+ (Object) Integer.valueOf(10));
+ JSONObject jaObjObj = new JSONObject(myCObjObj);
+
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaRaw));
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaStrObj));
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaStrInt));
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaObjObj));
+ }
+
+ /**
+ * Tests Number serialization.
+ */
+ @Test
+ public void verifyNumberOutput(){
+ /**
+ * MyNumberContainer is a POJO, so call JSONObject(bean),
+ * which builds a map of getter names/values
+ * The only getter is getMyNumber (key=myNumber),
+ * whose return value is MyNumber. MyNumber extends Number,
+ * but is not recognized as such by wrap() per current
+ * implementation, so wrap() returns the default new JSONObject(bean).
+ * The only getter is getNumber (key=number), whose return value is
+ * BigDecimal(42).
+ */
+ JSONObject jsonObject = new JSONObject(new MyNumberContainer());
+ String actual = jsonObject.toString();
+ String expected = "{\"myNumber\":{\"number\":42}}";
+ assertEquals("Equal", expected , actual);
+
+ /**
+ * JSONObject.put() handles objects differently than the
+ * bean constructor. Where the bean ctor wraps objects before
+ * placing them in the map, put() inserts the object without wrapping.
+ * In this case, a MyNumber instance is the value.
+ * 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();
+ expected = "{\"myNumber\":42}";
+ assertEquals("Equal", expected , actual);
+
+ /**
+ * Calls the JSONObject(Map) ctor, which calls wrap() for values.
+ * AtomicInteger is a Number, but is not recognized by wrap(), per
+ * current implementation. However, the type is
+ * 'java.util.concurrent.atomic', so due to the 'java' prefix,
+ * 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();
+ expected = "{\"myNumber\":\"42\"}";
+ assertEquals("Equal", expected , actual);
+
+ /**
+ * JSONObject.put() inserts the AtomicInteger directly into the
+ * map not calling wrap(). In toString()->write()->writeValue(),
+ * 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();
+ expected = "{\"myNumber\":42}";
+ assertEquals("Equal", expected , actual);
+
+ /**
+ * Calls the JSONObject(Map) ctor, which calls wrap() for values.
+ * Fraction is a Number, but is not recognized by wrap(), per
+ * current implementation. As a POJO, Fraction is handled as a
+ * 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.put() inserts the Fraction directly into the
+ * map not calling wrap(). In toString()->write()->writeValue(),
+ * Fraction is recognized as a Number, and converted via
+ * numberToString() into the unquoted string '4/2'. But the
+ * 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();
+ expected = "{\"myNumber\":\"4/2\"}"; // valid JSON, bug fixed
+ assertEquals("Equal", expected , actual);
+ }
+
+ /**
+ * Verifies that the put Collection has backwards compatibility with RAW types pre-java5.
+ */
+ @Test
+ public void verifyPutCollection() {
+
+ final JSONObject expected = new JSONObject("{\"myCollection\":[10]}");
+
+ @SuppressWarnings("rawtypes")
+ Collection myRawC = Collections.singleton(Integer.valueOf(10));
+ JSONObject jaRaw = new JSONObject();
+ jaRaw.put("myCollection", myRawC);
+
+ Collection myCObj = Collections.singleton((Object) Integer
+ .valueOf(10));
+ JSONObject jaObj = new JSONObject();
+ jaObj.put("myCollection", myCObj);
+
+ Collection myCInt = Collections.singleton(Integer
+ .valueOf(10));
+ JSONObject jaInt = new JSONObject();
+ jaInt.put("myCollection", myCInt);
+
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaRaw));
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaObj));
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaInt));
+ }
+
+
+ /**
+ * Verifies that the put Map has backwards compatibility with RAW types pre-java5.
+ */
+ @Test
+ public void verifyPutMap() {
+
+ final JSONObject expected = new JSONObject("{\"myMap\":{\"myKey\":10}}");
+
+ @SuppressWarnings("rawtypes")
+ Map myRawC = Collections.singletonMap("myKey", Integer.valueOf(10));
+ JSONObject jaRaw = new JSONObject();
+ jaRaw.put("myMap", myRawC);
+
+ Map myCStrObj = Collections.singletonMap("myKey",
+ (Object) Integer.valueOf(10));
+ JSONObject jaStrObj = new JSONObject();
+ jaStrObj.put("myMap", myCStrObj);
+
+ Map myCStrInt = Collections.singletonMap("myKey",
+ Integer.valueOf(10));
+ JSONObject jaStrInt = new JSONObject();
+ jaStrInt.put("myMap", myCStrInt);
+
+ Map, ?> myCObjObj = Collections.singletonMap((Object) "myKey",
+ (Object) Integer.valueOf(10));
+ JSONObject jaObjObj = new JSONObject();
+ jaObjObj.put("myMap", myCObjObj);
+
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaRaw));
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaStrObj));
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaStrInt));
+ assertTrue(
+ "The RAW Collection should give me the same as the Typed Collection",
+ expected.similar(jaObjObj));
+ }
+
+
+ /**
+ * JSONObjects can be built from a Map.
+ * In this test the map entries are not valid JSON types.
+ * The actual conversion is kind of interesting.
+ */
+ @Test
+ public void jsonObjectByMapWithUnsupportedValues() {
+ Map jsonMap = new HashMap();
+ // Just insert some random objects
+ jsonMap.put("key1", new CDL());
+ jsonMap.put("key2", new Exception());
+
+ JSONObject jsonObject = new JSONObject(jsonMap);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ 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")));
+ }
+
+ /**
+ * JSONObjects can be built from a Map.
+ * In this test one of the map values is null
+ */
+ @Test
+ public void jsonObjectByMapWithNullValue() {
+ Map map = new HashMap();
+ map.put("trueKey", new Boolean(true));
+ map.put("falseKey", new Boolean(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));
+ JSONObject jsonObject = new JSONObject(map);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 6 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 6);
+ assertTrue("expected \"trueKey\":true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
+ assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
+ 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 \"intKey\":42", Long.valueOf("42").equals(jsonObject.query("/intKey")));
+ assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey")));
+ }
+
+ /**
+ * JSONObject built from a bean. In this case all but one of the
+ * bean getters return valid JSON types
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void jsonObjectByBean1() {
+ /**
+ * Default access classes have to be mocked since JSONObject, which is
+ * not in the same package, cannot call MyBean methods by reflection.
+ */
+ MyBean myBean = mock(MyBean.class);
+ when(myBean.getDoubleKey()).thenReturn(-23.45e7);
+ when(myBean.getIntKey()).thenReturn(42);
+ when(myBean.getStringKey()).thenReturn("hello world!");
+ when(myBean.getEscapeStringKey()).thenReturn("h\be\tllo w\u1234orld!");
+ when(myBean.isTrueKey()).thenReturn(true);
+ when(myBean.isFalseKey()).thenReturn(false);
+ when(myBean.getStringReaderKey()).thenReturn(
+ new StringReader("") {
+ });
+
+ JSONObject jsonObject = new JSONObject(myBean);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 8 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 8);
+ assertTrue("expected 0 items in stringReaderKey", ((Map, ?>) (JsonPath.read(doc, "$.stringReaderKey"))).size() == 0);
+ assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
+ assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
+ assertTrue("expected hello world!","hello world!".equals(jsonObject.query("/stringKey")));
+ assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey")));
+ 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);
+ }
+
+ /**
+ * JSONObject built from a bean that has custom field names.
+ */
+ @Test
+ public void jsonObjectByBean2() {
+ JSONObject jsonObject = new JSONObject(new MyBeanCustomName());
+ assertNotNull(jsonObject);
+ assertEquals("Wrong number of keys found:",
+ 5,
+ jsonObject.keySet().size());
+ assertFalse("Normal field name (someString) processing did not work",
+ jsonObject.has("someString"));
+ assertFalse("Normal field name (myDouble) processing did not work",
+ jsonObject.has("myDouble"));
+ assertFalse("Normal field name (someFloat) processing did not work",
+ jsonObject.has("someFloat"));
+ assertFalse("Ignored field not found!",
+ jsonObject.has("ignoredInt"));
+ // getSomeInt() has no user-defined annotation
+ assertTrue("Normal field name (someInt) should have been found",
+ jsonObject.has("someInt"));
+ // the user-defined annotation does not replace any value, so someLong should be found
+ assertTrue("Normal field name (someLong) should have been found",
+ jsonObject.has("someLong"));
+ // myStringField replaces someString property name via user-defined annotation
+ assertTrue("Overridden String field name (myStringField) should have been found",
+ jsonObject.has("myStringField"));
+ // weird name replaces myDouble property name via user-defined annotation
+ assertTrue("Overridden String field name (Some Weird NAme that Normally Wouldn't be possible!) should have been found",
+ jsonObject.has("Some Weird NAme that Normally Wouldn't be possible!"));
+ // InterfaceField replaces someFloat property name via user-defined annotation
+ assertTrue("Overridden String field name (InterfaceField) should have been found",
+ jsonObject.has("InterfaceField"));
+ }
+
+ /**
+ * JSONObject built from a bean that has custom field names inherited from a parent class.
+ */
+ @Test
+ public void jsonObjectByBean3() {
+ JSONObject jsonObject = new JSONObject(new MyBeanCustomNameSubClass());
+ assertNotNull(jsonObject);
+ assertEquals("Wrong number of keys found:",
+ 7,
+ jsonObject.keySet().size());
+ assertFalse("Normal int field name (someInt) found, but was overridden",
+ jsonObject.has("someInt"));
+ assertFalse("Normal field name (myDouble) processing did not work",
+ jsonObject.has("myDouble"));
+ // myDouble was replaced by weird name, and then replaced again by AMoreNormalName via user-defined annotation
+ assertFalse("Overridden String field name (Some Weird NAme that Normally Wouldn't be possible!) should not be FOUND!",
+ jsonObject.has("Some Weird NAme that Normally Wouldn't be possible!"));
+ assertFalse("Normal field name (someFloat) found, but was overridden",
+ jsonObject.has("someFloat"));
+ assertFalse("Ignored field found! but was overridden",
+ jsonObject.has("ignoredInt"));
+ // shouldNotBeJSON property name was first ignored, then replaced by ShouldBeIgnored via user-defined annotations
+ assertFalse("Ignored field at the same level as forced name should not have been found",
+ jsonObject.has("ShouldBeIgnored"));
+ // able property name was replaced by Getable via user-defined annotation
+ assertFalse("Normally ignored field (able) with explicit property name should not have been found",
+ jsonObject.has("able"));
+ // property name someInt was replaced by newIntFieldName via user-defined annotation
+ assertTrue("Overridden int field name (newIntFieldName) should have been found",
+ jsonObject.has("newIntFieldName"));
+ // property name someLong was not replaced via user-defined annotation
+ assertTrue("Normal field name (someLong) should have been found",
+ jsonObject.has("someLong"));
+ // property name someString was replaced by myStringField via user-defined annotation
+ assertTrue("Overridden String field name (myStringField) should have been found",
+ jsonObject.has("myStringField"));
+ // property name myDouble was replaced by a weird name, followed by AMoreNormalName via user-defined annotations
+ assertTrue("Overridden double field name (AMoreNormalName) should have been found",
+ jsonObject.has("AMoreNormalName"));
+ // property name someFloat was replaced by InterfaceField via user-defined annotation
+ assertTrue("Overridden String field name (InterfaceField) should have been found",
+ jsonObject.has("InterfaceField"));
+ // property name ignoredInt was replaced by none, followed by forcedInt via user-defined annotations
+ assertTrue("Forced field should have been found!",
+ jsonObject.has("forcedInt"));
+ // property name able was replaced by Getable via user-defined annotation
+ assertTrue("Overridden boolean field name (Getable) should have been found",
+ jsonObject.has("Getable"));
+ }
+
+ /**
+ * A bean is also an object. But in order to test the JSONObject
+ * ctor that takes an object and a list of names,
+ * this particular bean needs some public
+ * data members, which have been added to the class.
+ */
+ @Test
+ public void jsonObjectByObjectAndNames() {
+ String[] keys = {"publicString", "publicInt"};
+ // just need a class that has public data members
+ MyPublicClass myPublicClass = new MyPublicClass();
+ JSONObject jsonObject = new JSONObject(myPublicClass, keys);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ 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")));
+ }
+
+ /**
+ * Exercise the JSONObject from resource bundle functionality.
+ * The test resource bundle is uncomplicated, but provides adequate test coverage.
+ */
+ @Test
+ public void jsonObjectByResourceBundle() {
+ JSONObject jsonObject = new
+ JSONObject("org.json.junit.data.StringsResourceBundle",
+ Locale.getDefault());
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 2 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 2);
+ assertTrue("expected 2 greetings items", ((Map,?>)(JsonPath.read(doc, "$.greetings"))).size() == 2);
+ assertTrue("expected \"hello\":\"Hello, \"", "Hello, ".equals(jsonObject.query("/greetings/hello")));
+ assertTrue("expected \"world\":\"World!\"", "World!".equals(jsonObject.query("/greetings/world")));
+ 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")));
+ }
+
+ /**
+ * Exercise the JSONObject.accumulate() method
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void jsonObjectAccumulate() {
+
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.accumulate("myArray", true);
+ jsonObject.accumulate("myArray", false);
+ jsonObject.accumulate("myArray", "hello world!");
+ jsonObject.accumulate("myArray", "h\be\tllo w\u1234orld!");
+ jsonObject.accumulate("myArray", 42);
+ jsonObject.accumulate("myArray", -23.45e7);
+ // include an unsupported object for coverage
+ try {
+ jsonObject.accumulate("myArray", Double.NaN);
+ fail("Expected exception");
+ } catch (JSONException ignored) {}
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 1 top level item", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 1);
+ assertTrue("expected 6 myArray items", ((List>)(JsonPath.read(doc, "$.myArray"))).size() == 6);
+ assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/myArray/0")));
+ assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/myArray/1")));
+ assertTrue("expected hello world!", "hello world!".equals(jsonObject.query("/myArray/2")));
+ 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")));
+ }
+
+ /**
+ * Exercise the JSONObject append() functionality
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void jsonObjectAppend() {
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.append("myArray", true);
+ jsonObject.append("myArray", false);
+ jsonObject.append("myArray", "hello world!");
+ jsonObject.append("myArray", "h\be\tllo w\u1234orld!");
+ jsonObject.append("myArray", 42);
+ jsonObject.append("myArray", -23.45e7);
+ // include an unsupported object for coverage
+ try {
+ jsonObject.append("myArray", Double.NaN);
+ fail("Expected exception");
+ } catch (JSONException ignored) {}
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 1 top level item", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 1);
+ assertTrue("expected 6 myArray items", ((List>)(JsonPath.read(doc, "$.myArray"))).size() == 6);
+ assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/myArray/0")));
+ assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/myArray/1")));
+ assertTrue("expected hello world!", "hello world!".equals(jsonObject.query("/myArray/2")));
+ 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")));
+ }
+
+ /**
+ * Exercise the JSONObject doubleToString() method
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ 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 };
+ for (int i = 0; i < expectedStrs.length; ++i) {
+ String actualStr = JSONObject.doubleToString(doubles[i]);
+ assertTrue("value expected ["+expectedStrs[i]+
+ "] found ["+actualStr+ "]",
+ expectedStrs[i].equals(actualStr));
+ }
+ }
+
+ /**
+ * Exercise some JSONObject get[type] and opt[type] methods
+ */
+ @Test
+ public void jsonObjectValues() {
+ String str =
+ "{"+
+ "\"trueKey\":true,"+
+ "\"falseKey\":false,"+
+ "\"trueStrKey\":\"true\","+
+ "\"falseStrKey\":\"false\","+
+ "\"stringKey\":\"hello world!\","+
+ "\"intKey\":42,"+
+ "\"intStrKey\":\"43\","+
+ "\"longKey\":1234567890123456789,"+
+ "\"longStrKey\":\"987654321098765432\","+
+ "\"doubleKey\":-23.45e7,"+
+ "\"doubleStrKey\":\"00001.000\","+
+ "\"BigDecimalStrKey\":\"19007199254740993.35481234487103587486413587843213584\","+
+ "\"negZeroKey\":-0.0,"+
+ "\"negZeroStrKey\":\"-0.0\","+
+ "\"arrayKey\":[0,1,2],"+
+ "\"objectKey\":{\"myKey\":\"myVal\"}"+
+ "}";
+ JSONObject jsonObject = new JSONObject(str);
+ assertTrue("trueKey should be true", jsonObject.getBoolean("trueKey"));
+ assertTrue("opt trueKey should be true", jsonObject.optBoolean("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("falseStrKey should be false", !jsonObject.getBoolean("falseStrKey"));
+ assertTrue("stringKey should be string",
+ jsonObject.getString("stringKey").equals("hello world!"));
+ assertTrue("doubleKey should be double",
+ jsonObject.getDouble("doubleKey") == -23.45e7);
+ assertTrue("doubleStrKey should be double",
+ jsonObject.getDouble("doubleStrKey") == 1);
+ assertTrue("doubleKey can be float",
+ jsonObject.getFloat("doubleKey") == -23.45e7f);
+ assertTrue("doubleStrKey can be float",
+ jsonObject.getFloat("doubleStrKey") == 1f);
+ assertTrue("opt doubleKey should be double",
+ jsonObject.optDouble("doubleKey") == -23.45e7);
+ assertTrue("opt doubleKey with Default should be double",
+ jsonObject.optDouble("doubleStrKey", Double.NaN) == 1);
+ assertTrue("opt negZeroKey should be a Double",
+ jsonObject.opt("negZeroKey") instanceof Double);
+ assertTrue("get negZeroKey should be a Double",
+ jsonObject.get("negZeroKey") instanceof Double);
+ assertTrue("optNumber negZeroKey should return Double",
+ jsonObject.optNumber("negZeroKey") instanceof Double);
+ assertTrue("optNumber negZeroStrKey should return Double",
+ jsonObject.optNumber("negZeroStrKey") instanceof Double);
+ assertTrue("opt negZeroKey should be double",
+ 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("optNumber negZeroKey should be -0.0",
+ Double.compare(jsonObject.optNumber("negZeroKey").doubleValue(), -0.0d) == 0);
+ assertTrue("optNumber negZeroStrKey should be -0.0",
+ Double.compare(jsonObject.optNumber("negZeroStrKey").doubleValue(), -0.0d) == 0);
+ assertTrue("optFloat doubleKey should be float",
+ jsonObject.optFloat("doubleKey") == -23.45e7f);
+ assertTrue("optFloat doubleKey with Default should be float",
+ jsonObject.optFloat("doubleStrKey", Float.NaN) == 1f);
+ assertTrue("intKey should be int",
+ jsonObject.optInt("intKey") == 42);
+ assertTrue("opt intKey should be int",
+ jsonObject.optInt("intKey", 0) == 42);
+ assertTrue("opt intKey with default should be int",
+ jsonObject.getInt("intKey") == 42);
+ assertTrue("intStrKey should be int",
+ jsonObject.getInt("intStrKey") == 43);
+ assertTrue("longKey should be long",
+ jsonObject.getLong("longKey") == 1234567890123456789L);
+ assertTrue("opt longKey should be long",
+ jsonObject.optLong("longKey") == 1234567890123456789L);
+ assertTrue("opt longKey with default should be long",
+ jsonObject.optLong("longKey", 0) == 1234567890123456789L);
+ assertTrue("longStrKey should be long",
+ jsonObject.getLong("longStrKey") == 987654321098765432L);
+ assertTrue("optNumber int should return Integer",
+ jsonObject.optNumber("intKey") instanceof Integer);
+ assertTrue("optNumber long should return Long",
+ jsonObject.optNumber("longKey") instanceof Long);
+ assertTrue("optNumber double should return BigDecimal",
+ jsonObject.optNumber("doubleKey") instanceof BigDecimal);
+ assertTrue("optNumber Str int should return Integer",
+ jsonObject.optNumber("intStrKey") instanceof Integer);
+ assertTrue("optNumber Str long should return Long",
+ jsonObject.optNumber("longStrKey") instanceof Long);
+ assertTrue("optNumber Str double should return BigDecimal",
+ jsonObject.optNumber("doubleStrKey") instanceof BigDecimal);
+ assertTrue("optNumber BigDecimalStrKey should return BigDecimal",
+ jsonObject.optNumber("BigDecimalStrKey") instanceof BigDecimal);
+ assertTrue("xKey should not exist",
+ jsonObject.isNull("xKey"));
+ assertTrue("stringKey should exist",
+ jsonObject.has("stringKey"));
+ assertTrue("opt stringKey should string",
+ jsonObject.optString("stringKey").equals("hello world!"));
+ assertTrue("opt stringKey with default should string",
+ jsonObject.optString("stringKey", "not found").equals("hello world!"));
+ JSONArray jsonArray = jsonObject.getJSONArray("arrayKey");
+ assertTrue("arrayKey should be JSONArray",
+ jsonArray.getInt(0) == 0 &&
+ jsonArray.getInt(1) == 1 &&
+ jsonArray.getInt(2) == 2);
+ jsonArray = jsonObject.optJSONArray("arrayKey");
+ assertTrue("opt arrayKey should be JSONArray",
+ jsonArray.getInt(0) == 0 &&
+ jsonArray.getInt(1) == 1 &&
+ jsonArray.getInt(2) == 2);
+ JSONObject jsonObjectInner = jsonObject.getJSONObject("objectKey");
+ assertTrue("objectKey should be JSONObject",
+ jsonObjectInner.get("myKey").equals("myVal"));
+ }
+
+ /**
+ * Check whether JSONObject handles large or high precision numbers correctly
+ */
+ @Test
+ 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!",
+ 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 );
+ /**
+ * This test documents a need for BigDecimal conversion.
+ */
+ Object obj = JSONObject.stringToValue( "299792.457999999984" );
+ assertTrue( "does not evaluate to 299792.457999999984 BigDecimal!",
+ obj.equals(new BigDecimal("299792.457999999984")) );
+ 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 );
+ 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 );
+
+ String str = new BigInteger( new Long( Long.MAX_VALUE ).toString() ).add( BigInteger.ONE ).toString();
+ assertTrue( "Really large integers currently evaluate to BigInteger",
+ JSONObject.stringToValue(str).equals(new BigInteger("9223372036854775808")));
+ }
+
+ /**
+ * This test documents numeric values which could be numerically
+ * handled as BigDecimal or BigInteger. It helps determine what outputs
+ * will change if those types are supported.
+ */
+ @Test
+ public void jsonValidNumberValuesNeitherLongNorIEEE754Compatible() {
+ // Valid JSON Numbers, probably should return BigDecimal or BigInteger objects
+ String str =
+ "{"+
+ "\"numberWithDecimals\":299792.457999999984,"+
+ "\"largeNumber\":12345678901234567890,"+
+ "\"preciseNumber\":0.2000000000000000111,"+
+ "\"largeExponent\":-23.45e2327"+
+ "}";
+ JSONObject jsonObject = new JSONObject(str);
+ // Comes back as a double, but loses precision
+ assertTrue( "numberWithDecimals currently evaluates to double 299792.458",
+ jsonObject.get( "numberWithDecimals" ).equals( new BigDecimal( "299792.457999999984" ) ) );
+ Object obj = jsonObject.get( "largeNumber" );
+ assertTrue("largeNumber currently evaluates to BigInteger",
+ new BigInteger("12345678901234567890").equals(obj));
+ // comes back as a double but loses precision
+ assertEquals( "preciseNumber currently evaluates to double 0.2",
+ 0.2, jsonObject.getDouble( "preciseNumber" ), 0.0);
+ obj = jsonObject.get( "largeExponent" );
+ assertTrue("largeExponent should evaluate as a BigDecimal",
+ new BigDecimal("-23.45e2327").equals(obj));
+ }
+
+ /**
+ * This test documents how JSON-Java handles invalid numeric input.
+ */
+ @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)));
+ }
+
+ /**
+ * Tests how JSONObject get[type] handles incorrect types
+ */
+ @Test
+ public void jsonObjectNonAndWrongValues() {
+ String str =
+ "{"+
+ "\"trueKey\":true,"+
+ "\"falseKey\":false,"+
+ "\"trueStrKey\":\"true\","+
+ "\"falseStrKey\":\"false\","+
+ "\"stringKey\":\"hello world!\","+
+ "\"intKey\":42,"+
+ "\"intStrKey\":\"43\","+
+ "\"longKey\":1234567890123456789,"+
+ "\"longStrKey\":\"987654321098765432\","+
+ "\"doubleKey\":-23.45e7,"+
+ "\"doubleStrKey\":\"00001.000\","+
+ "\"arrayKey\":[0,1,2],"+
+ "\"objectKey\":{\"myKey\":\"myVal\"}"+
+ "}";
+ JSONObject jsonObject = new JSONObject(str);
+ try {
+ jsonObject.getBoolean("nonKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("expecting an exception message",
+ "JSONObject[\"nonKey\"] not found.", e.getMessage());
+ }
+ try {
+ jsonObject.getBoolean("stringKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"stringKey\"] is not a Boolean.",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getString("nonKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"nonKey\"] not found.",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getString("trueKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"trueKey\"] is not a string.",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getDouble("nonKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"nonKey\"] not found.",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getDouble("stringKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"stringKey\"] is not a double.",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getFloat("nonKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"nonKey\"] not found.",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getFloat("stringKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"stringKey\"] is not a float.",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getInt("nonKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"nonKey\"] not found.",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getInt("stringKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"stringKey\"] is not a int.",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getLong("nonKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"nonKey\"] not found.",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getLong("stringKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"stringKey\"] is not a long.",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getJSONArray("nonKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"nonKey\"] not found.",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getJSONArray("stringKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"stringKey\"] is not a JSONArray.",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getJSONObject("nonKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"nonKey\"] not found.",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getJSONObject("stringKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"stringKey\"] is not a JSONObject.",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * This test documents an unexpected numeric behavior.
+ * A double that ends with .0 is parsed, serialized, then
+ * parsed again. On the second parse, it has become an int.
+ */
+ @Test
+ 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));
+
+ assertTrue("3.0 should remain a double",
+ jsonObject.getDouble(key30) == 3);
+ assertTrue("3.1 should remain a double",
+ jsonObject.getDouble(key31) == 3.1);
+
+ // turns 3.0 into 3.
+ String serializedString = jsonObject.toString();
+ JSONObject deserialized = new JSONObject(serializedString);
+ assertTrue("3.0 is now an int", deserialized.get(key30) instanceof Integer);
+ 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);
+ }
+
+ /**
+ * Document behaviors of big numbers. Includes both JSONObject
+ * and JSONArray tests
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void bigNumberOperations() {
+ /**
+ * JSONObject tries to parse BigInteger as a bean, but it only has
+ * one getter, getLowestBitSet(). The value is lost and an unhelpful
+ * 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);
+ assertTrue("JSONObject parses BigInteger as the Integer lowestBitSet",
+ obj instanceof Integer);
+ assertTrue("this bigInteger lowestBitSet happens to be 1",
+ obj.equals(1));
+
+ /**
+ * JSONObject tries to parse BigDecimal as a bean, but it has
+ * no getters, The value is lost and no value is stored.
+ * This should be fixed.
+ */
+ BigDecimal bigDecimal = new BigDecimal(
+ "123456789012345678901234567890.12345678901234567890123456789");
+ jsonObject = new JSONObject(bigDecimal);
+ assertTrue("large bigDecimal is not stored", jsonObject.isEmpty());
+
+ /**
+ * JSONObject put(String, Object) method stores and serializes
+ * bigInt and bigDec correctly. Nothing needs to change.
+ */
+ jsonObject = new JSONObject();
+ jsonObject.put("bigInt", bigInteger);
+ assertTrue("jsonObject.put() handles bigInt correctly",
+ jsonObject.get("bigInt").equals(bigInteger));
+ assertTrue("jsonObject.getBigInteger() handles bigInt correctly",
+ jsonObject.getBigInteger("bigInt").equals(bigInteger));
+ assertTrue("jsonObject.optBigInteger() handles bigInt correctly",
+ jsonObject.optBigInteger("bigInt", BigInteger.ONE).equals(bigInteger));
+ assertTrue("jsonObject serializes bigInt correctly",
+ jsonObject.toString().equals("{\"bigInt\":123456789012345678901234567890}"));
+ assertTrue("BigInteger as BigDecimal",
+ jsonObject.getBigDecimal("bigInt").equals(new BigDecimal(bigInteger)));
+
+
+ jsonObject = new JSONObject();
+ jsonObject.put("bigDec", bigDecimal);
+ assertTrue("jsonObject.put() handles bigDec correctly",
+ jsonObject.get("bigDec").equals(bigDecimal));
+ assertTrue("jsonObject.getBigDecimal() handles bigDec correctly",
+ jsonObject.getBigDecimal("bigDec").equals(bigDecimal));
+ assertTrue("jsonObject.optBigDecimal() handles bigDec correctly",
+ jsonObject.optBigDecimal("bigDec", BigDecimal.ONE).equals(bigDecimal));
+ assertTrue("jsonObject serializes bigDec correctly",
+ jsonObject.toString().equals(
+ "{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}"));
+
+ assertTrue("BigDecimal as BigInteger",
+ jsonObject.getBigInteger("bigDec").equals(bigDecimal.toBigInteger()));
+ /**
+ * exercise some exceptions
+ */
+ try {
+ // bigInt key does not exist
+ jsonObject.getBigDecimal("bigInt");
+ fail("expected an exeption");
+ } catch (JSONException ignored) {}
+ obj = jsonObject.optBigDecimal("bigInt", BigDecimal.ONE);
+ assertTrue("expected BigDecimal", obj.equals(BigDecimal.ONE));
+ jsonObject.put("stringKey", "abc");
+ try {
+ jsonObject.getBigDecimal("stringKey");
+ fail("expected an exeption");
+ } catch (JSONException ignored) {}
+ obj = jsonObject.optBigInteger("bigDec", BigInteger.ONE);
+ assertTrue("expected BigInteger", obj instanceof BigInteger);
+ assertEquals(bigDecimal.toBigInteger(), obj);
+
+ /**
+ * JSONObject.numberToString() works correctly, nothing to change.
+ */
+ String str = JSONObject.numberToString(bigInteger);
+ assertTrue("numberToString() handles bigInteger correctly",
+ str.equals("123456789012345678901234567890"));
+ str = JSONObject.numberToString(bigDecimal);
+ assertTrue("numberToString() handles bigDecimal correctly",
+ str.equals("123456789012345678901234567890.12345678901234567890123456789"));
+
+ /**
+ * JSONObject.stringToValue() turns bigInt into an accurate string,
+ * and rounds bigDec. This incorrect, but users may have come to
+ * expect this behavior. Change would be marginally better, but
+ * might inconvenience users.
+ */
+ obj = JSONObject.stringToValue(bigInteger.toString());
+ assertTrue("stringToValue() turns bigInteger string into Number",
+ obj instanceof Number);
+ obj = JSONObject.stringToValue(bigDecimal.toString());
+ assertTrue("stringToValue() changes bigDecimal Number",
+ obj instanceof Number);
+
+ /**
+ * wrap() vs put() big number behavior is now the same.
+ */
+ // bigInt map ctor
+ Map map = new HashMap();
+ map.put("bigInt", bigInteger);
+ jsonObject = new JSONObject(map);
+ String actualFromMapStr = jsonObject.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();
+ 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();
+ 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();
+ 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();
+ 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);
+ try {
+ jsonArray.getBigInteger(2);
+ fail("should not be able to get big int");
+ } catch (Exception ignored) {}
+ try {
+ jsonArray.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));
+
+ // bigInt,bigDec list ctor
+ List list = new ArrayList();
+ list.add(bigInteger);
+ list.add(bigDecimal);
+ jsonArray = new JSONArray(list);
+ String actualFromListStr = jsonArray.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();
+ // 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();
+ // 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"));
+ // bigInt,bigDec wrap()
+ obj = JSONObject.wrap(bigInteger);
+ assertTrue("wrap() returns big num",obj.equals(bigInteger));
+ obj = JSONObject.wrap(bigDecimal);
+ assertTrue("wrap() returns string",obj.equals(bigDecimal));
+
+ }
+
+ /**
+ * The purpose for the static method getNames() methods are not clear.
+ * This method is not called from within JSON-Java. Most likely
+ * uses are to prep names arrays for:
+ * JSONObject(JSONObject jo, String[] names)
+ * JSONObject(Object object, String names[]),
+ */
+ @Test
+ public void jsonObjectNames() {
+ JSONObject jsonObject;
+
+ // getNames() from null JSONObject
+ assertTrue("null names from null Object",
+ null == JSONObject.getNames((Object)null));
+
+ // getNames() from object with no fields
+ assertTrue("null names from Object with no fields",
+ null == JSONObject.getNames(new MyJsonString()));
+
+ // getNames from new JSONOjbect
+ jsonObject = new JSONObject();
+ String [] names = JSONObject.getNames(jsonObject);
+ assertTrue("names should be null", names == null);
+
+
+ // getNames() from empty JSONObject
+ String emptyStr = "{}";
+ jsonObject = new JSONObject(emptyStr);
+ assertTrue("empty JSONObject should have null names",
+ null == JSONObject.getNames(jsonObject));
+
+ // getNames() from JSONObject
+ String str =
+ "{"+
+ "\"trueKey\":true,"+
+ "\"falseKey\":false,"+
+ "\"stringKey\":\"hello world!\","+
+ "}";
+ jsonObject = new JSONObject(str);
+ names = JSONObject.getNames(jsonObject);
+ JSONArray jsonArray = new JSONArray(names);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider()
+ .parse(jsonArray.toString());
+ List> docList = JsonPath.read(doc, "$");
+ assertTrue("expected 3 items", docList.size() == 3);
+ 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);
+
+ /**
+ * getNames() from an enum with properties has an interesting result.
+ * It returns the enum values, not the selected enum properties
+ */
+ MyEnumField myEnumField = MyEnumField.VAL1;
+ names = JSONObject.getNames(myEnumField);
+
+ // validate JSON
+ jsonArray = new JSONArray(names);
+ doc = Configuration.defaultConfiguration().jsonProvider()
+ .parse(jsonArray.toString());
+ docList = JsonPath.read(doc, "$");
+ assertTrue("expected 3 items", docList.size() == 3);
+ assertTrue(
+ "expected to find VAL1",
+ ((List>) JsonPath.read(doc, "$[?(@=='VAL1')]")).size() == 1);
+ assertTrue(
+ "expected to find VAL2",
+ ((List>) JsonPath.read(doc, "$[?(@=='VAL2')]")).size() == 1);
+ assertTrue(
+ "expected to find VAL3",
+ ((List>) JsonPath.read(doc, "$[?(@=='VAL3')]")).size() == 1);
+
+ /**
+ * A bean is also an object. But in order to test the static
+ * method getNames(), this particular bean needs some public
+ * data members.
+ */
+ MyPublicClass myPublicClass = new MyPublicClass();
+ names = JSONObject.getNames(myPublicClass);
+
+ // validate JSON
+ jsonArray = new JSONArray(names);
+ doc = Configuration.defaultConfiguration().jsonProvider()
+ .parse(jsonArray.toString());
+ docList = JsonPath.read(doc, "$");
+ assertTrue("expected 2 items", docList.size() == 2);
+ assertTrue(
+ "expected to find publicString",
+ ((List>) JsonPath.read(doc, "$[?(@=='publicString')]")).size() == 1);
+ assertTrue(
+ "expected to find publicInt",
+ ((List>) JsonPath.read(doc, "$[?(@=='publicInt')]")).size() == 1);
+ }
+
+ /**
+ * Populate a JSONArray from an empty JSONObject names() method.
+ * It should be empty.
+ */
+ @Test
+ public void emptyJsonObjectNamesToJsonAray() {
+ JSONObject jsonObject = new JSONObject();
+ JSONArray jsonArray = jsonObject.names();
+ assertTrue("jsonArray should be null", jsonArray == null);
+ }
+
+ /**
+ * Populate a JSONArray from a JSONObject names() method.
+ * Confirm that it contains the expected names.
+ */
+ @Test
+ public void jsonObjectNamesToJsonAray() {
+ String str =
+ "{"+
+ "\"trueKey\":true,"+
+ "\"falseKey\":false,"+
+ "\"stringKey\":\"hello world!\","+
+ "}";
+
+ JSONObject jsonObject = new JSONObject(str);
+ JSONArray jsonArray = jsonObject.names();
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
+ assertTrue("expected 3 top level items", ((List>)(JsonPath.read(doc, "$"))).size() == 3);
+ 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);
+ }
+
+ /**
+ * Exercise the JSONObject increment() method.
+ */
+ @SuppressWarnings("cast")
+ @Test
+ public void jsonObjectIncrement() {
+ String str =
+ "{"+
+ "\"keyLong\":9999999991,"+
+ "\"keyDouble\":1.1"+
+ "}";
+ JSONObject jsonObject = new JSONObject(str);
+ jsonObject.increment("keyInt");
+ jsonObject.increment("keyInt");
+ jsonObject.increment("keyLong");
+ jsonObject.increment("keyDouble");
+ jsonObject.increment("keyInt");
+ jsonObject.increment("keyLong");
+ jsonObject.increment("keyDouble");
+ /**
+ * JSONObject constructor won't handle these types correctly, but
+ * adding them via put works.
+ */
+ jsonObject.put("keyFloat", 1.1f);
+ jsonObject.put("keyBigInt", new BigInteger("123456789123456789123456789123456780"));
+ jsonObject.put("keyBigDec", new BigDecimal("123456789123456789123456789123456780.1"));
+ jsonObject.increment("keyFloat");
+ jsonObject.increment("keyFloat");
+ jsonObject.increment("keyBigInt");
+ jsonObject.increment("keyBigDec");
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 6 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 6);
+ assertTrue("expected 3", Integer.valueOf(3).equals(jsonObject.query("/keyInt")));
+ assertTrue("expected 9999999993", Long.valueOf(9999999993L).equals(jsonObject.query("/keyLong")));
+ assertTrue("expected 3.1", BigDecimal.valueOf(3.1).equals(jsonObject.query("/keyDouble")));
+ assertTrue("expected 123456789123456789123456789123456781", new BigInteger("123456789123456789123456789123456781").equals(jsonObject.query("/keyBigInt")));
+ assertTrue("expected 123456789123456789123456789123456781.1", new BigDecimal("123456789123456789123456789123456781.1").equals(jsonObject.query("/keyBigDec")));
+
+ /**
+ * Should work the same way on any platform! @see https://docs.oracle
+ * .com/javase/specs/jls/se7/html/jls-4.html#jls-4.2.3 This is the
+ * effect of a float to double conversion and is inherent to the
+ * shortcomings of the IEEE 754 format, when converting 32-bit into
+ * double-precision 64-bit. Java type-casts float to double. A 32 bit
+ * float is type-casted to 64 bit double by simply appending zero-bits
+ * to the mantissa (and extended the signed exponent by 3 bits.) and
+ * there is no way to obtain more information than it is stored in the
+ * 32-bits float.
+ *
+ * Like 1/3 cannot be represented as base10 number because it is
+ * periodically, 1/5 (for example) cannot be represented as base2 number
+ * since it is periodically in base2 (take a look at
+ * http://www.h-schmidt.net/FloatConverter/) The same happens to 3.1,
+ * that decimal number (base10 representation) is periodic in base2
+ * representation, therefore appending zero-bits is inaccurate. Only
+ * repeating the periodically occurring bits (0110) would be a proper
+ * conversion. However one cannot detect from a 32 bit IEE754
+ * representation which bits would "repeat infinitely", since the
+ * missing bits would not fit into the 32 bit float, i.e. the
+ * information needed simply is not there!
+ */
+ assertEquals(Float.valueOf(3.1f), jsonObject.query("/keyFloat"));
+
+ /**
+ * float f = 3.1f; double df = (double) f; double d = 3.1d;
+ * System.out.println
+ * (Integer.toBinaryString(Float.floatToRawIntBits(f)));
+ * System.out.println
+ * (Long.toBinaryString(Double.doubleToRawLongBits(df)));
+ * System.out.println
+ * (Long.toBinaryString(Double.doubleToRawLongBits(d)));
+ *
+ * - Float:
+ * seeeeeeeemmmmmmmmmmmmmmmmmmmmmmm
+ * 1000000010001100110011001100110
+ * - Double
+ * seeeeeeeeeeemmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+ * 10000000 10001100110011001100110
+ * 100000000001000110011001100110011000000000000000000000000000000
+ * 100000000001000110011001100110011001100110011001100110011001101
+ */
+
+ /**
+ * Examples of well documented but probably unexpected behavior in
+ * java / with 32-bit float to 64-bit float conversion.
+ */
+ 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" );
+ 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() ) ) );
+
+ // 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 ) ) );
+
+ 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 )
+ 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);
+ // 1. The (Object)value will be typecasted to (Float)value since it is an instanceof Float actually nothing is done.
+ // 2. Float instance will be autoboxed into float because the + operator will work on primitives not Objects!
+ // 3. A float+float operation will be performed and results into a float primitive.
+ // 4. There is no method that matches the signature put( String key, float value), java-compiler will choose the method
+ // put( String key, double value) and does an implicit type-cast(!) by appending zero-bits to the mantissa
+ assertTrue( "JSONObject increment converts Float to Double", jo.get( "bug" ) instanceof Float );
+ // 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.)
+
+ }
+
+ /**
+ * Exercise JSONObject numberToString() method
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void jsonObjectNumberToString() {
+ String str;
+ Double dVal;
+ Integer iVal = 1;
+ str = JSONObject.numberToString(iVal);
+ assertTrue("expected "+iVal+" actual "+str, iVal.toString().equals(str));
+ dVal = 12.34;
+ str = JSONObject.numberToString(dVal);
+ assertTrue("expected "+dVal+" actual "+str, dVal.toString().equals(str));
+ dVal = 12.34e27;
+ str = JSONObject.numberToString(dVal);
+ assertTrue("expected "+dVal+" actual "+str, dVal.toString().equals(str));
+ // trailing .0 is truncated, so it doesn't quite match toString()
+ dVal = 5000000.0000000;
+ str = JSONObject.numberToString(dVal);
+ assertTrue("expected 5000000 actual "+str, str.equals("5000000"));
+ }
+
+ /**
+ * Exercise JSONObject put() and similar() methods
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void jsonObjectPut() {
+ String expectedStr =
+ "{"+
+ "\"trueKey\":true,"+
+ "\"falseKey\":false,"+
+ "\"arrayKey\":[0,1,2],"+
+ "\"objectKey\":{"+
+ "\"myKey1\":\"myVal1\","+
+ "\"myKey2\":\"myVal2\","+
+ "\"myKey3\":\"myVal3\","+
+ "\"myKey4\":\"myVal4\""+
+ "}"+
+ "}";
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("trueKey", true);
+ jsonObject.put("falseKey", false);
+ Integer [] intArray = { 0, 1, 2 };
+ jsonObject.put("arrayKey", Arrays.asList(intArray));
+ Map myMap = new HashMap();
+ myMap.put("myKey1", "myVal1");
+ myMap.put("myKey2", "myVal2");
+ myMap.put("myKey3", "myVal3");
+ myMap.put("myKey4", "myVal4");
+ jsonObject.put("objectKey", myMap);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 4 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 4);
+ assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
+ assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
+ assertTrue("expected 3 arrayKey items", ((List>)(JsonPath.read(doc, "$.arrayKey"))).size() == 3);
+ assertTrue("expected 0", Integer.valueOf(0).equals(jsonObject.query("/arrayKey/0")));
+ assertTrue("expected 1", Integer.valueOf(1).equals(jsonObject.query("/arrayKey/1")));
+ assertTrue("expected 2", Integer.valueOf(2).equals(jsonObject.query("/arrayKey/2")));
+ assertTrue("expected 4 objectKey items", ((Map,?>)(JsonPath.read(doc, "$.objectKey"))).size() == 4);
+ assertTrue("expected myVal1", "myVal1".equals(jsonObject.query("/objectKey/myKey1")));
+ 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")));
+
+ jsonObject.remove("trueKey");
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ assertTrue("unequal jsonObjects should not be similar",
+ !jsonObject.similar(expectedJsonObject));
+ assertTrue("jsonObject should not be similar to jsonArray",
+ !jsonObject.similar(new JSONArray()));
+
+ String aCompareValueStr = "{\"a\":\"aval\",\"b\":true}";
+ String bCompareValueStr = "{\"a\":\"notAval\",\"b\":true}";
+ JSONObject aCompareValueJsonObject = new JSONObject(aCompareValueStr);
+ JSONObject bCompareValueJsonObject = new JSONObject(bCompareValueStr);
+ assertTrue("different values should not be similar",
+ !aCompareValueJsonObject.similar(bCompareValueJsonObject));
+
+ String aCompareObjectStr = "{\"a\":\"aval\",\"b\":{}}";
+ String bCompareObjectStr = "{\"a\":\"aval\",\"b\":true}";
+ JSONObject aCompareObjectJsonObject = new JSONObject(aCompareObjectStr);
+ JSONObject bCompareObjectJsonObject = new JSONObject(bCompareObjectStr);
+ assertTrue("different nested JSONObjects should not be similar",
+ !aCompareObjectJsonObject.similar(bCompareObjectJsonObject));
+
+ String aCompareArrayStr = "{\"a\":\"aval\",\"b\":[]}";
+ String bCompareArrayStr = "{\"a\":\"aval\",\"b\":true}";
+ JSONObject aCompareArrayJsonObject = new JSONObject(aCompareArrayStr);
+ JSONObject bCompareArrayJsonObject = new JSONObject(bCompareArrayStr);
+ assertTrue("different nested JSONArrays should not be similar",
+ !aCompareArrayJsonObject.similar(bCompareArrayJsonObject));
+ }
+
+ /**
+ * Exercise JSONObject toString() method
+ */
+ @Test
+ public void jsonObjectToString() {
+ String str =
+ "{"+
+ "\"trueKey\":true,"+
+ "\"falseKey\":false,"+
+ "\"arrayKey\":[0,1,2],"+
+ "\"objectKey\":{"+
+ "\"myKey1\":\"myVal1\","+
+ "\"myKey2\":\"myVal2\","+
+ "\"myKey3\":\"myVal3\","+
+ "\"myKey4\":\"myVal4\""+
+ "}"+
+ "}";
+ JSONObject jsonObject = new JSONObject(str);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 4 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 4);
+ assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
+ assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
+ assertTrue("expected 3 arrayKey items", ((List>)(JsonPath.read(doc, "$.arrayKey"))).size() == 3);
+ assertTrue("expected 0", Integer.valueOf(0).equals(jsonObject.query("/arrayKey/0")));
+ assertTrue("expected 1", Integer.valueOf(1).equals(jsonObject.query("/arrayKey/1")));
+ assertTrue("expected 2", Integer.valueOf(2).equals(jsonObject.query("/arrayKey/2")));
+ assertTrue("expected 4 objectKey items", ((Map,?>)(JsonPath.read(doc, "$.objectKey"))).size() == 4);
+ assertTrue("expected myVal1", "myVal1".equals(jsonObject.query("/objectKey/myKey1")));
+ 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")));
+ }
+
+ /**
+ * Exercise JSONObject toString() method with various indent levels.
+ */
+ @Test
+ public void jsonObjectToStringIndent() {
+ String jsonObject0Str =
+ "{"+
+ "\"key1\":" +
+ "[1,2," +
+ "{\"key3\":true}" +
+ "],"+
+ "\"key2\":" +
+ "{\"key1\":\"val1\",\"key2\":" +
+ "{\"key2\":\"val2\"}" +
+ "},"+
+ "\"key3\":" +
+ "[" +
+ "[1,2.1]" +
+ "," +
+ "[null]" +
+ "]"+
+ "}";
+
+ String jsonObject1Str =
+ "{\n" +
+ " \"key1\": [\n" +
+ " 1,\n" +
+ " 2,\n" +
+ " {\"key3\": true}\n" +
+ " ],\n" +
+ " \"key2\": {\n" +
+ " \"key1\": \"val1\",\n" +
+ " \"key2\": {\"key2\": \"val2\"}\n" +
+ " },\n" +
+ " \"key3\": [\n" +
+ " [\n" +
+ " 1,\n" +
+ " 2.1\n" +
+ " ],\n" +
+ " [null]\n" +
+ " ]\n" +
+ "}";
+ String jsonObject4Str =
+ "{\n" +
+ " \"key1\": [\n" +
+ " 1,\n" +
+ " 2,\n" +
+ " {\"key3\": true}\n" +
+ " ],\n" +
+ " \"key2\": {\n" +
+ " \"key1\": \"val1\",\n" +
+ " \"key2\": {\"key2\": \"val2\"}\n" +
+ " },\n" +
+ " \"key3\": [\n" +
+ " [\n" +
+ " 1,\n" +
+ " 2.1\n" +
+ " ],\n" +
+ " [null]\n" +
+ " ]\n" +
+ "}";
+ JSONObject jsonObject = new JSONObject(jsonObject0Str);
+ // contents are tested in other methods, in this case just validate the spacing by
+ // checking length
+ assertEquals("toString() length",jsonObject0Str.length(), jsonObject.toString().length());
+ assertEquals("toString(0) length",jsonObject0Str.length(), jsonObject.toString(0).length());
+ assertEquals("toString(1) length",jsonObject1Str.length(), jsonObject.toString(1).length());
+ assertEquals("toString(4) length",jsonObject4Str.length(), jsonObject.toString(4).length());
+
+ JSONObject jo = new JSONObject().put("TABLE", new JSONObject().put("yhoo", new JSONObject()));
+ assertEquals("toString(2)","{\"TABLE\": {\"yhoo\": {}}}", jo.toString(2));
+ }
+
+ /**
+ * Explores how JSONObject handles maps. Insert a string/string map
+ * as a value in a JSONObject. It will remain a map. Convert the
+ * JSONObject to string, then create a new JSONObject from the string.
+ * In the new JSONObject, the value will be stored as a nested JSONObject.
+ * Confirm that map and nested JSONObject have the same contents.
+ */
+ @Test
+ public void jsonObjectToStringSuppressWarningOnCastToMap() {
+ JSONObject jsonObject = new JSONObject();
+ Map map = new HashMap<>();
+ map.put("abc", "def");
+ jsonObject.put("key", map);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ 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")));
+ }
+
+ /**
+ * Explores how JSONObject handles collections. Insert a string collection
+ * as a value in a JSONObject. It will remain a collection. Convert the
+ * JSONObject to string, then create a new JSONObject from the string.
+ * In the new JSONObject, the value will be stored as a nested JSONArray.
+ * Confirm that collection and nested JSONArray have the same contents.
+ */
+ @Test
+ public void jsonObjectToStringSuppressWarningOnCastToCollection() {
+ JSONObject jsonObject = new JSONObject();
+ Collection collection = new ArrayList();
+ collection.add("abc");
+ // ArrayList will be added as an object
+ jsonObject.put("key", collection);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ 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")));
+ }
+
+ /**
+ * Exercises the JSONObject.valueToString() method for various types
+ */
+ @Test
+ public void valueToString() {
+
+ assertTrue("null valueToString() incorrect",
+ "null".equals(JSONObject.valueToString(null)));
+ MyJsonString jsonString = new MyJsonString();
+ assertTrue("jsonstring valueToString() incorrect",
+ "my string".equals(JSONObject.valueToString(jsonString)));
+ assertTrue("boolean valueToString() incorrect",
+ "true".equals(JSONObject.valueToString(Boolean.TRUE)));
+ assertTrue("non-numeric double",
+ "null".equals(JSONObject.doubleToString(Double.POSITIVE_INFINITY)));
+ String jsonObjectStr =
+ "{"+
+ "\"key1\":\"val1\","+
+ "\"key2\":\"val2\","+
+ "\"key3\":\"val3\""+
+ "}";
+ JSONObject jsonObject = new JSONObject(jsonObjectStr);
+ assertTrue("jsonObject valueToString() incorrect",
+ JSONObject.valueToString(jsonObject).equals(jsonObject.toString()));
+ String jsonArrayStr =
+ "[1,2,3]";
+ JSONArray jsonArray = new JSONArray(jsonArrayStr);
+ assertTrue("jsonArray valueToString() incorrect",
+ JSONObject.valueToString(jsonArray).equals(jsonArray.toString()));
+ Map map = new HashMap();
+ map.put("key1", "val1");
+ map.put("key2", "val2");
+ map.put("key3", "val3");
+ assertTrue("map valueToString() incorrect",
+ jsonObject.toString().equals(JSONObject.valueToString(map)));
+ Collection collection = new ArrayList();
+ collection.add(new Integer(1));
+ collection.add(new Integer(2));
+ collection.add(new Integer(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) };
+ assertTrue("array valueToString() incorrect",
+ jsonArray.toString().equals(JSONObject.valueToString(array)));
+ }
+
+ /**
+ * Confirm that https://github.com/douglascrockford/JSON-java/issues/167 is fixed.
+ * The following code was throwing a ClassCastException in the
+ * JSONObject(Map) constructor
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void valueToStringConfirmException() {
+ Map myMap = new HashMap();
+ myMap.put(1, "myValue");
+ // this is the test, it should not throw an exception
+ String str = JSONObject.valueToString(myMap);
+ // confirm result, just in case
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(str);
+ assertTrue("expected 1 top level item", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 1);
+ assertTrue("expected myValue", "myValue".equals(JsonPath.read(doc, "$.1")));
+ }
+
+ /**
+ * Exercise the JSONObject wrap() method. Sometimes wrap() will change
+ * the object being wrapped, other times not. The purpose of wrap() is
+ * to ensure the value is packaged in a way that is compatible with how
+ * a JSONObject value or JSONArray value is supposed to be stored.
+ */
+ @Test
+ public void wrapObject() {
+ // wrap(null) returns NULL
+ assertTrue("null wrap() incorrect",
+ JSONObject.NULL == JSONObject.wrap(null));
+
+ // wrap(Integer) returns Integer
+ Integer in = new Integer(1);
+ assertTrue("Integer wrap() incorrect",
+ in == JSONObject.wrap(in));
+
+ /**
+ * This test is to document the preferred behavior if BigDecimal is
+ * supported. Previously bd returned as a string, since it
+ * is recognized as being a Java package class. Now with explicit
+ * support for big numbers, it remains a BigDecimal
+ */
+ Object bdWrap = JSONObject.wrap(BigDecimal.ONE);
+ assertTrue("BigDecimal.ONE evaluates to ONE",
+ bdWrap.equals(BigDecimal.ONE));
+
+ // wrap JSONObject returns JSONObject
+ String jsonObjectStr =
+ "{"+
+ "\"key1\":\"val1\","+
+ "\"key2\":\"val2\","+
+ "\"key3\":\"val3\""+
+ "}";
+ JSONObject jsonObject = new JSONObject(jsonObjectStr);
+ assertTrue("JSONObject wrap() incorrect",
+ jsonObject == JSONObject.wrap(jsonObject));
+
+ // wrap collection returns JSONArray
+ Collection collection = new ArrayList();
+ collection.add(new Integer(1));
+ collection.add(new Integer(2));
+ collection.add(new Integer(3));
+ JSONArray jsonArray = (JSONArray) (JSONObject.wrap(collection));
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
+ assertTrue("expected 3 top level items", ((List>)(JsonPath.read(doc, "$"))).size() == 3);
+ assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0")));
+ assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1")));
+ 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) };
+ JSONArray integerArrayJsonArray = (JSONArray)(JSONObject.wrap(array));
+
+ // validate JSON
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
+ assertTrue("expected 3 top level items", ((List>)(JsonPath.read(doc, "$"))).size() == 3);
+ assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0")));
+ assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1")));
+ assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2")));
+
+ // validate JSON
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(integerArrayJsonArray.toString());
+ assertTrue("expected 3 top level items", ((List>)(JsonPath.read(doc, "$"))).size() == 3);
+ assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0")));
+ assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1")));
+ assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2")));
+
+ // wrap map returns JSONObject
+ Map map = new HashMap();
+ map.put("key1", "val1");
+ map.put("key2", "val2");
+ map.put("key3", "val3");
+ JSONObject mapJsonObject = (JSONObject) (JSONObject.wrap(map));
+
+ // validate JSON
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(mapJsonObject.toString());
+ assertTrue("expected 3 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 3);
+ assertTrue("expected val1", "val1".equals(mapJsonObject.query("/key1")));
+ assertTrue("expected val2", "val2".equals(mapJsonObject.query("/key2")));
+ assertTrue("expected val3", "val3".equals(mapJsonObject.query("/key3")));
+ }
+
+
+ /**
+ * RFC 7159 defines control characters to be U+0000 through U+001F. This test verifies that the parser is checking for these in expected ways.
+ */
+ @Test
+ public void jsonObjectParseControlCharacters(){
+ for(int i = 0;i<=0x001f;i++){
+ final String charString = String.valueOf((char)i);
+ final String source = "{\"key\":\""+charString+"\"}";
+ try {
+ JSONObject jo = new JSONObject(source);
+ assertTrue("Expected "+charString+"("+i+") in the JSON Object but did not find it.",charString.equals(jo.getString("key")));
+ } 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'
+ );
+ }
+ }
+ }
+
+ /**
+ * Explore how JSONObject handles parsing errors.
+ */
+ @SuppressWarnings({"boxing", "unused"})
+ @Test
+ public void jsonObjectParsingErrors() {
+ 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());
+ }
+ try {
+ // does not end with '}'
+ String str = "{";
+ 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());
+ }
+ try {
+ // key with no ':'
+ String str = "{\"myKey\" = true}";
+ 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());
+ }
+ 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",
+ "Expected a ',' or '}' at 15 [character 16 line 1]",
+ e.getMessage());
+ }
+ try {
+ // append to wrong key
+ String str = "{\"myKey\":true, \"myOtherKey\":false}";
+ JSONObject jsonObject = new JSONObject(str);
+ jsonObject.append("myKey", "hello");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"myKey\"] is not a JSONArray (null).",
+ e.getMessage());
+ }
+ try {
+ // increment wrong key
+ String str = "{\"myKey\":true, \"myOtherKey\":false}";
+ JSONObject jsonObject = new JSONObject(str);
+ jsonObject.increment("myKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Unable to increment [\"myKey\"].",
+ e.getMessage());
+ }
+ try {
+ // invalid key
+ String str = "{\"myKey\":true, \"myOtherKey\":false}";
+ JSONObject jsonObject = new JSONObject(str);
+ jsonObject.get(null);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Null key.",
+ e.getMessage());
+ }
+ try {
+ // invalid numberToString()
+ JSONObject.numberToString((Number)null);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Null pointer",
+ e.getMessage());
+ }
+
+ try {
+ // multiple putOnce key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.putOnce("hello", "world");
+ jsonObject.putOnce("hello", "world!");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertTrue("", true);
+ }
+ try {
+ // test validity of invalid double
+ JSONObject.testValidity(Double.NaN);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertTrue("", true);
+ }
+ try {
+ // test validity of invalid float
+ JSONObject.testValidity(Float.NEGATIVE_INFINITY);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertTrue("", true);
+ }
+ 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"
+ + "}";
+ new JSONObject(str);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an expection message",
+ "Duplicate key \"attr03\" at 90 [character 13 line 5]",
+ e.getMessage());
+ }
+ 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"
+ + "}";
+ new JSONObject(str);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an expection message",
+ "Duplicate key \"attr03\" at 90 [character 13 line 5]",
+ e.getMessage());
+ }
+ 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"
+ + "}";
+ new JSONObject(str);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an expection message",
+ "Duplicate key \"attr03\" at 90 [character 13 line 5]",
+ e.getMessage());
+ }
+ 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"
+ + "}";
+ new JSONObject(str);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an expection message",
+ "Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
+ e.getMessage());
+ }
+ try {
+ // 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"
+ + "}";
+ new JSONObject(str);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an expection message",
+ "Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
+ e.getMessage());
+ }
+ try {
+ // 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"
+ + "}";
+ new JSONObject(str);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an expection message",
+ "Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
+ e.getMessage());
+ }
+ try {
+ // 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"
+ + "]";
+ new JSONArray(str);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an expection message",
+ "Duplicate key \"attr01\" at 124 [character 17 line 8]",
+ e.getMessage());
+ }
+ try {
+ // 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"
+ + "]";
+ new JSONArray(str);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an expection message",
+ "Duplicate key \"attr02-01\" at 269 [character 24 line 13]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Confirm behavior when putOnce() is called with null parameters
+ */
+ @Test
+ public void jsonObjectPutOnceNull() {
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.putOnce(null, null);
+ assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ jsonObject.putOnce("", null);
+ assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ jsonObject.putOnce(null, "");
+ assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ }
+
+ /**
+ * Exercise JSONObject opt(key, default) method.
+ */
+ @Test
+ public void jsonObjectOptDefault() {
+
+ String str = "{\"myKey\": \"myval\", \"hiKey\": null}";
+ JSONObject jsonObject = new JSONObject(str);
+
+ assertTrue("optBigDecimal() should return default BigDecimal",
+ BigDecimal.TEN.compareTo(jsonObject.optBigDecimal("myKey", BigDecimal.TEN))==0);
+ assertTrue("optBigInteger() should return default BigInteger",
+ BigInteger.TEN.compareTo(jsonObject.optBigInteger("myKey",BigInteger.TEN ))==0);
+ assertTrue("optBoolean() should return default boolean",
+ jsonObject.optBoolean("myKey", true));
+ assertTrue("optInt() should return default int",
+ 42 == jsonObject.optInt("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("optLong() should return default long",
+ 42l == jsonObject.optLong("myKey", 42l));
+ assertTrue("optDouble() should return default double",
+ 42.3d == jsonObject.optDouble("myKey", 42.3d));
+ assertTrue("optFloat() should return default float",
+ 42.3f == jsonObject.optFloat("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")));
+ }
+
+ /**
+ * Exercise JSONObject opt(key, default) method when the key doesn't exist.
+ */
+ @Test
+ public void jsonObjectOptNoKey() {
+
+ JSONObject jsonObject = new JSONObject();
+
+ assertNull(jsonObject.opt(null));
+
+ assertTrue("optBigDecimal() should return default BigDecimal",
+ BigDecimal.TEN.compareTo(jsonObject.optBigDecimal("myKey", BigDecimal.TEN))==0);
+ assertTrue("optBigInteger() should return default BigInteger",
+ BigInteger.TEN.compareTo(jsonObject.optBigInteger("myKey",BigInteger.TEN ))==0);
+ assertTrue("optBoolean() should return default boolean",
+ jsonObject.optBoolean("myKey", true));
+ assertTrue("optInt() should return default int",
+ 42 == jsonObject.optInt("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("optLong() should return default long",
+ 42l == jsonObject.optLong("myKey", 42l));
+ assertTrue("optDouble() should return default double",
+ 42.3d == jsonObject.optDouble("myKey", 42.3d));
+ assertTrue("optFloat() should return default float",
+ 42.3f == jsonObject.optFloat("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")));
+ }
+
+ /**
+ * Verifies that the opt methods properly convert string values.
+ */
+ @Test
+ public void jsonObjectOptStringConversion() {
+ JSONObject jo = new JSONObject("{\"int\":\"123\",\"true\":\"true\",\"false\":\"false\"}");
+ assertTrue("unexpected optBoolean value",jo.optBoolean("true",false)==true);
+ assertTrue("unexpected optBoolean value",jo.optBoolean("false",true)==false);
+ assertTrue("unexpected optInt value",jo.optInt("int",0)==123);
+ assertTrue("unexpected optLong value",jo.optLong("int",0)==123l);
+ assertTrue("unexpected optDouble value",jo.optDouble("int",0.0d)==123.0d);
+ assertTrue("unexpected optFloat value",jo.optFloat("int",0.0f)==123.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);
+ }
+
+ /**
+ * Verifies that the opt methods properly convert string values to numbers and coerce them consistently.
+ */
+ @Test
+ public void jsonObjectOptCoercion() {
+ JSONObject jo = new JSONObject("{\"largeNumberStr\":\"19007199254740993.35481234487103587486413587843213584\"}");
+ // currently the parser doesn't recognize BigDecimal, to we have to put it manually
+ jo.put("largeNumber", new BigDecimal("19007199254740993.35481234487103587486413587843213584"));
+
+ // Test type coercion from larger to smaller
+ 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.90071995E16f, jo.optFloat("largeNumber"),0.0f);
+ assertEquals(19007199254740993l, jo.optLong("largeNumber"));
+ assertEquals(1874919425, jo.optInt("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.90071995E16f, jo.optFloat("largeNumberStr"),0.0f);
+ assertEquals(19007199254740993l, jo.optLong("largeNumberStr"));
+ assertEquals(1874919425, jo.optInt("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((int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optInt("largeNumber"));
+ assertNotEquals((long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optLong("largeNumberStr"));
+ assertNotEquals((int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optInt("largeNumberStr"));
+ assertEquals(19007199254740992l, (long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"));
+ assertEquals(2147483647, (int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"));
+ }
+
+ /**
+ * Verifies that the optBigDecimal method properly converts values to BigDecimal and coerce them consistently.
+ */
+ @Test
+ public void jsonObjectOptBigDecimal() {
+ JSONObject jo = new JSONObject().put("int", 123).put("long", 654L)
+ .put("float", 1.234f).put("double", 2.345d)
+ .put("bigInteger", new BigInteger("1234"))
+ .put("bigDecimal", new BigDecimal("1234.56789"))
+ .put("nullVal", JSONObject.NULL);
+
+ assertEquals(new BigDecimal("123"),jo.optBigDecimal("int", null));
+ assertEquals(new BigDecimal("654"),jo.optBigDecimal("long", null));
+ assertEquals(new BigDecimal(1.234f),jo.optBigDecimal("float", null));
+ assertEquals(new BigDecimal(2.345d),jo.optBigDecimal("double", null));
+ assertEquals(new BigDecimal("1234"),jo.optBigDecimal("bigInteger", null));
+ assertEquals(new BigDecimal("1234.56789"),jo.optBigDecimal("bigDecimal", null));
+ assertNull(jo.optBigDecimal("nullVal", null));
+ assertEquals(jo.optBigDecimal("float", null),jo.getBigDecimal("float"));
+ assertEquals(jo.optBigDecimal("double", null),jo.getBigDecimal("double"));
+ }
+
+ /**
+ * Verifies that the optBigDecimal method properly converts values to BigDecimal and coerce them consistently.
+ */
+ @Test
+ public void jsonObjectOptBigInteger() {
+ JSONObject jo = new JSONObject().put("int", 123).put("long", 654L)
+ .put("float", 1.234f).put("double", 2.345d)
+ .put("bigInteger", new BigInteger("1234"))
+ .put("bigDecimal", new BigDecimal("1234.56789"))
+ .put("nullVal", JSONObject.NULL);
+
+ assertEquals(new BigInteger("123"),jo.optBigInteger("int", null));
+ assertEquals(new BigInteger("654"),jo.optBigInteger("long", null));
+ assertEquals(new BigInteger("1"),jo.optBigInteger("float", null));
+ assertEquals(new BigInteger("2"),jo.optBigInteger("double", null));
+ assertEquals(new BigInteger("1234"),jo.optBigInteger("bigInteger", null));
+ assertEquals(new BigInteger("1234"),jo.optBigInteger("bigDecimal", null));
+ assertNull(jo.optBigDecimal("nullVal", null));
+ }
+
+ /**
+ * Confirm behavior when JSONObject put(key, null object) is called
+ */
+ @Test
+ public void jsonObjectputNull() {
+
+ // put null should remove the item.
+ String str = "{\"myKey\": \"myval\"}";
+ JSONObject jsonObjectRemove = new JSONObject(str);
+ jsonObjectRemove.remove("myKey");
+ assertTrue("jsonObject should be empty", jsonObjectRemove.isEmpty());
+
+ JSONObject jsonObjectPutNull = new JSONObject(str);
+ jsonObjectPutNull.put("myKey", (Object) null);
+ assertTrue("jsonObject should be empty", jsonObjectPutNull.isEmpty());
+
+
+ }
+
+ /**
+ * Exercise JSONObject quote() method
+ * This purpose of quote() is to ensure that for strings with embedded
+ * quotes, the quotes are properly escaped.
+ */
+ @Test
+ public void jsonObjectQuote() {
+ String str;
+ str = "";
+ String quotedStr;
+ quotedStr = JSONObject.quote(str);
+ assertTrue("quote() expected escaped quotes, found "+quotedStr,
+ "\"\"".equals(quotedStr));
+ str = "\"\"";
+ quotedStr = JSONObject.quote(str);
+ assertTrue("quote() expected escaped quotes, found "+quotedStr,
+ "\"\\\"\\\"\"".equals(quotedStr));
+ str = "";
+ quotedStr = JSONObject.quote(str);
+ assertTrue("quote() expected escaped frontslash, found "+quotedStr,
+ "\"<\\/\"".equals(quotedStr));
+ str = "AB\bC";
+ quotedStr = JSONObject.quote(str);
+ assertTrue("quote() expected escaped backspace, found "+quotedStr,
+ "\"AB\\bC\"".equals(quotedStr));
+ str = "ABC\n";
+ quotedStr = JSONObject.quote(str);
+ assertTrue("quote() expected escaped newline, found "+quotedStr,
+ "\"ABC\\n\"".equals(quotedStr));
+ str = "AB\fC";
+ quotedStr = JSONObject.quote(str);
+ assertTrue("quote() expected escaped formfeed, found "+quotedStr,
+ "\"AB\\fC\"".equals(quotedStr));
+ str = "\r";
+ quotedStr = JSONObject.quote(str);
+ assertTrue("quote() expected escaped return, found "+quotedStr,
+ "\"\\r\"".equals(quotedStr));
+ str = "\u1234\u0088";
+ quotedStr = JSONObject.quote(str);
+ assertTrue("quote() expected escaped unicode, found "+quotedStr,
+ "\"\u1234\\u0088\"".equals(quotedStr));
+ }
+
+ /**
+ * Confirm behavior when JSONObject stringToValue() is called for an
+ * empty string
+ */
+ @Test
+ public void stringToValue() {
+ String str = "";
+ String valueStr = (String)(JSONObject.stringToValue(str));
+ assertTrue("stringToValue() expected empty String, found "+valueStr,
+ "".equals(valueStr));
+ }
+
+ /**
+ * Confirm behavior when toJSONArray is called with a null value
+ */
+ @Test
+ public void toJSONArray() {
+ assertTrue("toJSONArray() with null names should be null",
+ null == new JSONObject().toJSONArray(null));
+ }
+
+ /**
+ * Exercise the JSONObject write() method
+ */
+ @Test
+ 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()) {
+ 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]"));
+ }
+ }
+
+ /**
+ * Confirms that exceptions thrown when writing values are wrapped properly.
+ */
+ @Test
+ public void testJSONWriterException() {
+ final JSONObject jsonObject = new JSONObject();
+
+ jsonObject.put("someKey",new BrokenToString());
+
+ // test single element JSONObject
+ try(StringWriter writer = new StringWriter();) {
+ 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");
+ }
+
+ //test multiElement
+ jsonObject.put("somethingElse", "a value");
+
+ try (StringWriter writer = new StringWriter()) {
+ 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");
+ }
+
+ // test a more complex object
+ try (StringWriter writer = new StringWriter()) {
+ new JSONObject()
+ .put("somethingElse", "a value")
+ .put("someKey", new JSONArray()
+ .put(new JSONObject().put("key1", new BrokenToString())))
+ .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");
+ }
+
+ // test a more slightly complex object
+ try (StringWriter writer = new StringWriter()) {
+ new JSONObject()
+ .put("somethingElse", "a value")
+ .put("someKey", new JSONArray()
+ .put(new JSONObject().put("key1", new BrokenToString()))
+ .put(12345)
+ )
+ .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");
+ }
+
+ }
+
+
+ /**
+ * Exercise the JSONObject write() method
+ */
+/*
+ @Test
+ public void writeAppendable() {
+ String str = "{\"key1\":\"value1\",\"key2\":[1,2,3]}";
+ String expectedStr = str;
+ JSONObject jsonObject = new JSONObject(str);
+ StringBuilder stringBuilder = new StringBuilder();
+ Appendable appendable = jsonObject.write(stringBuilder);
+ String actualStr = appendable.toString();
+ assertTrue("write() expected " +expectedStr+
+ " but found " +actualStr,
+ expectedStr.equals(actualStr));
+ }
+*/
+
+ /**
+ * Exercise the JSONObject write(Writer, int, int) method
+ */
+ @Test
+ public void write3Param() throws IOException {
+ String str0 = "{\"key1\":\"value1\",\"key2\":[1,false,3.14]}";
+ String str2 =
+ "{\n" +
+ " \"key1\": \"value1\",\n" +
+ " \"key2\": [\n" +
+ " 1,\n" +
+ " false,\n" +
+ " 3.14\n" +
+ " ]\n" +
+ " }";
+ JSONObject jsonObject = new JSONObject(str0);
+ try (StringWriter stringWriter = new StringWriter();) {
+ 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]"));
+ }
+
+ try (StringWriter stringWriter = new StringWriter();) {
+ String actualStr = jsonObject.write(stringWriter,2,1).toString();
+
+ assertEquals("length", str2.length(), actualStr.length());
+ assertTrue("key1", actualStr.contains(" \"key1\": \"value1\""));
+ assertTrue("key2", actualStr.contains(" \"key2\": [\n" +
+ " 1,\n" +
+ " false,\n" +
+ " 3.14\n" +
+ " ]")
+ );
+ }
+ }
+
+ /**
+ * Exercise the JSONObject write(Appendable, int, int) method
+ */
+/*
+ @Test
+ public void write3ParamAppendable() {
+ String str0 = "{\"key1\":\"value1\",\"key2\":[1,false,3.14]}";
+ String str2 =
+ "{\n" +
+ " \"key1\": \"value1\",\n" +
+ " \"key2\": [\n" +
+ " 1,\n" +
+ " false,\n" +
+ " 3.14\n" +
+ " ]\n" +
+ " }";
+ JSONObject jsonObject = new JSONObject(str0);
+ String expectedStr = str0;
+ StringBuilder stringBuilder = new StringBuilder();
+ Appendable appendable = jsonObject.write(stringBuilder,0,0);
+ String actualStr = appendable.toString();
+ assertEquals(expectedStr, actualStr);
+
+ expectedStr = str2;
+ stringBuilder = new StringBuilder();
+ appendable = jsonObject.write(stringBuilder,2,1);
+ actualStr = appendable.toString();
+ assertEquals(expectedStr, actualStr);
+ }
+*/
+
+ /**
+ * Exercise the JSONObject equals() method
+ */
+ @Test
+ public void equals() {
+ String str = "{\"key\":\"value\"}";
+ JSONObject aJsonObject = new JSONObject(str);
+ assertTrue("Same JSONObject should be equal to itself",
+ aJsonObject.equals(aJsonObject));
+ }
+
+ /**
+ * JSON null is not the same as Java null. This test examines the differences
+ * in how they are handled by JSON-java.
+ */
+ @Test
+ public void jsonObjectNullOperations() {
+ /**
+ * The Javadoc for JSONObject.NULL states:
+ * "JSONObject.NULL is equivalent to the value that JavaScript calls null,
+ * whilst Java's null is equivalent to the value that JavaScript calls
+ * undefined."
+ *
+ * Standard ECMA-262 6th Edition / June 2015 (included to help explain the javadoc):
+ * undefined value: primitive value used when a variable has not been assigned a value
+ * Undefined type: type whose sole value is the undefined value
+ * null value: primitive value that represents the intentional absence of any object value
+ * Null type: type whose sole value is the null value
+ * Java SE8 language spec (included to help explain the javadoc):
+ * The Kinds of Types and Values ...
+ * There is also a special null type, the type of the expression null, which has no name.
+ * Because the null type has no name, it is impossible to declare a variable of the null
+ * type or to cast to the null type. The null reference is the only possible value of an
+ * expression of null type. The null reference can always be assigned or cast to any reference type.
+ * In practice, the programmer can ignore the null type and just pretend that null is merely
+ * a special literal that can be of any reference type.
+ * Extensible Markup Language (XML) 1.0 Fifth Edition / 26 November 2008
+ * No mention of null
+ * ECMA-404 1st Edition / October 2013:
+ * JSON Text ...
+ * These are three literal name tokens: ...
+ * null
+ *
+ * There seems to be no best practice to follow, it's all about what we
+ * want the code to do.
+ */
+
+ // add JSONObject.NULL then convert to string in the manner of XML.toString()
+ JSONObject jsonObjectJONull = new JSONObject();
+ Object obj = JSONObject.NULL;
+ jsonObjectJONull.put("key", obj);
+ Object value = jsonObjectJONull.opt("key");
+ assertTrue("opt() JSONObject.NULL should find JSONObject.NULL",
+ obj.equals(value));
+ value = jsonObjectJONull.get("key");
+ assertTrue("get() JSONObject.NULL should find JSONObject.NULL",
+ obj.equals(value));
+ if (value == null) {
+ value = "";
+ }
+ String string = value instanceof String ? (String)value : null;
+ assertTrue("XML toString() should convert JSONObject.NULL to null",
+ string == null);
+
+ // now try it with null
+ JSONObject jsonObjectNull = new JSONObject();
+ obj = null;
+ jsonObjectNull.put("key", obj);
+ value = jsonObjectNull.opt("key");
+ assertNull("opt() null should find null", value);
+ // what is this trying to do? It appears to test absolutely nothing...
+// if (value == null) {
+// value = "";
+// }
+// string = value instanceof String ? (String)value : null;
+// assertTrue("should convert null to empty string", "".equals(string));
+ try {
+ value = jsonObjectNull.get("key");
+ fail("get() null should throw exception");
+ } catch (Exception ignored) {}
+
+ /**
+ * XML.toString() then goes on to do something with the value
+ * if the key val is "content", then value.toString() will be
+ * called. This will evaluate to "null" for JSONObject.NULL,
+ * and the empty string for null.
+ * But if the key is anything else, then JSONObject.NULL will be emitted
+ * as null and null will be emitted as ""
+ */
+ String sJONull = XML.toString(jsonObjectJONull);
+ assertTrue("JSONObject.NULL should emit a null value",
+ "null ".equals(sJONull));
+ String sNull = XML.toString(jsonObjectNull);
+ assertTrue("null should emit an empty string", "".equals(sNull));
+ }
+
+ @Test(expected = JSONPointerException.class)
+ public void queryWithNoResult() {
+ new JSONObject().query("/a/b");
+ }
+
+ @Test
+ public void optQueryWithNoResult() {
+ assertNull(new JSONObject().optQuery("/a/b"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void optQueryWithSyntaxError() {
+ new JSONObject().optQuery("invalid");
+ }
+
+ @Test(expected = JSONException.class)
+ public void invalidEscapeSequence() {
+ String json = "{ \"\\url\": \"value\" }";
+ assertNull("Expected an exception",new JSONObject(json));
+ }
+
+ /**
+ * Exercise JSONObject toMap() method.
+ */
+ @Test
+ public void toMap() {
+ String jsonObjectStr =
+ "{" +
+ "\"key1\":" +
+ "[1,2," +
+ "{\"key3\":true}" +
+ "]," +
+ "\"key2\":" +
+ "{\"key1\":\"val1\",\"key2\":" +
+ "{\"key2\":null}," +
+ "\"key3\":42" +
+ "}," +
+ "\"key3\":" +
+ "[" +
+ "[\"value1\",2.1]" +
+ "," +
+ "[null]" +
+ "]" +
+ "}";
+
+ JSONObject jsonObject = new JSONObject(jsonObjectStr);
+ Map,?> map = jsonObject.toMap();
+
+ assertTrue("Map should not be null", map != null);
+ assertTrue("Map should have 3 elements", map.size() == 3);
+
+ List> key1List = (List>)map.get("key1");
+ assertTrue("key1 should not be null", key1List != null);
+ assertTrue("key1 list should have 3 elements", key1List.size() == 3);
+ assertTrue("key1 value 1 should be 1", key1List.get(0).equals(Integer.valueOf(1)));
+ assertTrue("key1 value 2 should be 2", key1List.get(1).equals(Integer.valueOf(2)));
+
+ Map,?> key1Value3Map = (Map,?>)key1List.get(2);
+ assertTrue("Map should not be null", key1Value3Map != null);
+ assertTrue("Map should have 1 element", key1Value3Map.size() == 1);
+ assertTrue("Map key3 should be true", key1Value3Map.get("key3").equals(Boolean.TRUE));
+
+ Map,?> key2Map = (Map,?>)map.get("key2");
+ assertTrue("key2 should not be null", key2Map != null);
+ assertTrue("key2 map should have 3 elements", key2Map.size() == 3);
+ assertTrue("key2 map key 1 should be val1", key2Map.get("key1").equals("val1"));
+ assertTrue("key2 map key 3 should be 42", key2Map.get("key3").equals(Integer.valueOf(42)));
+
+ Map,?> key2Val2Map = (Map,?>)key2Map.get("key2");
+ assertTrue("key2 map key 2 should not be null", key2Val2Map != null);
+ assertTrue("key2 map key 2 should have an entry", key2Val2Map.containsKey("key2"));
+ assertTrue("key2 map key 2 value should be null", key2Val2Map.get("key2") == null);
+
+ List> key3List = (List>)map.get("key3");
+ assertTrue("key3 should not be null", key3List != null);
+ assertTrue("key3 list should have 3 elements", key3List.size() == 2);
+
+ List> key3Val1List = (List>)key3List.get(0);
+ assertTrue("key3 list val 1 should not be null", key3Val1List != null);
+ assertTrue("key3 list val 1 should have 2 elements", key3Val1List.size() == 2);
+ assertTrue("key3 list val 1 list element 1 should be value1", key3Val1List.get(0).equals("value1"));
+ assertTrue("key3 list val 1 list element 2 should be 2.1", key3Val1List.get(1).equals(new BigDecimal("2.1")));
+
+ List> key3Val2List = (List>)key3List.get(1);
+ assertTrue("key3 list val 2 should not be null", key3Val2List != null);
+ assertTrue("key3 list val 2 should have 1 element", key3Val2List.size() == 1);
+ assertTrue("key3 list val 2 list element 1 should be null", key3Val2List.get(0) == null);
+
+ // Assert that toMap() is a deep copy
+ jsonObject.getJSONArray("key3").getJSONArray(0).put(0, "still value 1");
+ assertTrue("key3 list val 1 list element 1 should be value1", key3Val1List.get(0).equals("value1"));
+
+ // 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);
+ }
+
+ /**
+ * test that validates a singleton can be serialized as a bean.
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void testSingletonBean() {
+ final JSONObject jo = new JSONObject(Singleton.getInstance());
+ assertEquals(jo.keySet().toString(), 1, jo.length());
+ assertEquals(0, jo.get("someInt"));
+ assertEquals(null, jo.opt("someString"));
+
+ // Update the singleton values
+ Singleton.getInstance().setSomeInt(42);
+ Singleton.getInstance().setSomeString("Something");
+ final JSONObject jo2 = new JSONObject(Singleton.getInstance());
+ assertEquals(2, jo2.length());
+ assertEquals(42, jo2.get("someInt"));
+ assertEquals("Something", jo2.get("someString"));
+
+ // ensure our original jo hasn't changed.
+ assertEquals(0, jo.get("someInt"));
+ assertEquals(null, jo.opt("someString"));
+ }
+
+ /**
+ * test that validates a singleton can be serialized as a bean.
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void testSingletonEnumBean() {
+ final JSONObject jo = new JSONObject(SingletonEnum.getInstance());
+ assertEquals(jo.keySet().toString(), 1, jo.length());
+ assertEquals(0, jo.get("someInt"));
+ assertEquals(null, jo.opt("someString"));
+
+ // Update the singleton values
+ SingletonEnum.getInstance().setSomeInt(42);
+ SingletonEnum.getInstance().setSomeString("Something");
+ final JSONObject jo2 = new JSONObject(SingletonEnum.getInstance());
+ assertEquals(2, jo2.length());
+ assertEquals(42, jo2.get("someInt"));
+ assertEquals("Something", jo2.get("someString"));
+
+ // ensure our original jo hasn't changed.
+ assertEquals(0, jo.get("someInt"));
+ assertEquals(null, jo.opt("someString"));
+ }
+
+ /**
+ * Test to validate that a generic class can be serialized as a bean.
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void testGenericBean() {
+ GenericBean bean = new GenericBean<>(42);
+ final JSONObject jo = new JSONObject(bean);
+ assertEquals(jo.keySet().toString(), 8, jo.length());
+ assertEquals(42, jo.get("genericValue"));
+ assertEquals("Expected the getter to only be called once",
+ 1, bean.genericGetCounter);
+ assertEquals(0, bean.genericSetCounter);
+ }
+
+ /**
+ * Test to validate that a generic class can be serialized as a bean.
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void testGenericIntBean() {
+ GenericBeanInt bean = new GenericBeanInt(42);
+ final JSONObject jo = new JSONObject(bean);
+ assertEquals(jo.keySet().toString(), 10, jo.length());
+ assertEquals(42, jo.get("genericValue"));
+ assertEquals("Expected the getter to only be called once",
+ 1, bean.genericGetCounter);
+ assertEquals(0, bean.genericSetCounter);
+ }
+
+ /**
+ * Test to verify key
limitations in the JSONObject bean serializer.
+ */
+ @Test
+ public void testWierdListBean() {
+ @SuppressWarnings("boxing")
+ WeirdList bean = new WeirdList(42, 43, 44);
+ final JSONObject jo = new JSONObject(bean);
+ // get() should have a key of 0 length
+ // get(int) should be ignored base on parameter count
+ // getInt(int) should also be ignored based on parameter count
+ // add(Integer) should be ignore as it doesn't start with get/is and also has a parameter
+ // getALL should be mapped
+ assertEquals("Expected 1 key to be mapped. Instead found: "+jo.keySet().toString(),
+ 1, jo.length());
+ assertNotNull(jo.get("ALL"));
+ }
+
+ /**
+ * Tests the exception portions of populateMap.
+ */
+ @Test
+ public void testExceptionalBean() {
+ ExceptionalBean bean = new ExceptionalBean();
+ final JSONObject jo = new JSONObject(bean);
+ assertEquals("Expected 1 key to be mapped. Instead found: "+jo.keySet().toString(),
+ 1, jo.length());
+ assertTrue(jo.get("closeable") instanceof JSONObject);
+ assertTrue(jo.getJSONObject("closeable").has("string"));
+ }
+
+ @Test(expected=NullPointerException.class)
+ public void testPutNullBoolean() {
+ // null put key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.put(null, false);
+ fail("Expected an exception");
+ }
+ @Test(expected=NullPointerException.class)
+ public void testPutNullCollection() {
+ // null put key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.put(null, Collections.emptySet());
+ fail("Expected an exception");
+ }
+ @Test(expected=NullPointerException.class)
+ public void testPutNullDouble() {
+ // null put key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.put(null, 0.0d);
+ fail("Expected an exception");
+ }
+ @Test(expected=NullPointerException.class)
+ public void testPutNullFloat() {
+ // null put key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.put(null, 0.0f);
+ fail("Expected an exception");
+ }
+ @Test(expected=NullPointerException.class)
+ public void testPutNullInt() {
+ // null put key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.put(null, 0);
+ fail("Expected an exception");
+ }
+ @Test(expected=NullPointerException.class)
+ public void testPutNullLong() {
+ // null put key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.put(null, 0L);
+ fail("Expected an exception");
+ }
+ @Test(expected=NullPointerException.class)
+ public void testPutNullMap() {
+ // null put key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.put(null, Collections.emptyMap());
+ fail("Expected an exception");
+ }
+ @Test(expected=NullPointerException.class)
+ public void testPutNullObject() {
+ // null put key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.put(null, new Object());
+ fail("Expected an exception");
+ }
+
+}
diff --git a/src/test/java/org/json/junit/JSONPointerTest.java b/src/test/java/org/json/junit/JSONPointerTest.java
new file mode 100644
index 0000000..e06851e
--- /dev/null
+++ b/src/test/java/org/json/junit/JSONPointerTest.java
@@ -0,0 +1,384 @@
+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.
+*/
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.InputStream;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.json.JSONPointer;
+import org.json.JSONPointerException;
+import org.json.JSONTokener;
+import org.junit.Test;
+
+public class JSONPointerTest {
+
+ private static final JSONObject document;
+
+ static {
+ @SuppressWarnings("resource")
+ InputStream resourceAsStream = JSONPointerTest.class.getClassLoader().getResourceAsStream("jsonpointer-testdoc.json");
+ if(resourceAsStream == null) {
+ throw new ExceptionInInitializerError("Unable to locate test file. Please check your development environment configuration");
+ }
+ document = new JSONObject(new JSONTokener(resourceAsStream));
+ }
+
+ private Object query(String pointer) {
+ return new JSONPointer(pointer).queryFrom(document);
+ }
+
+ @Test
+ public void emptyPointer() {
+ assertSame(document, query(""));
+ }
+
+ @SuppressWarnings("unused")
+ @Test(expected = NullPointerException.class)
+ public void nullPointer() {
+ new JSONPointer((String) null);
+ }
+
+ @Test
+ public void objectPropertyQuery() {
+ assertSame(document.get("foo"), query("/foo"));
+ }
+
+ @Test
+ public void arrayIndexQuery() {
+ assertSame(document.getJSONArray("foo").get(0), query("/foo/0"));
+ }
+
+ @Test(expected = JSONPointerException.class)
+ public void stringPropOfArrayFailure() {
+ query("/foo/bar");
+ }
+
+ @Test
+ public void queryByEmptyKey() {
+ assertSame(document.get(""), query("/"));
+ }
+
+ @Test
+ public void queryByEmptyKeySubObject() {
+ assertSame(document.getJSONObject("obj").getJSONObject(""), query("/obj/"));
+ }
+
+ @Test
+ public void queryByEmptyKeySubObjectSubOject() {
+ assertSame(
+ document.getJSONObject("obj").getJSONObject("").get(""),
+ query("/obj//")
+ );
+ }
+
+ @Test
+ public void queryByEmptyKeySubObjectValue() {
+ assertSame(
+ document.getJSONObject("obj").getJSONObject("").get("subKey"),
+ query("/obj//subKey")
+ );
+ }
+
+ @Test
+ public void slashEscaping() {
+ assertSame(document.get("a/b"), query("/a~1b"));
+ }
+
+ @Test
+ public void tildeEscaping() {
+ assertSame(document.get("m~n"), query("/m~0n"));
+ }
+
+ @Test
+ public void backslashEscaping() {
+ assertSame(document.get("i\\j"), query("/i\\\\j"));
+ }
+
+ @Test
+ public void quotationEscaping() {
+ assertSame(document.get("k\"l"), query("/k\\\\\\\"l"));
+ }
+
+ @Test
+ public void whitespaceKey() {
+ assertSame(document.get(" "), query("/ "));
+ }
+
+ @Test
+ public void uriFragmentNotation() {
+ assertSame(document.get("foo"), query("#/foo"));
+ }
+
+ @Test
+ public void uriFragmentNotationRoot() {
+ assertSame(document, 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"));
+ }
+
+ @SuppressWarnings("unused")
+ @Test(expected = IllegalArgumentException.class)
+ public void syntaxError() {
+ new JSONPointer("key");
+ }
+
+ @Test(expected = JSONPointerException.class)
+ public void arrayIndexFailure() {
+ query("/foo/2");
+ }
+
+ @Test(expected = JSONPointerException.class)
+ public void primitiveFailure() {
+ query("/obj/key/failure");
+ }
+
+ @Test
+ public void builderTest() {
+ JSONPointer pointer = JSONPointer.builder()
+ .append("obj")
+ .append("other~key").append("another/key")
+ .append(0)
+ .build();
+ assertEquals("val", pointer.queryFrom(document));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void nullToken() {
+ JSONPointer.builder().append(null);
+ }
+
+ @Test
+ public void toStringEscaping() {
+ JSONPointer pointer = JSONPointer.builder()
+ .append("obj")
+ .append("other~key").append("another/key")
+ .append("\"")
+ .append(0)
+ .build();
+ assertEquals("/obj/other~0key/another~1key/\\\"/0", pointer.toString());
+ }
+
+ @Test
+ public void emptyPointerToString() {
+ assertEquals("", new JSONPointer("").toString());
+ }
+
+ @Test
+ public void toURIFragment() {
+ assertEquals("#/c%25d", new JSONPointer("/c%d").toURIFragment());
+ assertEquals("#/e%5Ef", new JSONPointer("/e^f").toURIFragment());
+ assertEquals("#/g%7Ch", new JSONPointer("/g|h").toURIFragment());
+ assertEquals("#/m%7En", new JSONPointer("/m~n").toURIFragment());
+ }
+
+ @Test
+ public void tokenListIsCopiedInConstructor() {
+ JSONPointer.Builder b = JSONPointer.builder().append("key1");
+ JSONPointer jp1 = b.build();
+ b.append("key2");
+ JSONPointer jp2 = b.build();
+ if(jp1.toString().equals(jp2.toString())) {
+ fail("Oops, my pointers are sharing a backing array");
+ }
+ }
+
+ /**
+ * Coverage for JSONObject query(String)
+ */
+ @Test
+ public void queryFromJSONObject() {
+ String str = "{"+
+ "\"stringKey\":\"hello world!\","+
+ "\"arrayKey\":[0,1,2],"+
+ "\"objectKey\": {"+
+ "\"a\":\"aVal\","+
+ "\"b\":\"bVal\""+
+ "}"+
+ "}";
+ JSONObject jsonObject = new JSONObject(str);
+ Object obj = jsonObject.query("/stringKey");
+ assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
+ obj = jsonObject.query("/arrayKey/1");
+ assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
+ obj = jsonObject.query("/objectKey/b");
+ assertTrue("Expected bVal", "bVal".equals(obj));
+ try {
+ obj = jsonObject.query("/a/b/c");
+ assertTrue("Expected JSONPointerException", false);
+ } catch (JSONPointerException e) {
+ assertTrue("Expected bad key/value exception",
+ "value [null] is not an array or object therefore its key b cannot be resolved".
+ equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Coverage for JSONObject query(JSONPointer)
+ */
+ @Test
+ public void queryFromJSONObjectUsingPointer() {
+ String str = "{"+
+ "\"stringKey\":\"hello world!\","+
+ "\"arrayKey\":[0,1,2],"+
+ "\"objectKey\": {"+
+ "\"a\":\"aVal\","+
+ "\"b\":\"bVal\""+
+ "}"+
+ "}";
+ JSONObject jsonObject = new JSONObject(str);
+ Object obj = jsonObject.query(new JSONPointer("/stringKey"));
+ assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
+ obj = jsonObject.query(new JSONPointer("/arrayKey/1"));
+ assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
+ obj = jsonObject.query(new JSONPointer("/objectKey/b"));
+ assertTrue("Expected bVal", "bVal".equals(obj));
+ try {
+ obj = jsonObject.query(new JSONPointer("/a/b/c"));
+ assertTrue("Expected JSONPointerException", false);
+ } catch (JSONPointerException e) {
+ assertTrue("Expected bad key/value exception",
+ "value [null] is not an array or object therefore its key b cannot be resolved".
+ equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Coverage for JSONObject optQuery(JSONPointer)
+ */
+ @Test
+ public void optQueryFromJSONObjectUsingPointer() {
+ String str = "{"+
+ "\"stringKey\":\"hello world!\","+
+ "\"arrayKey\":[0,1,2],"+
+ "\"objectKey\": {"+
+ "\"a\":\"aVal\","+
+ "\"b\":\"bVal\""+
+ "}"+
+ "}";
+ JSONObject jsonObject = new JSONObject(str);
+ Object obj = jsonObject.optQuery(new JSONPointer("/stringKey"));
+ assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
+ obj = jsonObject.optQuery(new JSONPointer("/arrayKey/1"));
+ assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
+ obj = jsonObject.optQuery(new JSONPointer("/objectKey/b"));
+ assertTrue("Expected bVal", "bVal".equals(obj));
+ obj = jsonObject.optQuery(new JSONPointer("/a/b/c"));
+ assertTrue("Expected null", obj == null);
+ }
+
+ /**
+ * Coverage for JSONArray query(String)
+ */
+ @Test
+ public void queryFromJSONArray() {
+ String str = "["+
+ "\"hello world!\","+
+ "[0,1,2],"+
+ "{"+
+ "\"a\":\"aVal\","+
+ "\"b\":\"bVal\""+
+ "}"+
+ "]";
+ JSONArray jsonArray = new JSONArray(str);
+ Object obj = jsonArray.query("/0");
+ assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
+ obj = jsonArray.query("/1/1");
+ assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
+ obj = jsonArray.query("/2/b");
+ assertTrue("Expected bVal", "bVal".equals(obj));
+ try {
+ obj = jsonArray.query("/a/b/c");
+ assertTrue("Expected JSONPointerException", false);
+ } catch (JSONPointerException e) {
+ assertTrue("Expected bad index exception",
+ "a is not an array index".equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Coverage for JSONArray query(JSONPointer)
+ */
+ @Test
+ public void queryFromJSONArrayUsingPointer() {
+ String str = "["+
+ "\"hello world!\","+
+ "[0,1,2],"+
+ "{"+
+ "\"a\":\"aVal\","+
+ "\"b\":\"bVal\""+
+ "}"+
+ "]";
+ JSONArray jsonArray = new JSONArray(str);
+ Object obj = jsonArray.query(new JSONPointer("/0"));
+ assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
+ obj = jsonArray.query(new JSONPointer("/1/1"));
+ assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
+ obj = jsonArray.query(new JSONPointer("/2/b"));
+ assertTrue("Expected bVal", "bVal".equals(obj));
+ try {
+ obj = jsonArray.query(new JSONPointer("/a/b/c"));
+ assertTrue("Expected JSONPointerException", false);
+ } catch (JSONPointerException e) {
+ assertTrue("Expected bad index exception",
+ "a is not an array index".equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Coverage for JSONArray optQuery(JSONPointer)
+ */
+ @Test
+ public void optQueryFromJSONArrayUsingPointer() {
+ String str = "["+
+ "\"hello world!\","+
+ "[0,1,2],"+
+ "{"+
+ "\"a\":\"aVal\","+
+ "\"b\":\"bVal\""+
+ "}"+
+ "]";
+ JSONArray jsonArray = new JSONArray(str);
+ Object obj = jsonArray.optQuery(new JSONPointer("/0"));
+ assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
+ obj = jsonArray.optQuery(new JSONPointer("/1/1"));
+ assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
+ obj = jsonArray.optQuery(new JSONPointer("/2/b"));
+ assertTrue("Expected bVal", "bVal".equals(obj));
+ obj = jsonArray.optQuery(new JSONPointer("/a/b/c"));
+ assertTrue("Expected null", obj == null);
+ }
+}
diff --git a/src/test/java/org/json/junit/JSONStringTest.java b/src/test/java/org/json/junit/JSONStringTest.java
new file mode 100644
index 0000000..788d8eb
--- /dev/null
+++ b/src/test/java/org/json/junit/JSONStringTest.java
@@ -0,0 +1,360 @@
+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.
+*/
+
+import static org.junit.Assert.*;
+
+import java.io.StringWriter;
+import java.util.*;
+
+import org.json.*;
+import org.junit.Test;
+
+/**
+ * Tests for JSONString implementations, and the difference between
+ * {@link JSONObject#valueToString} and {@link JSONObject#writeValue}.
+ */
+public class JSONStringTest {
+
+ /**
+ * This tests the JSONObject.writeValue() method. We can't test directly
+ * due to it being a package-protected method. Instead, we can call
+ * JSONArray.write(), which delegates the writing of each entry to
+ * writeValue().
+ */
+ @Test
+ public void writeValues() throws Exception {
+ JSONArray jsonArray = new JSONArray();
+ jsonArray.put((Object)null);
+
+ try (StringWriter writer = new StringWriter();) {
+ String output = jsonArray.write(writer).toString();
+ assertTrue("String values should be equal", "[null]".equals(output));
+
+ jsonArray = new JSONArray();
+ jsonArray.put(JSONObject.NULL);
+ }
+
+ try (StringWriter writer = new StringWriter();) {
+ String output = jsonArray.write(writer).toString();
+ assertTrue("String values should be equal", "[null]".equals(output));
+
+ jsonArray = new JSONArray();
+ jsonArray.put(new JSONObject());
+ }
+
+ try (StringWriter writer = new StringWriter();) {
+ String output = jsonArray.write(writer).toString();
+ assertTrue("String values should be equal", "[{}]".equals(output));
+
+ jsonArray = new JSONArray();
+ jsonArray.put(new JSONArray());
+ }
+
+ try (StringWriter writer = new StringWriter();) {
+ 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);
+ }
+
+ try (StringWriter writer = new StringWriter();) {
+ 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);
+ }
+
+ try (StringWriter writer = new StringWriter();) {
+ 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);
+ }
+
+ try (StringWriter writer = new StringWriter();) {
+ String output = jsonArray.write(writer).toString();
+ assertTrue("String values should be equal", "[[1,2,3]]".equals(output));
+
+ jsonArray = new JSONArray();
+ jsonArray.put(24);
+ }
+
+ try (StringWriter writer = new StringWriter();) {
+ String output = jsonArray.write(writer).toString();
+ assertTrue("String values should be equal", "[24]".equals(output));
+
+ jsonArray = new JSONArray();
+ jsonArray.put("string value");
+ }
+
+ try (StringWriter writer = new StringWriter();) {
+ String output = jsonArray.write(writer).toString();
+ assertTrue("String values should be equal", "[\"string value\"]".equals(output));
+
+ jsonArray = new JSONArray();
+ jsonArray.put(true);
+ }
+
+ try (StringWriter writer = new StringWriter();) {
+ String output = jsonArray.write(writer).toString();
+ assertTrue("String values should be equal", "[true]".equals(output));
+ }
+
+ }
+
+ /**
+ * This tests the JSONObject.valueToString() method. These should be
+ * identical to the values above, except for the enclosing [ and ].
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void valuesToString() throws Exception {
+
+ String output = JSONObject.valueToString(null);
+ assertTrue("String values should be equal", "null".equals(output));
+
+ output = JSONObject.valueToString(JSONObject.NULL);
+ assertTrue("String values should be equal", "null".equals(output));
+
+ output = JSONObject.valueToString(new JSONObject());
+ assertTrue("String values should be equal", "{}".equals(output));
+
+ output = JSONObject.valueToString(new JSONArray());
+ assertTrue("String values should be equal", "[]".equals(output));
+
+ Map,?> singleMap = Collections.singletonMap("key1", "value1");
+ output = JSONObject.valueToString(singleMap);
+ assertTrue("String values should be equal", "{\"key1\":\"value1\"}".equals(output));
+
+ List> singleList = Collections.singletonList("entry1");
+ output = JSONObject.valueToString(singleList);
+ assertTrue("String values should be equal", "[\"entry1\"]".equals(output));
+
+ int[] intArray = new int[] { 1, 2, 3 };
+ output = JSONObject.valueToString(intArray);
+ assertTrue("String values should be equal", "[1,2,3]".equals(output));
+
+ output = JSONObject.valueToString(24);
+ assertTrue("String values should be equal", "24".equals(output));
+
+ output = JSONObject.valueToString("string value");
+ assertTrue("String values should be equal", "\"string value\"".equals(output));
+
+ output = JSONObject.valueToString(true);
+ assertTrue("String values should be equal", "true".equals(output));
+
+ }
+
+ /**
+ * Test what happens when toJSONString() returns a well-formed JSON value.
+ * This is the usual case.
+ */
+ @Test
+ public void testJSONStringValue() throws Exception {
+ JSONStringValue jsonString = new JSONStringValue();
+ JSONArray jsonArray = new JSONArray();
+
+ jsonArray.put(jsonString);
+
+
+ try (StringWriter writer = new StringWriter();) {
+ 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));
+ }
+ }
+
+ /**
+ * Test what happens when toJSONString() returns null. In one case,
+ * use the object's toString() method. In the other, throw a JSONException.
+ */
+ @Test
+ public void testJSONNullStringValue() throws Exception {
+ JSONNullStringValue jsonString = new JSONNullStringValue();
+ JSONArray jsonArray = new JSONArray();
+
+ jsonArray.put(jsonString);
+
+ try (StringWriter writer = new StringWriter();) {
+ String output = jsonArray.write(writer).toString();
+ assertTrue("String values should be equal", "[\"the toString value\"]".equals(output));
+
+ // The only different between writeValue() and valueToString():
+ // in this case, valueToString throws a JSONException
+ try {
+ output = JSONObject.valueToString(jsonString);
+ fail("Expected an exception, got a String value");
+ } catch (Exception e) {
+ assertTrue("Expected JSONException", e instanceof JSONException);
+ assertTrue("Exception message does not match", "Bad value from toJSONString: null".equals(e.getMessage()));
+ }
+ }
+ }
+
+ /**
+ * Test what happens when toJSONString() returns an exception. In both
+ * cases, a JSONException is thrown, with the cause and message set from
+ * the original exception.
+ */
+ @Test
+ public void testJSONStringExceptionValue() {
+ JSONStringExceptionValue jsonString = new JSONStringExceptionValue();
+ JSONArray jsonArray = new JSONArray();
+
+ jsonArray.put(jsonString);
+
+ try (StringWriter writer = new StringWriter();) {
+ 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");
+ }
+
+ try {
+ JSONObject.valueToString(jsonString);
+ fail("Expected an exception, got a String value");
+ } catch (JSONException e) {
+ assertTrue("Exception message does not match", "the exception value".equals(e.getMessage()));
+ } catch(Exception e) {
+ fail("Expected JSONException");
+ }
+ }
+
+ /**
+ * Test what happens when a Java object's toString() returns a String value.
+ * This is the usual case.
+ */
+ @Test
+ public void testStringValue() throws Exception {
+ StringValue nonJsonString = new StringValue();
+ JSONArray jsonArray = new JSONArray();
+
+ jsonArray.put(nonJsonString);
+
+ try (StringWriter writer = new StringWriter();) {
+ 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));
+ }
+ }
+
+ /**
+ * Test what happens when a Java object's toString() returns null.
+ * Defaults to empty string.
+ */
+ @Test
+ public void testNullStringValue() throws Exception {
+ NullStringValue nonJsonString = new NullStringValue();
+ JSONArray jsonArray = new JSONArray();
+
+ jsonArray.put(nonJsonString);
+
+
+ try (StringWriter writer = new StringWriter();) {
+ 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));
+ }
+ }
+
+ /**
+ * A JSONString that returns a valid JSON string value.
+ */
+ private static final class JSONStringValue implements JSONString {
+
+ @Override
+ public String toJSONString() {
+ return "\"the JSON string value\"";
+ }
+
+ @Override
+ public String toString() {
+ return "the toString value for JSONStringValue";
+ }
+ }
+
+ /**
+ * A JSONString that returns null when calling toJSONString().
+ */
+ private static final class JSONNullStringValue implements JSONString {
+
+ @Override
+ public String toJSONString() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "the toString value";
+ }
+ }
+
+ /**
+ * A JSONString that throw an exception when calling toJSONString().
+ */
+ private static final class JSONStringExceptionValue implements JSONString {
+
+ @Override
+ public String toJSONString() {
+ throw new IllegalStateException("the exception value");
+ }
+
+ @Override
+ public String toString() {
+ return "the toString value for JSONStringExceptionValue";
+ }
+ }
+
+ public static final class StringValue {
+
+ @Override
+ public String toString() {
+ return "the toString value for StringValue";
+ }
+ }
+
+ public static final class NullStringValue {
+
+ @Override
+ public String toString() {
+ return null;
+ }
+ }
+}
diff --git a/src/test/java/org/json/junit/JSONStringerTest.java b/src/test/java/org/json/junit/JSONStringerTest.java
new file mode 100644
index 0000000..a99db3b
--- /dev/null
+++ b/src/test/java/org/json/junit/JSONStringerTest.java
@@ -0,0 +1,377 @@
+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.
+*/
+
+import static org.junit.Assert.*;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+import org.json.*;
+import org.junit.Test;
+
+import com.jayway.jsonpath.*;
+
+
+/**
+ * Tests for JSON-Java JSONStringer and JSONWriter.
+ */
+public class JSONStringerTest {
+
+ /**
+ * Object with a null key.
+ * Expects a JSONException.
+ */
+ @Test
+ public void nullKeyException() {
+ JSONStringer jsonStringer = new JSONStringer();
+ jsonStringer.object();
+ try {
+ jsonStringer.key(null);
+ assertTrue("Expected an exception", false);
+ } catch (JSONException e) {
+ assertTrue("Expected an exception message",
+ "Null key.".
+ equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Add a key with no object.
+ * Expects a JSONException.
+ */
+ @Test
+ public void outOfSequenceException() {
+ JSONStringer jsonStringer = new JSONStringer();
+ try {
+ jsonStringer.key("hi");
+ assertTrue("Expected an exception", false);
+ } catch (JSONException e) {
+ assertTrue("Expected an exception message",
+ "Misplaced key.".
+ equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Missplace an array.
+ * Expects a JSONException
+ */
+ @Test
+ public void missplacedArrayException() {
+ JSONStringer jsonStringer = new JSONStringer();
+ jsonStringer.object().endObject();
+ try {
+ jsonStringer.array();
+ assertTrue("Expected an exception", false);
+ } catch (JSONException e) {
+ assertTrue("Expected an exception message",
+ "Misplaced array.".
+ equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Missplace an endErray.
+ * Expects a JSONException
+ */
+ @Test
+ public void missplacedEndArrayException() {
+ JSONStringer jsonStringer = new JSONStringer();
+ jsonStringer.object();
+ try {
+ jsonStringer.endArray();
+ assertTrue("Expected an exception", false);
+ } catch (JSONException e) {
+ assertTrue("Expected an exception message",
+ "Misplaced endArray.".
+ equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Missplace an endObject.
+ * Expects a JSONException
+ */
+ @Test
+ public void missplacedEndObjectException() {
+ JSONStringer jsonStringer = new JSONStringer();
+ jsonStringer.array();
+ try {
+ jsonStringer.endObject();
+ assertTrue("Expected an exception", false);
+ } catch (JSONException e) {
+ assertTrue("Expected an exception message",
+ "Misplaced endObject.".
+ equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Missplace an object.
+ * Expects a JSONException.
+ */
+ @Test
+ public void missplacedObjectException() {
+ JSONStringer jsonStringer = new JSONStringer();
+ jsonStringer.object().endObject();
+ try {
+ jsonStringer.object();
+ assertTrue("Expected an exception", false);
+ } catch (JSONException e) {
+ assertTrue("Expected an exception message",
+ "Misplaced object.".
+ equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Exceeds implementation max nesting depth.
+ * Expects a JSONException
+ */
+ @Test
+ public void exceedNestDepthException() {
+ try {
+ JSONStringer s = new JSONStringer();
+ s.object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object();
+ s.key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object().
+ key("k").object().key("k").object().key("k").object().key("k").object().key("k").object();
+ fail("Expected an exception message");
+ } catch (JSONException e) {
+ assertTrue("Expected an exception message",
+ "Nesting too deep.".
+ equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Build a JSON doc using JSONString API calls,
+ * then convert to JSONObject
+ */
+ @Test
+ public void simpleObjectString() {
+ JSONStringer jsonStringer = new JSONStringer();
+ jsonStringer.object();
+ jsonStringer.key("trueValue").value(true);
+ jsonStringer.key("falseValue").value(false);
+ jsonStringer.key("nullValue").value(null);
+ jsonStringer.key("stringValue").value("hello world!");
+ jsonStringer.key("complexStringValue").value("h\be\tllo w\u1234orld!");
+ jsonStringer.key("intValue").value(42);
+ jsonStringer.key("doubleValue").value(-23.45e67);
+ jsonStringer.endObject();
+ String str = jsonStringer.toString();
+ JSONObject jsonObject = new JSONObject(str);
+
+ // validate JSON content
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 7 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 7);
+ assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueValue")));
+ assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseValue")));
+ assertTrue("expected null", JSONObject.NULL.equals(jsonObject.query("/nullValue")));
+ assertTrue("expected hello world!", "hello world!".equals(jsonObject.query("/stringValue")));
+ assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/complexStringValue")));
+ assertTrue("expected 42", Integer.valueOf(42).equals(jsonObject.query("/intValue")));
+ assertTrue("expected -23.45e67", BigDecimal.valueOf(-23.45e67).equals(jsonObject.query("/doubleValue")));
+ }
+
+ /**
+ * Build a JSON doc using JSONString API calls,
+ * then convert to JSONArray
+ */
+ @Test
+ public void simpleArrayString() {
+ JSONStringer jsonStringer = new JSONStringer();
+ jsonStringer.array();
+ jsonStringer.value(true);
+ jsonStringer.value(false);
+ jsonStringer.value(null);
+ jsonStringer.value("hello world!");
+ jsonStringer.value(42);
+ jsonStringer.value(-23.45e67);
+ jsonStringer.endArray();
+ String str = jsonStringer.toString();
+ JSONArray jsonArray = new JSONArray(str);
+
+ // validate JSON content
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
+ assertTrue("expected 6 top level items", ((List>)(JsonPath.read(doc, "$"))).size() == 6);
+ assertTrue("expected true", Boolean.TRUE.equals(jsonArray.query("/0")));
+ assertTrue("expected false", Boolean.FALSE.equals(jsonArray.query("/1")));
+ assertTrue("expected null", JSONObject.NULL.equals(jsonArray.query("/2")));
+ assertTrue("expected hello world!", "hello world!".equals(jsonArray.query("/3")));
+ assertTrue("expected 42", Integer.valueOf(42).equals(jsonArray.query("/4")));
+ assertTrue("expected -23.45e67", BigDecimal.valueOf(-23.45e67).equals(jsonArray.query("/5")));
+ }
+
+ /**
+ * Build a nested JSON doc using JSONString API calls, then convert to
+ * JSONObject. Will create a long cascade of output by reusing the
+ * returned values..
+ */
+ @Test
+ public void complexObjectString() {
+ JSONStringer jsonStringer = new JSONStringer();
+ jsonStringer.object().
+ key("trueValue").value(true).
+ key("falseValue").value(false).
+ key("nullValue").value(null).
+ key("stringValue").value("hello world!").
+ key("object2").object().
+ key("k1").value("v1").
+ key("k2").value("v2").
+ key("k3").value("v3").
+ key("array1").array().
+ value(1).
+ value(2).
+ object().
+ key("k4").value("v4").
+ key("k5").value("v5").
+ key("k6").value("v6").
+ key("array2").array().
+ value(5).
+ value(6).
+ value(7).
+ value(8).
+ endArray().
+ endObject().
+ value(3).
+ value(4).
+ endArray().
+ endObject().
+ key("complexStringValue").value("h\be\tllo w\u1234orld!").
+ key("intValue").value(42).
+ key("doubleValue").value(-23.45e67).
+ endObject();
+ String str = jsonStringer.toString();
+ JSONObject jsonObject = new JSONObject(str);
+
+ // validate JSON content
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 8 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 8);
+ assertTrue("expected 4 object2 items", ((Map,?>)(JsonPath.read(doc, "$.object2"))).size() == 4);
+ assertTrue("expected 5 array1 items", ((List>)(JsonPath.read(doc, "$.object2.array1"))).size() == 5);
+ assertTrue("expected 4 array[2] items", ((Map,?>)(JsonPath.read(doc, "$.object2.array1[2]"))).size() == 4);
+ assertTrue("expected 4 array1[2].array2 items", ((List>)(JsonPath.read(doc, "$.object2.array1[2].array2"))).size() == 4);
+ assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueValue")));
+ assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseValue")));
+ assertTrue("expected null", JSONObject.NULL.equals(jsonObject.query("/nullValue")));
+ assertTrue("expected hello world!", "hello world!".equals(jsonObject.query("/stringValue")));
+ assertTrue("expected 42", Integer.valueOf(42).equals(jsonObject.query("/intValue")));
+ assertTrue("expected -23.45e67", BigDecimal.valueOf(-23.45e67).equals(jsonObject.query("/doubleValue")));
+ assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/complexStringValue")));
+ assertTrue("expected v1", "v1".equals(jsonObject.query("/object2/k1")));
+ assertTrue("expected v2", "v2".equals(jsonObject.query("/object2/k2")));
+ assertTrue("expected v3", "v3".equals(jsonObject.query("/object2/k3")));
+ assertTrue("expected 1", Integer.valueOf(1).equals(jsonObject.query("/object2/array1/0")));
+ assertTrue("expected 2", Integer.valueOf(2).equals(jsonObject.query("/object2/array1/1")));
+ assertTrue("expected v4", "v4".equals(jsonObject.query("/object2/array1/2/k4")));
+ assertTrue("expected v5", "v5".equals(jsonObject.query("/object2/array1/2/k5")));
+ assertTrue("expected v6", "v6".equals(jsonObject.query("/object2/array1/2/k6")));
+ assertTrue("expected 5", Integer.valueOf(5).equals(jsonObject.query("/object2/array1/2/array2/0")));
+ assertTrue("expected 6", Integer.valueOf(6).equals(jsonObject.query("/object2/array1/2/array2/1")));
+ assertTrue("expected 7", Integer.valueOf(7).equals(jsonObject.query("/object2/array1/2/array2/2")));
+ assertTrue("expected 8", Integer.valueOf(8).equals(jsonObject.query("/object2/array1/2/array2/3")));
+ assertTrue("expected 3", Integer.valueOf(3).equals(jsonObject.query("/object2/array1/3")));
+ assertTrue("expected 4", Integer.valueOf(4).equals(jsonObject.query("/object2/array1/4")));
+ }
+
+}
diff --git a/src/test/java/org/json/junit/JSONTokenerTest.java b/src/test/java/org/json/junit/JSONTokenerTest.java
new file mode 100644
index 0000000..86a614c
--- /dev/null
+++ b/src/test/java/org/json/junit/JSONTokenerTest.java
@@ -0,0 +1,319 @@
+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.
+*/
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+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.junit.Test;
+
+/**
+ * Test specific to the {@link org.json.JSONTokener} class.
+ * @author John Aylward
+ *
+ */
+public class JSONTokenerTest {
+
+ /**
+ * verify that back() fails as expected.
+ * @throws IOException thrown if something unexpected happens.
+ */
+ @Test
+ public void verifyBackFailureZeroIndex() throws IOException {
+ try(Reader reader = new StringReader("some test string")) {
+ final JSONTokener tokener = new JSONTokener(reader);
+ try {
+ // this should fail since the index is 0;
+ tokener.back();
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Stepping back two steps is not supported", e.getMessage());
+ } catch (Exception e) {
+ fail("Unknown Exception type " + e.getClass().getCanonicalName()+" with message "+e.getMessage());
+ }
+
+ }
+ }
+ /**
+ * verify that back() fails as expected.
+ * @throws IOException thrown if something unexpected happens.
+ */
+ @Test
+ public void verifyBackFailureDoubleBack() throws IOException {
+ try(Reader reader = new StringReader("some test string")) {
+ final JSONTokener tokener = new JSONTokener(reader);
+ tokener.next();
+ tokener.back();
+ try {
+ // this should fail since the index is 0;
+ tokener.back();
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Stepping back two steps is not supported", e.getMessage());
+ } catch (Exception e) {
+ fail("Unknown Exception type " + e.getClass().getCanonicalName()+" with message "+e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void testValid() {
+ checkValid("0",Number.class);
+ checkValid(" 0 ",Number.class);
+ checkValid("23",Number.class);
+ checkValid("23.5",Number.class);
+ checkValid(" 23.5 ",Number.class);
+ checkValid("null",null);
+ checkValid(" null ",null);
+ checkValid("true",Boolean.class);
+ checkValid(" true\n",Boolean.class);
+ checkValid("false",Boolean.class);
+ checkValid("\nfalse ",Boolean.class);
+ checkValid("{}",JSONObject.class);
+ checkValid(" {} ",JSONObject.class);
+ checkValid("{\"a\":1}",JSONObject.class);
+ checkValid(" {\"a\":1} ",JSONObject.class);
+ checkValid("[]",JSONArray.class);
+ checkValid(" [] ",JSONArray.class);
+ checkValid("[1,2]",JSONArray.class);
+ checkValid("\n\n[1,2]\n\n",JSONArray.class);
+ checkValid("1 2", String.class);
+ }
+
+ @Test
+ public void testErrors() {
+ // Check that stream can detect that a value is found after
+ // the first one
+ checkError(" { \"a\":1 } 4 ");
+ checkError("null \"a\"");
+ checkError("{} true");
+ }
+
+ private Object checkValid(String testStr, Class> aClass) {
+ Object result = nextValue(testStr);
+
+ // Check class of object returned
+ if( null == aClass ) {
+ if(JSONObject.NULL.equals(result)) {
+ // OK
+ } else {
+ throw new JSONException("Unexpected class: "+result.getClass().getSimpleName());
+ }
+ } else {
+ if( null == result ) {
+ throw new JSONException("Unexpected null result");
+ } else if(!aClass.isAssignableFrom(result.getClass()) ) {
+ throw new JSONException("Unexpected class: "+result.getClass().getSimpleName());
+ }
+ }
+
+ return result;
+ }
+
+ private void checkError(String testStr) {
+ try {
+ nextValue(testStr);
+
+ fail("Error should be triggered: (\""+testStr+"\")");
+ } catch (JSONException e) {
+ // OK
+ }
+ }
+
+ /**
+ * Verifies that JSONTokener can read a stream that contains a value. After
+ * the reading is done, check that the stream is left in the correct state
+ * by reading the characters after. All valid cases should reach end of stream.
+ * @param testStr
+ * @return
+ * @throws Exception
+ */
+ private Object nextValue(String testStr) throws JSONException {
+ try(StringReader sr = new StringReader(testStr);){
+ JSONTokener tokener = new JSONTokener(sr);
+
+ Object result = tokener.nextValue();
+
+ if( result == null ) {
+ throw new JSONException("Unable to find value token in JSON stream: ("+tokener+"): "+testStr);
+ }
+
+ char c = tokener.nextClean();
+ if( 0 != c ) {
+ throw new JSONException("Unexpected character found at end of JSON stream: "+c+ " ("+tokener+"): "+testStr);
+ }
+
+ return result;
+ }
+
+ }
+
+ /**
+ * Tests the failure of the skipTo method with a buffered reader. Preferably
+ * we'd like this not to fail but at this time we don't have a good recovery.
+ *
+ * @throws IOException thrown if something unexpected happens.
+ */
+ @Test
+ public void testSkipToFailureWithBufferedReader() throws IOException {
+ final byte[] superLongBuffer = new byte[1000001];
+ // fill our buffer
+ for(int i=0;i keys = jsonObject.keys();
+ while (keys.hasNext()) {
+ String key = keys.next();
+ Object value = jsonObject.get(key);
+ Object expectedValue = expectedJsonObject.get(key);
+ compareActualVsExpectedObjects(value, expectedValue);
+ }
+ }
+
+ /**
+ * Compare two objects for equality. Might be JSONArray, JSONObject,
+ * or something else.
+ * @param value created by the code to be tested
+ * @param expectedValue created specifically for comparing
+ * @param key key to the jsonObject entry to be compared
+ */
+ private static void compareActualVsExpectedObjects(Object value,
+ Object expectedValue) {
+ if (value instanceof JSONObject && expectedValue instanceof JSONObject) {
+ // Compare JSONObjects
+ JSONObject jsonObject = (JSONObject)value;
+ JSONObject expectedJsonObject = (JSONObject)expectedValue;
+ compareActualVsExpectedJsonObjects(
+ jsonObject, expectedJsonObject);
+ } else if (value instanceof JSONArray && expectedValue instanceof JSONArray) {
+ // Compare JSONArrays
+ JSONArray jsonArray = (JSONArray)value;
+ JSONArray expectedJsonArray = (JSONArray)expectedValue;
+ compareActualVsExpectedJsonArrays(
+ jsonArray, expectedJsonArray);
+ } else {
+ /**
+ * Compare all other types using toString(). First, the types must
+ * also be equal, unless both are Number type. Certain helper
+ * classes (e.g. XML) may create Long instead of Integer for small
+ * int values.
+ */
+ if (!(value instanceof Number && expectedValue instanceof Number)) {
+ // Non-Number and non-matching types
+ assertTrue("object types should be equal for actual: "+
+ value.toString()+" ("+
+ value.getClass().toString()+") expected: "+
+ expectedValue.toString()+" ("+
+ expectedValue.getClass().toString()+")",
+ value.getClass().toString().equals(
+ expectedValue.getClass().toString()));
+ }
+ /**
+ * Same types or both Numbers, compare by toString()
+ */
+ assertTrue("string values should be equal for actual: "+
+ value.toString()+" expected: "+expectedValue.toString(),
+ value.toString().equals(expectedValue.toString()));
+ }
+ }
+}
diff --git a/src/test/java/org/json/junit/XMLConfigurationTest.java b/src/test/java/org/json/junit/XMLConfigurationTest.java
new file mode 100755
index 0000000..14c4ba0
--- /dev/null
+++ b/src/test/java/org/json/junit/XMLConfigurationTest.java
@@ -0,0 +1,958 @@
+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.
+*/
+
+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 org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.XML;
+import org.json.XMLParserConfiguration;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+
+/**
+ * Tests for JSON-Java XML.java with XMLParserConfiguration.java
+ */
+public class XMLConfigurationTest {
+ /**
+ * JUnit supports temporary files and folders that are cleaned up after the test.
+ * https://garygregory.wordpress.com/2010/01/20/junit-tip-use-rules-to-manage-temporary-files-and-folders/
+ */
+ @Rule
+ public TemporaryFolder testFolder = new TemporaryFolder();
+
+ /**
+ * JSONObject from a null XML string.
+ * Expects a NullPointerException
+ */
+ @Test(expected=NullPointerException.class)
+ public void shouldHandleNullXML() {
+ String xmlStr = null;
+ JSONObject jsonObject =
+ XML.toJSONObject(xmlStr, XMLParserConfiguration.KEEP_STRINGS);
+ assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ }
+
+ /**
+ * Empty JSONObject from an empty XML string.
+ */
+ @Test
+ public void shouldHandleEmptyXML() {
+
+ String xmlStr = "";
+ JSONObject jsonObject =
+ XML.toJSONObject(xmlStr, XMLParserConfiguration.KEEP_STRINGS);
+ assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ }
+
+ /**
+ * Empty JSONObject from a non-XML string.
+ */
+ @Test
+ public void shouldHandleNonXML() {
+ String xmlStr = "{ \"this is\": \"not xml\"}";
+ JSONObject jsonObject =
+ XML.toJSONObject(xmlStr, XMLParserConfiguration.KEEP_STRINGS);
+ assertTrue("xml string should be empty", jsonObject.isEmpty());
+ }
+
+ /**
+ * Invalid XML string (tag contains a frontslash).
+ * Expects a JSONException
+ */
+ @Test
+ public void shouldHandleInvalidSlashInTag() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " \n"+
+ " abc street \n"+
+ " \n"+
+ " ";
+ try {
+ XML.toJSONObject(xmlStr, XMLParserConfiguration.KEEP_STRINGS);
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Misshaped tag at 176 [character 14 line 4]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Invalid XML string ('!' char in tag)
+ * Expects a JSONException
+ */
+ @Test
+ public void shouldHandleInvalidBangInTag() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " \n"+
+ " \n"+
+ " \n"+
+ " ";
+ try {
+ XML.toJSONObject(xmlStr, XMLParserConfiguration.KEEP_STRINGS);
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Misshaped meta tag at 214 [character 12 line 7]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Invalid XML string ('!' char and no closing tag brace)
+ * Expects a JSONException
+ */
+ @Test
+ public void shouldHandleInvalidBangNoCloseInTag() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " \n"+
+ " \n"+
+ " ";
+ try {
+ XML.toJSONObject(xmlStr, XMLParserConfiguration.KEEP_STRINGS);
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Misshaped meta tag at 213 [character 12 line 7]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Invalid XML string (no end brace for tag)
+ * Expects JSONException
+ */
+ @Test
+ public void shouldHandleNoCloseStartTag() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " \n"+
+ " \n"+
+ " ";
+ try {
+ XML.toJSONObject(xmlStr, XMLParserConfiguration.KEEP_STRINGS);
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Misplaced '<' at 193 [character 4 line 6]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Invalid XML string (partial CDATA chars in tag name)
+ * Expects JSONException
+ */
+ @Test
+ public void shouldHandleInvalidCDATABangInTag() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " Joe Tester \n"+
+ " \n"+
+ " \n"+
+ " ";
+ try {
+ XMLParserConfiguration config =
+ new XMLParserConfiguration("altContent");
+ XML.toJSONObject(xmlStr, config);
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Expected 'CDATA[' at 204 [character 11 line 5]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Null JSONObject in XML.toString()
+ */
+ @Test
+ public void shouldHandleNullJSONXML() {
+ JSONObject jsonObject= null;
+ String actualXml = XML.toString(jsonObject, null,
+ XMLParserConfiguration.KEEP_STRINGS);
+ assertEquals("generated XML does not equal expected XML","\"null\"",actualXml);
+ }
+
+ /**
+ * Empty JSONObject in XML.toString()
+ */
+ @Test
+ public void shouldHandleEmptyJSONXML() {
+ JSONObject jsonObject= new JSONObject();
+ String xmlStr = XML.toString(jsonObject, null,
+ XMLParserConfiguration.KEEP_STRINGS);
+ assertTrue("xml string should be empty", xmlStr.isEmpty());
+ }
+
+ /**
+ * No SML start tag. The ending tag ends up being treated as content.
+ */
+ @Test
+ public void shouldHandleNoStartTag() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " \n"+
+ " >\n"+
+ " \n"+
+ " ";
+ String expectedStr =
+ "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\",\""+
+ "content\":\">\"},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+
+ "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}";
+ JSONObject jsonObject = XML.toJSONObject(xmlStr,
+ XMLParserConfiguration.KEEP_STRINGS);
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Valid XML to JSONObject
+ */
+ @Test
+ public void shouldHandleSimpleXML() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " Joe Tester \n"+
+ " [CDATA[Baker street 5] \n"+
+ " \n"+
+ " true \n"+
+ " false \n"+
+ " null \n"+
+ " 42 \n"+
+ " -23 \n"+
+ " -23.45 \n"+
+ " -23x.45 \n"+
+ " 1, 2, 3, 4.1, 5.2 \n"+
+ " \n"+
+ " ";
+
+ String expectedStr =
+ "{\"addresses\":{\"address\":{\"street\":\"[CDATA[Baker street 5]\","+
+ "\"name\":\"Joe Tester\",\"NothingHere\":\"\",TrueValue:true,\n"+
+ "\"FalseValue\":false,\"NullValue\":null,\"PositiveValue\":42,\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");
+ compareStringToJSONObject(xmlStr, expectedStr, config);
+ compareReaderToJSONObject(xmlStr, expectedStr, config);
+ compareFileToJSONObject(xmlStr, expectedStr);
+ }
+
+ /**
+ * Valid XML with comments to JSONObject
+ */
+ @Test
+ public void shouldHandleCommentsInXML() {
+
+ String xmlStr =
+ "\n"+
+ "\n"+
+ "\n"+
+ " \n"+
+ " comment ]]>\n"+
+ " Joe Tester \n"+
+ " \n"+
+ " Baker street 5 \n"+
+ " \n"+
+ " ";
+ XMLParserConfiguration config =
+ new XMLParserConfiguration("altContent");
+ JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
+ String expectedStr = "{\"addresses\":{\"address\":{\"street\":\"Baker "+
+ "street 5\",\"name\":\"Joe Tester\",\"altContent\":\" this is -- "+
+ " comment \"}}}";
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Valid XML to XML.toString()
+ */
+ @Test
+ public void shouldHandleToString() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " [CDATA[Joe & T > e < s " t ' er]] \n"+
+ " Baker street 5 \n"+
+ " 1, 2, 3, 4.1, 5.2 \n"+
+ " \n"+
+ " ";
+
+ String expectedStr =
+ "{\"addresses\":{\"address\":{\"street\":\"Baker street 5\","+
+ "\"name\":\"[CDATA[Joe & T > e < s \\\" t \\\' er]]\","+
+ "\"ArrayOfNum\":\"1, 2, 3, 4.1, 5.2\"\n"+
+ "},\"xsi:noNamespaceSchemaLocation\":"+
+ "\"test.xsd\",\"xmlns:xsi\":\"http://www.w3.org/2001/"+
+ "XMLSchema-instance\"}}";
+
+ JSONObject jsonObject = XML.toJSONObject(xmlStr,
+ XMLParserConfiguration.KEEP_STRINGS);
+ String xmlToStr = XML.toString(jsonObject, null,
+ XMLParserConfiguration.KEEP_STRINGS);
+ JSONObject finalJsonObject = XML.toJSONObject(xmlToStr,
+ XMLParserConfiguration.KEEP_STRINGS);
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Converting a JSON doc containing '>' content to JSONObject, then
+ * XML.toString() should result in valid XML.
+ */
+ @Test
+ public void shouldHandleContentNoArraytoString() {
+ String expectedStr =
+ "{\"addresses\":{\"altContent\":\">\"}}";
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ XMLParserConfiguration config = new XMLParserConfiguration("altContent");
+ String finalStr = XML.toString(expectedJsonObject, null, config);
+ String expectedFinalStr = "> ";
+ assertTrue("Should handle expectedFinal: ["+expectedStr+"] final: ["+
+ finalStr+"]", expectedFinalStr.equals(finalStr));
+ }
+
+ /**
+ * Converting a JSON doc containing a 'content' array to JSONObject, then
+ * XML.toString() should result in valid XML.
+ * TODO: This is probably an error in how the 'content' keyword is used.
+ */
+ @Test
+ public void shouldHandleContentArraytoString() {
+ String expectedStr =
+ "{\"addresses\":{\"altContent\":[1, 2, 3]}}";
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ XMLParserConfiguration config = new XMLParserConfiguration("altContent");
+ String finalStr = XML.toString(expectedJsonObject, null, config);
+ String expectedFinalStr = ""+
+ "1\n2\n3"+
+ " ";
+ assertTrue("Should handle expectedFinal: ["+expectedStr+"] final: ["+
+ finalStr+"]", expectedFinalStr.equals(finalStr));
+ }
+
+ /**
+ * Converting a JSON doc containing a named array to JSONObject, then
+ * XML.toString() should result in valid XML.
+ */
+ @Test
+ public void shouldHandleArraytoString() {
+ String expectedStr =
+ "{\"addresses\":{"+
+ "\"something\":[1, 2, 3]}}";
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ String finalStr = XML.toString(expectedJsonObject, null,
+ XMLParserConfiguration.KEEP_STRINGS);
+ String expectedFinalStr = ""+
+ "1 2 3 "+
+ " ";
+ assertTrue("Should handle expectedFinal: ["+expectedStr+"] final: ["+
+ finalStr+"]", expectedFinalStr.equals(finalStr));
+ }
+
+ /**
+ * Tests that the XML output for empty arrays is consistent.
+ */
+ @Test
+ public void shouldHandleEmptyArray(){
+ final JSONObject jo1 = new JSONObject();
+ jo1.put("array",new Object[]{});
+ final JSONObject jo2 = new JSONObject();
+ jo2.put("array",new JSONArray());
+
+ final String expected = " ";
+ String output1 = XML.toString(jo1, "jo",
+ XMLParserConfiguration.KEEP_STRINGS);
+ assertEquals("Expected an empty root tag", expected, output1);
+ String output2 = XML.toString(jo2, "jo",
+ XMLParserConfiguration.KEEP_STRINGS);
+ assertEquals("Expected an empty root tag", expected, output2);
+ }
+
+ /**
+ * Tests that the XML output for arrays is consistent when an internal array is empty.
+ */
+ @Test
+ public void shouldHandleEmptyMultiArray(){
+ final JSONObject jo1 = new JSONObject();
+ jo1.put("arr",new Object[]{"One", new String[]{}, "Four"});
+ final JSONObject jo2 = new JSONObject();
+ jo2.put("arr",new JSONArray(new Object[]{"One", new JSONArray(new String[]{}), "Four"}));
+
+ final String expected = "One Four ";
+ String output1 = XML.toString(jo1, "jo",
+ XMLParserConfiguration.KEEP_STRINGS);
+ assertEquals("Expected a matching array", expected, output1);
+ String output2 = XML.toString(jo2, "jo",
+ XMLParserConfiguration.KEEP_STRINGS);
+
+ assertEquals("Expected a matching array", expected, output2);
+ }
+
+ /**
+ * Tests that the XML output for arrays is consistent when arrays are not empty.
+ */
+ @Test
+ public void shouldHandleNonEmptyArray(){
+ final JSONObject jo1 = new JSONObject();
+ jo1.put("arr",new String[]{"One", "Two", "Three"});
+ final JSONObject jo2 = new JSONObject();
+ jo2.put("arr",new JSONArray(new String[]{"One", "Two", "Three"}));
+
+ final String expected = "One Two Three ";
+ String output1 = XML.toString(jo1, "jo",
+ XMLParserConfiguration.KEEP_STRINGS);
+ assertEquals("Expected a non empty root tag", expected, output1);
+ String output2 = XML.toString(jo2, "jo",
+ XMLParserConfiguration.KEEP_STRINGS);
+ assertEquals("Expected a non empty root tag", expected, output2);
+ }
+
+ /**
+ * Tests that the XML output for arrays is consistent when arrays are not empty and contain internal arrays.
+ */
+ @Test
+ public void shouldHandleMultiArray(){
+ final JSONObject jo1 = new JSONObject();
+ jo1.put("arr",new Object[]{"One", new String[]{"Two", "Three"}, "Four"});
+ final JSONObject jo2 = new JSONObject();
+ jo2.put("arr",new JSONArray(new Object[]{"One", new JSONArray(new String[]{"Two", "Three"}), "Four"}));
+
+ final String expected = "One Two Three Four ";
+ String output1 = XML.toString(jo1, "jo",
+ XMLParserConfiguration.KEEP_STRINGS);
+ assertEquals("Expected a matching array", expected, output1);
+ String output2 = XML.toString(jo2, "jo",
+ XMLParserConfiguration.KEEP_STRINGS);
+ assertEquals("Expected a matching array", expected, output2);
+ }
+
+ /**
+ * Converting a JSON doc containing a named array of nested arrays to
+ * JSONObject, then XML.toString() should result in valid XML.
+ */
+ @Test
+ public void shouldHandleNestedArraytoString() {
+ String xmlStr =
+ "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\","+
+ "\"outer\":[[1], [2], [3]]},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+
+ "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}";
+ JSONObject jsonObject = new JSONObject(xmlStr);
+ String finalStr = XML.toString(jsonObject, null,
+ XMLParserConfiguration.ORIGINAL);
+ JSONObject finalJsonObject = XML.toJSONObject(finalStr);
+ String expectedStr = " "+
+ "1 2 "+
+ "3 "+
+ "test.xsdhttp://www.w3.org/2001/XMLSche"+
+ "ma-instance ";
+ JSONObject expectedJsonObject = XML.toJSONObject(expectedStr,
+ XMLParserConfiguration.ORIGINAL);
+ Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject);
+ }
+
+
+ /**
+ * Possible bug:
+ * Illegal node-names must be converted to legal XML-node-names.
+ * The given example shows 2 nodes which are valid for JSON, but not for XML.
+ * Therefore illegal arguments should be converted to e.g. an underscore (_).
+ */
+ @Test
+ public void shouldHandleIllegalJSONNodeNames()
+ {
+ JSONObject inputJSON = new JSONObject();
+ inputJSON.append("123IllegalNode", "someValue1");
+ inputJSON.append("Illegal@node", "someValue2");
+
+ String result = XML.toString(inputJSON, null,
+ XMLParserConfiguration.KEEP_STRINGS);
+
+ /*
+ * This is invalid XML. Names should not begin with digits or contain
+ * certain values, including '@'. One possible solution is to replace
+ * illegal chars with '_', in which case the expected output would be:
+ * <___IllegalNode>someValue1someValue2
+ */
+ String expected = "<123IllegalNode>someValue1123IllegalNode>someValue2 ";
+
+ assertEquals("Length", expected.length(), result.length());
+ assertTrue("123IllegalNode", result.contains("<123IllegalNode>someValue1123IllegalNode>"));
+ assertTrue("Illegal@node", result.contains("someValue2 "));
+ }
+
+ /**
+ * JSONObject with NULL value, to XML.toString()
+ */
+ @Test
+ public void shouldHandleNullNodeValue()
+ {
+ JSONObject inputJSON = new JSONObject();
+ inputJSON.put("nullValue", JSONObject.NULL);
+ // This is a possible preferred result
+ // String expectedXML = " ";
+ /**
+ * This is the current behavior. JSONObject.NULL is emitted as
+ * the string, "null".
+ */
+ String actualXML = "null ";
+ String resultXML = XML.toString(inputJSON, null,
+ XMLParserConfiguration.KEEP_STRINGS);
+ assertEquals(actualXML, resultXML);
+ }
+
+ /**
+ * Investigate exactly how the "content" keyword works
+ */
+ @Test
+ public void contentOperations() {
+ /*
+ * When a standalone 0) then return]]> ";
+ JSONObject jsonObject = XML.toJSONObject(xmlStr,
+ XMLParserConfiguration.KEEP_STRINGS);
+ assertTrue("1. 3 items", 3 == jsonObject.length());
+ assertTrue("1. empty tag1", "".equals(jsonObject.get("tag1")));
+ assertTrue("1. empty tag2", "".equals(jsonObject.get("tag2")));
+ assertTrue("1. content found", "if (a < b && a > 0) then return".equals(jsonObject.get("content")));
+
+ // multiple consecutive standalone cdatas are accumulated into an array
+ xmlStr = " 0) then return]]> ";
+ jsonObject = XML.toJSONObject(xmlStr,
+ new XMLParserConfiguration(true, "altContent"));
+ assertTrue("2. 3 items", 3 == jsonObject.length());
+ assertTrue("2. empty tag1", "".equals(jsonObject.get("tag1")));
+ assertTrue("2. empty tag2", "".equals(jsonObject.get("tag2")));
+ assertTrue("2. content array found", jsonObject.get("altContent") instanceof JSONArray);
+ JSONArray jsonArray = jsonObject.getJSONArray("altContent");
+ assertTrue("2. array size", jsonArray.length() == 2);
+ assertTrue("2. content array entry 0", "if (a < b && a > 0) then return".equals(jsonArray.get(0)));
+ assertTrue("2. content array entry 1", "here is another cdata".equals(jsonArray.get(1)));
+
+ /*
+ * text content is accumulated in a "content" inside a local JSONObject.
+ * If there is only one instance, it is saved in the context (a different JSONObject
+ * from the calling code. and the content element is discarded.
+ */
+ xmlStr = "value 1 ";
+ jsonObject = XML.toJSONObject(xmlStr,
+ new XMLParserConfiguration(true, "altContent"));
+ assertTrue("3. 2 items", 1 == jsonObject.length());
+ assertTrue("3. value tag1", "value 1".equals(jsonObject.get("tag1")));
+
+ /*
+ * array-style text content (multiple tags with the same name) is
+ * accumulated in a local JSONObject with key="content" and value=JSONArray,
+ * saved in the context, and then the local JSONObject is discarded.
+ */
+ xmlStr = "value 1 2 true ";
+ jsonObject = XML.toJSONObject(xmlStr,
+ new XMLParserConfiguration(true, "altContent"));
+ assertTrue("4. 1 item", 1 == jsonObject.length());
+ assertTrue("4. content array found", jsonObject.get("tag1") instanceof JSONArray);
+ jsonArray = jsonObject.getJSONArray("tag1");
+ assertTrue("4. array size", jsonArray.length() == 3);
+ assertTrue("4. content array entry 0", "value 1".equals(jsonArray.get(0)));
+ assertTrue("4. content array entry 1", jsonArray.getInt(1) == 2);
+ assertTrue("4. content array entry 2", jsonArray.getBoolean(2) == true);
+
+ /*
+ * Complex content is accumulated in a "content" field. For example, an element
+ * may contain a mix of child elements and text. Each text segment is
+ * accumulated to content.
+ */
+ xmlStr = "val1 val2 ";
+ jsonObject = XML.toJSONObject(xmlStr,
+ new XMLParserConfiguration(true, "altContent"));
+ assertTrue("5. 1 item", 1 == jsonObject.length());
+ assertTrue("5. jsonObject found", jsonObject.get("tag1")
+ instanceof JSONObject);
+ jsonObject = jsonObject.getJSONObject("tag1");
+ assertTrue("5. 2 contained items", 2 == jsonObject.length());
+ assertTrue("5. contained tag", "".equals(jsonObject.get("tag2")));
+ assertTrue("5. contained content jsonArray found",
+ jsonObject.get("altContent") instanceof JSONArray);
+ jsonArray = jsonObject.getJSONArray("altContent");
+ assertTrue("5. array size", jsonArray.length() == 2);
+ assertTrue("5. content array entry 0", "val1".equals(jsonArray.get(0)));
+ assertTrue("5. content array entry 1", "val2".equals(jsonArray.get(1)));
+
+ /*
+ * If there is only 1 complex text content, then it is accumulated in a
+ * "content" field as a string.
+ */
+ xmlStr = "val1 ";
+ jsonObject = XML.toJSONObject(xmlStr,
+ new XMLParserConfiguration(true, "altContent"));
+ assertTrue("6. 1 item", 1 == jsonObject.length());
+ assertTrue("6. jsonObject found", jsonObject.get("tag1") instanceof JSONObject);
+ jsonObject = jsonObject.getJSONObject("tag1");
+ assertTrue("6. contained content found",
+ "val1".equals(jsonObject.get("altContent")));
+ assertTrue("6. contained tag2", "".equals(jsonObject.get("tag2")));
+
+ /*
+ * In this corner case, the content sibling happens to have key=content
+ * We end up with an array within an array, and no content element.
+ * This is probably a bug.
+ */
+ xmlStr = "val1 ";
+ jsonObject = XML.toJSONObject(xmlStr,
+ new XMLParserConfiguration(true, "altContent"));
+ assertTrue("7. 1 item", 1 == jsonObject.length());
+ assertTrue("7. jsonArray found",
+ jsonObject.get("tag1") instanceof JSONArray);
+ jsonArray = jsonObject.getJSONArray("tag1");
+ assertTrue("array size 1", jsonArray.length() == 1);
+ assertTrue("7. contained array found", jsonArray.get(0)
+ instanceof JSONArray);
+ jsonArray = jsonArray.getJSONArray(0);
+ assertTrue("7. inner array size 2", jsonArray.length() == 2);
+ assertTrue("7. inner array item 0", "val1".equals(jsonArray.get(0)));
+ assertTrue("7. inner array item 1", "".equals(jsonArray.get(1)));
+
+ /*
+ * Confirm behavior of original issue
+ */
+ String jsonStr =
+ "{"+
+ "\"Profile\": {"+
+ "\"list\": {"+
+ "\"history\": {"+
+ "\"entries\": ["+
+ "{"+
+ "\"deviceId\": \"id\","+
+ "\"altContent\": {"+
+ "\"material\": ["+
+ "{"+
+ "\"stuff\": false"+
+ "}"+
+ "]"+
+ "}"+
+ "}"+
+ "]"+
+ "}"+
+ "}"+
+ "}"+
+ "}";
+ jsonObject = new JSONObject(jsonStr);
+ xmlStr = XML.toString(jsonObject, null,
+ new XMLParserConfiguration(true, "altContent"));
+ /*
+ * This is the created XML. Looks like content was mistaken for
+ * complex (child node + text) XML.
+ *
+ *
+ *
+ *
+ * id
+ * {"material":[{"stuff":false}]}
+ *
+ *
+ *
+ *
+ */
+ assertTrue("nothing to test here, see comment on created XML, above", true);
+ }
+
+ /**
+ * JSON string lost leading zero and converted "True" to true.
+ */
+ @Test
+ 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));
+ Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);
+ }
+
+ /**
+ * 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);
+ final String revertedXml =
+ XML.toString(XML.toJSONObject(originalXml, config),
+ null, config);
+ assertNotEquals(revertedXml, originalXml);
+ }
+
+ /**
+ * test passes when using the new method toJsonArray.
+ */
+ @Test
+ public void testToJsonXML() {
+ 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 json = XML.toJSONObject(originalXml,
+ new XMLParserConfiguration(true));
+ Util.compareActualVsExpectedJsonObjects(json, expected);
+
+ final String reverseXml = XML.toString(json);
+ // this reversal isn't exactly the same. use JSONML for an exact reversal
+ final String expectedReverseXml = "01 01 1 00 0 True ";
+
+ assertEquals("length",expectedReverseXml.length(), reverseXml.length());
+ assertTrue("array contents", reverseXml.contains("01 1 00 0 "));
+ assertTrue("item contents", reverseXml.contains("01 "));
+ assertTrue("title contents", reverseXml.contains("True "));
+ }
+
+ /**
+ * test to validate certain conditions of XML unescaping.
+ */
+ @Test
+ public void testUnescape() {
+ assertEquals("{\"xml\":\"Can cope <;\"}",
+ XML.toJSONObject("Can cope <; ",
+ XMLParserConfiguration.KEEP_STRINGS).toString());
+ assertEquals("Can cope <; ", XML.unescape("Can cope <; "));
+
+ assertEquals("{\"xml\":\"Can cope & ;\"}",
+ XML.toJSONObject("Can cope & ; ",
+ XMLParserConfiguration.KEEP_STRINGS).toString());
+ assertEquals("Can cope & ; ", XML.unescape("Can cope & ; "));
+
+ assertEquals("{\"xml\":\"Can cope &;\"}",
+ XML.toJSONObject("Can cope &; ",
+ XMLParserConfiguration.KEEP_STRINGS).toString());
+ assertEquals("Can cope &; ", XML.unescape("Can cope &; "));
+
+ // unicode entity
+ assertEquals("{\"xml\":\"Can cope 4;\"}",
+ XML.toJSONObject("Can cope 4; ",
+ XMLParserConfiguration.KEEP_STRINGS).toString());
+ assertEquals("Can cope 4; ", XML.unescape("Can cope 4; "));
+
+ // double escaped
+ assertEquals("{\"xml\":\"Can cope <\"}",
+ XML.toJSONObject("Can cope < ",
+ XMLParserConfiguration.KEEP_STRINGS).toString());
+ assertEquals("Can cope < ", XML.unescape("Can cope < "));
+
+ assertEquals("{\"xml\":\"Can cope 4\"}",
+ XML.toJSONObject("Can cope 4 ",
+ XMLParserConfiguration.KEEP_STRINGS).toString());
+ assertEquals("Can cope 4 ", XML.unescape("Can cope 4 "));
+
+ }
+
+ /**
+ * Confirm XMLParserConfiguration functionality
+ */
+ @Test
+ public void testConfig() {
+ /**
+ * 1st param is whether to keep the raw string, or call
+ * XML.stringToValue(), which may convert the token to
+ * boolean, null, or number.
+ * 2nd param is what JSON name to use for strings that are
+ * evaluated as xml content data in complex objects, e.g.
+ *
+ * value
+ * content data
+ *
+ */
+
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " content 1\n"+
+ " Sherlock Holmes \n"+
+ " content 2\n"+
+ " Baker street 5 \n"+
+ " content 3\n"+
+ " 1 \n"+
+ " \n"+
+ " ";
+
+ // keep strings, use the altContent tag
+ XMLParserConfiguration config =
+ new XMLParserConfiguration(true, "altContent");
+ JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
+ // num is parsed as a string
+ assertEquals(jsonObject.getJSONObject("addresses").
+ getJSONObject("address").getString("num"), "1");
+ // complex content is collected in an 'altContent' array
+ JSONArray jsonArray = jsonObject.getJSONObject("addresses").
+ getJSONObject("address").getJSONArray("altContent");
+ String expectedStr = "[\"content 1\", \"content 2\", \"content 3\"]";
+ JSONArray expectedJsonArray = new JSONArray(expectedStr);
+ Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
+
+ // keepstrings only
+ jsonObject = XML.toJSONObject(xmlStr,
+ XMLParserConfiguration.KEEP_STRINGS);
+ // num is parsed as a string
+ assertEquals(jsonObject.getJSONObject("addresses").
+ getJSONObject("address").getString("num"), "1");
+ // complex content is collected in an 'content' array
+ jsonArray = jsonObject.getJSONObject("addresses").
+ getJSONObject("address").getJSONArray("content");
+ expectedJsonArray = new JSONArray(expectedStr);
+ Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
+
+ // use alternate content name
+ config = new XMLParserConfiguration("altContent");
+ jsonObject = XML.toJSONObject(xmlStr, config);
+ // num is parsed as a number
+ assertEquals(jsonObject.getJSONObject("addresses").
+ getJSONObject("address").getInt("num"), 1);
+ // complex content is collected in an 'altContent' array
+ jsonArray = jsonObject.getJSONObject("addresses").
+ getJSONObject("address").getJSONArray("altContent");
+ expectedJsonArray = new JSONArray(expectedStr);
+ Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
+
+ }
+
+
+ /**
+ * Convenience method, given an input string and expected result,
+ * convert to JSONObject and compare actual to expected result.
+ * @param xmlStr the string to parse
+ * @param expectedStr the expected JSON string
+ * @param config provides more flexible XML parsing
+ * flexible XML parsing.
+ */
+ private void compareStringToJSONObject(String xmlStr, String expectedStr,
+ XMLParserConfiguration config) {
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Convenience method, given an input string and expected result,
+ * convert to JSONObject via reader and compare actual to expected result.
+ * @param xmlStr the string to parse
+ * @param expectedStr the expected JSON string
+ * @param config provides more flexible XML parsing
+ */
+ private void compareReaderToJSONObject(String xmlStr, String expectedStr,
+ XMLParserConfiguration config) {
+ /*
+ * Commenting out this method until the JSON-java code is updated
+ * to support XML.toJSONObject(reader)
+ */
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ try(Reader reader = new StringReader(xmlStr);) {
+ JSONObject jsonObject = XML.toJSONObject(reader, config);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ } catch (IOException e) {
+ assertTrue("IO Reader error: " +e.getMessage(), false);
+ }
+ }
+
+ /**
+ * Convenience method, given an input string and expected result, convert to
+ * JSONObject via file and compare actual to expected result.
+ *
+ * @param xmlStr
+ * the string to parse
+ * @param expectedStr
+ * the expected JSON string
+ * @throws IOException
+ */
+ private void compareFileToJSONObject(String xmlStr, String expectedStr) {
+ /*
+ * Commenting out this method until the JSON-java code is updated
+ * to support XML.toJSONObject(reader)
+ */
+ try {
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ File tempFile = this.testFolder.newFile("fileToJSONObject.xml");
+ try(FileWriter fileWriter = new FileWriter(tempFile);){
+ fileWriter.write(xmlStr);
+ }
+ try(Reader reader = new FileReader(tempFile);){
+ JSONObject jsonObject = XML.toJSONObject(reader);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ }
+ } catch (IOException e) {
+ assertTrue("file writer 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
new file mode 100644
index 0000000..d594961
--- /dev/null
+++ b/src/test/java/org/json/junit/XMLTest.java
@@ -0,0 +1,901 @@
+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.
+*/
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+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 org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.XML;
+import org.json.XMLParserConfiguration;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+
+/**
+ * Tests for JSON-Java XML.java
+ * Note: noSpace() will be tested by JSONMLTest
+ */
+public class XMLTest {
+ /**
+ * JUnit supports temporary files and folders that are cleaned up after the test.
+ * https://garygregory.wordpress.com/2010/01/20/junit-tip-use-rules-to-manage-temporary-files-and-folders/
+ */
+ @Rule
+ public TemporaryFolder testFolder = new TemporaryFolder();
+
+ /**
+ * JSONObject from a null XML string.
+ * Expects a NullPointerException
+ */
+ @Test(expected=NullPointerException.class)
+ public void shouldHandleNullXML() {
+ String xmlStr = null;
+ JSONObject jsonObject = XML.toJSONObject(xmlStr);
+ assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ }
+
+ /**
+ * Empty JSONObject from an empty XML string.
+ */
+ @Test
+ public void shouldHandleEmptyXML() {
+
+ String xmlStr = "";
+ JSONObject jsonObject = XML.toJSONObject(xmlStr);
+ assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ }
+
+ /**
+ * Empty JSONObject from a non-XML string.
+ */
+ @Test
+ public void shouldHandleNonXML() {
+ String xmlStr = "{ \"this is\": \"not xml\"}";
+ JSONObject jsonObject = XML.toJSONObject(xmlStr);
+ assertTrue("xml string should be empty", jsonObject.isEmpty());
+ }
+
+ /**
+ * Invalid XML string (tag contains a frontslash).
+ * Expects a JSONException
+ */
+ @Test
+ public void shouldHandleInvalidSlashInTag() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " \n"+
+ " abc street \n"+
+ " \n"+
+ " ";
+ try {
+ XML.toJSONObject(xmlStr);
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Misshaped tag at 176 [character 14 line 4]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Invalid XML string ('!' char in tag)
+ * Expects a JSONException
+ */
+ @Test
+ public void shouldHandleInvalidBangInTag() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " \n"+
+ " \n"+
+ " \n"+
+ " ";
+ try {
+ XML.toJSONObject(xmlStr);
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Misshaped meta tag at 214 [character 12 line 7]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Invalid XML string ('!' char and no closing tag brace)
+ * Expects a JSONException
+ */
+ @Test
+ public void shouldHandleInvalidBangNoCloseInTag() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " \n"+
+ " \n"+
+ " ";
+ try {
+ XML.toJSONObject(xmlStr);
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Misshaped meta tag at 213 [character 12 line 7]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Invalid XML string (no end brace for tag)
+ * Expects JSONException
+ */
+ @Test
+ public void shouldHandleNoCloseStartTag() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " \n"+
+ " \n"+
+ " ";
+ try {
+ XML.toJSONObject(xmlStr);
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Misplaced '<' at 193 [character 4 line 6]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Invalid XML string (partial CDATA chars in tag name)
+ * Expects JSONException
+ */
+ @Test
+ public void shouldHandleInvalidCDATABangInTag() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " Joe Tester \n"+
+ " \n"+
+ " \n"+
+ " ";
+ try {
+ XML.toJSONObject(xmlStr);
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Expected 'CDATA[' at 204 [character 11 line 5]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Null JSONObject in XML.toString()
+ */
+ @Test
+ public void shouldHandleNullJSONXML() {
+ JSONObject jsonObject= null;
+ String actualXml=XML.toString(jsonObject);
+ assertEquals("generated XML does not equal expected XML","\"null\"",actualXml);
+ }
+
+ /**
+ * Empty JSONObject in XML.toString()
+ */
+ @Test
+ public void shouldHandleEmptyJSONXML() {
+ JSONObject jsonObject= new JSONObject();
+ String xmlStr = XML.toString(jsonObject);
+ assertTrue("xml string should be empty", xmlStr.isEmpty());
+ }
+
+ /**
+ * No SML start tag. The ending tag ends up being treated as content.
+ */
+ @Test
+ public void shouldHandleNoStartTag() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " \n"+
+ " >\n"+
+ " \n"+
+ " ";
+ String expectedStr =
+ "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\",\""+
+ "content\":\">\"},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+
+ "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}";
+ JSONObject jsonObject = XML.toJSONObject(xmlStr);
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Valid XML to JSONObject
+ */
+ @Test
+ public void shouldHandleSimpleXML() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " Joe Tester \n"+
+ " [CDATA[Baker street 5] \n"+
+ " \n"+
+ " true \n"+
+ " false \n"+
+ " null \n"+
+ " 42 \n"+
+ " -23 \n"+
+ " -23.45 \n"+
+ " -23x.45 \n"+
+ " 1, 2, 3, 4.1, 5.2 \n"+
+ " \n"+
+ " ";
+
+ String expectedStr =
+ "{\"addresses\":{\"address\":{\"street\":\"[CDATA[Baker street 5]\","+
+ "\"name\":\"Joe Tester\",\"NothingHere\":\"\",TrueValue:true,\n"+
+ "\"FalseValue\":false,\"NullValue\":null,\"PositiveValue\":42,\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\"}}";
+
+ compareStringToJSONObject(xmlStr, expectedStr);
+ compareReaderToJSONObject(xmlStr, expectedStr);
+ compareFileToJSONObject(xmlStr, expectedStr);
+ }
+
+ /**
+ * Tests to verify that supported escapes in XML are converted to actual values.
+ */
+ @Test
+ public void testXmlEscapeToJson(){
+ String xmlStr =
+ "\n"+
+ ""+
+ "\" "+
+ "A €33 "+
+ "A €22€ "+
+ "some text © "+
+ "" " & ' < > "+
+ "𝄢 𐅥 " +
+ " ";
+ String expectedStr =
+ "{\"root\":{" +
+ "\"rawQuote\":\"\\\"\"," +
+ "\"euro\":\"A €33\"," +
+ "\"euroX\":\"A €22€\"," +
+ "\"unknown\":\"some text ©\"," +
+ "\"known\":\"\\\" \\\" & ' < >\"," +
+ "\"high\":\"𝄢 𐅥\""+
+ "}}";
+
+ compareStringToJSONObject(xmlStr, expectedStr);
+ compareReaderToJSONObject(xmlStr, expectedStr);
+ compareFileToJSONObject(xmlStr, expectedStr);
+ }
+
+ /**
+ * Tests that control characters are escaped.
+ */
+ @Test
+ public void testJsonToXmlEscape(){
+ final String jsonSrc = "{\"amount\":\"10,00 €\","
+ + "\"description\":\"Ação Válida\u0085\","
+ + "\"xmlEntities\":\"\\\" ' & < >\""
+ + "}";
+ JSONObject json = new JSONObject(jsonSrc);
+ String xml = XML.toString(json);
+ //test control character not existing
+ assertFalse("Escaping \u0085 failed. Found in XML output.", xml.contains("\u0085"));
+ assertTrue("Escaping \u0085 failed. Entity not found in XML output.", xml.contains("
"));
+ // test normal unicode existing
+ assertTrue("Escaping € failed. Not found in XML output.", xml.contains("€"));
+ assertTrue("Escaping ç failed. Not found in XML output.", xml.contains("ç"));
+ assertTrue("Escaping ã failed. Not found in XML output.", xml.contains("ã"));
+ assertTrue("Escaping á failed. Not found in XML output.", xml.contains("á"));
+ // test XML Entities converted
+ assertTrue("Escaping \" failed. Not found in XML output.", xml.contains("""));
+ assertTrue("Escaping ' failed. Not found in XML output.", xml.contains("'"));
+ assertTrue("Escaping & failed. Not found in XML output.", xml.contains("&"));
+ assertTrue("Escaping < failed. Not found in XML output.", xml.contains("<"));
+ assertTrue("Escaping > failed. Not found in XML output.", xml.contains(">"));
+ }
+
+ /**
+ * Valid XML with comments to JSONObject
+ */
+ @Test
+ public void shouldHandleCommentsInXML() {
+
+ String xmlStr =
+ "\n"+
+ "\n"+
+ "\n"+
+ " \n"+
+ " comment ]]>\n"+
+ " Joe Tester \n"+
+ " \n"+
+ " Baker street 5 \n"+
+ " \n"+
+ " ";
+ JSONObject jsonObject = XML.toJSONObject(xmlStr);
+ String expectedStr = "{\"addresses\":{\"address\":{\"street\":\"Baker "+
+ "street 5\",\"name\":\"Joe Tester\",\"content\":\" this is -- "+
+ " comment \"}}}";
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Valid XML to XML.toString()
+ */
+ @Test
+ public void shouldHandleToString() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " [CDATA[Joe & T > e < s " t ' er]] \n"+
+ " Baker street 5 \n"+
+ " 1, 2, 3, 4.1, 5.2 \n"+
+ " \n"+
+ " ";
+
+ String expectedStr =
+ "{\"addresses\":{\"address\":{\"street\":\"Baker street 5\","+
+ "\"name\":\"[CDATA[Joe & T > e < s \\\" t \\\' er]]\","+
+ "\"ArrayOfNum\":\"1, 2, 3, 4.1, 5.2\"\n"+
+ "},\"xsi:noNamespaceSchemaLocation\":"+
+ "\"test.xsd\",\"xmlns:xsi\":\"http://www.w3.org/2001/"+
+ "XMLSchema-instance\"}}";
+
+ JSONObject jsonObject = XML.toJSONObject(xmlStr);
+ String xmlToStr = XML.toString(jsonObject);
+ JSONObject finalJsonObject = XML.toJSONObject(xmlToStr);
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Converting a JSON doc containing '>' content to JSONObject, then
+ * XML.toString() should result in valid XML.
+ */
+ @Test
+ public void shouldHandleContentNoArraytoString() {
+ String expectedStr = "{\"addresses\":{\"content\":\">\"}}";
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ String finalStr = XML.toString(expectedJsonObject);
+ String expectedFinalStr = "> ";
+ assertEquals("Should handle expectedFinal: ["+expectedStr+"] final: ["+
+ finalStr+"]", expectedFinalStr, finalStr);
+ }
+
+ /**
+ * Converting a JSON doc containing a 'content' array to JSONObject, then
+ * XML.toString() should result in valid XML.
+ * TODO: This is probably an error in how the 'content' keyword is used.
+ */
+ @Test
+ public void shouldHandleContentArraytoString() {
+ String expectedStr =
+ "{\"addresses\":{" +
+ "\"content\":[1, 2, 3]}}";
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ String finalStr = XML.toString(expectedJsonObject);
+ String expectedFinalStr = ""+
+ "1\n2\n3 ";
+ assertEquals("Should handle expectedFinal: ["+expectedStr+"] final: ["+
+ finalStr+"]", expectedFinalStr, finalStr);
+ }
+
+ /**
+ * Converting a JSON doc containing a named array to JSONObject, then
+ * XML.toString() should result in valid XML.
+ */
+ @Test
+ public void shouldHandleArraytoString() {
+ String expectedStr =
+ "{\"addresses\":{"+
+ "\"something\":[1, 2, 3]}}";
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ String finalStr = XML.toString(expectedJsonObject);
+ String expectedFinalStr = ""+
+ "1 2 3 "+
+ " ";
+ assertEquals("Should handle expectedFinal: ["+expectedStr+"] final: ["+
+ finalStr+"]", expectedFinalStr, finalStr);
+ }
+
+ /**
+ * Tests that the XML output for empty arrays is consistent.
+ */
+ @Test
+ public void shouldHandleEmptyArray(){
+ final JSONObject jo1 = new JSONObject();
+ jo1.put("array",new Object[]{});
+ final JSONObject jo2 = new JSONObject();
+ jo2.put("array",new JSONArray());
+
+ final String expected = " ";
+ String output1 = XML.toString(jo1,"jo");
+ assertEquals("Expected an empty root tag", expected, output1);
+ String output2 = XML.toString(jo2,"jo");
+ assertEquals("Expected an empty root tag", expected, output2);
+ }
+
+ /**
+ * Tests that the XML output for arrays is consistent when an internal array is empty.
+ */
+ @Test
+ public void shouldHandleEmptyMultiArray(){
+ final JSONObject jo1 = new JSONObject();
+ jo1.put("arr",new Object[]{"One", new String[]{}, "Four"});
+ final JSONObject jo2 = new JSONObject();
+ jo2.put("arr",new JSONArray(new Object[]{"One", new JSONArray(new String[]{}), "Four"}));
+
+ final String expected = "One Four ";
+ String output1 = XML.toString(jo1,"jo");
+ assertEquals("Expected a matching array", expected, output1);
+ String output2 = XML.toString(jo2,"jo");
+ assertEquals("Expected a matching array", expected, output2);
+ }
+
+ /**
+ * Tests that the XML output for arrays is consistent when arrays are not empty.
+ */
+ @Test
+ public void shouldHandleNonEmptyArray(){
+ final JSONObject jo1 = new JSONObject();
+ jo1.put("arr",new String[]{"One", "Two", "Three"});
+ final JSONObject jo2 = new JSONObject();
+ jo2.put("arr",new JSONArray(new String[]{"One", "Two", "Three"}));
+
+ final String expected = "One Two Three ";
+ String output1 = XML.toString(jo1,"jo");
+ assertEquals("Expected a non empty root tag", expected, output1);
+ String output2 = XML.toString(jo2,"jo");
+ assertEquals("Expected a non empty root tag", expected, output2);
+ }
+
+ /**
+ * Tests that the XML output for arrays is consistent when arrays are not empty and contain internal arrays.
+ */
+ @Test
+ public void shouldHandleMultiArray(){
+ final JSONObject jo1 = new JSONObject();
+ jo1.put("arr",new Object[]{"One", new String[]{"Two", "Three"}, "Four"});
+ final JSONObject jo2 = new JSONObject();
+ jo2.put("arr",new JSONArray(new Object[]{"One", new JSONArray(new String[]{"Two", "Three"}), "Four"}));
+
+ final String expected = "One Two Three Four ";
+ String output1 = XML.toString(jo1,"jo");
+ assertEquals("Expected a matching array", expected, output1);
+ String output2 = XML.toString(jo2,"jo");
+ assertEquals("Expected a matching array", expected, output2);
+ }
+
+ /**
+ * Converting a JSON doc containing a named array of nested arrays to
+ * JSONObject, then XML.toString() should result in valid XML.
+ */
+ @Test
+ public void shouldHandleNestedArraytoString() {
+ String xmlStr =
+ "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\","+
+ "\"outer\":[[1], [2], [3]]},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+
+ "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}";
+ JSONObject jsonObject = new JSONObject(xmlStr);
+ String finalStr = XML.toString(jsonObject);
+ JSONObject finalJsonObject = XML.toJSONObject(finalStr);
+ String expectedStr = " "+
+ "1 2 "+
+ "3 "+
+ "test.xsdhttp://www.w3.org/2001/XMLSche"+
+ "ma-instance ";
+ JSONObject expectedJsonObject = XML.toJSONObject(expectedStr);
+ Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject);
+ }
+
+
+ /**
+ * Possible bug:
+ * Illegal node-names must be converted to legal XML-node-names.
+ * The given example shows 2 nodes which are valid for JSON, but not for XML.
+ * Therefore illegal arguments should be converted to e.g. an underscore (_).
+ */
+ @Test
+ public void shouldHandleIllegalJSONNodeNames()
+ {
+ JSONObject inputJSON = new JSONObject();
+ inputJSON.append("123IllegalNode", "someValue1");
+ inputJSON.append("Illegal@node", "someValue2");
+
+ String result = XML.toString(inputJSON);
+
+ /*
+ * This is invalid XML. Names should not begin with digits or contain
+ * certain values, including '@'. One possible solution is to replace
+ * illegal chars with '_', in which case the expected output would be:
+ * <___IllegalNode>someValue1someValue2
+ */
+ String expected = "<123IllegalNode>someValue1123IllegalNode>someValue2 ";
+
+ assertEquals("length",expected.length(), result.length());
+ assertTrue("123IllegalNode",result.contains("<123IllegalNode>someValue1123IllegalNode>"));
+ assertTrue("Illegal@node",result.contains("someValue2 "));
+ }
+
+ /**
+ * JSONObject with NULL value, to XML.toString()
+ */
+ @Test
+ public void shouldHandleNullNodeValue()
+ {
+ JSONObject inputJSON = new JSONObject();
+ inputJSON.put("nullValue", JSONObject.NULL);
+ // This is a possible preferred result
+ // String expectedXML = " ";
+ /**
+ * This is the current behavior. JSONObject.NULL is emitted as
+ * the string, "null".
+ */
+ String actualXML = "null ";
+ String resultXML = XML.toString(inputJSON);
+ assertEquals(actualXML, resultXML);
+ }
+
+ /**
+ * Investigate exactly how the "content" keyword works
+ */
+ @Test
+ public void contentOperations() {
+ /*
+ * When a standalone 0) then return]]> ";
+ JSONObject jsonObject = XML.toJSONObject(xmlStr);
+ assertTrue("1. 3 items", 3 == jsonObject.length());
+ assertTrue("1. empty tag1", "".equals(jsonObject.get("tag1")));
+ assertTrue("1. empty tag2", "".equals(jsonObject.get("tag2")));
+ assertTrue("1. content found", "if (a < b && a > 0) then return".equals(jsonObject.get("content")));
+
+ // multiple consecutive standalone cdatas are accumulated into an array
+ xmlStr = " 0) then return]]> ";
+ jsonObject = XML.toJSONObject(xmlStr);
+ assertTrue("2. 3 items", 3 == jsonObject.length());
+ assertTrue("2. empty tag1", "".equals(jsonObject.get("tag1")));
+ assertTrue("2. empty tag2", "".equals(jsonObject.get("tag2")));
+ assertTrue("2. content array found", jsonObject.get("content") instanceof JSONArray);
+ JSONArray jsonArray = jsonObject.getJSONArray("content");
+ assertTrue("2. array size", jsonArray.length() == 2);
+ assertTrue("2. content array entry 0", "if (a < b && a > 0) then return".equals(jsonArray.get(0)));
+ assertTrue("2. content array entry 1", "here is another cdata".equals(jsonArray.get(1)));
+
+ /*
+ * text content is accumulated in a "content" inside a local JSONObject.
+ * If there is only one instance, it is saved in the context (a different JSONObject
+ * from the calling code. and the content element is discarded.
+ */
+ xmlStr = "value 1 ";
+ jsonObject = XML.toJSONObject(xmlStr);
+ assertTrue("3. 2 items", 1 == jsonObject.length());
+ assertTrue("3. value tag1", "value 1".equals(jsonObject.get("tag1")));
+
+ /*
+ * array-style text content (multiple tags with the same name) is
+ * accumulated in a local JSONObject with key="content" and value=JSONArray,
+ * saved in the context, and then the local JSONObject is discarded.
+ */
+ xmlStr = "value 1 2 true ";
+ jsonObject = XML.toJSONObject(xmlStr);
+ assertTrue("4. 1 item", 1 == jsonObject.length());
+ assertTrue("4. content array found", jsonObject.get("tag1") instanceof JSONArray);
+ jsonArray = jsonObject.getJSONArray("tag1");
+ assertTrue("4. array size", jsonArray.length() == 3);
+ assertTrue("4. content array entry 0", "value 1".equals(jsonArray.get(0)));
+ assertTrue("4. content array entry 1", jsonArray.getInt(1) == 2);
+ assertTrue("4. content array entry 2", jsonArray.getBoolean(2) == true);
+
+ /*
+ * Complex content is accumulated in a "content" field. For example, an element
+ * may contain a mix of child elements and text. Each text segment is
+ * accumulated to content.
+ */
+ xmlStr = "val1 val2 ";
+ jsonObject = XML.toJSONObject(xmlStr);
+ assertTrue("5. 1 item", 1 == jsonObject.length());
+ assertTrue("5. jsonObject found", jsonObject.get("tag1") instanceof JSONObject);
+ jsonObject = jsonObject.getJSONObject("tag1");
+ assertTrue("5. 2 contained items", 2 == jsonObject.length());
+ assertTrue("5. contained tag", "".equals(jsonObject.get("tag2")));
+ assertTrue("5. contained content jsonArray found", jsonObject.get("content") instanceof JSONArray);
+ jsonArray = jsonObject.getJSONArray("content");
+ assertTrue("5. array size", jsonArray.length() == 2);
+ assertTrue("5. content array entry 0", "val1".equals(jsonArray.get(0)));
+ assertTrue("5. content array entry 1", "val2".equals(jsonArray.get(1)));
+
+ /*
+ * If there is only 1 complex text content, then it is accumulated in a
+ * "content" field as a string.
+ */
+ xmlStr = "val1 ";
+ jsonObject = XML.toJSONObject(xmlStr);
+ assertTrue("6. 1 item", 1 == jsonObject.length());
+ assertTrue("6. jsonObject found", jsonObject.get("tag1") instanceof JSONObject);
+ jsonObject = jsonObject.getJSONObject("tag1");
+ assertTrue("6. contained content found", "val1".equals(jsonObject.get("content")));
+ assertTrue("6. contained tag2", "".equals(jsonObject.get("tag2")));
+
+ /*
+ * In this corner case, the content sibling happens to have key=content
+ * We end up with an array within an array, and no content element.
+ * This is probably a bug.
+ */
+ xmlStr = "val1 ";
+ jsonObject = XML.toJSONObject(xmlStr);
+ assertTrue("7. 1 item", 1 == jsonObject.length());
+ assertTrue("7. jsonArray found", jsonObject.get("tag1") instanceof JSONArray);
+ jsonArray = jsonObject.getJSONArray("tag1");
+ assertTrue("array size 1", jsonArray.length() == 1);
+ assertTrue("7. contained array found", jsonArray.get(0) instanceof JSONArray);
+ jsonArray = jsonArray.getJSONArray(0);
+ assertTrue("7. inner array size 2", jsonArray.length() == 2);
+ assertTrue("7. inner array item 0", "val1".equals(jsonArray.get(0)));
+ assertTrue("7. inner array item 1", "".equals(jsonArray.get(1)));
+
+ /*
+ * Confirm behavior of original issue
+ */
+ String jsonStr =
+ "{"+
+ "\"Profile\": {"+
+ "\"list\": {"+
+ "\"history\": {"+
+ "\"entries\": ["+
+ "{"+
+ "\"deviceId\": \"id\","+
+ "\"content\": {"+
+ "\"material\": ["+
+ "{"+
+ "\"stuff\": false"+
+ "}"+
+ "]"+
+ "}"+
+ "}"+
+ "]"+
+ "}"+
+ "}"+
+ "}"+
+ "}";
+ jsonObject = new JSONObject(jsonStr);
+ xmlStr = XML.toString(jsonObject);
+ /*
+ * This is the created XML. Looks like content was mistaken for
+ * complex (child node + text) XML.
+ *
+ *
+ *
+ *
+ * id
+ * {"material":[{"stuff":false}]}
+ *
+ *
+ *
+ *
+ */
+ assertTrue("nothing to test here, see comment on created XML, above", true);
+ }
+
+ /**
+ * Convenience method, given an input string and expected result,
+ * convert to JSONObject and compare actual to expected result.
+ * @param xmlStr the string to parse
+ * @param expectedStr the expected JSON string
+ */
+ private void compareStringToJSONObject(String xmlStr, String expectedStr) {
+ JSONObject jsonObject = XML.toJSONObject(xmlStr);
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Convenience method, given an input string and expected result,
+ * convert to JSONObject via reader and compare actual to expected result.
+ * @param xmlStr the string to parse
+ * @param expectedStr the expected JSON string
+ */
+ private void compareReaderToJSONObject(String xmlStr, String expectedStr) {
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ Reader reader = new StringReader(xmlStr);
+ JSONObject jsonObject = XML.toJSONObject(reader);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ }
+
+ /**
+ * Convenience method, given an input string and expected result, convert to
+ * JSONObject via file and compare actual to expected result.
+ *
+ * @param xmlStr
+ * the string to parse
+ * @param expectedStr
+ * the expected JSON string
+ * @throws IOException
+ */
+ 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.write(xmlStr);
+ }
+ try(Reader reader = new FileReader(tempFile);){
+ JSONObject jsonObject = XML.toJSONObject(reader);
+ Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject);
+ }
+ } catch (IOException e) {
+ fail("file writer error: " +e.getMessage());
+ }
+ }
+
+ /**
+ * JSON string lost leading zero and converted "True" to true.
+ */
+ @Test
+ public void testToJSONArray_jsonOutput() {
+ final String originalXml = "01 1 00 0 True ";
+ final JSONObject expectedJson = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0],\"title\":true}}");
+ final JSONObject actualJsonOutput = XML.toJSONObject(originalXml, false);
+
+ Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expectedJson);
+ }
+
+ /**
+ * JSON string cannot be reverted to original xml.
+ */
+ @Test
+ public void testToJSONArray_reversibility() {
+ final String originalXml = "01 1 00 0 True ";
+ final String revertedXml = XML.toString(XML.toJSONObject(originalXml, false));
+
+ assertNotEquals(revertedXml, originalXml);
+ }
+
+ /**
+ * test passes when using the new method toJsonArray.
+ */
+ @Test
+ public void testToJsonXML() {
+ 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 actual = XML.toJSONObject(originalXml,true);
+
+ Util.compareActualVsExpectedJsonObjects(actual, expected);
+
+ final String reverseXml = XML.toString(actual);
+ // this reversal isn't exactly the same. use JSONML for an exact reversal
+ // the order of the elements may be differnet as well.
+ final String expectedReverseXml = "01 01 1 00 0 True ";
+
+ assertEquals("length",expectedReverseXml.length(), reverseXml.length());
+ assertTrue("array contents", reverseXml.contains("01 1 00 0 "));
+ assertTrue("item contents", reverseXml.contains("01 "));
+ assertTrue("title contents", reverseXml.contains("True "));
+ }
+
+ /**
+ * test to validate certain conditions of XML unescaping.
+ */
+ @Test
+ public void testUnescape() {
+ assertEquals("{\"xml\":\"Can cope <;\"}",
+ XML.toJSONObject("Can cope <; ").toString());
+ assertEquals("Can cope <; ", XML.unescape("Can cope <; "));
+
+ assertEquals("{\"xml\":\"Can cope & ;\"}",
+ XML.toJSONObject("Can cope & ; ").toString());
+ assertEquals("Can cope & ; ", XML.unescape("Can cope & ; "));
+
+ assertEquals("{\"xml\":\"Can cope &;\"}",
+ XML.toJSONObject("Can cope &; ").toString());
+ assertEquals("Can cope &; ", XML.unescape("Can cope &; "));
+
+ // unicode entity
+ assertEquals("{\"xml\":\"Can cope 4;\"}",
+ XML.toJSONObject("Can cope 4; ").toString());
+ assertEquals("Can cope 4; ", XML.unescape("Can cope 4; "));
+
+ // double escaped
+ assertEquals("{\"xml\":\"Can cope <\"}",
+ XML.toJSONObject("Can cope < ").toString());
+ assertEquals("Can cope < ", XML.unescape("Can cope < "));
+
+ assertEquals("{\"xml\":\"Can cope 4\"}",
+ XML.toJSONObject("Can cope 4 ").toString());
+ assertEquals("Can cope 4 ", XML.unescape("Can cope 4 "));
+
+ }
+
+ /**
+ * test passes when xsi:nil="true" converting to null (JSON specification-like nil conversion enabled)
+ */
+ @Test
+ public void testToJsonWithNullWhenNilConversionEnabled() {
+ final String originalXml = " ";
+ final String expectedJsonString = "{\"root\":{\"id\":null}}";
+
+ final JSONObject json = XML.toJSONObject(originalXml, new XMLParserConfiguration(false, "content", true));
+ assertEquals(expectedJsonString, json.toString());
+ }
+
+ /**
+ * test passes when xsi:nil="true" not converting to null (JSON specification-like nil conversion disabled)
+ */
+ @Test
+ public void testToJsonWithNullWhenNilConversionDisabled() {
+ final String originalXml = " ";
+ final String expectedJsonString = "{\"root\":{\"id\":{\"xsi:nil\":true}}}";
+
+ final JSONObject json = XML.toJSONObject(originalXml, new XMLParserConfiguration());
+ assertEquals(expectedJsonString, json.toString());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/json/junit/data/BrokenToString.java b/src/test/java/org/json/junit/data/BrokenToString.java
new file mode 100644
index 0000000..585d751
--- /dev/null
+++ b/src/test/java/org/json/junit/data/BrokenToString.java
@@ -0,0 +1,13 @@
+package org.json.junit.data;
+
+/**
+ * test class for verifying write errors.
+ * @author John Aylward
+ *
+ */
+public class BrokenToString {
+ @Override
+ public String toString() {
+ throw new IllegalStateException("Something went horribly wrong!");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/json/junit/data/ExceptionalBean.java b/src/test/java/org/json/junit/data/ExceptionalBean.java
new file mode 100644
index 0000000..72d6c0c
--- /dev/null
+++ b/src/test/java/org/json/junit/data/ExceptionalBean.java
@@ -0,0 +1,66 @@
+/**
+ *
+ */
+package org.json.junit.data;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Object for testing the exception handling in {@link JSONObject#populateMap}.
+ *
+ * @author John Aylward
+ */
+public class ExceptionalBean {
+ /**
+ * @return a closeable.
+ */
+ public Closeable getCloseable() {
+ // anonymous inner class did not work...
+ return new MyCloseable();
+ }
+
+ /**
+ * @return Nothing really. Just can't be void.
+ * @throws IllegalAccessException
+ * always thrown
+ */
+ public int getIllegalAccessException() throws IllegalAccessException {
+ throw new IllegalAccessException("Yup, it's illegal");
+ }
+
+ /**
+ * @return Nothing really. Just can't be void.
+ * @throws IllegalArgumentException
+ * always thrown
+ */
+ public int getIllegalArgumentException() throws IllegalArgumentException {
+ throw new IllegalArgumentException("Yup, it's illegal");
+ }
+
+ /**
+ * @return Nothing really. Just can't be void.
+ * @throws InvocationTargetException
+ * always thrown
+ */
+ public int getInvocationTargetException() throws InvocationTargetException {
+ throw new InvocationTargetException(new Exception("Yup, it's illegal"));
+ }
+
+ /** My closeable class. */
+ public static final class MyCloseable implements Closeable {
+
+ /**
+ * @return a string
+ */
+ public String getString() {
+ return "Yup, it's closeable";
+ }
+
+ @Override
+ public void close() throws IOException {
+ throw new IOException("Closing is too hard!");
+ }
+ }
+}
diff --git a/src/test/java/org/json/junit/data/Fraction.java b/src/test/java/org/json/junit/data/Fraction.java
new file mode 100644
index 0000000..c418179
--- /dev/null
+++ b/src/test/java/org/json/junit/data/Fraction.java
@@ -0,0 +1,180 @@
+package org.json.junit.data;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+
+/**
+ * basic fraction class, no frills.
+ * @author John Aylward
+ *
+ */
+public class Fraction extends Number implements Comparable {
+ /**
+ * serial id.
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * value as a big decimal.
+ */
+ private final BigDecimal bigDecimal;
+
+ /**
+ * value of the denominator.
+ */
+ private final BigInteger denominator;
+ /**
+ * value of the numerator.
+ */
+ private final BigInteger numerator;
+
+ /**
+ * @param numerator
+ * numerator
+ * @param denominator
+ * denominator
+ */
+ public Fraction(final BigInteger numerator, final BigInteger denominator) {
+ super();
+ if (numerator == null || denominator == null) {
+ throw new IllegalArgumentException("All values must be non-null");
+ }
+ if (denominator.compareTo(BigInteger.ZERO)==0) {
+ throw new IllegalArgumentException("Divide by zero");
+ }
+
+ final BigInteger n;
+ final BigInteger d;
+ // normalize fraction
+ if (denominator.signum()<0) {
+ n = numerator.negate();
+ d = denominator.negate();
+ } else {
+ n = numerator;
+ d = denominator;
+ }
+ this.numerator = n;
+ this.denominator = d;
+ if (n.compareTo(BigInteger.ZERO)==0) {
+ this.bigDecimal = BigDecimal.ZERO;
+ } else if (n.compareTo(d)==0) {// i.e. 4/4, 10/10
+ this.bigDecimal = BigDecimal.ONE;
+ } else {
+ this.bigDecimal = new BigDecimal(this.numerator).divide(new BigDecimal(this.denominator),
+ RoundingMode.HALF_EVEN);
+ }
+ }
+
+ /**
+ * @param numerator
+ * numerator
+ * @param denominator
+ * denominator
+ */
+ public Fraction(final long numerator, final long denominator) {
+ this(BigInteger.valueOf(numerator),BigInteger.valueOf(denominator));
+ }
+
+ /**
+ * @return the decimal
+ */
+ public BigDecimal bigDecimalValue() {
+ return this.bigDecimal;
+ }
+
+ @Override
+ public int compareTo(final Fraction o) {
+ // .equals call this, so no .equals compare allowed
+
+ // if they are the same reference, just return equals
+ if (this == o) {
+ return 0;
+ }
+
+ // if my denominators are already equal, just compare the numerators
+ if (this.denominator.compareTo(o.denominator)==0) {
+ return this.numerator.compareTo(o.numerator);
+ }
+
+ // get numerators of common denominators
+ // a x ay xb
+ // --- --- = ---- ----
+ // b y by yb
+ final BigInteger thisN = this.numerator.multiply(o.denominator);
+ final BigInteger otherN = o.numerator.multiply(this.denominator);
+
+ return thisN.compareTo(otherN);
+ }
+
+ @Override
+ public double doubleValue() {
+ return this.bigDecimal.doubleValue();
+ }
+
+ /**
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (this.getClass() != obj.getClass()) {
+ return false;
+ }
+ final Fraction other = (Fraction) obj;
+ return this.compareTo(other) == 0;
+ }
+
+ @Override
+ public float floatValue() {
+ return this.bigDecimal.floatValue();
+ }
+
+ /**
+ * @return the denominator
+ */
+ public BigInteger getDenominator() {
+ return this.denominator;
+ }
+
+ /**
+ * @return the numerator
+ */
+ public BigInteger getNumerator() {
+ return this.numerator;
+ }
+
+ /**
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (this.bigDecimal == null ? 0 : this.bigDecimal.hashCode());
+ return result;
+ }
+
+ @Override
+ public int intValue() {
+ return this.bigDecimal.intValue();
+ }
+
+ @Override
+ public long longValue() {
+ return this.bigDecimal.longValue();
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return this.numerator + "/" + this.denominator;
+ }
+}
diff --git a/src/test/java/org/json/junit/data/GenericBean.java b/src/test/java/org/json/junit/data/GenericBean.java
new file mode 100644
index 0000000..da6370d
--- /dev/null
+++ b/src/test/java/org/json/junit/data/GenericBean.java
@@ -0,0 +1,79 @@
+package org.json.junit.data;
+
+import java.io.StringReader;
+
+/**
+ *
+ * @author John Aylward
+ *
+ * @param
+ * generic number value
+ */
+public class GenericBean> implements MyBean {
+ /**
+ * @param genericValue
+ * value to initiate with
+ */
+ public GenericBean(T genericValue) {
+ super();
+ this.genericValue = genericValue;
+ }
+
+ /** */
+ protected T genericValue;
+ /** to be used by the calling test to see how often the getter is called */
+ public int genericGetCounter;
+ /** to be used by the calling test to see how often the setter is called */
+ public int genericSetCounter;
+
+ /** @return the genericValue */
+ public T getGenericValue() {
+ this.genericGetCounter++;
+ return this.genericValue;
+ }
+
+ /**
+ * @param genericValue
+ * generic value to set
+ */
+ public void setGenericValue(T genericValue) {
+ this.genericSetCounter++;
+ this.genericValue = genericValue;
+ }
+
+ @Override
+ public Integer getIntKey() {
+ return Integer.valueOf(42);
+ }
+
+ @Override
+ public Double getDoubleKey() {
+ return Double.valueOf(4.2);
+ }
+
+ @Override
+ public String getStringKey() {
+ return "MyString Key";
+ }
+
+ @Override
+ public String getEscapeStringKey() {
+ return "\"My String with \"s";
+ }
+
+ @Override
+ public Boolean isTrueKey() {
+ return Boolean.TRUE;
+ }
+
+ @Override
+ public Boolean isFalseKey() {
+ return Boolean.FALSE;
+ }
+
+ @Override
+ public StringReader getStringReaderKey() {
+ return new StringReader("Some String Value in a reader");
+ }
+
+}
diff --git a/src/test/java/org/json/junit/data/GenericBeanInt.java b/src/test/java/org/json/junit/data/GenericBeanInt.java
new file mode 100644
index 0000000..5056611
--- /dev/null
+++ b/src/test/java/org/json/junit/data/GenericBeanInt.java
@@ -0,0 +1,69 @@
+/**
+ *
+ */
+package org.json.junit.data;
+
+/**
+ * @author john
+ *
+ */
+public class GenericBeanInt extends GenericBean {
+ /** */
+ final char a = 'A';
+
+ /** @return the a */
+ public char getA() {
+ return this.a;
+ }
+
+ /**
+ * Should not be beanable
+ *
+ * @return false
+ */
+ public boolean getable() {
+ return false;
+ }
+
+ /**
+ * Should not be beanable
+ *
+ * @return false
+ */
+ public boolean get() {
+ return false;
+ }
+
+ /**
+ * Should not be beanable
+ *
+ * @return false
+ */
+ public boolean is() {
+ return false;
+ }
+
+ /**
+ * Should be beanable
+ *
+ * @return false
+ */
+ public boolean isB() {
+ return this.genericValue.equals((Integer.valueOf(this.a+1)));
+ }
+
+ /**
+ * @param genericValue
+ * the value to initiate with.
+ */
+ public GenericBeanInt(Integer genericValue) {
+ super(genericValue);
+ }
+
+ /** override to generate a bridge method */
+ @Override
+ public Integer getGenericValue() {
+ return super.getGenericValue();
+ }
+
+}
diff --git a/src/test/java/org/json/junit/data/MyBean.java b/src/test/java/org/json/junit/data/MyBean.java
new file mode 100644
index 0000000..3190981
--- /dev/null
+++ b/src/test/java/org/json/junit/data/MyBean.java
@@ -0,0 +1,16 @@
+package org.json.junit.data;
+
+import java.io.*;
+
+/**
+ * Used in testing when Bean behavior is needed
+ */
+public interface MyBean {
+ public Integer getIntKey();
+ public Double getDoubleKey();
+ public String getStringKey();
+ public String getEscapeStringKey();
+ public Boolean isTrueKey();
+ public Boolean isFalseKey();
+ public StringReader getStringReaderKey();
+}
\ No newline at end of file
diff --git a/src/test/java/org/json/junit/data/MyBeanCustomName.java b/src/test/java/org/json/junit/data/MyBeanCustomName.java
new file mode 100644
index 0000000..56756c2
--- /dev/null
+++ b/src/test/java/org/json/junit/data/MyBeanCustomName.java
@@ -0,0 +1,20 @@
+package org.json.junit.data;
+
+import org.json.JSONPropertyName;
+
+/**
+ * Test bean for the {@link JSONPropertyName} annotation.
+ */
+public class MyBeanCustomName implements MyBeanCustomNameInterface {
+ public int getSomeInt() { return 42; }
+ @JSONPropertyName("")
+ public long getSomeLong() { return 42L; }
+ @JSONPropertyName("myStringField")
+ public String getSomeString() { return "someStringValue"; }
+ @JSONPropertyName("Some Weird NAme that Normally Wouldn't be possible!")
+ public double getMyDouble() { return 0.0d; }
+ @Override
+ public float getSomeFloat() { return 2.0f; }
+ @Override
+ public int getIgnoredInt() { return 40; }
+}
diff --git a/src/test/java/org/json/junit/data/MyBeanCustomNameInterface.java b/src/test/java/org/json/junit/data/MyBeanCustomNameInterface.java
new file mode 100644
index 0000000..b25b578
--- /dev/null
+++ b/src/test/java/org/json/junit/data/MyBeanCustomNameInterface.java
@@ -0,0 +1,11 @@
+package org.json.junit.data;
+
+import org.json.JSONPropertyIgnore;
+import org.json.JSONPropertyName;
+
+public interface MyBeanCustomNameInterface {
+ @JSONPropertyName("InterfaceField")
+ float getSomeFloat();
+ @JSONPropertyIgnore
+ int getIgnoredInt();
+}
\ No newline at end of file
diff --git a/src/test/java/org/json/junit/data/MyBeanCustomNameSubClass.java b/src/test/java/org/json/junit/data/MyBeanCustomNameSubClass.java
new file mode 100644
index 0000000..8f0500c
--- /dev/null
+++ b/src/test/java/org/json/junit/data/MyBeanCustomNameSubClass.java
@@ -0,0 +1,32 @@
+/**
+ *
+ */
+package org.json.junit.data;
+
+import org.json.JSONPropertyIgnore;
+import org.json.JSONPropertyName;
+
+/**
+ * Test bean to verify that the {@link org.json.JSONPropertyName} annotation
+ * is inherited.
+ */
+public class MyBeanCustomNameSubClass extends MyBeanCustomName {
+ @Override
+ @JSONPropertyName("forcedInt")
+ public int getIgnoredInt() { return 42*42; }
+ @Override
+ @JSONPropertyName("newIntFieldName")
+ public int getSomeInt() { return 43; }
+ @Override
+ public String getSomeString() { return "subClassString"; }
+ @Override
+ @JSONPropertyName("AMoreNormalName")
+ public double getMyDouble() { return 1.0d; }
+ @Override
+ public float getSomeFloat() { return 3.0f; }
+ @JSONPropertyIgnore
+ @JSONPropertyName("ShouldBeIgnored")
+ public boolean getShouldNotBeJSON() { return true; }
+ @JSONPropertyName("Getable")
+ public boolean getable() { return true; }
+}
diff --git a/src/test/java/org/json/junit/data/MyBigNumberBean.java b/src/test/java/org/json/junit/data/MyBigNumberBean.java
new file mode 100644
index 0000000..934dfee
--- /dev/null
+++ b/src/test/java/org/json/junit/data/MyBigNumberBean.java
@@ -0,0 +1,11 @@
+package org.json.junit.data;
+
+import java.math.*;
+
+/**
+ * Used in testing when a Bean containing big numbers is needed
+ */
+public interface MyBigNumberBean {
+ public BigInteger getBigInteger();
+ public BigDecimal getBigDecimal();
+}
\ No newline at end of file
diff --git a/src/test/java/org/json/junit/data/MyEnum.java b/src/test/java/org/json/junit/data/MyEnum.java
new file mode 100644
index 0000000..50d9a4f
--- /dev/null
+++ b/src/test/java/org/json/junit/data/MyEnum.java
@@ -0,0 +1,10 @@
+package org.json.junit.data;
+
+/**
+ * An enum with no methods or data
+ */
+public enum MyEnum {
+ VAL1,
+ VAL2,
+ VAL3;
+}
diff --git a/src/test/java/org/json/junit/data/MyEnumClass.java b/src/test/java/org/json/junit/data/MyEnumClass.java
new file mode 100644
index 0000000..0486694
--- /dev/null
+++ b/src/test/java/org/json/junit/data/MyEnumClass.java
@@ -0,0 +1,22 @@
+package org.json.junit.data;
+
+/**
+ * this is simply a class that contains some enum instances
+ */
+public class MyEnumClass {
+ private MyEnum myEnum;
+ private MyEnumField myEnumField;
+
+ public MyEnum getMyEnum() {
+ return this.myEnum;
+ }
+ public void setMyEnum(MyEnum myEnum) {
+ this.myEnum = myEnum;
+ }
+ public MyEnumField getMyEnumField() {
+ return this.myEnumField;
+ }
+ public void setMyEnumField(MyEnumField myEnumField) {
+ this.myEnumField = myEnumField;
+ }
+}
diff --git a/src/test/java/org/json/junit/data/MyEnumField.java b/src/test/java/org/json/junit/data/MyEnumField.java
new file mode 100644
index 0000000..60e89de
--- /dev/null
+++ b/src/test/java/org/json/junit/data/MyEnumField.java
@@ -0,0 +1,28 @@
+package org.json.junit.data;
+
+/**
+ * An enum that contains getters and some internal fields
+ */
+@SuppressWarnings("boxing")
+public enum MyEnumField {
+ VAL1(1, "val 1"),
+ VAL2(2, "val 2"),
+ VAL3(3, "val 3");
+
+ private String value;
+ private Integer intVal;
+ private MyEnumField(Integer intVal, String value) {
+ this.value = value;
+ this.intVal = intVal;
+ }
+ public String getValue() {
+ return this.value;
+ }
+ public Integer getIntVal() {
+ return this.intVal;
+ }
+ @Override
+ public String toString(){
+ return this.value;
+ }
+}
diff --git a/src/test/java/org/json/junit/data/MyJsonString.java b/src/test/java/org/json/junit/data/MyJsonString.java
new file mode 100644
index 0000000..4ddde53
--- /dev/null
+++ b/src/test/java/org/json/junit/data/MyJsonString.java
@@ -0,0 +1,14 @@
+package org.json.junit.data;
+
+import org.json.*;
+
+/**
+ * Used in testing when a JSONString is needed
+ */
+public class MyJsonString implements JSONString {
+
+ @Override
+ public String toJSONString() {
+ return "my string";
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/json/junit/data/MyLocaleBean.java b/src/test/java/org/json/junit/data/MyLocaleBean.java
new file mode 100755
index 0000000..5d3cb52
--- /dev/null
+++ b/src/test/java/org/json/junit/data/MyLocaleBean.java
@@ -0,0 +1,12 @@
+package org.json.junit.data;
+
+public class MyLocaleBean {
+ private final String id = "beanId";
+ private final String i = "beanI";
+ public String getId() {
+ return this.id;
+ }
+ public String getI() {
+ return this.i;
+ }
+}
diff --git a/src/test/java/org/json/junit/data/MyNumber.java b/src/test/java/org/json/junit/data/MyNumber.java
new file mode 100644
index 0000000..4b625af
--- /dev/null
+++ b/src/test/java/org/json/junit/data/MyNumber.java
@@ -0,0 +1,97 @@
+package org.json.junit.data;
+
+import java.math.BigDecimal;
+
+/**
+ * Number override for testing. Number overrides should always override
+ * toString, hashCode, and Equals.
+ *
+ * @see The
+ * Numbers Classes
+ * @see Formatting
+ * Numeric Print Output
+ *
+ * @author John Aylward
+ */
+public class MyNumber extends Number {
+ private Number number = BigDecimal.valueOf(42);
+ /**
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * @return number!
+ */
+ public Number getNumber() {
+ return this.number;
+ }
+
+ @Override
+ public int intValue() {
+ return getNumber().intValue();
+ }
+
+ @Override
+ public long longValue() {
+ return getNumber().longValue();
+ }
+
+ @Override
+ public float floatValue() {
+ return getNumber().floatValue();
+ }
+
+ @Override
+ public double doubleValue() {
+ return getNumber().doubleValue();
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ *
+ * Number overrides should in general always override the toString method.
+ */
+ @Override
+ public String toString() {
+ return getNumber().toString();
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((this.number == null) ? 0 : this.number.hashCode());
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof MyNumber)) {
+ return false;
+ }
+ MyNumber other = (MyNumber) obj;
+ if (this.number == null) {
+ if (other.number != null) {
+ return false;
+ }
+ } else if (!this.number.equals(other.number)) {
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/src/test/java/org/json/junit/data/MyNumberContainer.java b/src/test/java/org/json/junit/data/MyNumberContainer.java
new file mode 100644
index 0000000..6527652
--- /dev/null
+++ b/src/test/java/org/json/junit/data/MyNumberContainer.java
@@ -0,0 +1,13 @@
+package org.json.junit.data;
+
+/**
+ * Class that holds our MyNumber override as a property.
+ * @author John Aylward
+ */
+public class MyNumberContainer {
+ private MyNumber myNumber = new MyNumber();
+ /**
+ * @return a MyNumber.
+ */
+ public Number getMyNumber() {return this.myNumber;}
+}
diff --git a/src/test/java/org/json/junit/data/MyPublicClass.java b/src/test/java/org/json/junit/data/MyPublicClass.java
new file mode 100644
index 0000000..1f30386
--- /dev/null
+++ b/src/test/java/org/json/junit/data/MyPublicClass.java
@@ -0,0 +1,10 @@
+package org.json.junit.data;
+
+/**
+ * Need a class with some public data members for testing
+ */
+@SuppressWarnings("boxing")
+public class MyPublicClass {
+ public Integer publicInt = 42;
+ public String publicString = "abc";
+}
diff --git a/src/test/java/org/json/junit/data/Singleton.java b/src/test/java/org/json/junit/data/Singleton.java
new file mode 100644
index 0000000..224b48a
--- /dev/null
+++ b/src/test/java/org/json/junit/data/Singleton.java
@@ -0,0 +1,91 @@
+package org.json.junit.data;
+
+/**
+ * Sample singleton for use with bean testing.
+ *
+ * @author John Aylward
+ *
+ */
+public final class Singleton {
+ /** */
+ private int someInt;
+ /** */
+ private String someString;
+ /** single instance. */
+ private static final Singleton INSTANCE = new Singleton();
+
+ /** @return the singleton instance. */
+ public static final Singleton getInstance() {
+ return INSTANCE;
+ }
+
+ /** */
+ private Singleton() {
+ if (INSTANCE != null) {
+ throw new IllegalStateException("Already instantiated");
+ }
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return INSTANCE;
+ }
+
+ /** @return someInt */
+ public int getSomeInt() {
+ return this.someInt;
+ }
+
+ /**
+ * sets someInt.
+ *
+ * @param someInt
+ * the someInt to set
+ */
+ public void setSomeInt(int someInt) {
+ this.someInt = someInt;
+ }
+
+ /** @return someString */
+ public String getSomeString() {
+ return this.someString;
+ }
+
+ /**
+ * sets someString.
+ *
+ * @param someString
+ * the someString to set
+ */
+ public void setSomeString(String someString) {
+ this.someString = someString;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + this.someInt;
+ result = prime * result + ((this.someString == null) ? 0 : this.someString.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Singleton other = (Singleton) obj;
+ if (this.someInt != other.someInt)
+ return false;
+ if (this.someString == null) {
+ if (other.someString != null)
+ return false;
+ } else if (!this.someString.equals(other.someString))
+ return false;
+ return true;
+ }
+}
diff --git a/src/test/java/org/json/junit/data/SingletonEnum.java b/src/test/java/org/json/junit/data/SingletonEnum.java
new file mode 100644
index 0000000..3dd0309
--- /dev/null
+++ b/src/test/java/org/json/junit/data/SingletonEnum.java
@@ -0,0 +1,62 @@
+package org.json.junit.data;
+
+/**
+ * Sample singleton done as an Enum for use with bean testing.
+ *
+ * @author John Aylward
+ *
+ */
+public enum SingletonEnum {
+ /**
+ * the singleton instance.
+ */
+ INSTANCE;
+ /** */
+ private int someInt;
+ /** */
+ private String someString;
+
+ /** single instance. */
+
+ /**
+ * @return the singleton instance. In a real application, I'd hope no one did
+ * this to an enum singleton.
+ */
+ public static final SingletonEnum getInstance() {
+ return INSTANCE;
+ }
+
+ /** */
+ private SingletonEnum() {
+ }
+
+ /** @return someInt */
+ public int getSomeInt() {
+ return this.someInt;
+ }
+
+ /**
+ * sets someInt.
+ *
+ * @param someInt
+ * the someInt to set
+ */
+ public void setSomeInt(int someInt) {
+ this.someInt = someInt;
+ }
+
+ /** @return someString */
+ public String getSomeString() {
+ return this.someString;
+ }
+
+ /**
+ * sets someString.
+ *
+ * @param someString
+ * the someString to set
+ */
+ public void setSomeString(String someString) {
+ this.someString = someString;
+ }
+}
diff --git a/src/test/java/org/json/junit/data/StringsResourceBundle.java b/src/test/java/org/json/junit/data/StringsResourceBundle.java
new file mode 100644
index 0000000..4479350
--- /dev/null
+++ b/src/test/java/org/json/junit/data/StringsResourceBundle.java
@@ -0,0 +1,19 @@
+package org.json.junit.data;
+
+import java.util.*;
+
+/**
+ * A resource bundle class
+ */
+public class StringsResourceBundle extends ListResourceBundle {
+ @Override
+ public Object[][] getContents() {
+ return contents;
+ }
+ static final Object[][] contents = {
+ {"greetings.hello", "Hello, "},
+ {"greetings.world", "World!"},
+ {"farewells.later", "Later, "},
+ {"farewells.gator", "Alligator!"}
+ };
+}
\ No newline at end of file
diff --git a/src/test/java/org/json/junit/data/WeirdList.java b/src/test/java/org/json/junit/data/WeirdList.java
new file mode 100644
index 0000000..3560586
--- /dev/null
+++ b/src/test/java/org/json/junit/data/WeirdList.java
@@ -0,0 +1,68 @@
+/**
+ *
+ */
+package org.json.junit.data;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author John Aylward
+ */
+public class WeirdList {
+ /** */
+ private final List list = new ArrayList<>();
+
+ /**
+ * @param vals
+ */
+ public WeirdList(Integer... vals) {
+ this.list.addAll(Arrays.asList(vals));
+ }
+
+ /**
+ * @return a copy of the list
+ */
+ public List get() {
+ return new ArrayList<>(this.list);
+ }
+
+ /**
+ * @return a copy of the list
+ */
+ public List getALL() {
+ return new ArrayList<>(this.list);
+ }
+
+ /**
+ * get a value at an index.
+ *
+ * @param i
+ * index to get
+ * @return the value at the index
+ */
+ public Integer get(int i) {
+ return this.list.get(i);
+ }
+
+ /**
+ * get a value at an index.
+ *
+ * @param i
+ * index to get
+ * @return the value at the index
+ */
+ @SuppressWarnings("boxing")
+ public int getInt(int i) {
+ return this.list.get(i);
+ }
+
+ /**
+ * @param value
+ * new value to add to the end of the list
+ */
+ public void add(Integer value) {
+ this.list.add(value);
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/jsonpointer-testdoc.json b/src/test/resources/jsonpointer-testdoc.json
new file mode 100644
index 0000000..657ccdd
--- /dev/null
+++ b/src/test/resources/jsonpointer-testdoc.json
@@ -0,0 +1,28 @@
+{
+ "foo":
+ [
+ "bar",
+ "baz"
+ ],
+ "": 0,
+ "a/b": 1,
+ "c%d": 2,
+ "e^f": 3,
+ "g|h": 4,
+ "i\\j": 5,
+ "k\"l": 6,
+ " ": 7,
+ "m~n": 8,
+ "obj" : {
+ "key" : "value",
+ "other~key" : {
+ "another/key" : [
+ "val"
+ ]
+ },
+ "" : {
+ "" : "empty key of an object with an empty key",
+ "subKey" : "Some other value"
+ }
+ }
+}
\ No newline at end of file