entry : this.entrySet()) {
+ String name = entry.getKey();
+ Object valueThis = entry.getValue();
+ Object valueOther = ((JSONObject)other).get(name);
+ if(valueThis == valueOther) {
+ continue;
+ }
+ if(valueThis == null) {
+ return false;
+ }
+
+ if (!checkObjectType(valueThis, valueOther)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Convenience function. Compares types of two objects.
+ * @param valueThis Object whose type is being checked
+ * @param valueOther Reference object
+ * @return true if match, else false
+ */
+ private boolean checkObjectType(Object valueThis, Object valueOther) {
+ if (valueThis instanceof JSONObject) {
+ return ((JSONObject)valueThis).similar(valueOther);
+ } else if (valueThis instanceof JSONArray) {
+ return ((JSONArray)valueThis).similar(valueOther);
+ } else if (valueThis instanceof Number && valueOther instanceof Number) {
+ return isNumberSimilar((Number)valueThis, (Number)valueOther);
+ } else if (valueThis instanceof JSONString && valueOther instanceof JSONString) {
+ return ((JSONString) valueThis).toJSONString().equals(((JSONString) valueOther).toJSONString());
+ } else if (!valueThis.equals(valueOther)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Compares two numbers to see if they are similar.
+ *
+ * If either of the numbers are Double or Float instances, then they are checked to have
+ * a finite value. If either value is not finite (NaN or ±infinity), then this
+ * function will always return false. If both numbers are finite, they are first checked
+ * to be the same type and implement {@link Comparable}. If they do, then the actual
+ * {@link Comparable#compareTo(Object)} is called. If they are not the same type, or don't
+ * implement Comparable, then they are converted to {@link BigDecimal}s. Finally the
+ * BigDecimal values are compared using {@link BigDecimal#compareTo(BigDecimal)}.
+ *
+ * @param l the Left value to compare. Can not be null.
+ * @param r the right value to compare. Can not be null.
+ * @return true if the numbers are similar, false otherwise.
+ */
+ static boolean isNumberSimilar(Number l, Number r) {
+ if (!numberIsFinite(l) || !numberIsFinite(r)) {
+ // non-finite numbers are never similar
+ return false;
+ }
+
+ // if the classes are the same and implement Comparable
+ // then use the built in compare first.
+ if(l.getClass().equals(r.getClass()) && l instanceof Comparable) {
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ int compareTo = ((Comparable)l).compareTo(r);
+ return compareTo==0;
+ }
+
+ // BigDecimal should be able to handle all of our number types that we support through
+ // documentation. Convert to BigDecimal first, then use the Compare method to
+ // decide equality.
+ final BigDecimal lBigDecimal = objectToBigDecimal(l, null, false);
+ final BigDecimal rBigDecimal = objectToBigDecimal(r, null, false);
+ if (lBigDecimal == null || rBigDecimal == null) {
+ return false;
+ }
+ return lBigDecimal.compareTo(rBigDecimal) == 0;
+ }
+
+ private static boolean numberIsFinite(Number n) {
+ if (n instanceof Double && (((Double) n).isInfinite() || ((Double) n).isNaN())) {
+ return false;
+ } else if (n instanceof Float && (((Float) n).isInfinite() || ((Float) n).isNaN())) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Tests if the value should be tried as a decimal. It makes no test if there are actual digits.
+ *
* @param val value to test
* @return true if the string is "-0" or if it contains '.', 'e', or 'E', false otherwise.
*/
@@ -2093,76 +2663,6 @@ protected static boolean isDecimalNotation(final String val) {
return val.indexOf('.') > -1 || val.indexOf('e') > -1
|| val.indexOf('E') > -1 || "-0".equals(val);
}
-
- /**
- * Converts a string to a number using the narrowest possible type. Possible
- * returns for this function are BigDecimal, Double, BigInteger, Long, and Integer.
- * When a Double is returned, it should always be a valid Double and not NaN or +-infinity.
- *
- * @param val value to convert
- * @return Number representation of the value.
- * @throws NumberFormatException thrown if the value is not a valid number. A public
- * caller should catch this and wrap it in a {@link JSONException} if applicable.
- */
- protected 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.");
- }
/**
* Try to convert a string into a number, boolean, or null. If the string
@@ -2202,11 +2702,102 @@ public static Object stringToValue(String string) {
try {
return stringToNumber(string);
} catch (Exception ignore) {
+ // Do nothing
}
}
return string;
}
+ /**
+ * Converts a string to a number using the narrowest possible type. Possible
+ * returns for this function are BigDecimal, Double, BigInteger, Long, and Integer.
+ * When a Double is returned, it should always be a valid Double and not NaN or +-infinity.
+ *
+ * @param val value to convert
+ * @return Number representation of the value.
+ * @throws NumberFormatException thrown if the value is not a valid number. A public
+ * caller should catch this and wrap it in a {@link JSONException} if applicable.
+ */
+ protected static Number stringToNumber(final String val) throws NumberFormatException {
+ char initial = val.charAt(0);
+ if ((initial >= '0' && initial <= '9') || initial == '-') {
+ // decimal representation
+ if (isDecimalNotation(val)) {
+ return getNumber(val, initial);
+ }
+ // block items like 00 01 etc. Java number parsers treat these as Octal.
+ checkForInvalidNumberFormat(val, initial);
+ // integer representation.
+ // This will narrow any values to the smallest reasonable Object representation
+ // (Integer, Long, or BigInteger)
+
+ // BigInteger down conversion: We use a similar bitLength compare as
+ // BigInteger#intValueExact uses. Increases GC, but objects hold
+ // only what they need. i.e. Less runtime overhead if the value is
+ // long lived.
+ BigInteger bi = new BigInteger(val);
+ if(bi.bitLength() <= 31){
+ return Integer.valueOf(bi.intValue());
+ }
+ if(bi.bitLength() <= 63){
+ return Long.valueOf(bi.longValue());
+ }
+ return bi;
+ }
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+
+ /**
+ * Convenience function. Block items like 00 01 etc. Java number parsers treat these as Octal.
+ * @param val value to convert
+ * @param initial first char of val
+ * @throws exceptions if numbers are formatted incorrectly
+ */
+ private static void checkForInvalidNumberFormat(String val, char initial) {
+ 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.");
+ }
+ }
+ }
+
+ /**
+ * Convenience function. Handles val if it is a number
+ * @param val value to convert
+ * @param initial first char of val
+ * @return val as a BigDecimal
+ */
+ private static Number getNumber(String val, char initial) {
+ // 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.");
+ }
+ }
+ }
+
/**
* Throw an exception if the object is a NaN or infinite number.
*
@@ -2216,18 +2807,8 @@ public static Object stringToValue(String string) {
* If o is a non-finite number.
*/
public static void testValidity(Object o) throws JSONException {
- if (o != null) {
- if (o instanceof Double) {
- if (((Double) o).isInfinite() || ((Double) o).isNaN()) {
- throw new JSONException(
- "JSON does not allow non-finite numbers.");
- }
- } else if (o instanceof Float) {
- if (((Float) o).isInfinite() || ((Float) o).isNaN()) {
- throw new JSONException(
- "JSON does not allow non-finite numbers.");
- }
- }
+ if (o instanceof Number && !numberIsFinite((Number) o)) {
+ throw new JSONException("JSON does not allow non-finite numbers.");
}
}
@@ -2260,7 +2841,7 @@ public JSONArray toJSONArray(JSONArray names) throws JSONException {
*
* Warning: This method assumes that the data structure is acyclical.
*
- *
+ *
* @return a printable, displayable, portable, transmittable representation
* of the object, beginning with { (left
* brace) and ending with } (right
@@ -2277,11 +2858,11 @@ public String toString() {
/**
* Make a pretty-printed JSON text of this JSONObject.
- *
+ *
*
If
{@code indentFactor > 0} and the {@link JSONObject}
* has only one key, then the object will be output on a single line:
* {@code {"key": 1}}
- *
+ *
* If an object has 2 or more keys, then it will be output across
* multiple lines:
{@code {
* "key1": 1,
@@ -2301,11 +2882,13 @@ public String toString() {
* @throws JSONException
* If the object contains an invalid number.
*/
+ @SuppressWarnings("resource")
public String toString(int indentFactor) throws JSONException {
- StringWriter w = new StringWriter();
- synchronized (w.getBuffer()) {
- return this.write(w, indentFactor, 0).toString();
- }
+ // 6 characters are the minimum to serialise a key value pair e.g.: "k":1,
+ // and we don't want to oversize the initial capacity
+ int initialSize = map.size() * 6;
+ Writer w = new StringBuilderWriter(Math.max(initialSize, 16));
+ return this.write(w, indentFactor, 0).toString();
}
/**
@@ -2353,31 +2936,59 @@ public static String valueToString(Object value) throws JSONException {
* @return The wrapped value
*/
public static Object wrap(Object object) {
+ return wrap(object, null);
+ }
+
+ /**
+ * Wrap an object, if necessary. If the object is null, return the NULL
+ * object. If it is an array or collection, wrap it in a JSONArray. If it is
+ * a map, wrap it in a JSONObject. If it is a standard property (Double,
+ * String, et al) then it is already wrapped. Otherwise, if it comes from
+ * one of the java packages, turn it into a string. And if it doesn't, try
+ * to wrap it in a JSONObject. If the wrapping fails, then null is returned.
+ *
+ * @param object
+ * The object to wrap
+ * @param recursionDepth
+ * Variable for tracking the count of nested object creations.
+ * @param jsonParserConfiguration
+ * Variable to pass parser custom configuration for json parsing.
+ * @return The wrapped value
+ */
+ static Object wrap(Object object, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
+ return wrap(object, null, recursionDepth, jsonParserConfiguration);
+ }
+
+ private static Object wrap(Object object, Set objectsRecord) {
+ return wrap(object, objectsRecord, 0, new JSONParserConfiguration());
+ }
+
+ private static Object wrap(Object object, Set objectsRecord, int recursionDepth, JSONParserConfiguration jsonParserConfiguration) {
try {
- if (object == null) {
+ if (NULL.equals(object)) {
return NULL;
}
if (object instanceof JSONObject || object instanceof JSONArray
- || NULL.equals(object) || object instanceof JSONString
+ || object instanceof JSONString || object instanceof String
|| object instanceof Byte || object instanceof Character
|| object instanceof Short || object instanceof Integer
|| object instanceof Long || object instanceof Boolean
|| object instanceof Float || object instanceof Double
- || object instanceof String || object instanceof BigInteger
- || object instanceof BigDecimal || object instanceof Enum) {
+ || object instanceof BigInteger || object instanceof BigDecimal
+ || object instanceof Enum) {
return object;
}
if (object instanceof Collection) {
Collection> coll = (Collection>) object;
- return new JSONArray(coll);
+ return new JSONArray(coll, recursionDepth, jsonParserConfiguration);
}
if (object.getClass().isArray()) {
return new JSONArray(object);
}
if (object instanceof Map) {
Map, ?> map = (Map, ?>) object;
- return new JSONObject(map);
+ return new JSONObject(map, recursionDepth, jsonParserConfiguration);
}
Package objectPackage = object.getClass().getPackage();
String objectPackageName = objectPackage != null ? objectPackage
@@ -2387,7 +2998,13 @@ public static Object wrap(Object object) {
|| object.getClass().getClassLoader() == null) {
return object.toString();
}
+ if (objectsRecord != null) {
+ return new JSONObject(object, objectsRecord);
+ }
return new JSONObject(object);
+ }
+ catch (JSONException exception) {
+ throw exception;
} catch (Exception exception) {
return null;
}
@@ -2407,28 +3024,21 @@ public Writer write(Writer writer) throws JSONException {
return this.write(writer, 0, 0);
}
+ @SuppressWarnings("resource")
static final Writer writeValue(Writer writer, Object value,
int indentFactor, int indent) throws JSONException, IOException {
if (value == null || value.equals(null)) {
writer.write("null");
} else if (value instanceof JSONString) {
- Object o;
- try {
- o = ((JSONString) value).toJSONString();
- } catch (Exception e) {
- throw new JSONException(e);
- }
- writer.write(o != null ? o.toString() : quote(value.toString()));
+ // may throw an exception
+ processJsonStringToWriteValue(writer, value);
+ } else if (value instanceof String) {
+ // assuming most values are Strings, so testing it early
+ quote(value.toString(), writer);
+ return writer;
} else if (value instanceof Number) {
- // not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary
- final String numberAsString = numberToString((Number) value);
- if(NUMBER_PATTERN.matcher(numberAsString).matches()) {
- writer.write(numberAsString);
- } else {
- // The Number value is not a valid JSON number.
- // Instead we will quote it as a string
- quote(numberAsString, writer);
- }
+ // may throw an exception
+ processNumberToWriteValue(writer, (Number) value);
} else if (value instanceof Boolean) {
writer.write(value.toString());
} else if (value instanceof Enum>) {
@@ -2451,6 +3061,41 @@ static final Writer writeValue(Writer writer, Object value,
return writer;
}
+ /**
+ * Convenience function to reduce cog complexity of calling method; writes value if string is valid
+ * @param writer Object doing the writing
+ * @param value Value to be written
+ * @throws IOException if something goes wrong
+ */
+ private static void processJsonStringToWriteValue(Writer writer, Object value) throws IOException {
+ // JSONString must be checked first, so it can overwrite behaviour of other types below
+ Object o;
+ try {
+ o = ((JSONString) value).toJSONString();
+ } catch (Exception e) {
+ throw new JSONException(e);
+ }
+ writer.write(o != null ? o.toString() : quote(value.toString()));
+ }
+
+ /**
+ * Convenience function to reduce cog complexity of calling method; writes value if number is valid
+ * @param writer Object doing the writing
+ * @param value Value to be written
+ * @throws IOException if something goes wrong
+ */
+ private static void processNumberToWriteValue(Writer writer, Number value) throws IOException {
+ // not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary
+ final String numberAsString = numberToString(value);
+ if(NUMBER_PATTERN.matcher(numberAsString).matches()) {
+ writer.write(numberAsString);
+ } else {
+ // The Number value is not a valid JSON number.
+ // Instead we will quote it as a string
+ quote(numberAsString, writer);
+ }
+ }
+
static final void indent(Writer writer, int indent) throws IOException {
for (int i = 0; i < indent; i += 1) {
writer.write(' ');
@@ -2459,11 +3104,11 @@ static final void indent(Writer writer, int indent) throws IOException {
/**
* Write the contents of the JSONObject as JSON text to a writer.
- *
+ *
* If
{@code indentFactor > 0} and the {@link JSONObject}
* has only one key, then the object will be output on a single line:
* {@code {"key": 1}}
- *
+ *
* If an object has 2 or more keys, then it will be output across
* multiple lines:
{@code {
* "key1": 1,
@@ -2484,6 +3129,7 @@ static final void indent(Writer writer, int indent) throws IOException {
* @throws JSONException if a called function has an error or a write error
* occurs
*/
+ @SuppressWarnings("resource")
public Writer write(Writer writer, int indentFactor, int indent)
throws JSONException {
try {
@@ -2499,38 +3145,10 @@ public Writer write(Writer writer, int indentFactor, int indent)
if (indentFactor > 0) {
writer.write(' ');
}
- try{
- writeValue(writer, entry.getValue(), indentFactor, indent);
- } catch (Exception e) {
- throw new JSONException("Unable to write JSONObject value for key: " + key, e);
- }
+ // might throw an exception
+ attemptWriteValue(writer, indentFactor, indent, entry, key);
} else if (length != 0) {
- final int newIndent = indent + indentFactor;
- for (final Entry entry : this.entrySet()) {
- if (needsComma) {
- writer.write(',');
- }
- if (indentFactor > 0) {
- writer.write('\n');
- }
- indent(writer, newIndent);
- final String key = entry.getKey();
- writer.write(quote(key));
- writer.write(':');
- if (indentFactor > 0) {
- writer.write(' ');
- }
- try {
- writeValue(writer, entry.getValue(), indentFactor, newIndent);
- } catch (Exception e) {
- throw new JSONException("Unable to write JSONObject value for key: " + key, e);
- }
- needsComma = true;
- }
- if (indentFactor > 0) {
- writer.write('\n');
- }
- indent(writer, indent);
+ writeContent(writer, indentFactor, indent, needsComma);
}
writer.write('}');
return writer;
@@ -2539,6 +3157,68 @@ public Writer write(Writer writer, int indentFactor, int indent)
}
}
+ /**
+ * Convenience function. Writer attempts to write formatted content
+ * @param writer
+ * Writes the serialized JSON
+ * @param indentFactor
+ * The number of spaces to add to each level of indentation.
+ * @param indent
+ * The indentation of the top level.
+ * @param needsComma
+ * Boolean flag indicating a comma is needed
+ * @throws IOException
+ * If something goes wrong
+ */
+ private void writeContent(Writer writer, int indentFactor, int indent, boolean needsComma) throws IOException {
+ final int newIndent = indent + indentFactor;
+ for (final Entry entry : this.entrySet()) {
+ if (needsComma) {
+ writer.write(',');
+ }
+ if (indentFactor > 0) {
+ writer.write('\n');
+ }
+ indent(writer, newIndent);
+ final String key = entry.getKey();
+ writer.write(quote(key));
+ writer.write(':');
+ if (indentFactor > 0) {
+ writer.write(' ');
+ }
+ attemptWriteValue(writer, indentFactor, newIndent, entry, key);
+ needsComma = true;
+ }
+ if (indentFactor > 0) {
+ writer.write('\n');
+ }
+ indent(writer, indent);
+ }
+
+ /**
+ * Convenience function. Writer attempts to write a value.
+ * @param writer
+ * Writes the serialized JSON
+ * @param indentFactor
+ * The number of spaces to add to each level of indentation.
+ * @param indent
+ * The indentation of the top level.
+ * @param entry
+ * Contains the value being written
+ * @param key
+ * Identifies the value
+ * @throws JSONException if a called function has an error or a write error
+ * occurs
+
+ */
+ private static void attemptWriteValue(Writer writer, int indentFactor, int indent, Entry entry, String key) {
+ try{
+ writeValue(writer, entry.getValue(), indentFactor, indent);
+ } catch (Exception e) {
+ throw new JSONException("Unable to write JSONObject value for key: " + key, e);
+ }
+ }
+
/**
* Returns a java.util.Map containing all of the entries in this object.
* If an entry in the object is a JSONArray or JSONObject it will also
@@ -2565,7 +3245,7 @@ public Map toMap() {
}
return results;
}
-
+
/**
* Create a new JSONException in a common format for incorrect conversions.
* @param key name of the key
@@ -2576,26 +3256,279 @@ public Map toMap() {
private static JSONException wrongValueFormatException(
String key,
String valueType,
+ Object value,
Throwable cause) {
+ if(value == null) {
+
+ return new JSONException(
+ "JSONObject[" + quote(key) + "] is not a " + valueType + " (null)."
+ , cause);
+ }
+ // don't try to toString collections or known object types that could be large.
+ if(value instanceof Map || value instanceof Iterable || value instanceof JSONObject) {
+ return new JSONException(
+ "JSONObject[" + quote(key) + "] is not a " + valueType + " (" + value.getClass() + ")."
+ , cause);
+ }
return new JSONException(
- "JSONObject[" + quote(key) + "] is not a " + valueType + "."
+ "JSONObject[" + quote(key) + "] is not a " + valueType + " (" + value.getClass() + " : " + value + ")."
, cause);
}
-
+
/**
- * Create a new JSONException in a common format for incorrect conversions.
+ * Create a new JSONException in a common format for recursive object definition.
* @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) {
+ private static JSONException recursivelyDefinedObjectException(String key) {
return new JSONException(
- "JSONObject[" + quote(key) + "] is not a " + valueType + " (" + value + ")."
- , cause);
+ "JavaBean object contains recursively defined member variable of key " + quote(key)
+ );
+ }
+
+ /**
+ * Helper method to extract the raw Class from Type.
+ */
+ private Class> getRawType(Type type) {
+ if (type instanceof Class) {
+ return (Class>) type;
+ } else if (type instanceof ParameterizedType) {
+ return (Class>) ((ParameterizedType) type).getRawType();
+ } else if (type instanceof GenericArrayType) {
+ return Object[].class; // Simplified handling for arrays
+ }
+ return Object.class; // Fallback
+ }
+
+ /**
+ * Extracts the element Type for a Collection Type.
+ */
+ private Type getElementType(Type type) {
+ if (type instanceof ParameterizedType) {
+ Type[] args = ((ParameterizedType) type).getActualTypeArguments();
+ return args.length > 0 ? args[0] : Object.class;
+ }
+ return Object.class;
+ }
+
+ /**
+ * Extracts the key and value Types for a Map Type.
+ */
+ private Type[] getMapTypes(Type type) {
+ if (type instanceof ParameterizedType) {
+ Type[] args = ((ParameterizedType) type).getActualTypeArguments();
+ if (args.length == 2) {
+ return args;
+ }
+ }
+ return new Type[]{Object.class, Object.class}; // Default: String keys, Object values
+ }
+
+ /**
+ * Deserializes a JSON string into an instance of the specified class.
+ *
+ * This method attempts to map JSON key-value pairs to the corresponding fields
+ * of the given class. It supports basic data types including int, double, float,
+ * long, and boolean (as well as their boxed counterparts). The class must have a
+ * no-argument constructor, and the field names in the class must match the keys
+ * in the JSON string.
+ *
+ * @param jsonString json in string format
+ * @param clazz the class of the object to be returned
+ * @return an instance of Object T with fields populated from the JSON string
+ */
+ public static T fromJson(String jsonString, Class clazz) {
+ JSONObject jsonObject = new JSONObject(jsonString);
+ return jsonObject.fromJson(clazz);
+ }
+
+ /**
+ * Deserializes a JSON string into an instance of the specified class.
+ *
+ * This method attempts to map JSON key-value pairs to the corresponding fields
+ * of the given class. It supports basic data types including {@code int}, {@code double},
+ * {@code float}, {@code long}, and {@code boolean}, as well as their boxed counterparts.
+ * The target class must have a no-argument constructor, and its field names must match
+ * the keys in the JSON string.
+ *
+ *
Note: Only classes that are explicitly supported and registered within
+ * the {@code JSONObject} context can be deserialized. If the provided class is not among those,
+ * this method will not be able to deserialize it. This ensures that only a limited and
+ * controlled set of types can be instantiated from JSON for safety and predictability.
+ *
+ * @param clazz the class of the object to be returned
+ * @param the type of the object
+ * @return an instance of type {@code T} with fields populated from the JSON string
+ * @throws IllegalArgumentException if the class is not supported for deserialization
+ */
+ @SuppressWarnings("unchecked")
+ public T fromJson(Class clazz) {
+ try {
+ T obj = clazz.getDeclaredConstructor().newInstance();
+ for (Field field : clazz.getDeclaredFields()) {
+ field.setAccessible(true);
+ String fieldName = field.getName();
+ if (has(fieldName)) {
+ Object value = get(fieldName);
+ Type fieldType = field.getGenericType();
+ Object convertedValue = convertValue(value, fieldType);
+ field.set(obj, convertedValue);
+ }
+ }
+ return obj;
+ } catch (NoSuchMethodException e) {
+ throw new JSONException("No no-arg constructor for class: " + clazz.getName(), e);
+ } catch (Exception e) {
+ throw new JSONException("Failed to instantiate or set field for class: " + clazz.getName(), e);
+ }
+ }
+
+ /**
+ * Recursively converts a value to the target Type, handling nested generics for Collections and Maps.
+ */
+ private Object convertValue(Object value, Type targetType) throws JSONException {
+ if (value == null) {
+ return null;
+ }
+
+ Class> rawType = getRawType(targetType);
+
+ // Direct assignment
+ if (rawType.isAssignableFrom(value.getClass())) {
+ return value;
+ }
+
+ if (rawType == int.class || rawType == Integer.class) {
+ return ((Number) value).intValue();
+ } else if (rawType == double.class || rawType == Double.class) {
+ return ((Number) value).doubleValue();
+ } else if (rawType == float.class || rawType == Float.class) {
+ return ((Number) value).floatValue();
+ } else if (rawType == long.class || rawType == Long.class) {
+ return ((Number) value).longValue();
+ } else if (rawType == boolean.class || rawType == Boolean.class) {
+ return value;
+ } else if (rawType == String.class) {
+ return value;
+ } else if (rawType == BigDecimal.class) {
+ return new BigDecimal((String) value);
+ } else if (rawType == BigInteger.class) {
+ return new BigInteger((String) value);
+ }
+
+ // Enum conversion
+ if (rawType.isEnum() && value instanceof String) {
+ return stringToEnum(rawType, (String) value);
+ }
+
+ // Collection handling (e.g., List>>)
+ if (Collection.class.isAssignableFrom(rawType)) {
+ if (value instanceof JSONArray) {
+ Type elementType = getElementType(targetType);
+ return fromJsonArray((JSONArray) value, rawType, elementType);
+ }
+ }
+ // Map handling (e.g., Map>)
+ else if (Map.class.isAssignableFrom(rawType) && value instanceof JSONObject) {
+ Type[] mapTypes = getMapTypes(targetType);
+ Type keyType = mapTypes[0];
+ Type valueType = mapTypes[1];
+ return convertToMap((JSONObject) value, keyType, valueType, rawType);
+ }
+ // POJO handling (including custom classes like Tuple)
+ else if (!rawType.isPrimitive() && !rawType.isEnum() && value instanceof JSONObject) {
+ // Recurse with the raw class for POJO deserialization
+ return ((JSONObject) value).fromJson(rawType);
+ }
+
+ // Fallback
+ return value.toString();
+ }
+
+ /**
+ * Converts a JSONObject to a Map with the specified generic key and value Types.
+ * Supports nested types via recursive convertValue.
+ */
+ private Map, ?> convertToMap(JSONObject jsonMap, Type keyType, Type valueType, Class> mapType) throws JSONException {
+ try {
+ @SuppressWarnings("unchecked")
+ Map createdMap = new HashMap();
+
+ for (Object keyObj : jsonMap.keySet()) {
+ String keyStr = (String) keyObj;
+ Object mapValue = jsonMap.get(keyStr);
+ // Convert key (e.g., String to Integer for Map)
+ Object convertedKey = convertValue(keyStr, keyType);
+ // Convert value recursively (handles nesting)
+ Object convertedValue = convertValue(mapValue, valueType);
+ createdMap.put(convertedKey, convertedValue);
+ }
+ return createdMap;
+ } catch (Exception e) {
+ throw new JSONException("Failed to convert JSONObject to Map: " + mapType.getName(), e);
+ }
+ }
+
+ /**
+ * Converts a String to an Enum value.
+ */
+ private E stringToEnum(Class> enumClass, String value) throws JSONException {
+ try {
+ @SuppressWarnings("unchecked")
+ Class enumType = (Class) enumClass;
+ Method valueOfMethod = enumType.getMethod("valueOf", String.class);
+ return (E) valueOfMethod.invoke(null, value);
+ } catch (Exception e) {
+ throw new JSONException("Failed to convert string to enum: " + value + " for " + enumClass.getName(), e);
+ }
+ }
+
+ /**
+ * Deserializes a JSONArray into a Collection, supporting nested generics.
+ * Uses recursive convertValue for elements.
+ */
+ @SuppressWarnings("unchecked")
+ private Collection fromJsonArray(JSONArray jsonArray, Class> collectionType, Type elementType) throws JSONException {
+ try {
+ Collection collection = getCollection(collectionType);
+
+ for (int i = 0; i < jsonArray.length(); i++) {
+ Object jsonElement = jsonArray.get(i);
+ // Recursively convert each element using the full element Type (handles nesting)
+ Object convertedValue = convertValue(jsonElement, elementType);
+ collection.add((T) convertedValue);
+ }
+ return collection;
+ } catch (Exception e) {
+ throw new JSONException("Failed to convert JSONArray to Collection: " + collectionType.getName(), e);
+ }
+ }
+
+ /**
+ * Creates and returns a new instance of a supported {@link Collection} implementation
+ * based on the specified collection type.
+ *
+ * This method currently supports the following collection types:
+ *
+ * {@code List.class}
+ * {@code ArrayList.class}
+ * {@code Set.class}
+ * {@code HashSet.class}
+ *
+ * If the provided type does not match any of the supported types, a {@link JSONException}
+ * is thrown.
+ *
+ * @param collectionType the {@link Class} object representing the desired collection type
+ * @return a new empty instance of the specified collection type
+ * @throws JSONException if the specified type is not a supported collection type
+ */
+ private Collection getCollection(Class> collectionType) throws JSONException {
+ if (collectionType == List.class || collectionType == ArrayList.class) {
+ return new ArrayList();
+ } else if (collectionType == Set.class || collectionType == HashSet.class) {
+ return new HashSet();
+ } else {
+ throw new JSONException("Unsupported Collection type: " + collectionType.getName());
+ }
}
}
diff --git a/src/main/java/org/json/JSONParserConfiguration.java b/src/main/java/org/json/JSONParserConfiguration.java
new file mode 100644
index 000000000..0cfa2eaef
--- /dev/null
+++ b/src/main/java/org/json/JSONParserConfiguration.java
@@ -0,0 +1,152 @@
+package org.json;
+
+/**
+ * Configuration object for the JSON parser. The configuration is immutable.
+ */
+public class JSONParserConfiguration extends ParserConfiguration {
+ /**
+ * Used to indicate whether to overwrite duplicate key or not.
+ */
+ private boolean overwriteDuplicateKey;
+
+ /**
+ * Used to indicate whether to convert java null values to JSONObject.NULL or ignoring the entry when converting java maps.
+ */
+ private boolean useNativeNulls;
+
+ /**
+ * Configuration with the default values.
+ */
+ public JSONParserConfiguration() {
+ super();
+ this.overwriteDuplicateKey = false;
+ // DO NOT DELETE THE FOLLOWING LINE -- it is used for strictMode testing
+ // this.strictMode = true;
+ }
+
+ /**
+ * This flag, when set to true, instructs the parser to enforce strict mode when parsing JSON text.
+ * Garbage chars at the end of the doc, unquoted string, and single-quoted strings are all disallowed.
+ */
+ private boolean strictMode;
+
+ @Override
+ protected JSONParserConfiguration clone() {
+ JSONParserConfiguration clone = new JSONParserConfiguration();
+ clone.overwriteDuplicateKey = overwriteDuplicateKey;
+ clone.strictMode = strictMode;
+ clone.maxNestingDepth = maxNestingDepth;
+ clone.keepStrings = keepStrings;
+ clone.useNativeNulls = useNativeNulls;
+ return clone;
+ }
+
+ /**
+ * Defines the maximum nesting depth that the parser will descend before throwing an exception
+ * when parsing a map into JSONObject or parsing a {@link java.util.Collection} instance into
+ * JSONArray. The default max nesting depth is 512, which means the parser will throw a JsonException
+ * if the maximum depth is reached.
+ *
+ * @param maxNestingDepth the maximum nesting depth allowed to the JSON parser
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public JSONParserConfiguration withMaxNestingDepth(final int maxNestingDepth) {
+ JSONParserConfiguration clone = this.clone();
+ clone.maxNestingDepth = maxNestingDepth;
+
+ return clone;
+ }
+
+ /**
+ * Controls the parser's behavior when meeting duplicate keys.
+ * If set to false, the parser will throw a JSONException when meeting a duplicate key.
+ * Or the duplicate key's value will be overwritten.
+ *
+ * @param overwriteDuplicateKey defines should the parser overwrite duplicate keys.
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public JSONParserConfiguration withOverwriteDuplicateKey(final boolean overwriteDuplicateKey) {
+ JSONParserConfiguration clone = this.clone();
+ clone.overwriteDuplicateKey = overwriteDuplicateKey;
+
+ return clone;
+ }
+
+ /**
+ * Controls the parser's behavior when meeting Java null values while converting maps.
+ * If set to true, the parser will put a JSONObject.NULL into the resulting JSONObject.
+ * Or the map entry will be ignored.
+ *
+ * @param useNativeNulls defines if the parser should convert null values in Java maps
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public JSONParserConfiguration withUseNativeNulls(final boolean useNativeNulls) {
+ JSONParserConfiguration clone = this.clone();
+ clone.useNativeNulls = useNativeNulls;
+
+ return clone;
+ }
+
+ /**
+ * Sets the strict mode configuration for the JSON parser with default true value
+ *
+ * When strict mode is enabled, the parser will throw a JSONException if it encounters an invalid character
+ * immediately following the final ']' character in the input. This is useful for ensuring strict adherence to the
+ * JSON syntax, as any characters after the final closing bracket of a JSON array are considered invalid.
+ * @return a new JSONParserConfiguration instance with the updated strict mode setting
+ */
+ public JSONParserConfiguration withStrictMode() {
+ return withStrictMode(true);
+ }
+
+ /**
+ * Sets the strict mode configuration for the JSON parser.
+ *
+ * When strict mode is enabled, the parser will throw a JSONException if it encounters an invalid character
+ * immediately following the final ']' character in the input. This is useful for ensuring strict adherence to the
+ * JSON syntax, as any characters after the final closing bracket of a JSON array are considered invalid.
+ *
+ * @param mode a boolean value indicating whether strict mode should be enabled or not
+ * @return a new JSONParserConfiguration instance with the updated strict mode setting
+ */
+ public JSONParserConfiguration withStrictMode(final boolean mode) {
+ JSONParserConfiguration clone = this.clone();
+ clone.strictMode = mode;
+
+ return clone;
+ }
+
+ /**
+ * The parser's behavior when meeting duplicate keys, controls whether the parser should
+ * overwrite duplicate keys or not.
+ *
+ * @return The overwriteDuplicateKey configuration value.
+ */
+ public boolean isOverwriteDuplicateKey() {
+ return this.overwriteDuplicateKey;
+ }
+
+ /**
+ * The parser's behavior when meeting a null value in a java map, controls whether the parser should
+ * write a JSON entry with a null value (isUseNativeNulls() == true)
+ * or ignore that map entry (isUseNativeNulls() == false).
+ *
+ * @return The useNativeNulls configuration value.
+ */
+ public boolean isUseNativeNulls() {
+ return this.useNativeNulls;
+ }
+
+
+ /**
+ * The parser throws an Exception when strict mode is true and tries to parse invalid JSON characters.
+ * Otherwise, the parser is more relaxed and might tolerate some invalid characters.
+ *
+ * @return the current strict mode setting.
+ */
+ public boolean isStrictMode() {
+ return this.strictMode;
+ }
+}
diff --git a/src/main/java/org/json/JSONPointer.java b/src/main/java/org/json/JSONPointer.java
index e8a0b78c9..34066c1aa 100644
--- a/src/main/java/org/json/JSONPointer.java
+++ b/src/main/java/org/json/JSONPointer.java
@@ -10,27 +10,7 @@
import java.util.List;
/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
/**
@@ -62,6 +42,12 @@ public class JSONPointer {
*/
public static class Builder {
+ /**
+ * Constructs a new Builder object.
+ */
+ public Builder() {
+ }
+
// Segments for the eventual JSONPointer string
private final List refTokens = new ArrayList();
@@ -141,7 +127,7 @@ public JSONPointer(final String pointer) {
if (pointer == null) {
throw new NullPointerException("pointer cannot be null");
}
- if (pointer.isEmpty() || pointer.equals("#")) {
+ if (pointer.isEmpty() || "#".equals(pointer)) {
this.refTokens = Collections.emptyList();
return;
}
@@ -183,14 +169,21 @@ public JSONPointer(final String pointer) {
//}
}
+ /**
+ * Constructs a new JSONPointer instance with the provided list of reference tokens.
+ *
+ * @param refTokens A list of strings representing the reference tokens for the JSON Pointer.
+ * Each token identifies a step in the path to the targeted value.
+ */
public JSONPointer(List refTokens) {
this.refTokens = new ArrayList(refTokens);
}
+ /**
+ * @see rfc6901 section 3
+ */
private static String unescape(String token) {
- return token.replace("~1", "/").replace("~0", "~")
- .replace("\\\"", "\"")
- .replace("\\\\", "\\");
+ return token.replace("~1", "/").replace("~0", "~");
}
/**
@@ -253,7 +246,7 @@ private static Object readByIndexToken(Object current, String indexToken) throws
*/
@Override
public String toString() {
- StringBuilder rval = new StringBuilder("");
+ StringBuilder rval = new StringBuilder();
for (String token: this.refTokens) {
rval.append('/').append(escape(token));
}
@@ -263,16 +256,15 @@ public String toString() {
/**
* Escapes path segment values to an unambiguous form.
* The escape char to be inserted is '~'. The chars to be escaped
- * are ~, which maps to ~0, and /, which maps to ~1. Backslashes
- * and double quote chars are also escaped.
+ * are ~, which maps to ~0, and /, which maps to ~1.
* @param token the JSONPointer segment value to be escaped
* @return the escaped value for the token
+ *
+ * @see rfc6901 section 3
*/
private static String escape(String token) {
return token.replace("~", "~0")
- .replace("/", "~1")
- .replace("\\", "\\\\")
- .replace("\"", "\\\"");
+ .replace("/", "~1");
}
/**
diff --git a/src/main/java/org/json/JSONPointerException.java b/src/main/java/org/json/JSONPointerException.java
index 0ce1aeb29..dc5a25ad6 100644
--- a/src/main/java/org/json/JSONPointerException.java
+++ b/src/main/java/org/json/JSONPointerException.java
@@ -1,27 +1,7 @@
package org.json;
/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
/**
@@ -34,10 +14,21 @@ of this software and associated documentation files (the "Software"), to deal
public class JSONPointerException extends JSONException {
private static final long serialVersionUID = 8872944667561856751L;
+ /**
+ * Constructs a new JSONPointerException with the specified error message.
+ *
+ * @param message The detail message describing the reason for the exception.
+ */
public JSONPointerException(String message) {
super(message);
}
+ /**
+ * Constructs a new JSONPointerException with the specified error message and cause.
+ *
+ * @param message The detail message describing the reason for the exception.
+ * @param cause The cause of the exception.
+ */
public JSONPointerException(String message, Throwable cause) {
super(message, cause);
}
diff --git a/src/main/java/org/json/JSONPropertyIgnore.java b/src/main/java/org/json/JSONPropertyIgnore.java
index 682de7447..d3a5bc5a1 100644
--- a/src/main/java/org/json/JSONPropertyIgnore.java
+++ b/src/main/java/org/json/JSONPropertyIgnore.java
@@ -1,27 +1,7 @@
package org.json;
/*
-Copyright (c) 2018 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static java.lang.annotation.ElementType.METHOD;
@@ -31,13 +11,13 @@ of this software and associated documentation files (the "Software"), to deal
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
-@Documented
-@Retention(RUNTIME)
-@Target({METHOD})
/**
* Use this annotation on a getter method to override the Bean name
* parser for Bean -> JSONObject mapping. If this annotation is
* present at any level in the class hierarchy, then the method will
* not be serialized from the bean into the JSONObject.
*/
+@Documented
+@Retention(RUNTIME)
+@Target({METHOD})
public @interface JSONPropertyIgnore { }
diff --git a/src/main/java/org/json/JSONPropertyName.java b/src/main/java/org/json/JSONPropertyName.java
index a1bcd58bf..0e4123f37 100644
--- a/src/main/java/org/json/JSONPropertyName.java
+++ b/src/main/java/org/json/JSONPropertyName.java
@@ -1,27 +1,7 @@
package org.json;
/*
-Copyright (c) 2018 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static java.lang.annotation.ElementType.METHOD;
@@ -31,16 +11,17 @@ of this software and associated documentation files (the "Software"), to deal
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
-@Documented
-@Retention(RUNTIME)
-@Target({METHOD})
/**
* Use this annotation on a getter method to override the Bean name
* parser for Bean -> JSONObject mapping. A value set to empty string ""
* will have the Bean parser fall back to the default field name processing.
*/
+@Documented
+@Retention(RUNTIME)
+@Target({METHOD})
public @interface JSONPropertyName {
/**
+ * The value of the JSON property.
* @return The name of the property as to be used in the JSON Object.
*/
String value();
diff --git a/src/main/java/org/json/JSONString.java b/src/main/java/org/json/JSONString.java
index bcd9a8128..cd8d1847d 100644
--- a/src/main/java/org/json/JSONString.java
+++ b/src/main/java/org/json/JSONString.java
@@ -1,27 +1,7 @@
package org.json;
/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
/**
diff --git a/src/main/java/org/json/JSONStringer.java b/src/main/java/org/json/JSONStringer.java
index bb9e7a4cf..2f6cf9ed8 100644
--- a/src/main/java/org/json/JSONStringer.java
+++ b/src/main/java/org/json/JSONStringer.java
@@ -1,27 +1,7 @@
package org.json;
/*
-Copyright (c) 2006 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import java.io.StringWriter;
@@ -50,7 +30,7 @@ of this software and associated documentation files (the "Software"), to deal
*
* The first method called must be array or object.
* There are no methods for adding commas or colons. JSONStringer adds them for
- * you. Objects and arrays can be nested up to 20 levels deep.
+ * you. Objects and arrays can be nested up to 200 levels deep.
*
* This can sometimes be easier than using a JSONObject to build a string.
* @author JSON.org
diff --git a/src/main/java/org/json/JSONTokener.java b/src/main/java/org/json/JSONTokener.java
index e6821de32..07ff18c99 100644
--- a/src/main/java/org/json/JSONTokener.java
+++ b/src/main/java/org/json/JSONTokener.java
@@ -1,34 +1,10 @@
package org.json;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.StringReader;
+import java.io.*;
+import java.nio.charset.Charset;
/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
/**
@@ -56,13 +32,27 @@ public class JSONTokener {
/** the number of characters read in the previous line. */
private long characterPreviousLine;
+ // access to this object is required for strict mode checking
+ private JSONParserConfiguration jsonParserConfiguration;
/**
* Construct a JSONTokener from a Reader. The caller must close the Reader.
*
- * @param reader A reader.
+ * @param reader the source.
*/
public JSONTokener(Reader reader) {
+ this(reader, new JSONParserConfiguration());
+ }
+
+ /**
+ * Construct a JSONTokener from a Reader with a given JSONParserConfiguration. The caller must close the Reader.
+ *
+ * @param reader the source.
+ * @param jsonParserConfiguration A JSONParserConfiguration instance that controls the behavior of the parser.
+ *
+ */
+ public JSONTokener(Reader reader, JSONParserConfiguration jsonParserConfiguration) {
+ this.jsonParserConfiguration = jsonParserConfiguration;
this.reader = reader.markSupported()
? reader
: new BufferedReader(reader);
@@ -75,25 +65,60 @@ public JSONTokener(Reader reader) {
this.line = 1;
}
-
/**
* Construct a JSONTokener from an InputStream. The caller must close the input stream.
* @param inputStream The source.
*/
public JSONTokener(InputStream inputStream) {
- this(new InputStreamReader(inputStream));
+ this(inputStream, new JSONParserConfiguration());
+ }
+
+ /**
+ * Construct a JSONTokener from an InputStream. The caller must close the input stream.
+ * @param inputStream The source.
+ * @param jsonParserConfiguration A JSONParserConfiguration instance that controls the behavior of the parser.
+ */
+ public JSONTokener(InputStream inputStream, JSONParserConfiguration jsonParserConfiguration) {
+ this(new InputStreamReader(inputStream, Charset.forName("UTF-8")), jsonParserConfiguration);
}
/**
* Construct a JSONTokener from a string.
*
- * @param s A source string.
+ * @param source A source string.
*/
- public JSONTokener(String s) {
- this(new StringReader(s));
+ public JSONTokener(String source) {
+ this(new StringReader(source));
}
+ /**
+ * Construct a JSONTokener from an InputStream. The caller must close the input stream.
+ * @param source The source.
+ * @param jsonParserConfiguration A JSONParserConfiguration instance that controls the behavior of the parser.
+ */
+ public JSONTokener(String source, JSONParserConfiguration jsonParserConfiguration) {
+ this(new StringReader(source), jsonParserConfiguration);
+ }
+
+ /**
+ * Getter
+ * @return jsonParserConfiguration
+ */
+ public JSONParserConfiguration getJsonParserConfiguration() {
+ return jsonParserConfiguration;
+ }
+
+ /**
+ * Setter
+ * @param jsonParserConfiguration new value for jsonParserConfiguration
+ *
+ * @deprecated method should not be used
+ */
+ @Deprecated
+ public void setJsonParserConfiguration(JSONParserConfiguration jsonParserConfiguration) {
+ this.jsonParserConfiguration = jsonParserConfiguration;
+ }
/**
* Back up one character. This provides a sort of lookahead capability,
@@ -145,7 +170,7 @@ public static int dehexchar(char c) {
/**
* Checks if the end of the input has been reached.
- *
+ *
* @return true if at the end of the file and we didn't step back
*/
public boolean end() {
@@ -210,6 +235,12 @@ public char next() throws JSONException {
return this.previous;
}
+ /**
+ * Get the last character read from the input or '\0' if nothing has been read yet.
+ * @return the last character read from the input.
+ */
+ protected char getPrevious() { return this.previous;}
+
/**
* Increments the internal indexes according to the previous character
* read and the character passed as the current character.
@@ -317,7 +348,8 @@ public String nextString(char quote) throws JSONException {
case 0:
case '\n':
case '\r':
- throw this.syntaxError("Unterminated string");
+ throw this.syntaxError("Unterminated string. " +
+ "Character with int code " + (int) c + " is not allowed within a quoted string.");
case '\\':
c = this.next();
switch (c) {
@@ -337,10 +369,12 @@ public String nextString(char quote) throws JSONException {
sb.append('\r');
break;
case 'u':
+ String next = this.next(4);
try {
- sb.append((char)Integer.parseInt(this.next(4), 16));
+ sb.append((char)Integer.parseInt(next, 16));
} catch (NumberFormatException e) {
- throw this.syntaxError("Illegal escape.", e);
+ throw this.syntaxError("Illegal escape. " +
+ "\\u must be followed by a 4 digit hexadecimal number. \\" + next + " is not valid.", e);
}
break;
case '"':
@@ -350,7 +384,7 @@ public String nextString(char quote) throws JSONException {
sb.append(c);
break;
default:
- throw this.syntaxError("Illegal escape.");
+ throw this.syntaxError("Illegal escape. Escape sequence \\" + c + " is not valid.");
}
break;
default:
@@ -420,18 +454,38 @@ public String nextTo(String delimiters) throws JSONException {
*/
public Object nextValue() throws JSONException {
char c = this.nextClean();
+ switch (c) {
+ case '{':
+ this.back();
+ try {
+ return new JSONObject(this, jsonParserConfiguration);
+ } catch (StackOverflowError e) {
+ throw new JSONException("JSON Array or Object depth too large to process.", e);
+ }
+ case '[':
+ this.back();
+ try {
+ return new JSONArray(this, jsonParserConfiguration);
+ } catch (StackOverflowError e) {
+ throw new JSONException("JSON Array or Object depth too large to process.", e);
+ }
+ }
+ return nextSimpleValue(c);
+ }
+
+ Object nextSimpleValue(char c) {
String string;
+ // Strict mode only allows strings with explicit double quotes
+ if (jsonParserConfiguration != null &&
+ jsonParserConfiguration.isStrictMode() &&
+ c == '\'') {
+ throw this.syntaxError("Strict mode error: Single quoted strings are not allowed");
+ }
switch (c) {
case '"':
case '\'':
return this.nextString(c);
- case '{':
- this.back();
- return new JSONObject(this);
- case '[':
- this.back();
- return new JSONArray(this);
}
/*
@@ -455,8 +509,28 @@ public Object nextValue() throws JSONException {
string = sb.toString().trim();
if ("".equals(string)) {
throw this.syntaxError("Missing value");
+ } else if (jsonParserConfiguration != null &&
+ jsonParserConfiguration.isStrictMode() && string.endsWith(".")) {
+ throw this.syntaxError(String.format("Strict mode error: Value '%s' ends with dot", string));
+ }
+ Object obj = JSONObject.stringToValue(string);
+ // if obj is a boolean, look at string
+ if (jsonParserConfiguration != null &&
+ jsonParserConfiguration.isStrictMode()) {
+ if (obj instanceof Boolean && !"true".equals(string) && !"false".equals(string)) {
+ // Strict mode only allows lowercase true or false
+ throw this.syntaxError(String.format("Strict mode error: Value '%s' is not lowercase boolean", obj));
+ }
+ else if (obj == JSONObject.NULL && !"null".equals(string)) {
+ // Strint mode only allows lowercase null
+ throw this.syntaxError(String.format("Strict mode error: Value '%s' is not lowercase null", obj));
+ }
+ else if (obj instanceof String) {
+ // Strict mode only allows strings with explicit double quotes
+ throw this.syntaxError(String.format("Strict mode error: Value '%s' is not surrounded by quotes", obj));
+ }
}
- return JSONObject.stringToValue(string);
+ return obj;
}
@@ -528,4 +602,15 @@ public String toString() {
return " at " + this.index + " [character " + this.character + " line " +
this.line + "]";
}
+
+ /**
+ * Closes the underlying reader, releasing any resources associated with it.
+ *
+ * @throws IOException If an I/O error occurs while closing the reader.
+ */
+ public void close() throws IOException {
+ if(reader!=null){
+ reader.close();
+ }
+ }
}
diff --git a/src/main/java/org/json/JSONWriter.java b/src/main/java/org/json/JSONWriter.java
index dafb1b264..11f4a5c7e 100644
--- a/src/main/java/org/json/JSONWriter.java
+++ b/src/main/java/org/json/JSONWriter.java
@@ -5,27 +5,7 @@
import java.util.Map;
/*
-Copyright (c) 2006 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
/**
diff --git a/src/main/java/org/json/ParserConfiguration.java b/src/main/java/org/json/ParserConfiguration.java
new file mode 100644
index 000000000..06cc44366
--- /dev/null
+++ b/src/main/java/org/json/ParserConfiguration.java
@@ -0,0 +1,126 @@
+package org.json;
+/*
+Public Domain.
+*/
+
+/**
+ * Configuration base object for parsers. The configuration is immutable.
+ */
+@SuppressWarnings({""})
+public class ParserConfiguration {
+ /**
+ * Used to indicate there's no defined limit to the maximum nesting depth when parsing a document.
+ */
+ public static final int UNDEFINED_MAXIMUM_NESTING_DEPTH = -1;
+
+ /**
+ * The default maximum nesting depth when parsing a document.
+ */
+ public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512;
+
+ /**
+ * Specifies if values should be kept as strings (true), or if
+ * they should try to be guessed into JSON values (numeric, boolean, string).
+ */
+ protected boolean keepStrings;
+
+ /**
+ * The maximum nesting depth when parsing an object.
+ */
+ protected int maxNestingDepth;
+
+ /**
+ * Constructs a new ParserConfiguration with default settings.
+ */
+ public ParserConfiguration() {
+ this.keepStrings = false;
+ this.maxNestingDepth = DEFAULT_MAXIMUM_NESTING_DEPTH;
+ }
+
+ /**
+ * Constructs a new ParserConfiguration with the specified settings.
+ *
+ * @param keepStrings A boolean indicating whether to preserve strings during parsing.
+ * @param maxNestingDepth An integer representing the maximum allowed nesting depth.
+ */
+ protected ParserConfiguration(final boolean keepStrings, final int maxNestingDepth) {
+ this.keepStrings = keepStrings;
+ this.maxNestingDepth = maxNestingDepth;
+ }
+
+ /**
+ * Provides a new instance of the same configuration.
+ */
+ @Override
+ protected ParserConfiguration clone() {
+ // future modifications to this method should always ensure a "deep"
+ // clone in the case of collections. i.e. if a Map is added as a configuration
+ // item, a new map instance should be created and if possible each value in the
+ // map should be cloned as well. If the values of the map are known to also
+ // be immutable, then a shallow clone of the map is acceptable.
+ return new ParserConfiguration(
+ this.keepStrings,
+ this.maxNestingDepth
+ );
+ }
+
+ /**
+ * When parsing the XML into JSONML, specifies if values should be kept as strings (true), or if
+ * they should try to be guessed into JSON values (numeric, boolean, string).
+ *
+ * @return The keepStrings configuration value.
+ */
+ public boolean isKeepStrings() {
+ return this.keepStrings;
+ }
+
+ /**
+ * When parsing the XML into JSONML, specifies if values should be kept as strings (true), or if
+ * they should try to be guessed into JSON values (numeric, boolean, string)
+ *
+ * @param newVal new value to use for the keepStrings configuration option.
+ * @param the type of the configuration object
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ @SuppressWarnings("unchecked")
+ public T withKeepStrings(final boolean newVal) {
+ T newConfig = (T) this.clone();
+ newConfig.keepStrings = newVal;
+ return newConfig;
+ }
+
+ /**
+ * The maximum nesting depth that the parser will descend before throwing an exception
+ * when parsing an object (e.g. Map, Collection) into JSON-related objects.
+ *
+ * @return the maximum nesting depth set for this configuration
+ */
+ public int getMaxNestingDepth() {
+ return maxNestingDepth;
+ }
+
+ /**
+ * Defines the maximum nesting depth that the parser will descend before throwing an exception
+ * when parsing an object (e.g. Map, Collection) into JSON-related objects.
+ * The default max nesting depth is 512, which means the parser will throw a JsonException if
+ * the maximum depth is reached.
+ * Using any negative value as a parameter is equivalent to setting no limit to the nesting depth,
+ * which means the parses will go as deep as the maximum call stack size allows.
+ *
+ * @param maxNestingDepth the maximum nesting depth allowed to the XML parser
+ * @param the type of the configuration object
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ @SuppressWarnings("unchecked")
+ public T withMaxNestingDepth(int maxNestingDepth) {
+ T newConfig = (T) this.clone();
+
+ if (maxNestingDepth > UNDEFINED_MAXIMUM_NESTING_DEPTH) {
+ newConfig.maxNestingDepth = maxNestingDepth;
+ } else {
+ newConfig.maxNestingDepth = UNDEFINED_MAXIMUM_NESTING_DEPTH;
+ }
+
+ return newConfig;
+ }
+}
diff --git a/src/main/java/org/json/Property.java b/src/main/java/org/json/Property.java
index 7caeebb07..ba6c56967 100644
--- a/src/main/java/org/json/Property.java
+++ b/src/main/java/org/json/Property.java
@@ -1,27 +1,7 @@
package org.json;
/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import java.util.Enumeration;
@@ -33,6 +13,13 @@ of this software and associated documentation files (the "Software"), to deal
* @version 2015-05-05
*/
public class Property {
+
+ /**
+ * Constructs a new Property object.
+ */
+ public Property() {
+ }
+
/**
* Converts a property file object into a JSONObject. The property file object is a table of name value pairs.
* @param properties java.util.Properties
diff --git a/src/main/java/org/json/StringBuilderWriter.java b/src/main/java/org/json/StringBuilderWriter.java
new file mode 100644
index 000000000..4aaa4903f
--- /dev/null
+++ b/src/main/java/org/json/StringBuilderWriter.java
@@ -0,0 +1,92 @@
+package org.json;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Performance optimised alternative for {@link java.io.StringWriter}
+ * using internally a {@link StringBuilder} instead of a {@link StringBuffer}.
+ */
+public class StringBuilderWriter extends Writer {
+ private final StringBuilder builder;
+
+ /**
+ * Create a new string builder writer using the default initial string-builder buffer size.
+ */
+ public StringBuilderWriter() {
+ builder = new StringBuilder();
+ lock = builder;
+ }
+
+ /**
+ * Create a new string builder writer using the specified initial string-builder buffer size.
+ *
+ * @param initialSize The number of {@code char} values that will fit into this buffer
+ * before it is automatically expanded
+ *
+ * @throws IllegalArgumentException If {@code initialSize} is negative
+ */
+ public StringBuilderWriter(int initialSize) {
+ builder = new StringBuilder(initialSize);
+ lock = builder;
+ }
+
+ @Override
+ public void write(int c) {
+ builder.append((char) c);
+ }
+
+ @Override
+ public void write(char[] cbuf, int offset, int length) {
+ if ((offset < 0) || (offset > cbuf.length) || (length < 0) ||
+ ((offset + length) > cbuf.length) || ((offset + length) < 0)) {
+ throw new IndexOutOfBoundsException();
+ } else if (length == 0) {
+ return;
+ }
+ builder.append(cbuf, offset, length);
+ }
+
+ @Override
+ public void write(String str) {
+ builder.append(str);
+ }
+
+ @Override
+ public void write(String str, int offset, int length) {
+ builder.append(str, offset, offset + length);
+ }
+
+ @Override
+ public StringBuilderWriter append(CharSequence csq) {
+ write(String.valueOf(csq));
+ return this;
+ }
+
+ @Override
+ public StringBuilderWriter append(CharSequence csq, int start, int end) {
+ if (csq == null) {
+ csq = "null";
+ }
+ return append(csq.subSequence(start, end));
+ }
+
+ @Override
+ public StringBuilderWriter append(char c) {
+ write(c);
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return builder.toString();
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void close() throws IOException {
+ }
+}
diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java
index 805a5c376..e14bb34e9 100644
--- a/src/main/java/org/json/XML.java
+++ b/src/main/java/org/json/XML.java
@@ -1,36 +1,15 @@
package org.json;
/*
-Copyright (c) 2015 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import java.io.Reader;
import java.io.StringReader;
-import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Iterator;
-
+import java.util.NoSuchElementException;
/**
* This provides static methods to convert an XML text into a JSONObject, and to
@@ -42,6 +21,12 @@ of this software and associated documentation files (the "Software"), to deal
@SuppressWarnings("boxing")
public class XML {
+ /**
+ * Constructs a new XML object.
+ */
+ public XML() {
+ }
+
/** The Character '&'. */
public static final Character AMP = '&';
@@ -74,6 +59,9 @@ public class XML {
*/
public static final String NULL_ATTR = "xsi:nil";
+ /**
+ * Represents the XML attribute name for specifying type information.
+ */
public static final String TYPE_ATTR = "xsi:type";
/**
@@ -93,7 +81,7 @@ private static Iterable codePointIterator(final String string) {
public Iterator iterator() {
return new Iterator() {
private int nextIndex = 0;
- private int length = string.length();
+ private final int length = string.length();
@Override
public boolean hasNext() {
@@ -102,6 +90,9 @@ public boolean hasNext() {
@Override
public Integer next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
int result = string.codePointAt(this.nextIndex);
this.nextIndex += Character.charCount(result);
return result;
@@ -119,7 +110,7 @@ public void remove() {
/**
* Replace special characters with XML escapes:
*
- * {@code
+ * {@code
* & (ampersand) is replaced by &
* < (less than) is replaced by <
* > (greater than) is replaced by >
@@ -250,10 +241,14 @@ public static void noSpace(String string) throws JSONException {
* The JSONObject that will include the new material.
* @param name
* The tag name.
+ * @param config
+ * The XML parser configuration.
+ * @param currentNestingDepth
+ * The current nesting depth.
* @return true if the close tag is processed.
- * @throws JSONException
+ * @throws JSONException Thrown if any parsing error occurs.
*/
- private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config)
+ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, int currentNestingDepth)
throws JSONException {
char c;
int i;
@@ -364,10 +359,20 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
&& TYPE_ATTR.equals(string)) {
xmlXsiTypeConverter = config.getXsiTypeMap().get(token);
} else if (!nilAttributeFound) {
- jsonObject.accumulate(string,
- config.isKeepStrings()
- ? ((String) token)
- : stringToValue((String) token));
+ Object obj = stringToValue((String) token);
+ if (obj instanceof Boolean) {
+ jsonObject.accumulate(string,
+ config.isKeepBooleanAsString()
+ ? ((String) token)
+ : obj);
+ } else if (obj instanceof Number) {
+ jsonObject.accumulate(string,
+ config.isKeepNumberAsString()
+ ? ((String) token)
+ : obj);
+ } else {
+ jsonObject.accumulate(string, stringToValue((String) token));
+ }
}
token = null;
} else {
@@ -380,12 +385,23 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
if (x.nextToken() != GT) {
throw x.syntaxError("Misshaped tag");
}
- if (nilAttributeFound) {
- context.accumulate(tagName, JSONObject.NULL);
- } else if (jsonObject.length() > 0) {
- context.accumulate(tagName, jsonObject);
+ if (config.getForceList().contains(tagName)) {
+ // Force the value to be an array
+ if (nilAttributeFound) {
+ context.append(tagName, JSONObject.NULL);
+ } else if (jsonObject.length() > 0) {
+ context.append(tagName, jsonObject);
+ } else {
+ context.put(tagName, new JSONArray());
+ }
} else {
- context.accumulate(tagName, "");
+ if (nilAttributeFound) {
+ context.accumulate(tagName, JSONObject.NULL);
+ } else if (jsonObject.length() > 0) {
+ context.accumulate(tagName, jsonObject);
+ } else {
+ context.accumulate(tagName, "");
+ }
}
return false;
@@ -405,22 +421,57 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
jsonObject.accumulate(config.getcDataTagName(),
stringToValue(string, xmlXsiTypeConverter));
} else {
- jsonObject.accumulate(config.getcDataTagName(),
- config.isKeepStrings() ? string : stringToValue(string));
+ Object obj = stringToValue((String) token);
+ if (obj instanceof Boolean) {
+ jsonObject.accumulate(config.getcDataTagName(),
+ config.isKeepBooleanAsString()
+ ? ((String) token)
+ : obj);
+ } else if (obj instanceof Number) {
+ jsonObject.accumulate(config.getcDataTagName(),
+ config.isKeepNumberAsString()
+ ? ((String) token)
+ : obj);
+ } else if (obj == JSONObject.NULL) {
+ jsonObject.accumulate(config.getcDataTagName(),
+ config.isKeepStrings() ? ((String) token) : obj);
+ } else {
+ jsonObject.accumulate(config.getcDataTagName(), stringToValue((String) token));
+ }
}
}
} else if (token == LT) {
// Nested element
- if (parse(x, jsonObject, tagName, config)) {
- if (jsonObject.length() == 0) {
- context.accumulate(tagName, "");
- } else if (jsonObject.length() == 1
- && jsonObject.opt(config.getcDataTagName()) != null) {
- context.accumulate(tagName, jsonObject.opt(config.getcDataTagName()));
+ if (currentNestingDepth == config.getMaxNestingDepth()) {
+ throw x.syntaxError("Maximum nesting depth of " + config.getMaxNestingDepth() + " reached");
+ }
+
+ if (parse(x, jsonObject, tagName, config, currentNestingDepth + 1)) {
+ if (config.getForceList().contains(tagName)) {
+ // Force the value to be an array
+ if (jsonObject.length() == 0) {
+ context.put(tagName, new JSONArray());
+ } else if (jsonObject.length() == 1
+ && jsonObject.opt(config.getcDataTagName()) != null) {
+ context.append(tagName, jsonObject.opt(config.getcDataTagName()));
+ } else {
+ context.append(tagName, jsonObject);
+ }
} else {
- context.accumulate(tagName, jsonObject);
+ if (jsonObject.length() == 0) {
+ context.accumulate(tagName, "");
+ } else if (jsonObject.length() == 1
+ && jsonObject.opt(config.getcDataTagName()) != null) {
+ context.accumulate(tagName, jsonObject.opt(config.getcDataTagName()));
+ } else {
+ if (!config.shouldTrimWhiteSpace()) {
+ removeEmpty(jsonObject, config);
+ }
+ context.accumulate(tagName, jsonObject);
+ }
}
+
return false;
}
}
@@ -431,60 +482,48 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
}
}
}
-
/**
- * This method tries to convert the given string value to the target object
- * @param string String to convert
- * @param typeConverter value converter to convert string to integer, boolean e.t.c
- * @return JSON value of this string or the string
+ * This method removes any JSON entry which has the key set by XMLParserConfiguration.cDataTagName
+ * and contains whitespace as this is caused by whitespace between tags. See test XMLTest.testNestedWithWhitespaceTrimmingDisabled.
+ * @param jsonObject JSONObject which may require deletion
+ * @param config The XMLParserConfiguration which includes the cDataTagName
*/
- public static Object stringToValue(String string, XMLXsiTypeConverter> typeConverter) {
- if(typeConverter != null) {
- return typeConverter.convert(string);
+ private static void removeEmpty(final JSONObject jsonObject, final XMLParserConfiguration config) {
+ if (jsonObject.has(config.getcDataTagName())) {
+ final Object s = jsonObject.get(config.getcDataTagName());
+ if (s instanceof String) {
+ if (isStringAllWhiteSpace(s.toString())) {
+ jsonObject.remove(config.getcDataTagName());
+ }
+ }
+ else if (s instanceof JSONArray) {
+ final JSONArray sArray = (JSONArray) s;
+ for (int k = sArray.length()-1; k >= 0; k--){
+ final Object eachString = sArray.get(k);
+ if (eachString instanceof String) {
+ String s1 = (String) eachString;
+ if (isStringAllWhiteSpace(s1)) {
+ sArray.remove(k);
+ }
+ }
+ }
+ if (sArray.isEmpty()) {
+ jsonObject.remove(config.getcDataTagName());
+ }
+ }
}
- return stringToValue(string);
}
- /**
- * This method is the same as {@link JSONObject#stringToValue(String)}.
- *
- * @param string String to convert
- * @return JSON value of this string or the string
- */
- // To maintain compatibility with the Android API, this method is a direct copy of
- // the one in JSONObject. Changes made here should be reflected there.
- // This method should not make calls out of the XML object.
- public static Object stringToValue(String string) {
- if ("".equals(string)) {
- return string;
- }
-
- // check JSON key words true/false/null
- if ("true".equalsIgnoreCase(string)) {
- return Boolean.TRUE;
- }
- if ("false".equalsIgnoreCase(string)) {
- return Boolean.FALSE;
- }
- if ("null".equalsIgnoreCase(string)) {
- return JSONObject.NULL;
- }
-
- /*
- * If it might be a number, try converting it. If a number cannot be
- * produced, then the value will just be a string.
- */
-
- char initial = string.charAt(0);
- if ((initial >= '0' && initial <= '9') || initial == '-') {
- try {
- return stringToNumber(string);
- } catch (Exception ignore) {
+ private static boolean isStringAllWhiteSpace(final String s) {
+ for (int k = 0; k -1 || "-0".equals(val);
}
+ /**
+ * This method tries to convert the given string value to the target object
+ * @param string String to convert
+ * @param typeConverter value converter to convert string to integer, boolean e.t.c
+ * @return JSON value of this string or the string
+ */
+ public static Object stringToValue(String string, XMLXsiTypeConverter> typeConverter) {
+ if(typeConverter != null) {
+ return typeConverter.convert(string);
+ }
+ return stringToValue(string);
+ }
+
+ /**
+ * This method is the same as {@link JSONObject#stringToValue(String)}.
+ *
+ * @param string String to convert
+ * @return JSON value of this string or the string
+ */
+ // To maintain compatibility with the Android API, this method is a direct copy of
+ // the one in JSONObject. Changes made here should be reflected there.
+ // This method should not make calls out of the XML object.
+ public static Object stringToValue(String string) {
+ if ("".equals(string)) {
+ return string;
+ }
+
+ // check JSON key words true/false/null
+ if ("true".equalsIgnoreCase(string)) {
+ return Boolean.TRUE;
+ }
+ if ("false".equalsIgnoreCase(string)) {
+ return Boolean.FALSE;
+ }
+ if ("null".equalsIgnoreCase(string)) {
+ return JSONObject.NULL;
+ }
+
+ /*
+ * If it might be a number, try converting it. If a number cannot be
+ * produced, then the value will just be a string.
+ */
+
+ char initial = string.charAt(0);
+ if ((initial >= '0' && initial <= '9') || initial == '-') {
+ try {
+ return stringToNumber(string);
+ } catch (Exception ignore) {
+ }
+ }
+ return string;
+ }
/**
* Convert a well-formed (but not necessarily valid) XML string into a
@@ -565,7 +656,7 @@ private static boolean isDecimalNotation(final String val) {
* name/value pairs and arrays of values. JSON does not does not like to
* distinguish between elements and attributes. Sequences of similar
* elements are represented as JSONArrays. Content text may be placed in a
- * "content" member. Comments, prologs, DTDs, and {@code
+ * "content" member. Comments, prologs, DTDs, and {@code
* <[ [ ]]>}
* are ignored.
*
@@ -586,7 +677,7 @@ public static JSONObject toJSONObject(String string) throws JSONException {
* name/value pairs and arrays of values. JSON does not does not like to
* distinguish between elements and attributes. Sequences of similar
* elements are represented as JSONArrays. Content text may be placed in a
- * "content" member. Comments, prologs, DTDs, and {@code
+ * "content" member. Comments, prologs, DTDs, and {@code
* <[ [ ]]>}
* are ignored.
*
@@ -626,6 +717,44 @@ public static JSONObject toJSONObject(Reader reader, boolean keepStrings) throws
return toJSONObject(reader, XMLParserConfiguration.ORIGINAL);
}
+ /**
+ * Convert a well-formed (but not necessarily valid) XML into a
+ * JSONObject. Some information may be lost in this transformation because
+ * JSON is a data format and XML is a document format. XML uses elements,
+ * attributes, and content text, while JSON uses unordered collections of
+ * name/value pairs and arrays of values. JSON does not does not like to
+ * distinguish between elements and attributes. Sequences of similar
+ * elements are represented as JSONArrays. Content text may be placed in a
+ * "content" member. Comments, prologs, DTDs, and {@code
+ * <[ [ ]]>}
+ * are ignored.
+ *
+ * All numbers are converted as strings, for 1, 01, 29.0 will not be coerced to
+ * numbers but will instead be the exact value as seen in the XML document depending
+ * on how flag is set.
+ * All booleans are converted as strings, for true, false will not be coerced to
+ * booleans but will instead be the exact value as seen in the XML document depending
+ * on how flag is set.
+ *
+ * @param reader The XML source reader.
+ * @param keepNumberAsString If true, then numeric values will not be coerced into
+ * numeric values and will instead be left as strings
+ * @param keepBooleanAsString If true, then boolean values will not be coerced into
+ * * numeric values and will instead be left as strings
+ * @return A JSONObject containing the structured data from the XML string.
+ * @throws JSONException Thrown if there is an errors while parsing the string
+ */
+ public static JSONObject toJSONObject(Reader reader, boolean keepNumberAsString, boolean keepBooleanAsString) throws JSONException {
+ XMLParserConfiguration xmlParserConfiguration = new XMLParserConfiguration();
+ if(keepNumberAsString) {
+ xmlParserConfiguration = xmlParserConfiguration.withKeepNumberAsString(keepNumberAsString);
+ }
+ if(keepBooleanAsString) {
+ xmlParserConfiguration = xmlParserConfiguration.withKeepBooleanAsString(keepBooleanAsString);
+ }
+ return toJSONObject(reader, xmlParserConfiguration);
+ }
+
/**
* Convert a well-formed (but not necessarily valid) XML into a
* JSONObject. Some information may be lost in this transformation because
@@ -648,11 +777,11 @@ public static JSONObject toJSONObject(Reader reader, boolean keepStrings) throws
*/
public static JSONObject toJSONObject(Reader reader, XMLParserConfiguration config) throws JSONException {
JSONObject jo = new JSONObject();
- XMLTokener x = new XMLTokener(reader);
+ XMLTokener x = new XMLTokener(reader, config);
while (x.more()) {
x.skipPast("<");
if(x.more()) {
- parse(x, jo, null, config);
+ parse(x, jo, null, config, 0);
}
}
return jo;
@@ -666,7 +795,7 @@ public static JSONObject toJSONObject(Reader reader, XMLParserConfiguration conf
* name/value pairs and arrays of values. JSON does not does not like to
* distinguish between elements and attributes. Sequences of similar
* elements are represented as JSONArrays. Content text may be placed in a
- * "content" member. Comments, prologs, DTDs, and {@code
+ * "content" member. Comments, prologs, DTDs, and {@code
* <[ [ ]]>}
* are ignored.
*
@@ -692,7 +821,39 @@ public static JSONObject toJSONObject(String string, boolean keepStrings) throws
* name/value pairs and arrays of values. JSON does not does not like to
* distinguish between elements and attributes. Sequences of similar
* elements are represented as JSONArrays. Content text may be placed in a
- * "content" member. Comments, prologs, DTDs, and {@code
+ * "content" member. Comments, prologs, DTDs, and {@code
+ * <[ [ ]]>}
+ * are ignored.
+ *
+ * All numbers are converted as strings, for 1, 01, 29.0 will not be coerced to
+ * numbers but will instead be the exact value as seen in the XML document depending
+ * on how flag is set.
+ * All booleans are converted as strings, for true, false will not be coerced to
+ * booleans but will instead be the exact value as seen in the XML document depending
+ * on how flag is set.
+ *
+ * @param string
+ * The source string.
+ * @param keepNumberAsString If true, then numeric values will not be coerced into
+ * numeric values and will instead be left as strings
+ * @param keepBooleanAsString If true, then boolean values will not be coerced into
+ * numeric values and will instead be left as strings
+ * @return A JSONObject containing the structured data from the XML string.
+ * @throws JSONException Thrown if there is an errors while parsing the string
+ */
+ public static JSONObject toJSONObject(String string, boolean keepNumberAsString, boolean keepBooleanAsString) throws JSONException {
+ return toJSONObject(new StringReader(string), keepNumberAsString, keepBooleanAsString);
+ }
+
+ /**
+ * Convert a well-formed (but not necessarily valid) XML string into a
+ * JSONObject. Some information may be lost in this transformation because
+ * JSON is a data format and XML is a document format. XML uses elements,
+ * attributes, and content text, while JSON uses unordered collections of
+ * name/value pairs and arrays of values. JSON does not does not like to
+ * distinguish between elements and attributes. Sequences of similar
+ * elements are represented as JSONArrays. Content text may be placed in a
+ * "content" member. Comments, prologs, DTDs, and {@code
* <[ [ ]]>}
* are ignored.
*
@@ -749,6 +910,28 @@ public static String toString(final Object object, final String tagName) {
*/
public static String toString(final Object object, final String tagName, final XMLParserConfiguration config)
throws JSONException {
+ return toString(object, tagName, config, 0, 0);
+ }
+
+ /**
+ * Convert a JSONObject into a well-formed, element-normal XML string,
+ * either pretty print or single-lined depending on indent factor.
+ *
+ * @param object
+ * A JSONObject.
+ * @param tagName
+ * The optional name of the enclosing tag.
+ * @param config
+ * Configuration that can control output to XML.
+ * @param indentFactor
+ * The number of spaces to add to each level of indentation.
+ * @param indent
+ * The current ident level in spaces.
+ * @return
+ * @throws JSONException
+ */
+ private static String toString(final Object object, final String tagName, final XMLParserConfiguration config, int indentFactor, int indent)
+ throws JSONException {
StringBuilder sb = new StringBuilder();
JSONArray ja;
JSONObject jo;
@@ -758,9 +941,14 @@ public static String toString(final Object object, final String tagName, final X
// Emit
if (tagName != null) {
+ sb.append(indent(indent));
sb.append('<');
sb.append(tagName);
sb.append('>');
+ if(indentFactor > 0){
+ sb.append("\n");
+ indent += indentFactor;
+ }
}
// Loop thru the keys.
@@ -803,31 +991,52 @@ public static String toString(final Object object, final String tagName, final X
sb.append('<');
sb.append(key);
sb.append('>');
- sb.append(toString(val, null, config));
+ sb.append(toString(val, null, config, indentFactor, indent));
sb.append("");
sb.append(key);
sb.append('>');
} else {
- sb.append(toString(val, key, config));
+ sb.append(toString(val, key, config, indentFactor, indent));
}
}
} else if ("".equals(value)) {
- sb.append('<');
- sb.append(key);
- sb.append("/>");
+ if (config.isCloseEmptyTag()){
+ sb.append(indent(indent));
+ sb.append('<');
+ sb.append(key);
+ sb.append(">");
+ sb.append("");
+ sb.append(key);
+ sb.append(">");
+ if (indentFactor > 0) {
+ sb.append("\n");
+ }
+ }else {
+ sb.append(indent(indent));
+ sb.append('<');
+ sb.append(key);
+ sb.append("/>");
+ if (indentFactor > 0) {
+ sb.append("\n");
+ }
+ }
// Emit a new tag
} else {
- sb.append(toString(value, key, config));
+ sb.append(toString(value, key, config, indentFactor, indent));
}
}
if (tagName != null) {
// Emit the close tag
+ sb.append(indent(indent - indentFactor));
sb.append("");
sb.append(tagName);
sb.append('>');
+ if(indentFactor > 0){
+ sb.append("\n");
+ }
}
return sb.toString();
@@ -846,15 +1055,85 @@ public static String toString(final Object object, final String tagName, final X
// XML does not have good support for arrays. If an array
// appears in a place where XML is lacking, synthesize an
// element.
- sb.append(toString(val, tagName == null ? "array" : tagName, config));
+ sb.append(toString(val, tagName == null ? "array" : tagName, config, indentFactor, indent));
}
return sb.toString();
}
+
string = (object == null) ? "null" : escape(object.toString());
- return (tagName == null) ? "\"" + string + "\""
- : (string.length() == 0) ? "<" + tagName + "/>" : "<" + tagName
- + ">" + string + "" + tagName + ">";
+ String indentationSuffix = (indentFactor > 0) ? "\n" : "";
+ if(tagName == null){
+ return indent(indent) + "\"" + string + "\"" + indentationSuffix;
+ } else if(string.length() == 0){
+ return indent(indent) + "<" + tagName + "/>" + indentationSuffix;
+ } else {
+ return indent(indent) + "<" + tagName
+ + ">" + string + "" + tagName + ">" + indentationSuffix;
+ }
+ }
+
+ /**
+ * Convert a JSONObject into a well-formed, pretty printed element-normal XML string.
+ *
+ * @param object
+ * A JSONObject.
+ * @param indentFactor
+ * The number of spaces to add to each level of indentation.
+ * @return A string.
+ * @throws JSONException Thrown if there is an error parsing the string
+ */
+ public static String toString(Object object, int indentFactor){
+ return toString(object, null, XMLParserConfiguration.ORIGINAL, indentFactor);
+ }
+
+ /**
+ * Convert a JSONObject into a well-formed, pretty printed element-normal XML string.
+ *
+ * @param object
+ * A JSONObject.
+ * @param tagName
+ * The optional name of the enclosing tag.
+ * @param indentFactor
+ * The number of spaces to add to each level of indentation.
+ * @return A string.
+ * @throws JSONException Thrown if there is an error parsing the string
+ */
+ public static String toString(final Object object, final String tagName, int indentFactor) {
+ return toString(object, tagName, XMLParserConfiguration.ORIGINAL, indentFactor);
+ }
+ /**
+ * Convert a JSONObject into a well-formed, pretty printed element-normal XML string.
+ *
+ * @param object
+ * A JSONObject.
+ * @param tagName
+ * The optional name of the enclosing tag.
+ * @param config
+ * Configuration that can control output to XML.
+ * @param indentFactor
+ * The number of spaces to add to each level of indentation.
+ * @return A string.
+ * @throws JSONException Thrown if there is an error parsing the string
+ */
+ public static String toString(final Object object, final String tagName, final XMLParserConfiguration config, int indentFactor)
+ throws JSONException {
+ return toString(object, tagName, config, indentFactor, 0);
+ }
+
+ /**
+ * Return a String consisting of a number of space characters specified by indent
+ *
+ * @param indent
+ * The number of spaces to be appended to the String.
+ * @return
+ */
+ private static final String indent(int indent) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < indent; i++) {
+ sb.append(' ');
+ }
+ return sb.toString();
}
}
diff --git a/src/main/java/org/json/XMLParserConfiguration.java b/src/main/java/org/json/XMLParserConfiguration.java
index b9e752c28..de84b90cb 100644
--- a/src/main/java/org/json/XMLParserConfiguration.java
+++ b/src/main/java/org/json/XMLParserConfiguration.java
@@ -1,31 +1,13 @@
package org.json;
/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
/**
@@ -33,7 +15,23 @@ of this software and associated documentation files (the "Software"), to deal
* @author AylwardJ
*/
@SuppressWarnings({""})
-public class XMLParserConfiguration {
+public class XMLParserConfiguration extends ParserConfiguration {
+
+ /**
+ * The default maximum nesting depth when parsing a XML document to JSON.
+ */
+// public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512; // We could override
+
+ /**
+ * Allow user to control how numbers are parsed
+ */
+ private boolean keepNumberAsString;
+
+ /**
+ * Allow user to control how booleans are parsed
+ */
+ private boolean keepBooleanAsString;
+
/** Original Configuration of the XML Parser. */
public static final XMLParserConfiguration ORIGINAL
= new XMLParserConfiguration();
@@ -41,19 +39,13 @@ public class XMLParserConfiguration {
public static final XMLParserConfiguration KEEP_STRINGS
= new XMLParserConfiguration().withKeepStrings(true);
- /**
- * When parsing the XML into JSON, specifies if values should be kept as strings (true), or if
- * they should try to be guessed into JSON values (numeric, boolean, string)
- */
- private boolean keepStrings;
-
/**
* The name of the key in a JSON Object that indicates a CDATA section. Historically this has
* been the value "content" but can be changed. Use null to indicate no CDATA
* processing.
*/
private String cDataTagName;
-
+
/**
* When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
* should be kept as attribute(false), or they should be converted to
@@ -61,20 +53,44 @@ public class XMLParserConfiguration {
*/
private boolean convertNilAttributeToNull;
+ /**
+ * When creating an XML from JSON Object, an empty tag by default will self-close.
+ * If it has to be closed explicitly, with empty content between start and end tag,
+ * this flag is to be turned on.
+ */
+ private boolean closeEmptyTag;
+
/**
* This will allow type conversion for values in XML if xsi:type attribute is defined
*/
private Map> xsiTypeMap;
+ /**
+ * When parsing the XML into JSON, specifies the tags whose values should be converted
+ * to arrays
+ */
+ private Set forceList;
+
+
+ /**
+ * Flag to indicate whether white space should be trimmed when parsing XML.
+ * The default behaviour is to trim white space. When this is set to false, inputting XML
+ * with tags that are the same as the value of cDataTagName is unsupported. It is recommended to set cDataTagName
+ * to a distinct value in this case.
+ */
+ private boolean shouldTrimWhiteSpace;
+
/**
* Default parser configuration. Does not keep strings (tries to implicitly convert
- * values), and the CDATA Tag Name is "content".
+ * values), and the CDATA Tag Name is "content". Trims whitespace.
*/
public XMLParserConfiguration () {
- this.keepStrings = false;
+ super();
this.cDataTagName = "content";
this.convertNilAttributeToNull = false;
this.xsiTypeMap = Collections.emptyMap();
+ this.forceList = Collections.emptySet();
+ this.shouldTrimWhiteSpace = true;
}
/**
@@ -94,7 +110,7 @@ public XMLParserConfiguration (final boolean keepStrings) {
* Configure the parser string processing to try and convert XML values to JSON values and
* use the passed CDATA Tag Name the processing value. Pass null to
* disable CDATA processing
- * @param cDataTagNamenull to disable CDATA processing. Any other value
+ * @param cDataTagName null to disable CDATA processing. Any other value
* to use that value as the JSONObject key name to process as CDATA.
* @deprecated This constructor has been deprecated in favor of using the new builder
* pattern for the configuration.
@@ -109,7 +125,7 @@ public XMLParserConfiguration (final String cDataTagName) {
* Configure the parser to use custom settings.
* @param keepStrings true to parse all values as string.
* false to try and convert XML string values into a JSON value.
- * @param cDataTagNamenull to disable CDATA processing. Any other value
+ * @param cDataTagName null to disable CDATA processing. Any other value
* to use that value as the JSONObject key name to process as CDATA.
* @deprecated This constructor has been deprecated in favor of using the new builder
* pattern for the configuration.
@@ -117,7 +133,7 @@ public XMLParserConfiguration (final String cDataTagName) {
*/
@Deprecated
public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName) {
- this.keepStrings = keepStrings;
+ super(keepStrings, DEFAULT_MAXIMUM_NESTING_DEPTH);
this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = false;
}
@@ -136,7 +152,9 @@ public XMLParserConfiguration (final boolean keepStrings, final String cDataTagN
*/
@Deprecated
public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, final boolean convertNilAttributeToNull) {
- this.keepStrings = keepStrings;
+ super(false, DEFAULT_MAXIMUM_NESTING_DEPTH);
+ this.keepNumberAsString = keepStrings;
+ this.keepBooleanAsString = keepStrings;
this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = convertNilAttributeToNull;
}
@@ -151,13 +169,21 @@ public XMLParserConfiguration (final boolean keepStrings, final String cDataTagN
* false to parse values with attribute xsi:nil="true" as {"xsi:nil":true}.
* @param xsiTypeMap new HashMap>() to parse values with attribute
* xsi:type="integer" as integer, xsi:type="string" as string
+ * @param forceList new HashSet() to parse the provided tags' values as arrays
+ * @param maxNestingDepth int to limit the nesting depth
+ * @param closeEmptyTag boolean to turn on explicit end tag for tag with empty value
*/
private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName,
- final boolean convertNilAttributeToNull, final Map> xsiTypeMap ) {
- this.keepStrings = keepStrings;
+ final boolean convertNilAttributeToNull, final Map> xsiTypeMap, final Set forceList,
+ final int maxNestingDepth, final boolean closeEmptyTag, final boolean keepNumberAsString, final boolean keepBooleanAsString) {
+ super(false, maxNestingDepth);
+ this.keepNumberAsString = keepNumberAsString;
+ this.keepBooleanAsString = keepBooleanAsString;
this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = convertNilAttributeToNull;
this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap);
+ this.forceList = Collections.unmodifiableSet(forceList);
+ this.closeEmptyTag = closeEmptyTag;
}
/**
@@ -170,36 +196,69 @@ protected XMLParserConfiguration clone() {
// item, a new map instance should be created and if possible each value in the
// map should be cloned as well. If the values of the map are known to also
// be immutable, then a shallow clone of the map is acceptable.
- return new XMLParserConfiguration(
+ final XMLParserConfiguration config = new XMLParserConfiguration(
this.keepStrings,
this.cDataTagName,
this.convertNilAttributeToNull,
- this.xsiTypeMap
+ this.xsiTypeMap,
+ this.forceList,
+ this.maxNestingDepth,
+ this.closeEmptyTag,
+ this.keepNumberAsString,
+ this.keepBooleanAsString
);
+ config.shouldTrimWhiteSpace = this.shouldTrimWhiteSpace;
+ return config;
}
-
+
/**
* When parsing the XML into JSON, specifies if values should be kept as strings (true), or if
* they should try to be guessed into JSON values (numeric, boolean, string)
- *
- * @return The {@link #keepStrings} configuration value.
+ *
+ * @param newVal
+ * new value to use for the keepStrings configuration option.
+ *
+ * @return The existing configuration will not be modified. A new configuration is returned.
*/
- public boolean isKeepStrings() {
- return this.keepStrings;
+ @SuppressWarnings("unchecked")
+ @Override
+ public XMLParserConfiguration withKeepStrings(final boolean newVal) {
+ XMLParserConfiguration newConfig = this.clone();
+ newConfig.keepStrings = newVal;
+ newConfig.keepNumberAsString = newVal;
+ newConfig.keepBooleanAsString = newVal;
+ return newConfig;
}
/**
- * When parsing the XML into JSON, specifies if values should be kept as strings (true), or if
+ * When parsing the XML into JSON, specifies if numbers should be kept as strings (1), or if
* they should try to be guessed into JSON values (numeric, boolean, string)
- *
+ *
* @param newVal
- * new value to use for the {@link #keepStrings} configuration option.
- *
+ * new value to use for the keepNumberAsString configuration option.
+ *
* @return The existing configuration will not be modified. A new configuration is returned.
*/
- public XMLParserConfiguration withKeepStrings(final boolean newVal) {
+ public XMLParserConfiguration withKeepNumberAsString(final boolean newVal) {
XMLParserConfiguration newConfig = this.clone();
- newConfig.keepStrings = newVal;
+ newConfig.keepNumberAsString = newVal;
+ newConfig.keepStrings = newConfig.keepBooleanAsString && newConfig.keepNumberAsString;
+ return newConfig;
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies if booleans should be kept as strings (true), or if
+ * they should try to be guessed into JSON values (numeric, boolean, string)
+ *
+ * @param newVal
+ * new value to use for the withKeepBooleanAsString configuration option.
+ *
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public XMLParserConfiguration withKeepBooleanAsString(final boolean newVal) {
+ XMLParserConfiguration newConfig = this.clone();
+ newConfig.keepBooleanAsString = newVal;
+ newConfig.keepStrings = newConfig.keepBooleanAsString && newConfig.keepNumberAsString;
return newConfig;
}
@@ -207,21 +266,41 @@ public XMLParserConfiguration withKeepStrings(final boolean newVal) {
* The name of the key in a JSON Object that indicates a CDATA section. Historically this has
* been the value "content" but can be changed. Use null to indicate no CDATA
* processing.
- *
- * @return The {@link #cDataTagName} configuration value.
+ *
+ * @return The cDataTagName configuration value.
*/
public String getcDataTagName() {
return this.cDataTagName;
}
+ /**
+ * When parsing the XML into JSONML, specifies if numbers should be kept as strings (true), or if
+ * they should try to be guessed into JSON values (numeric, boolean, string).
+ *
+ * @return The keepStrings configuration value.
+ */
+ public boolean isKeepNumberAsString() {
+ return this.keepNumberAsString;
+ }
+
+ /**
+ * When parsing the XML into JSONML, specifies if booleans should be kept as strings (true), or if
+ * they should try to be guessed into JSON values (numeric, boolean, string).
+ *
+ * @return The keepStrings configuration value.
+ */
+ public boolean isKeepBooleanAsString() {
+ return this.keepBooleanAsString;
+ }
+
/**
* The name of the key in a JSON Object that indicates a CDATA section. Historically this has
* been the value "content" but can be changed. Use null to indicate no CDATA
* processing.
- *
+ *
* @param newVal
- * new value to use for the {@link #cDataTagName} configuration option.
- *
+ * new value to use for the cDataTagName configuration option.
+ *
* @return The existing configuration will not be modified. A new configuration is returned.
*/
public XMLParserConfiguration withcDataTagName(final String newVal) {
@@ -234,8 +313,8 @@ public XMLParserConfiguration withcDataTagName(final String newVal) {
* When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
* should be kept as attribute(false), or they should be converted to
* null(true)
- *
- * @return The {@link #convertNilAttributeToNull} configuration value.
+ *
+ * @return The convertNilAttributeToNull configuration value.
*/
public boolean isConvertNilAttributeToNull() {
return this.convertNilAttributeToNull;
@@ -245,10 +324,10 @@ public boolean isConvertNilAttributeToNull() {
* When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
* should be kept as attribute(false), or they should be converted to
* null(true)
- *
+ *
* @param newVal
- * new value to use for the {@link #convertNilAttributeToNull} configuration option.
- *
+ * new value to use for the convertNilAttributeToNull configuration option.
+ *
* @return The existing configuration will not be modified. A new configuration is returned.
*/
public XMLParserConfiguration withConvertNilAttributeToNull(final boolean newVal) {
@@ -262,7 +341,7 @@ public XMLParserConfiguration withConvertNilAttributeToNull(final boolean newVal
* will be converted to target type defined to client in this configuration
* {@code Map>} to parse values with attribute
* xsi:type="integer" as integer, xsi:type="string" as string
- * @return {@link #xsiTypeMap} unmodifiable configuration map.
+ * @return xsiTypeMap unmodifiable configuration map.
*/
public Map> getXsiTypeMap() {
return this.xsiTypeMap;
@@ -283,4 +362,83 @@ public XMLParserConfiguration withXsiTypeMap(final Map} to parse the provided tags' values as arrays
+ * @return forceList unmodifiable configuration set.
+ */
+ public Set getForceList() {
+ return this.forceList;
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies that tags that will be converted to arrays
+ * in this configuration {@code Set} to parse the provided tags' values as arrays
+ * @param forceList {@code new HashSet()} to parse the provided tags' values as arrays
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public XMLParserConfiguration withForceList(final Set forceList) {
+ XMLParserConfiguration newConfig = this.clone();
+ Set cloneForceList = new HashSet(forceList);
+ newConfig.forceList = Collections.unmodifiableSet(cloneForceList);
+ return newConfig;
+ }
+
+ /**
+ * Defines the maximum nesting depth that the parser will descend before throwing an exception
+ * when parsing the XML into JSON. The default max nesting depth is 512, which means the parser
+ * will throw a JsonException if the maximum depth is reached.
+ * Using any negative value as a parameter is equivalent to setting no limit to the nesting depth,
+ * which means the parses will go as deep as the maximum call stack size allows.
+ * @param maxNestingDepth the maximum nesting depth allowed to the XML parser
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public XMLParserConfiguration withMaxNestingDepth(int maxNestingDepth) {
+ return super.withMaxNestingDepth(maxNestingDepth);
+ }
+
+ /**
+ * To enable explicit end tag with empty value.
+ * @param closeEmptyTag new value for the closeEmptyTag property
+ * @return same instance of configuration with empty tag config updated
+ */
+ public XMLParserConfiguration withCloseEmptyTag(boolean closeEmptyTag){
+ XMLParserConfiguration clonedConfiguration = this.clone();
+ clonedConfiguration.closeEmptyTag = closeEmptyTag;
+ return clonedConfiguration;
+ }
+
+ /**
+ * Sets whether whitespace should be trimmed inside of tags. *NOTE* Do not use this if
+ * you expect your XML tags to have names that are the same as cDataTagName as this is unsupported.
+ * cDataTagName should be set to a distinct value in these cases.
+ * @param shouldTrimWhiteSpace boolean to set trimming on or off. Off is default.
+ * @return same instance of configuration with empty tag config updated
+ */
+ public XMLParserConfiguration withShouldTrimWhitespace(boolean shouldTrimWhiteSpace){
+ XMLParserConfiguration clonedConfiguration = this.clone();
+ clonedConfiguration.shouldTrimWhiteSpace = shouldTrimWhiteSpace;
+ return clonedConfiguration;
+ }
+
+ /**
+ * Checks if the parser should automatically close empty XML tags.
+ *
+ * @return {@code true} if empty XML tags should be automatically closed, {@code false} otherwise.
+ */
+ public boolean isCloseEmptyTag() {
+ return this.closeEmptyTag;
+ }
+
+ /**
+ * Checks if the parser should trim white spaces from XML content.
+ *
+ * @return {@code true} if white spaces should be trimmed, {@code false} otherwise.
+ */
+ public boolean shouldTrimWhiteSpace() {
+ return this.shouldTrimWhiteSpace;
+ }
}
diff --git a/src/main/java/org/json/XMLTokener.java b/src/main/java/org/json/XMLTokener.java
index 3bbd3824b..bc18b31c9 100644
--- a/src/main/java/org/json/XMLTokener.java
+++ b/src/main/java/org/json/XMLTokener.java
@@ -1,27 +1,7 @@
package org.json;
/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import java.io.Reader;
@@ -40,6 +20,8 @@ public class XMLTokener extends JSONTokener {
*/
public static final java.util.HashMap entity;
+ private XMLParserConfiguration configuration = XMLParserConfiguration.ORIGINAL;
+
static {
entity = new java.util.HashMap(8);
entity.put("amp", XML.AMP);
@@ -65,6 +47,16 @@ public XMLTokener(String s) {
super(s);
}
+ /**
+ * Construct an XMLTokener from a Reader and an XMLParserConfiguration.
+ * @param r A source reader.
+ * @param configuration the configuration that can be used to set certain flags
+ */
+ public XMLTokener(Reader r, XMLParserConfiguration configuration) {
+ super(r);
+ this.configuration = configuration;
+ }
+
/**
* Get the text in the CDATA block.
* @return The string up to the ]]>.
@@ -103,7 +95,7 @@ public Object nextContent() throws JSONException {
StringBuilder sb;
do {
c = next();
- } while (Character.isWhitespace(c));
+ } while (Character.isWhitespace(c) && configuration.shouldTrimWhiteSpace());
if (c == 0) {
return null;
}
@@ -117,7 +109,9 @@ public Object nextContent() throws JSONException {
}
if (c == '<') {
back();
- return sb.toString().trim();
+ if (configuration.shouldTrimWhiteSpace()) {
+ return sb.toString().trim();
+ } else return sb.toString();
}
if (c == '&') {
sb.append(nextEntity(c));
diff --git a/src/main/java/org/json/XMLXsiTypeConverter.java b/src/main/java/org/json/XMLXsiTypeConverter.java
index 0f8a8c332..ea6739d34 100644
--- a/src/main/java/org/json/XMLXsiTypeConverter.java
+++ b/src/main/java/org/json/XMLXsiTypeConverter.java
@@ -1,26 +1,6 @@
package org.json;
/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
/**
@@ -62,5 +42,12 @@ of this software and associated documentation files (the "Software"), to deal
* @param return type of convert method
*/
public interface XMLXsiTypeConverter {
+
+ /**
+ * Converts an XML xsi:type attribute value to the specified type {@code T}.
+ *
+ * @param value The string representation of the XML xsi:type attribute value to be converted.
+ * @return An object of type {@code T} representing the converted value.
+ */
T convert(String value);
}
diff --git a/src/test/java/org/json/junit/CDLTest.java b/src/test/java/org/json/junit/CDLTest.java
index 48586b741..e5eb9eda8 100644
--- a/src/test/java/org/json/junit/CDLTest.java
+++ b/src/test/java/org/json/junit/CDLTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
@@ -44,14 +24,13 @@ public class CDLTest {
* String of lines where the column names are in the first row,
* and all subsequent rows are values. All keys and values should be legal.
*/
- String lines = new String(
- "Col 1, Col 2, \tCol 3, Col 4, Col 5, Col 6, Col 7\n" +
- "val1, val2, val3, val4, val5, val6, val7\n" +
- "1, 2, 3, 4\t, 5, 6, 7\n" +
- "true, false, true, true, false, false, false\n" +
- "0.23, 57.42, 5e27, -234.879, 2.34e5, 0.0, 9e-3\n" +
- "\"va\tl1\", \"v\bal2\", \"val3\", \"val\f4\", \"val5\", va\'l6, val7\n"
- );
+ private static final String LINES = "Col 1, Col 2, \tCol 3, Col 4, Col 5, Col 6, Col 7\n" +
+ "val1, val2, val3, val4, val5, val6, val7\n" +
+ "1, 2, 3, 4\t, 5, 6, 7\n" +
+ "true, false, true, true, false, false, false\n" +
+ "0.23, 57.42, 5e27, -234.879, 2.34e5, 0.0, 9e-3\n" +
+ "\"va\tl1\", \"v\bal2\", \"val3\", \"val\f4\", \"val5\", \"va'l6\", val7\n";
+
/**
* CDL.toJSONArray() adds all values as strings, with no filtering or
@@ -59,12 +38,54 @@ public class CDLTest {
* values all must be quoted in the cases where the JSONObject parsing
* might normally convert the value into a non-string.
*/
- String expectedLines = new String(
- "[{Col 1:val1, Col 2:val2, Col 3:val3, Col 4:val4, Col 5:val5, Col 6:val6, Col 7:val7}, "+
- "{Col 1:\"1\", Col 2:\"2\", Col 3:\"3\", Col 4:\"4\", Col 5:\"5\", Col 6:\"6\", Col 7:\"7\"}, "+
- "{Col 1:\"true\", Col 2:\"false\", Col 3:\"true\", Col 4:\"true\", Col 5:\"false\", Col 6:\"false\", Col 7:\"false\"}, "+
- "{Col 1:\"0.23\", Col 2:\"57.42\", Col 3:\"5e27\", Col 4:\"-234.879\", Col 5:\"2.34e5\", Col 6:\"0.0\", Col 7:\"9e-3\"}, "+
- "{Col 1:\"va\tl1\", Col 2:\"v\bal2\", Col 3:val3, Col 4:\"val\f4\", Col 5:val5, Col 6:va\'l6, Col 7:val7}]");
+ private static final String EXPECTED_LINES =
+ "[ " +
+ "{" +
+ "\"Col 1\":\"val1\", " +
+ "\"Col 2\":\"val2\", " +
+ "\"Col 3\":\"val3\", " +
+ "\"Col 4\":\"val4\", " +
+ "\"Col 5\":\"val5\", " +
+ "\"Col 6\":\"val6\", " +
+ "\"Col 7\":\"val7\"" +
+ "}, " +
+ " {" +
+ "\"Col 1\":\"1\", " +
+ "\"Col 2\":\"2\", " +
+ "\"Col 3\":\"3\", " +
+ "\"Col 4\":\"4\", " +
+ "\"Col 5\":\"5\", " +
+ "\"Col 6\":\"6\", " +
+ "\"Col 7\":\"7\"" +
+ "}, " +
+ " {" +
+ "\"Col 1\":\"true\", " +
+ "\"Col 2\":\"false\", " +
+ "\"Col 3\":\"true\", " +
+ "\"Col 4\":\"true\", " +
+ "\"Col 5\":\"false\", " +
+ "\"Col 6\":\"false\", " +
+ "\"Col 7\":\"false\"" +
+ "}, " +
+ "{" +
+ "\"Col 1\":\"0.23\", " +
+ "\"Col 2\":\"57.42\", " +
+ "\"Col 3\":\"5e27\", " +
+ "\"Col 4\":\"-234.879\", " +
+ "\"Col 5\":\"2.34e5\", " +
+ "\"Col 6\":\"0.0\", " +
+ "\"Col 7\":\"9e-3\"" +
+ "}, " +
+ "{" +
+ "\"Col 1\":\"va\tl1\", " +
+ "\"Col 2\":\"v\bal2\", " +
+ "\"Col 3\":\"val3\", " +
+ "\"Col 4\":\"val\f4\", " +
+ "\"Col 5\":\"val5\", " +
+ "\"Col 6\":\"va'l6\", " +
+ "\"Col 7\":\"val7\"" +
+ "}" +
+ "]";
/**
* Attempts to create a JSONArray from a null string.
@@ -147,6 +168,33 @@ public void unbalancedEscapedQuote(){
}
}
+ /**
+ * Csv parsing skip last row if last field of this row is empty #943
+ */
+ @Test
+ public void csvParsingCatchesLastRow(){
+ String data = "Field 1,Field 2,Field 3\n" +
+ "value11,value12,\n" +
+ "value21,value22,";
+
+ JSONArray jsonArray = CDL.toJSONArray(data);
+
+ JSONArray expectedJsonArray = new JSONArray();
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("Field 1", "value11");
+ jsonObject.put("Field 2", "value12");
+ jsonObject.put("Field 3", "");
+ expectedJsonArray.put(jsonObject);
+
+ jsonObject = new JSONObject();
+ jsonObject.put("Field 1", "value21");
+ jsonObject.put("Field 2", "value22");
+ jsonObject.put("Field 3", "");
+ expectedJsonArray.put(jsonObject);
+
+ Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
+ }
+
/**
* Assert that there is no error for a single escaped quote within a properly embedded quote.
*/
@@ -190,7 +238,7 @@ public void badEscapedQuote(){
CDL.toJSONArray(badLine);
fail("Expecting an exception");
} catch (JSONException e) {
- System.out.println("Message" + e.getMessage());
+ //System.out.println("Message" + e.getMessage());
assertEquals("Expecting an exception message",
"Bad character 'V' (86). at 20 [character 9 line 2]",
e.getMessage());
@@ -214,8 +262,7 @@ public void nullJSONArrayToString() {
public void emptyString() {
String emptyStr = "";
JSONArray jsonArray = CDL.toJSONArray(emptyStr);
- assertTrue("CDL should return null when the input string is empty",
- jsonArray == null);
+ assertNull("CDL should return null when the input string is empty", jsonArray);
}
/**
@@ -274,7 +321,7 @@ public void checkSpecialChars() {
jsonObject.put("Col \r1", "V1");
// \r will be filtered from value
jsonObject.put("Col 2", "V2\r");
- assertTrue("expected length should be 1",jsonArray.length() == 1);
+ assertEquals("expected length should be 1", 1, jsonArray.length());
String cdlStr = CDL.toString(jsonArray);
jsonObject = jsonArray.getJSONObject(0);
assertTrue(cdlStr.contains("\"Col 1\""));
@@ -288,8 +335,15 @@ public void checkSpecialChars() {
*/
@Test
public void textToJSONArray() {
- JSONArray jsonArray = CDL.toJSONArray(this.lines);
- JSONArray expectedJsonArray = new JSONArray(this.expectedLines);
+ JSONArray jsonArray = CDL.toJSONArray(LINES);
+ JSONArray expectedJsonArray = new JSONArray(EXPECTED_LINES);
+ Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
+ }
+ @Test
+ public void textToJSONArrayPipeDelimited() {
+ char delimiter = '|';
+ JSONArray jsonArray = CDL.toJSONArray(LINES.replaceAll(",", String.valueOf(delimiter)), delimiter);
+ JSONArray expectedJsonArray = new JSONArray(EXPECTED_LINES);
Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
}
@@ -299,11 +353,11 @@ public void textToJSONArray() {
*/
@Test
public void jsonArrayToJSONArray() {
- String nameArrayStr = "[Col1, Col2]";
+ String nameArrayStr = "[\"Col1\", \"Col2\"]";
String values = "V1, V2";
JSONArray nameJSONArray = new JSONArray(nameArrayStr);
JSONArray jsonArray = CDL.toJSONArray(nameJSONArray, values);
- JSONArray expectedJsonArray = new JSONArray("[{Col1:V1,Col2:V2}]");
+ JSONArray expectedJsonArray = new JSONArray("[{\"Col1\":\"V1\",\"Col2\":\"V2\"}]");
Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
}
@@ -313,10 +367,24 @@ public void jsonArrayToJSONArray() {
*/
@Test
public void textToJSONArrayAndBackToString() {
- JSONArray jsonArray = CDL.toJSONArray(this.lines);
+ JSONArray jsonArray = CDL.toJSONArray(LINES);
String jsonStr = CDL.toString(jsonArray);
JSONArray finalJsonArray = CDL.toJSONArray(jsonStr);
- JSONArray expectedJsonArray = new JSONArray(this.expectedLines);
+ JSONArray expectedJsonArray = new JSONArray(EXPECTED_LINES);
+ Util.compareActualVsExpectedJsonArrays(finalJsonArray, expectedJsonArray);
+ }
+
+ /**
+ * Create a JSONArray from a string of lines,
+ * then convert to string and then back to JSONArray
+ * with a custom delimiter
+ */
+ @Test
+ public void textToJSONArrayAndBackToStringCustomDelimiter() {
+ JSONArray jsonArray = CDL.toJSONArray(LINES, ',');
+ String jsonStr = CDL.toString(jsonArray, ';');
+ JSONArray finalJsonArray = CDL.toJSONArray(jsonStr, ';');
+ JSONArray expectedJsonArray = new JSONArray(EXPECTED_LINES);
Util.compareActualVsExpectedJsonArrays(finalJsonArray, expectedJsonArray);
}
diff --git a/src/test/java/org/json/junit/CookieListTest.java b/src/test/java/org/json/junit/CookieListTest.java
index c3f647f90..0af96401b 100644
--- a/src/test/java/org/json/junit/CookieListTest.java
+++ b/src/test/java/org/json/junit/CookieListTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
diff --git a/src/test/java/org/json/junit/CookieTest.java b/src/test/java/org/json/junit/CookieTest.java
index 7e7b62b45..edd8a7eeb 100644
--- a/src/test/java/org/json/junit/CookieTest.java
+++ b/src/test/java/org/json/junit/CookieTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
diff --git a/src/test/java/org/json/junit/EnumTest.java b/src/test/java/org/json/junit/EnumTest.java
index ed2c87a6b..1496a636a 100644
--- a/src/test/java/org/json/junit/EnumTest.java
+++ b/src/test/java/org/json/junit/EnumTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.assertEquals;
@@ -93,7 +73,7 @@ public void jsonObjectFromEnum() {
/**
* To serialize an enum by its set of allowed values, use getNames()
- * and the the JSONObject Object with names constructor.
+ * and the JSONObject Object with names constructor.
*/
@Test
public void jsonObjectFromEnumWithNames() {
diff --git a/src/test/java/org/json/junit/HTTPTest.java b/src/test/java/org/json/junit/HTTPTest.java
index 8182b6059..703d5ad2f 100644
--- a/src/test/java/org/json/junit/HTTPTest.java
+++ b/src/test/java/org/json/junit/HTTPTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
diff --git a/src/test/java/org/json/junit/HTTPTokenerTest.java b/src/test/java/org/json/junit/HTTPTokenerTest.java
new file mode 100644
index 000000000..28dd40353
--- /dev/null
+++ b/src/test/java/org/json/junit/HTTPTokenerTest.java
@@ -0,0 +1,107 @@
+package org.json.junit;
+
+import org.json.HTTPTokener;
+import org.json.JSONException;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+/**
+ * Tests for JSON-Java HTTPTokener.java
+ */
+public class HTTPTokenerTest {
+
+ /**
+ * Test parsing a simple unquoted token.
+ */
+ @Test
+ public void parseSimpleToken() {
+ HTTPTokener tokener = new HTTPTokener("Content-Type");
+ String token = tokener.nextToken();
+ assertEquals("Content-Type", token);
+ }
+
+ /**
+ * Test parsing multiple tokens separated by whitespace.
+ */
+ @Test
+ public void parseMultipleTokens() {
+ HTTPTokener tokener = new HTTPTokener("Content-Type application/json");
+ String token1 = tokener.nextToken();
+ String token2 = tokener.nextToken();
+ assertEquals("Content-Type", token1);
+ assertEquals("application/json", token2);
+ }
+
+ /**
+ * Test parsing a double-quoted token.
+ */
+ @Test
+ public void parseDoubleQuotedToken() {
+ HTTPTokener tokener = new HTTPTokener("\"application/json\"");
+ String token = tokener.nextToken();
+ assertEquals("application/json", token);
+ }
+
+ /**
+ * Test parsing a single-quoted token.
+ */
+ @Test
+ public void parseSingleQuotedToken() {
+ HTTPTokener tokener = new HTTPTokener("'application/json'");
+ String token = tokener.nextToken();
+ assertEquals("application/json", token);
+ }
+
+ /**
+ * Test parsing a quoted token that includes spaces and semicolons.
+ */
+ @Test
+ public void parseQuotedTokenWithSpaces() {
+ HTTPTokener tokener = new HTTPTokener("\"text/html; charset=UTF-8\"");
+ String token = tokener.nextToken();
+ assertEquals("text/html; charset=UTF-8", token);
+ }
+
+ /**
+ * Test that unterminated quoted strings throw a JSONException.
+ */
+ @Test
+ public void throwExceptionOnUnterminatedString() {
+ HTTPTokener tokener = new HTTPTokener("\"incomplete");
+ JSONException exception = assertThrows(JSONException.class, tokener::nextToken);
+ assertTrue(exception.getMessage().contains("Unterminated string"));
+ }
+
+ /**
+ * Test behavior with empty input string.
+ */
+ @Test
+ public void parseEmptyInput() {
+ HTTPTokener tokener = new HTTPTokener("");
+ String token = tokener.nextToken();
+ assertEquals("", token);
+ }
+
+ /**
+ * Test behavior with input consisting only of whitespace.
+ */
+ @Test
+ public void parseWhitespaceOnly() {
+ HTTPTokener tokener = new HTTPTokener(" \t \n ");
+ String token = tokener.nextToken();
+ assertEquals("", token);
+ }
+
+ /**
+ * Test parsing tokens separated by multiple whitespace characters.
+ */
+ @Test
+ public void parseTokensWithMultipleWhitespace() {
+ HTTPTokener tokener = new HTTPTokener("GET /index.html");
+ String method = tokener.nextToken();
+ String path = tokener.nextToken();
+ assertEquals("GET", method);
+ assertEquals("/index.html", path);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/json/junit/JSONArrayTest.java b/src/test/java/org/json/junit/JSONArrayTest.java
index 7673157ed..429620396 100644
--- a/src/test/java/org/json/junit/JSONArrayTest.java
+++ b/src/test/java/org/json/junit/JSONArrayTest.java
@@ -1,36 +1,19 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.IOException;
+import java.io.InputStream;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
@@ -46,7 +29,13 @@ of this software and associated documentation files (the "Software"), to deal
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
+import org.json.JSONParserConfiguration;
import org.json.JSONPointerException;
+import org.json.JSONString;
+import org.json.JSONTokener;
+import org.json.ParserConfiguration;
+import org.json.junit.data.MyJsonString;
+import org.junit.Ignore;
import org.junit.Test;
import com.jayway.jsonpath.Configuration;
@@ -87,6 +76,7 @@ public class JSONArrayTest {
@Test
public void verifySimilar() {
final String string1 = "HasSameRef";
+ final String string2 = "HasDifferentRef";
JSONArray obj1 = new JSONArray()
.put("abc")
.put(string1)
@@ -101,10 +91,20 @@ public void verifySimilar() {
.put("abc")
.put(new String(string1))
.put(2);
+
+ JSONArray obj4 = new JSONArray()
+ .put("abc")
+ .put(2.0)
+ .put(new String(string1));
+
+ JSONArray obj5 = new JSONArray()
+ .put("abc")
+ .put(2.0)
+ .put(new String(string2));
- assertFalse("Should eval to false", obj1.similar(obj2));
-
- assertTrue("Should eval to true", obj1.similar(obj3));
+ assertFalse("obj1-obj2 Should eval to false", obj1.similar(obj2));
+ assertTrue("obj1-obj3 Should eval to true", obj1.similar(obj3));
+ assertFalse("obj4-obj5 Should eval to false", obj4.similar(obj5));
}
/**
@@ -122,7 +122,7 @@ public void nullException() {
* Expects a JSONException.
*/
@Test
- public void emptStr() {
+ public void emptyStr() {
String str = "";
try {
assertNull("Should throw an exception", new JSONArray(str));
@@ -223,6 +223,23 @@ public void verifyConstructor() {
assertTrue(
"The RAW Collection should give me the same as the Typed Collection",
expected.similar(jaObj));
+ Util.checkJSONArrayMaps(expected);
+ Util.checkJSONArrayMaps(jaObj);
+ Util.checkJSONArrayMaps(jaRaw);
+ Util.checkJSONArrayMaps(jaInt);
+ }
+
+ @Test
+ public void jsonArrayByListWithNestedNullValue() {
+ List> list = new ArrayList>();
+ Map sub = new HashMap();
+ sub.put("nullKey", null);
+ list.add(sub);
+ JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withUseNativeNulls(true);
+ JSONArray jsonArray = new JSONArray(list, parserConfiguration);
+ JSONObject subObject = jsonArray.getJSONObject(0);
+ assertTrue(subObject.has("nullKey"));
+ assertEquals(JSONObject.NULL, subObject.get("nullKey"));
}
/**
@@ -256,11 +273,17 @@ public void verifyPutAll() {
jsonArray.length(),
len);
+ // collection as object
+ @SuppressWarnings("RedundantCast")
+ Object myListAsObject = (Object) myList;
+ jsonArray.putAll(myListAsObject);
+
for (int i = 0; i < myList.size(); i++) {
assertEquals("collection elements should be equal",
myList.get(i),
jsonArray.getString(myInts.length + i));
}
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -294,6 +317,9 @@ public void verifyPutCollection() {
assertTrue(
"The RAW Collection should give me the same as the Typed Collection",
expected.similar(jaInt));
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ jaRaw, jaObj, jaInt
+ )));
}
@@ -337,6 +363,9 @@ public void verifyPutMap() {
assertTrue(
"The RAW Collection should give me the same as the Typed Collection",
expected.similar(jaObjObj));
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ expected, jaRaw, jaStrObj, jaStrInt, jaObjObj
+ )));
}
/**
@@ -361,14 +390,16 @@ public void getArrayValues() {
"hello".equals(jsonArray.getString(4)));
// doubles
assertTrue("Array double",
- new Double(23.45e-4).equals(jsonArray.getDouble(5)));
+ Double.valueOf(23.45e-4).equals(jsonArray.getDouble(5)));
assertTrue("Array string double",
- new Double(23.45).equals(jsonArray.getDouble(6)));
+ Double.valueOf(23.45).equals(jsonArray.getDouble(6)));
+ assertTrue("Array double can be float",
+ Float.valueOf(23.45e-4f).equals(jsonArray.getFloat(5)));
// ints
assertTrue("Array value int",
- new Integer(42).equals(jsonArray.getInt(7)));
+ Integer.valueOf(42).equals(jsonArray.getInt(7)));
assertTrue("Array value string int",
- new Integer(43).equals(jsonArray.getInt(8)));
+ Integer.valueOf(43).equals(jsonArray.getInt(8)));
// nested objects
JSONArray nestedJsonArray = jsonArray.getJSONArray(9);
assertTrue("Array value JSONArray", nestedJsonArray != null);
@@ -376,11 +407,12 @@ public void getArrayValues() {
assertTrue("Array value JSONObject", nestedJsonObject != null);
// longs
assertTrue("Array value long",
- new Long(0).equals(jsonArray.getLong(11)));
+ Long.valueOf(0).equals(jsonArray.getLong(11)));
assertTrue("Array value string long",
- new Long(-1).equals(jsonArray.getLong(12)));
+ Long.valueOf(-1).equals(jsonArray.getLong(12)));
assertTrue("Array value null", jsonArray.isNull(-1));
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -396,7 +428,7 @@ public void failedGetArrayValues() {
assertTrue("expected getBoolean to fail", false);
} catch (JSONException e) {
assertEquals("Expected an exception message",
- "JSONArray[4] is not a boolean.",e.getMessage());
+ "JSONArray[4] is not a boolean (class java.lang.String : hello).",e.getMessage());
}
try {
jsonArray.get(-1);
@@ -410,42 +442,67 @@ public void failedGetArrayValues() {
assertTrue("expected getDouble to fail", false);
} catch (JSONException e) {
assertEquals("Expected an exception message",
- "JSONArray[4] is not a double.",e.getMessage());
+ "JSONArray[4] is not a double (class java.lang.String : hello).",e.getMessage());
}
try {
jsonArray.getInt(4);
assertTrue("expected getInt to fail", false);
} catch (JSONException e) {
assertEquals("Expected an exception message",
- "JSONArray[4] is not a int.",e.getMessage());
+ "JSONArray[4] is not a int (class java.lang.String : hello).",e.getMessage());
}
try {
jsonArray.getJSONArray(4);
assertTrue("expected getJSONArray to fail", false);
} catch (JSONException e) {
assertEquals("Expected an exception message",
- "JSONArray[4] is not a JSONArray.",e.getMessage());
+ "JSONArray[4] is not a JSONArray (class java.lang.String : hello).",e.getMessage());
}
try {
jsonArray.getJSONObject(4);
assertTrue("expected getJSONObject to fail", false);
} catch (JSONException e) {
assertEquals("Expected an exception message",
- "JSONArray[4] is not a JSONObject.",e.getMessage());
+ "JSONArray[4] is not a JSONObject (class java.lang.String : hello).",e.getMessage());
}
try {
jsonArray.getLong(4);
assertTrue("expected getLong to fail", false);
} catch (JSONException e) {
assertEquals("Expected an exception message",
- "JSONArray[4] is not a long.",e.getMessage());
+ "JSONArray[4] is not a long (class java.lang.String : hello).",e.getMessage());
}
try {
jsonArray.getString(5);
assertTrue("expected getString to fail", false);
} catch (JSONException e) {
assertEquals("Expected an exception message",
- "JSONArray[5] is not a String.",e.getMessage());
+ "JSONArray[5] is not a String (class java.math.BigDecimal : 0.002345).",e.getMessage());
+ }
+ Util.checkJSONArrayMaps(jsonArray);
+ }
+
+ /**
+ * The JSON parser is permissive of unambiguous unquoted keys and values.
+ * Such JSON text should be allowed, even if it does not strictly conform
+ * to the spec. However, after being parsed, toString() should emit strictly
+ * conforming JSON text.
+ */
+ @Test
+ public void unquotedText() {
+ String str = "[value1, something!, (parens), foo@bar.com, 23, 23+45]";
+ List expected = Arrays.asList("value1", "something!", "(parens)", "foo@bar.com", 23, "23+45");
+
+ // Test should fail if default strictMode is true, pass if false
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+ if (jsonParserConfiguration.isStrictMode()) {
+ try {
+ JSONArray jsonArray = new JSONArray(str);
+ assertEquals("Expected to throw exception due to invalid string", true, false);
+ } catch (JSONException e) { }
+ } else {
+ JSONArray jsonArray = new JSONArray(str);
+ assertEquals(expected, jsonArray.toList());
}
}
@@ -483,6 +540,7 @@ public void join() {
assertTrue("expected value4", "value4".equals(jsonArray.query("/10/key4")));
assertTrue("expected 0", Integer.valueOf(0).equals(jsonArray.query("/11")));
assertTrue("expected \"-1\"", "-1".equals(jsonArray.query("/12")));
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -496,6 +554,9 @@ public void length() {
assertTrue("expected JSONArray length 13. instead found "+jsonArray.length(), jsonArray.length() == 13);
JSONArray nestedJsonArray = jsonArray.getJSONArray(9);
assertTrue("expected JSONArray length 1", nestedJsonArray.length() == 1);
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ jsonArray, nestedJsonArray
+ )));
}
/**
@@ -522,43 +583,75 @@ public void opt() {
assertTrue("Array opt boolean implicit default",
Boolean.FALSE == jsonArray.optBoolean(-1));
+ assertTrue("Array opt boolean object",
+ Boolean.TRUE.equals(jsonArray.optBooleanObject(0)));
+ assertTrue("Array opt boolean object default",
+ Boolean.FALSE.equals(jsonArray.optBooleanObject(-1, Boolean.FALSE)));
+ assertTrue("Array opt boolean object implicit default",
+ Boolean.FALSE.equals(jsonArray.optBooleanObject(-1)));
+
assertTrue("Array opt double",
- new Double(23.45e-4).equals(jsonArray.optDouble(5)));
+ Double.valueOf(23.45e-4).equals(jsonArray.optDouble(5)));
assertTrue("Array opt double default",
- new Double(1).equals(jsonArray.optDouble(0, 1)));
+ Double.valueOf(1).equals(jsonArray.optDouble(0, 1)));
assertTrue("Array opt double default implicit",
- new Double(jsonArray.optDouble(99)).isNaN());
+ Double.valueOf(jsonArray.optDouble(99)).isNaN());
+
+ assertTrue("Array opt double object",
+ Double.valueOf(23.45e-4).equals(jsonArray.optDoubleObject(5)));
+ assertTrue("Array opt double object default",
+ Double.valueOf(1).equals(jsonArray.optDoubleObject(0, 1D)));
+ assertTrue("Array opt double object default implicit",
+ jsonArray.optDoubleObject(99).isNaN());
assertTrue("Array opt float",
- new Float(23.45e-4).equals(jsonArray.optFloat(5)));
+ Float.valueOf(Double.valueOf(23.45e-4).floatValue()).equals(jsonArray.optFloat(5)));
assertTrue("Array opt float default",
- new Float(1).equals(jsonArray.optFloat(0, 1)));
+ Float.valueOf(1).equals(jsonArray.optFloat(0, 1)));
assertTrue("Array opt float default implicit",
- new Float(jsonArray.optFloat(99)).isNaN());
+ Float.valueOf(jsonArray.optFloat(99)).isNaN());
+
+ assertTrue("Array opt float object",
+ Float.valueOf(23.45e-4F).equals(jsonArray.optFloatObject(5)));
+ assertTrue("Array opt float object default",
+ Float.valueOf(1).equals(jsonArray.optFloatObject(0, 1F)));
+ assertTrue("Array opt float object default implicit",
+ jsonArray.optFloatObject(99).isNaN());
assertTrue("Array opt Number",
BigDecimal.valueOf(23.45e-4).equals(jsonArray.optNumber(5)));
assertTrue("Array opt Number default",
- new Double(1).equals(jsonArray.optNumber(0, 1d)));
+ Double.valueOf(1).equals(jsonArray.optNumber(0, 1d)));
assertTrue("Array opt Number default implicit",
- new Double(jsonArray.optNumber(99,Double.NaN).doubleValue()).isNaN());
+ Double.valueOf(jsonArray.optNumber(99,Double.NaN).doubleValue()).isNaN());
assertTrue("Array opt int",
- new Integer(42).equals(jsonArray.optInt(7)));
+ Integer.valueOf(42).equals(jsonArray.optInt(7)));
assertTrue("Array opt int default",
- new Integer(-1).equals(jsonArray.optInt(0, -1)));
+ Integer.valueOf(-1).equals(jsonArray.optInt(0, -1)));
assertTrue("Array opt int default implicit",
0 == jsonArray.optInt(0));
+ assertTrue("Array opt int object",
+ Integer.valueOf(42).equals(jsonArray.optIntegerObject(7)));
+ assertTrue("Array opt int object default",
+ Integer.valueOf(-1).equals(jsonArray.optIntegerObject(0, -1)));
+ assertTrue("Array opt int object default implicit",
+ Integer.valueOf(0).equals(jsonArray.optIntegerObject(0)));
+
JSONArray nestedJsonArray = jsonArray.optJSONArray(9);
assertTrue("Array opt JSONArray", nestedJsonArray != null);
- assertTrue("Array opt JSONArray default",
+ assertTrue("Array opt JSONArray null",
null == jsonArray.optJSONArray(99));
+ assertTrue("Array opt JSONArray default",
+ "value".equals(jsonArray.optJSONArray(99, new JSONArray("[\"value\"]")).getString(0)));
JSONObject nestedJsonObject = jsonArray.optJSONObject(10);
assertTrue("Array opt JSONObject", nestedJsonObject != null);
- assertTrue("Array opt JSONObject default",
+ assertTrue("Array opt JSONObject null",
null == jsonArray.optJSONObject(99));
+ assertTrue("Array opt JSONObject default",
+ "value".equals(jsonArray.optJSONObject(99, new JSONObject("{\"key\":\"value\"}")).getString("key")));
assertTrue("Array opt long",
0 == jsonArray.optLong(11));
@@ -567,10 +660,21 @@ public void opt() {
assertTrue("Array opt long default implicit",
0 == jsonArray.optLong(-1));
+ assertTrue("Array opt long object",
+ Long.valueOf(0).equals(jsonArray.optLongObject(11)));
+ assertTrue("Array opt long object default",
+ Long.valueOf(-2).equals(jsonArray.optLongObject(-1, -2L)));
+ assertTrue("Array opt long object default implicit",
+ Long.valueOf(0).equals(jsonArray.optLongObject(-1)));
+
assertTrue("Array opt string",
"hello".equals(jsonArray.optString(4)));
assertTrue("Array opt string default implicit",
"".equals(jsonArray.optString(-1)));
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ jsonArray, nestedJsonArray
+ )));
+ Util.checkJSONObjectMaps(nestedJsonObject);
}
/**
@@ -580,12 +684,19 @@ public void opt() {
public void optStringConversion(){
JSONArray ja = new JSONArray("[\"123\",\"true\",\"false\"]");
assertTrue("unexpected optBoolean value",ja.optBoolean(1,false)==true);
+ assertTrue("unexpected optBooleanObject value",Boolean.valueOf(true).equals(ja.optBooleanObject(1,false)));
assertTrue("unexpected optBoolean value",ja.optBoolean(2,true)==false);
+ assertTrue("unexpected optBooleanObject value",Boolean.valueOf(false).equals(ja.optBooleanObject(2,true)));
assertTrue("unexpected optInt value",ja.optInt(0,0)==123);
+ assertTrue("unexpected optIntegerObject value",Integer.valueOf(123).equals(ja.optIntegerObject(0,0)));
assertTrue("unexpected optLong value",ja.optLong(0,0)==123);
+ assertTrue("unexpected optLongObject value",Long.valueOf(123).equals(ja.optLongObject(0,0L)));
assertTrue("unexpected optDouble value",ja.optDouble(0,0.0)==123.0);
+ assertTrue("unexpected optDoubleObject value",Double.valueOf(123.0).equals(ja.optDoubleObject(0,0.0)));
assertTrue("unexpected optBigInteger value",ja.optBigInteger(0,BigInteger.ZERO).compareTo(new BigInteger("123"))==0);
- assertTrue("unexpected optBigDecimal value",ja.optBigDecimal(0,BigDecimal.ZERO).compareTo(new BigDecimal("123"))==0); }
+ assertTrue("unexpected optBigDecimal value",ja.optBigDecimal(0,BigDecimal.ZERO).compareTo(new BigDecimal("123"))==0);
+ Util.checkJSONArrayMaps(ja);
+ }
/**
* Exercise the JSONArray.put(value) method with various parameters
@@ -603,8 +714,8 @@ public void put() {
String jsonArrayStr =
"["+
- "hello,"+
- "world"+
+ "\"hello\","+
+ "\"world\""+
"]";
// 2
jsonArray.put(new JSONArray(jsonArrayStr));
@@ -661,6 +772,8 @@ public void put() {
assertTrue("expected 2 items in [9]", ((List>)(JsonPath.read(doc, "$[9]"))).size() == 2);
assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/9/0")));
assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/9/1")));
+ Util.checkJSONArrayMaps(jsonArray);
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -679,8 +792,8 @@ public void putIndex() {
String jsonArrayStr =
"["+
- "hello,"+
- "world"+
+ "\"hello\","+
+ "\"world\""+
"]";
// 2
jsonArray.put(2, new JSONArray(jsonArrayStr));
@@ -740,6 +853,8 @@ public void putIndex() {
assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/9/1")));
assertTrue("expected 1 item in [10]", ((Map,?>)(JsonPath.read(doc, "$[10]"))).size() == 1);
assertTrue("expected v1", "v1".equals(jsonArray.query("/10/k1")));
+ Util.checkJSONObjectMaps(jsonObject);
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -756,6 +871,7 @@ public void remove() {
jsonArray.remove(0);
assertTrue("array should be empty", null == jsonArray.remove(5));
assertTrue("jsonArray should be empty", jsonArray.isEmpty());
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -795,6 +911,12 @@ public void notSimilar() {
otherJsonArray.put("world");
assertTrue("arrays values differ",
!jsonArray.similar(otherJsonArray));
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ jsonArray, otherJsonArray
+ )));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObject, otherJsonObject
+ )));
}
/**
@@ -878,6 +1000,7 @@ public void jsonArrayToStringIndent() {
for (String s : jsonArray4Strs) {
list.contains(s);
}
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -889,6 +1012,9 @@ public void toJSONObject() {
JSONArray jsonArray = new JSONArray();
assertTrue("toJSONObject should return null",
null == jsonArray.toJSONObject(names));
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ names, jsonArray
+ )));
}
/**
@@ -910,6 +1036,7 @@ public void objectArrayVsIsArray() {
assertTrue("expected 5", Integer.valueOf(5).equals(jsonArray.query("/4")));
assertTrue("expected 6", Integer.valueOf(6).equals(jsonArray.query("/5")));
assertTrue("expected 7", Integer.valueOf(7).equals(jsonArray.query("/6")));
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -934,12 +1061,12 @@ public void iteratorTest() {
assertTrue("Array double [23.45e-4]",
new BigDecimal("0.002345").equals(it.next()));
assertTrue("Array string double",
- new Double(23.45).equals(Double.parseDouble((String)it.next())));
+ Double.valueOf(23.45).equals(Double.parseDouble((String)it.next())));
assertTrue("Array value int",
- new Integer(42).equals(it.next()));
+ Integer.valueOf(42).equals(it.next()));
assertTrue("Array value string int",
- new Integer(43).equals(Integer.parseInt((String)it.next())));
+ Integer.valueOf(43).equals(Integer.parseInt((String)it.next())));
JSONArray nestedJsonArray = (JSONArray)it.next();
assertTrue("Array value JSONArray", nestedJsonArray != null);
@@ -948,10 +1075,14 @@ public void iteratorTest() {
assertTrue("Array value JSONObject", nestedJsonObject != null);
assertTrue("Array value long",
- new Long(0).equals(((Number) it.next()).longValue()));
+ Long.valueOf(0).equals(((Number) it.next()).longValue()));
assertTrue("Array value string long",
- new Long(-1).equals(Long.parseLong((String) it.next())));
+ Long.valueOf(-1).equals(Long.parseLong((String) it.next())));
assertTrue("should be at end of array", !it.hasNext());
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ jsonArray, nestedJsonArray
+ )));
+ Util.checkJSONObjectMaps(nestedJsonObject);
}
@Test(expected = JSONPointerException.class)
@@ -994,6 +1125,7 @@ public void write() throws IOException {
} finally {
stringWriter.close();
}
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -1053,9 +1185,11 @@ public void write3Param() throws IOException {
&& actualStr.contains("\"key2\": false")
&& actualStr.contains("\"key3\": 3.14")
);
+ Util.checkJSONArrayMaps(finalArray);
} finally {
stringWriter.close();
}
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -1166,6 +1300,7 @@ public void toList() {
// assert that the new list is mutable
assertTrue("Removing an entry should succeed", list.remove(2) != null);
assertTrue("List should have 2 elements", list.size() == 2);
+ Util.checkJSONArrayMaps(jsonArray);
}
/**
@@ -1174,13 +1309,13 @@ public void toList() {
*/
@Test
public void testJSONArrayInt() {
- assertNotNull(new JSONArray(0));
- assertNotNull(new JSONArray(5));
- // Check Size -> Even though the capacity of the JSONArray can be specified using a positive
- // integer but the length of JSONArray always reflects upon the items added into it.
- assertEquals(0l, new JSONArray(10).length());
+ assertNotNull(new JSONArray(0));
+ assertNotNull(new JSONArray(5));
+ // Check Size -> Even though the capacity of the JSONArray can be specified using a positive
+ // integer but the length of JSONArray always reflects upon the items added into it.
+ // assertEquals(0l, new JSONArray(10).length());
try {
- assertNotNull("Should throw an exception", new JSONArray(-1));
+ assertNotNull("Should throw an exception", new JSONArray(-1));
} catch (JSONException e) {
assertEquals("Expected an exception message",
"JSONArray initial capacity cannot be negative.",
@@ -1207,8 +1342,8 @@ public void testObjectConstructor() {
((Collection)o).add("test");
((Collection)o).add(false);
try {
- a = new JSONArray(o);
- assertNull("Should error", a);
+ JSONArray a0 = new JSONArray(o);
+ assertNull("Should error", a0);
} catch (JSONException ex) {
}
@@ -1216,10 +1351,11 @@ public void testObjectConstructor() {
// this is required for backwards compatibility
o = a;
try {
- a = new JSONArray(o);
- assertNull("Should error", a);
+ JSONArray a1 = new JSONArray(o);
+ assertNull("Should error", a1);
} catch (JSONException ex) {
}
+ Util.checkJSONArrayMaps(a);
}
/**
@@ -1236,6 +1372,9 @@ public void testJSONArrayConstructor() {
for(int i = 0; i < a1.length(); i++) {
assertEquals("index " + i + " are equal", a1.get(i), a2.get(i));
}
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ a1, a2
+ )));
}
/**
@@ -1253,5 +1392,156 @@ public void testJSONArrayPutAll() {
for(int i = 0; i < a1.length(); i++) {
assertEquals("index " + i + " are equal", a1.get(i), a2.get(i));
}
+ Util.checkJSONArraysMaps(new ArrayList(Arrays.asList(
+ a1, a2
+ )));
+ }
+
+ /**
+ * Tests if calling JSONArray clear() method actually makes the JSONArray empty
+ */
+ @Test(expected = JSONException.class)
+ public void jsonArrayClearMethodTest() {
+ //Adds random stuff to the JSONArray
+ JSONArray jsonArray = new JSONArray();
+ jsonArray.put(123);
+ jsonArray.put("456");
+ jsonArray.put(new JSONArray());
+ jsonArray.clear(); //Clears the JSONArray
+ assertTrue("expected jsonArray.length() == 0", jsonArray.length() == 0); //Check if its length is 0
+ jsonArray.getInt(0); //Should throws org.json.JSONException: JSONArray[0] not found
+ Util.checkJSONArrayMaps(jsonArray);
+ }
+
+ /**
+ * Tests for stack overflow. See https://github.com/stleary/JSON-java/issues/654
+ */
+ @Ignore("This test relies on system constraints and may not always pass. See: https://github.com/stleary/JSON-java/issues/821")
+ @Test(expected = JSONException.class)
+ public void issue654StackOverflowInputWellFormed() {
+ //String input = new String(java.util.Base64.getDecoder().decode(base64Bytes));
+ final InputStream resourceAsStream = JSONArrayTest.class.getClassLoader().getResourceAsStream("Issue654WellFormedArray.json");
+ JSONTokener tokener = new JSONTokener(resourceAsStream);
+ JSONArray json_input = new JSONArray(tokener);
+ assertNotNull(json_input);
+ fail("Excepected Exception due to stack overflow.");
+ Util.checkJSONArrayMaps(json_input);
+ }
+
+ @Test
+ public void testIssue682SimilarityOfJSONString() {
+ JSONArray ja1 = new JSONArray()
+ .put(new MyJsonString())
+ .put(2);
+ JSONArray ja2 = new JSONArray()
+ .put(new MyJsonString())
+ .put(2);
+ assertTrue(ja1.similar(ja2));
+
+ JSONArray ja3 = new JSONArray()
+ .put(new JSONString() {
+ @Override
+ public String toJSONString() {
+ return "\"different value\"";
+ }
+ })
+ .put(2);
+ assertFalse(ja1.similar(ja3));
+ }
+
+ @Test(expected = JSONException.class)
+ public void testRecursiveDepth() {
+ HashMap map = new HashMap<>();
+ map.put("t", map);
+ new JSONArray().put(map);
+ }
+
+ @Test(expected = JSONException.class)
+ public void testRecursiveDepthAtPosition() {
+ HashMap map = new HashMap<>();
+ map.put("t", map);
+ new JSONArray().put(0, map);
+ }
+
+ @Test(expected = JSONException.class)
+ public void testRecursiveDepthArray() {
+ ArrayList array = new ArrayList<>();
+ array.add(array);
+ new JSONArray(array);
+ }
+
+ @Test
+ public void testRecursiveDepthAtPositionDefaultObject() {
+ HashMap map = JSONObjectTest.buildNestedMap(ParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
+ new JSONArray().put(0, map);
+ }
+
+ @Test
+ public void testRecursiveDepthAtPosition1000Object() {
+ HashMap map = JSONObjectTest.buildNestedMap(1000);
+ new JSONArray().put(0, map, new JSONParserConfiguration().withMaxNestingDepth(1000));
+ }
+
+ @Test(expected = JSONException.class)
+ public void testRecursiveDepthAtPosition1001Object() {
+ HashMap map = JSONObjectTest.buildNestedMap(1001);
+ new JSONArray().put(0, map);
+ }
+
+ @Test(expected = JSONException.class)
+ public void testRecursiveDepthArrayLimitedMaps() {
+ ArrayList array = new ArrayList<>();
+ array.add(array);
+ new JSONArray(array);
+ }
+
+ @Test
+ public void testRecursiveDepthArrayForDefaultLevels() {
+ ArrayList array = buildNestedArray(ParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
+ new JSONArray(array, new JSONParserConfiguration());
+ }
+
+ @Test
+ public void testRecursiveDepthArrayFor1000Levels() {
+ try {
+ ArrayList array = buildNestedArray(1000);
+ JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withMaxNestingDepth(1000);
+ new JSONArray(array, parserConfiguration);
+ } catch (StackOverflowError e) {
+ String javaVersion = System.getProperty("java.version");
+ if (javaVersion.startsWith("11.")) {
+ System.out.println(
+ "testRecursiveDepthArrayFor1000Levels() allowing intermittent stackoverflow, Java Version: "
+ + javaVersion);
+ } else {
+ String errorStr = "testRecursiveDepthArrayFor1000Levels() unexpected stackoverflow, Java Version: "
+ + javaVersion;
+ System.out.println(errorStr);
+ throw new RuntimeException(errorStr);
+ }
+ }
+ }
+
+ @Test(expected = JSONException.class)
+ public void testRecursiveDepthArrayFor1001Levels() {
+ ArrayList array = buildNestedArray(1001);
+ new JSONArray(array);
+ }
+
+ @Test
+ public void testStrictModeJSONTokener_expectException(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
+ JSONTokener tokener = new JSONTokener("[\"value\"]invalidCharacters", jsonParserConfiguration);
+
+ assertThrows(JSONException.class, () -> { new JSONArray(tokener); });
+ }
+
+ public static ArrayList buildNestedArray(int maxDepth) {
+ if (maxDepth <= 0) {
+ return new ArrayList<>();
+ }
+ ArrayList nestedArray = new ArrayList<>();
+ nestedArray.add(buildNestedArray(maxDepth - 1));
+ return nestedArray;
}
}
diff --git a/src/test/java/org/json/junit/JSONMLTest.java b/src/test/java/org/json/junit/JSONMLTest.java
index 8f3de42cf..5a360dd59 100644
--- a/src/test/java/org/json/junit/JSONMLTest.java
+++ b/src/test/java/org/json/junit/JSONMLTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
@@ -31,19 +11,19 @@ of this software and associated documentation files (the "Software"), to deal
/**
* Tests for org.json.JSONML.java
- *
+ *
* Certain inputs are expected to result in exceptions. These tests are
* executed first. JSONML provides an API to:
- * Convert an XML string into a JSONArray or a JSONObject.
+ * Convert an XML string into a JSONArray or a JSONObject.
* Convert a JSONArray or JSONObject into an XML string.
* Both fromstring and tostring operations operations should be symmetrical
- * within the limits of JSONML.
+ * within the limits of JSONML.
* It should be possible to perform the following operations, which should
* result in the original string being recovered, within the limits of the
* underlying classes:
* Convert a string -> JSONArray -> string -> JSONObject -> string
* Convert a string -> JSONObject -> string -> JSONArray -> string
- *
+ *
*/
public class JSONMLTest {
@@ -76,7 +56,7 @@ public void emptyXMLException() {
/**
* Attempts to call JSONML.toString() with a null JSONArray.
- * Expects a NullPointerException.
+ * Expects a NullPointerException.
*/
@Test(expected=NullPointerException.class)
public void nullJSONXMLException() {
@@ -89,7 +69,7 @@ public void nullJSONXMLException() {
/**
* Attempts to call JSONML.toString() with a null JSONArray.
- * Expects a JSONException.
+ * Expects a JSONException.
*/
@Test
public void emptyJSONXMLException() {
@@ -145,7 +125,7 @@ public void emptyTagException() {
"[\"addresses\","+
"{\"xsi:noNamespaceSchemaLocation\":\"test.xsd\","+
"\"xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"},"+
- // this array has no name
+ // this array has no name
"["+
"[\"name\"],"+
"[\"nocontent\"],"+
@@ -158,7 +138,7 @@ public void emptyTagException() {
assertTrue("Expecting an exception", false);
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONArray[0] is not a String.",
+ "JSONArray[0] is not a String (class org.json.JSONArray).",
e.getMessage());
}
}
@@ -200,7 +180,7 @@ public void spaceInTagException() {
}
/**
- * Attempts to transform a malformed XML document
+ * Attempts to transform a malformed XML document
* (element tag has a frontslash) to a JSONArray.\
* Expects a JSONException
*/
@@ -211,7 +191,7 @@ public void invalidSlashInTagException() {
* In this case, the XML is invalid because the 'name' element
* contains an invalid frontslash.
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -236,7 +216,7 @@ public void invalidSlashInTagException() {
*/
@Test
public void invalidBangInTagException() {
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -266,7 +246,7 @@ public void invalidBangNoCloseInTagException() {
* In this case, the XML is invalid because an element
* starts with '!' and has no closing tag
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -296,7 +276,7 @@ public void noCloseStartTagException() {
* In this case, the XML is invalid because an element
* has no closing '>'.
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -326,7 +306,7 @@ public void noCloseEndTagException() {
* In this case, the XML is invalid because an element
* has no name after the closing tag ''.
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -356,7 +336,7 @@ public void noCloseEndBraceException() {
* In this case, the XML is invalid because an element
* has '>' after the closing tag '' and name.
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -384,9 +364,9 @@ public void invalidCDATABangInTagException() {
/**
* xmlStr contains XML text which is transformed into a JSONArray.
* In this case, the XML is invalid because an element
- * does not have a complete CDATA string.
+ * does not have a complete CDATA string.
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -408,7 +388,7 @@ public void invalidCDATABangInTagException() {
/**
* Convert an XML document into a JSONArray, then use JSONML.toString()
* to convert it into a string. This string is then converted back into
- * a JSONArray. Both JSONArrays are compared against a control to
+ * a JSONArray. Both JSONArrays are compared against a control to
* confirm the contents.
*/
@Test
@@ -425,7 +405,7 @@ public void toJSONArray() {
* which is used to create a final JSONArray, which is also compared
* against the expected JSONArray.
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -434,7 +414,7 @@ public void toJSONArray() {
" >\n"+
"\n"+
" ";
- String expectedStr =
+ String expectedStr =
"[\"addresses\","+
"{\"xsi:noNamespaceSchemaLocation\":\"test.xsd\","+
"\"xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"},"+
@@ -454,12 +434,12 @@ public void toJSONArray() {
}
/**
- * Convert an XML document into a JSONObject. Use JSONML.toString() to
+ * Convert an XML document into a JSONObject. Use JSONML.toString() to
* convert it back into a string, and then re-convert it into a JSONObject.
* Both JSONObjects are compared against a control JSONObject to confirm
* the contents.
*
- * Next convert the XML document into a JSONArray. Use JSONML.toString() to
+ * Next convert the XML document into a JSONArray. Use JSONML.toString() to
* convert it back into a string, and then re-convert it into a JSONArray.
* Both JSONArrays are compared against a control JSONArray to confirm
* the contents.
@@ -472,23 +452,23 @@ public void toJSONObjectToJSONArray() {
/**
* xmlStr contains XML text which is transformed into a JSONObject,
* restored to XML, transformed into a JSONArray, and then restored
- * to XML again. Both JSONObject and JSONArray should contain the same
+ * to XML again. Both JSONObject and JSONArray should contain the same
* information and should produce the same XML, allowing for non-ordered
* attributes.
- *
+ *
* Transformation to JSONObject:
* The elementName is stored as a string where key="tagName"
* Attributes are simply stored as key/value pairs
* If the element has either content or child elements, they are stored
* in a jsonArray with key="childNodes".
- *
+ *
* Transformation to JSONArray:
* 1st entry = elementname
* 2nd entry = attributes object (if present)
* 3rd entry = content (if present)
* 4th entry = child element JSONArrays (if present)
*/
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
@@ -605,7 +585,7 @@ public void toJSONObjectToJSONArray() {
"\"tagName\":\"addresses\""+
"}";
- String expectedJSONArrayStr =
+ String expectedJSONArrayStr =
"["+
"\"addresses\","+
"{"+
@@ -645,7 +625,7 @@ public void toJSONObjectToJSONArray() {
"\"subValue\","+
"{\"svAttr\":\"svValue\"},"+
"\"abc\""+
- "],"+
+ "]"+
"],"+
"[\"value\",3],"+
"[\"value\",4.1],"+
@@ -665,12 +645,12 @@ public void toJSONObjectToJSONArray() {
JSONObject finalJsonObject = JSONML.toJSONObject(jsonObjectXmlToStr);
Util.compareActualVsExpectedJsonObjects(finalJsonObject, expectedJsonObject);
- // create a JSON array from the original string and make sure it
+ // create a JSON array from the original string and make sure it
// looks as expected
JSONArray jsonArray = JSONML.toJSONArray(xmlStr);
JSONArray expectedJsonArray = new JSONArray(expectedJSONArrayStr);
Util.compareActualVsExpectedJsonArrays(jsonArray,expectedJsonArray);
-
+
// restore the XML, then make another JSONArray and make sure it
// looks as expected
String jsonArrayXmlToStr = JSONML.toString(jsonArray);
@@ -688,14 +668,14 @@ public void toJSONObjectToJSONArray() {
* Convert an XML document which contains embedded comments into
* a JSONArray. Use JSONML.toString() to turn it into a string, then
* reconvert it into a JSONArray. Compare both JSONArrays to a control
- * JSONArray to confirm the contents.
+ * JSONArray to confirm the contents.
*
* This test shows how XML comments are handled.
*/
@Test
public void commentsInXML() {
- String xmlStr =
+ String xmlStr =
"\n"+
"\n"+
"\n"+
@@ -754,7 +734,7 @@ public void testToJSONArray_reversibility2() {
final String expectedJsonString = "[\"root\",[\"id\",\"01\"],[\"id\",\"1\"],[\"id\",\"00\"],[\"id\",\"0\"],[\"item\",{\"id\":\"01\"}],[\"title\",\"True\"]]";
final JSONArray json = JSONML.toJSONArray(originalXml,true);
assertEquals(expectedJsonString, json.toString());
-
+
final String reverseXml = JSONML.toString(json);
assertEquals(originalXml, reverseXml);
}
@@ -769,7 +749,7 @@ public void testToJSONArray_reversibility3() {
final String revertedXml = JSONML.toString(jsonArray);
assertEquals(revertedXml, originalXml);
}
-
+
/**
* JSON string cannot be reverted to original xml. See test result in
* comment below.
@@ -782,15 +762,15 @@ public void testToJSONObject_reversibility() {
final String xml = JSONML.toString(originalObject);
final JSONObject revertedObject = JSONML.toJSONObject(xml, false);
final String newJson = revertedObject.toString();
- assertTrue("JSON Objects are not similar",originalObject.similar(revertedObject));
- assertEquals("original JSON does not equal the new JSON",originalJson, newJson);
+ assertTrue("JSON Objects are not similar", originalObject.similar(revertedObject));
+ assertTrue("JSON Strings are not similar", new JSONObject(originalJson).similar(new JSONObject(newJson)));
}
// these tests do not pass for the following reasons:
// 1. Our XML parser does not handle generic HTML entities, only valid XML entities. Hence
// or other HTML specific entities would fail on reversability
// 2. Our JSON implementation for storing the XML attributes uses the standard unordered map.
-// This means that can not be reversed reliably.
+// This means that can not be reversed reliably.
//
// /**
// * Test texts taken from jsonml.org. Currently our implementation FAILS this conversion but shouldn't.
@@ -803,13 +783,13 @@ public void testToJSONObject_reversibility() {
// final String expectedJsonString = "[\"table\",{\"class\" : \"MyTable\",\"style\" : \"background-color:yellow\"},[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#550758\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:red\"},\"Example text here\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#993101\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:green\"},\"127624015\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#E33D87\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:blue\"},\"\u00A0\",[\"span\",{ \"style\" : \"background-color:maroon\" },\"\u00A9\"],\"\u00A0\"]]]";
// final JSONArray json = JSONML.toJSONArray(originalXml,true);
// final String actualJsonString = json.toString();
-//
+//
// final String reverseXml = JSONML.toString(json);
// assertNotEquals(originalXml, reverseXml);
//
// assertNotEquals(expectedJsonString, actualJsonString);
// }
-//
+//
// /**
// * Test texts taken from jsonml.org but modified to have XML entities only.
// */
@@ -819,15 +799,15 @@ public void testToJSONObject_reversibility() {
// final String expectedJsonString = "[\"table\",{\"class\" : \"MyTable\",\"style\" : \"background-color:yellow\"},[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#550758\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:red\"},\"Example text here\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#993101\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:green\"},\"127624015\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#E33D87\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:blue\"},\"&\",[\"span\",{ \"style\" : \"background-color:maroon\" },\">\"],\"<\"]]]";
// final JSONArray jsonML = JSONML.toJSONArray(originalXml,true);
// final String actualJsonString = jsonML.toString();
-//
+//
// final String reverseXml = JSONML.toString(jsonML);
// // currently not equal because the hashing of the attribute objects makes the attribute
-// // order not happen the same way twice
+// // order not happen the same way twice
// assertEquals(originalXml, reverseXml);
//
// assertEquals(expectedJsonString, actualJsonString);
// }
-
+
@Test (timeout = 6000)
public void testIssue484InfinteLoop1() {
try {
@@ -839,11 +819,11 @@ public void testIssue484InfinteLoop1() {
ex.getMessage());
}
}
-
+
@Test (timeout = 6000)
public void testIssue484InfinteLoop2() {
try {
- String input = "??*\n" +
+ String input = "??*\n" +
"??|?CglR??`??>?w??PIlr??D?$?-?o??O?*??{OD?Y??`2a????NM?bq?:O?>S$?J?B.gUK?m\b??zE???!v]???????c??????h???s???g???`?qbi??:Zl?)?}1^??k?0??:$V?$?Ovs(}J??????2;gQ????Tg?K?`?h%c?hmGA?");
+
+ final int maxNestingDepth = 42;
+
+ try {
+ JSONML.toJSONArray(wayTooLongMalformedXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
+ e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
+ }
+ }
+
+
+ @Test
+ public void testToJSONArrayMaxNestingDepthIsRespectedWithValidXML() {
+ final String perfectlyFineXML = "\n" +
+ " \n" +
+ " sonoo \n" +
+ " 56000 \n" +
+ " true \n" +
+ " \n" +
+ " \n";
+
+ final int maxNestingDepth = 1;
+
+ try {
+ JSONML.toJSONArray(perfectlyFineXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
+ e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
+ }
+ }
+
+ @Test
+ public void testToJSONArrayMaxNestingDepthWithValidFittingXML() {
+ final String perfectlyFineXML = "\n" +
+ " \n" +
+ " sonoo \n" +
+ " 56000 \n" +
+ " true \n" +
+ " \n" +
+ " \n";
+
+ final int maxNestingDepth = 3;
+
+ try {
+ JSONML.toJSONArray(perfectlyFineXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ fail("XML document should be parsed as its maximum depth fits the maxNestingDepth " +
+ "parameter of the JSONMLParserConfiguration used");
+ }
+ }
+
+
+ @Test
+ public void testToJSONObjectMaxDefaultNestingDepthIsRespected() {
+ final String wayTooLongMalformedXML = new String(new char[6000]).replace("\0", "");
+
+ try {
+ JSONML.toJSONObject(wayTooLongMalformedXML, JSONMLParserConfiguration.ORIGINAL);
+
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
+ e.getMessage().startsWith("Maximum nesting depth of " + JSONMLParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH));
+ }
+ }
+
+ @Test
+ public void testToJSONObjectUnlimitedNestingDepthIsPossible() {
+ int actualDepth = JSONMLParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH +10;
+ final String deeperThanDefaultMax = new String(new char[actualDepth]).replace("\0", " ") +
+ "value" +
+ new String(new char[actualDepth]).replace("\0", " ");
+
+ try {
+ JSONML.toJSONObject(deeperThanDefaultMax, JSONMLParserConfiguration.ORIGINAL
+ .withMaxNestingDepth(JSONMLParserConfiguration.UNDEFINED_MAXIMUM_NESTING_DEPTH));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ fail("XML document should be parsed beyond the default maximum depth if maxNestingDepth " +
+ "parameter is set to -1 in JSONMLParserConfiguration");
+ }
+ }
+
+
+ @Test
+ public void testToJSONObjectMaxNestingDepthOf42IsRespected() {
+ final String wayTooLongMalformedXML = new String(new char[6000]).replace("\0", "");
+
+ final int maxNestingDepth = 42;
+
+ try {
+ JSONML.toJSONObject(wayTooLongMalformedXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
+ e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
+ }
+ }
+
+ @Test
+ public void testToJSONObjectMaxNestingDepthIsRespectedWithValidXML() {
+ final String perfectlyFineXML = "\n" +
+ " \n" +
+ " sonoo \n" +
+ " 56000 \n" +
+ " true \n" +
+ " \n" +
+ " \n";
+
+ final int maxNestingDepth = 1;
+
+ try {
+ JSONML.toJSONObject(perfectlyFineXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
+ e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
+ }
+ }
+
+ @Test
+ public void testToJSONObjectMaxNestingDepthWithValidFittingXML() {
+ final String perfectlyFineXML = "\n" +
+ " \n" +
+ " sonoo \n" +
+ " 56000 \n" +
+ " true \n" +
+ " \n" +
+ " \n";
+
+ final int maxNestingDepth = 3;
+
+ try {
+ JSONML.toJSONObject(perfectlyFineXML, JSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ fail("XML document should be parsed as its maximum depth fits the maxNestingDepth " +
+ "parameter of the JSONMLParserConfiguration used");
+ }
+ }
+
}
diff --git a/src/test/java/org/json/junit/JSONObjectLocaleTest.java b/src/test/java/org/json/junit/JSONObjectLocaleTest.java
index 5112bf56e..e1a9dd64e 100755
--- a/src/test/java/org/json/junit/JSONObjectLocaleTest.java
+++ b/src/test/java/org/json/junit/JSONObjectLocaleTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
@@ -56,25 +36,31 @@ public void jsonObjectByLocaleBean() {
MyLocaleBean myLocaleBean = new MyLocaleBean();
- /**
- * This is just the control case which happens when the locale.ROOT
- * lowercasing behavior is the same as the current locale.
- */
- Locale.setDefault(new Locale("en"));
- JSONObject jsonen = new JSONObject(myLocaleBean);
- assertEquals("expected size 2, found: " +jsonen.length(), 2, jsonen.length());
- assertEquals("expected jsonen[i] == beanI", "beanI", jsonen.getString("i"));
- assertEquals("expected jsonen[id] == beanId", "beanId", jsonen.getString("id"));
+ // save and restore the current default locale, to avoid any side effects on other executions in the same JVM
+ Locale defaultLocale = Locale.getDefault();
+ try {
+ /**
+ * This is just the control case which happens when the locale.ROOT
+ * lowercasing behavior is the same as the current locale.
+ */
+ Locale.setDefault(new Locale("en"));
+ JSONObject jsonen = new JSONObject(myLocaleBean);
+ assertEquals("expected size 2, found: " +jsonen.length(), 2, jsonen.length());
+ assertEquals("expected jsonen[i] == beanI", "beanI", jsonen.getString("i"));
+ assertEquals("expected jsonen[id] == beanId", "beanId", jsonen.getString("id"));
- /**
- * Without the JSON-Java change, these keys would be stored internally as
- * starting with the letter, 'ı' (dotless i), since the lowercasing of
- * the getI and getId keys would be specific to the Turkish locale.
- */
- Locale.setDefault(new Locale("tr"));
- JSONObject jsontr = new JSONObject(myLocaleBean);
- assertEquals("expected size 2, found: " +jsontr.length(), 2, jsontr.length());
- assertEquals("expected jsontr[i] == beanI", "beanI", jsontr.getString("i"));
- assertEquals("expected jsontr[id] == beanId", "beanId", jsontr.getString("id"));
+ /**
+ * Without the JSON-Java change, these keys would be stored internally as
+ * starting with the letter, 'ı' (dotless i), since the lowercasing of
+ * the getI and getId keys would be specific to the Turkish locale.
+ */
+ Locale.setDefault(new Locale("tr"));
+ JSONObject jsontr = new JSONObject(myLocaleBean);
+ assertEquals("expected size 2, found: " +jsontr.length(), 2, jsontr.length());
+ assertEquals("expected jsontr[i] == beanI", "beanI", jsontr.getString("i"));
+ assertEquals("expected jsontr[id] == beanId", "beanId", jsontr.getString("id"));
+ } finally {
+ Locale.setDefault(defaultLocale);
+ }
}
}
diff --git a/src/test/java/org/json/junit/JSONObjectNumberTest.java b/src/test/java/org/json/junit/JSONObjectNumberTest.java
new file mode 100644
index 000000000..0f2af2902
--- /dev/null
+++ b/src/test/java/org/json/junit/JSONObjectNumberTest.java
@@ -0,0 +1,146 @@
+package org.json.junit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(value = Parameterized.class)
+public class JSONObjectNumberTest {
+ private final String objectString;
+ private Integer value = 50;
+
+ @Parameters(name = "{index}: {0}")
+ public static Collection data() {
+ return Arrays.asList(new Object[][]{
+ {"{\"value\":50}", 1},
+ {"{\"value\":50.0}", 1},
+ {"{\"value\":5e1}", 1},
+ {"{\"value\":5E1}", 1},
+ {"{\"value\":5e1}", 1},
+ {"{\"value\":\"50\"}", 1},
+ {"{\"value\":-50}", -1},
+ {"{\"value\":-50.0}", -1},
+ {"{\"value\":-5e1}", -1},
+ {"{\"value\":-5E1}", -1},
+ {"{\"value\":-5e1}", -1},
+ {"{\"value\":\"-50\"}", -1}
+ // JSON does not support octal or hex numbers;
+ // see https://stackoverflow.com/a/52671839/6323312
+ // "{value:062}", // octal 50
+ // "{value:0x32}" // hex 50
+ });
+ }
+
+ public JSONObjectNumberTest(String objectString, int resultIsNegative) {
+ this.objectString = objectString;
+ this.value *= resultIsNegative;
+ }
+
+ private JSONObject object;
+
+ @Before
+ public void setJsonObject() {
+ object = new JSONObject(objectString);
+ }
+
+ @Test
+ public void testGetNumber() {
+ assertEquals(value.intValue(), object.getNumber("value").intValue());
+ }
+
+ @Test
+ public void testGetBigDecimal() {
+ assertTrue(BigDecimal.valueOf(value).compareTo(object.getBigDecimal("value")) == 0);
+ }
+
+ @Test
+ public void testGetBigInteger() {
+ assertEquals(BigInteger.valueOf(value), object.getBigInteger("value"));
+ }
+
+ @Test
+ public void testGetFloat() {
+ assertEquals(value.floatValue(), object.getFloat("value"), 0.0f);
+ }
+
+ @Test
+ public void testGetDouble() {
+ assertEquals(value.doubleValue(), object.getDouble("value"), 0.0d);
+ }
+
+ @Test
+ public void testGetInt() {
+ assertEquals(value.intValue(), object.getInt("value"));
+ }
+
+ @Test
+ public void testGetLong() {
+ assertEquals(value.longValue(), object.getLong("value"));
+ }
+
+ @Test
+ public void testOptNumber() {
+ assertEquals(value.intValue(), object.optNumber("value").intValue());
+ }
+
+ @Test
+ public void testOptBigDecimal() {
+ assertTrue(BigDecimal.valueOf(value).compareTo(object.optBigDecimal("value", null)) == 0);
+ }
+
+ @Test
+ public void testOptBigInteger() {
+ assertEquals(BigInteger.valueOf(value), object.optBigInteger("value", null));
+ }
+
+ @Test
+ public void testOptFloat() {
+ assertEquals(value.floatValue(), object.optFloat("value"), 0.0f);
+ }
+
+ @Test
+ public void testOptFloatObject() {
+ assertEquals((Float) value.floatValue(), object.optFloatObject("value"), 0.0f);
+ }
+
+ @Test
+ public void testOptDouble() {
+ assertEquals(value.doubleValue(), object.optDouble("value"), 0.0d);
+ }
+
+ @Test
+ public void testOptDoubleObject() {
+ assertEquals((Double) value.doubleValue(), object.optDoubleObject("value"), 0.0d);
+ }
+
+ @Test
+ public void testOptInt() {
+ assertEquals(value.intValue(), object.optInt("value"));
+ }
+
+ @Test
+ public void testOptIntegerObject() {
+ assertEquals((Integer) value.intValue(), object.optIntegerObject("value"));
+ }
+
+ @Test
+ public void testOptLong() {
+ assertEquals(value.longValue(), object.optLong("value"));
+ }
+
+ @Test
+ public void testOptLongObject() {
+ assertEquals((Long) value.longValue(), object.optLongObject("value"));
+ }
+}
diff --git a/src/test/java/org/json/junit/JSONObjectRecordTest.java b/src/test/java/org/json/junit/JSONObjectRecordTest.java
new file mode 100644
index 000000000..f1a673d28
--- /dev/null
+++ b/src/test/java/org/json/junit/JSONObjectRecordTest.java
@@ -0,0 +1,179 @@
+package org.json.junit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.StringReader;
+
+import org.json.JSONObject;
+import org.json.junit.data.GenericBeanInt;
+import org.json.junit.data.MyEnum;
+import org.json.junit.data.MyNumber;
+import org.json.junit.data.PersonRecord;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Tests for JSONObject support of Java record types.
+ *
+ * NOTE: These tests are currently ignored because PersonRecord is not an actual Java record.
+ * The implementation now correctly detects actual Java records using reflection (Class.isRecord()).
+ * These tests will need to be enabled and run with Java 17+ where PersonRecord can be converted
+ * to an actual record type.
+ *
+ * This ensures backward compatibility - regular classes with lowercase method names will not
+ * be treated as records unless they are actual Java record types.
+ */
+public class JSONObjectRecordTest {
+
+ /**
+ * Tests that JSONObject can be created from a record-style class.
+ * Record-style classes use accessor methods like name() instead of getName().
+ *
+ * NOTE: Ignored until PersonRecord is converted to an actual Java record (requires Java 17+)
+ */
+ @Test
+ @Ignore("Requires actual Java record type - PersonRecord needs to be a real record (Java 17+)")
+ public void jsonObjectByRecord() {
+ PersonRecord person = new PersonRecord("John Doe", 30, true);
+ JSONObject jsonObject = new JSONObject(person);
+
+ assertEquals("Expected 3 keys in the JSONObject", 3, jsonObject.length());
+ assertEquals("John Doe", jsonObject.get("name"));
+ assertEquals(30, jsonObject.get("age"));
+ assertEquals(true, jsonObject.get("active"));
+ }
+
+ /**
+ * Test that Object methods (toString, hashCode, equals, etc.) are not included
+ *
+ * NOTE: Ignored until PersonRecord is converted to an actual Java record (requires Java 17+)
+ */
+ @Test
+ @Ignore("Requires actual Java record type - PersonRecord needs to be a real record (Java 17+)")
+ public void recordStyleClassShouldNotIncludeObjectMethods() {
+ PersonRecord person = new PersonRecord("Jane Doe", 25, false);
+ JSONObject jsonObject = new JSONObject(person);
+
+ // Should NOT include Object methods
+ assertFalse("Should not include toString", jsonObject.has("toString"));
+ assertFalse("Should not include hashCode", jsonObject.has("hashCode"));
+ assertFalse("Should not include equals", jsonObject.has("equals"));
+ assertFalse("Should not include clone", jsonObject.has("clone"));
+ assertFalse("Should not include wait", jsonObject.has("wait"));
+ assertFalse("Should not include notify", jsonObject.has("notify"));
+ assertFalse("Should not include notifyAll", jsonObject.has("notifyAll"));
+
+ // Should only have the 3 record fields
+ assertEquals("Should only have 3 fields", 3, jsonObject.length());
+ }
+
+ /**
+ * Test that enum methods are not included when processing an enum
+ */
+ @Test
+ public void enumsShouldNotIncludeEnumMethods() {
+ MyEnum myEnum = MyEnum.VAL1;
+ JSONObject jsonObject = new JSONObject(myEnum);
+
+ // Should NOT include enum-specific methods like name(), ordinal(), values(), valueOf()
+ assertFalse("Should not include name method", jsonObject.has("name"));
+ assertFalse("Should not include ordinal method", jsonObject.has("ordinal"));
+ assertFalse("Should not include declaringClass", jsonObject.has("declaringClass"));
+
+ // Enums should still work with traditional getters if they have any
+ // But should not pick up the built-in enum methods
+ }
+
+ /**
+ * Test that Number subclass methods are not included
+ */
+ @Test
+ public void numberSubclassesShouldNotIncludeNumberMethods() {
+ MyNumber myNumber = new MyNumber();
+ JSONObject jsonObject = new JSONObject(myNumber);
+
+ // Should NOT include Number methods like intValue(), longValue(), etc.
+ assertFalse("Should not include intValue", jsonObject.has("intValue"));
+ assertFalse("Should not include longValue", jsonObject.has("longValue"));
+ assertFalse("Should not include doubleValue", jsonObject.has("doubleValue"));
+ assertFalse("Should not include floatValue", jsonObject.has("floatValue"));
+
+ // Should include the actual getter
+ assertTrue("Should include number", jsonObject.has("number"));
+ assertEquals("Should have 1 field", 1, jsonObject.length());
+ }
+
+ /**
+ * Test that generic bean with get() and is() methods works correctly
+ */
+ @Test
+ public void genericBeanWithGetAndIsMethodsShouldNotBeIncluded() {
+ GenericBeanInt bean = new GenericBeanInt(42);
+ JSONObject jsonObject = new JSONObject(bean);
+
+ // Should NOT include standalone get() or is() methods
+ assertFalse("Should not include standalone 'get' method", jsonObject.has("get"));
+ assertFalse("Should not include standalone 'is' method", jsonObject.has("is"));
+
+ // Should include the actual getters
+ assertTrue("Should include genericValue field", jsonObject.has("genericValue"));
+ assertTrue("Should include a field", jsonObject.has("a"));
+ }
+
+ /**
+ * Test that java.* classes don't have their methods picked up
+ */
+ @Test
+ public void javaLibraryClassesShouldNotIncludeTheirMethods() {
+ StringReader reader = new StringReader("test");
+ JSONObject jsonObject = new JSONObject(reader);
+
+ // Should NOT include java.io.Reader methods like read(), reset(), etc.
+ assertFalse("Should not include read method", jsonObject.has("read"));
+ assertFalse("Should not include reset method", jsonObject.has("reset"));
+ assertFalse("Should not include ready method", jsonObject.has("ready"));
+ assertFalse("Should not include skip method", jsonObject.has("skip"));
+
+ // Reader should produce empty JSONObject (no valid properties)
+ assertEquals("Reader should produce empty JSON", 0, jsonObject.length());
+ }
+
+ /**
+ * Test mixed case - object with both traditional getters and record-style accessors
+ *
+ * NOTE: Ignored until PersonRecord is converted to an actual Java record (requires Java 17+)
+ */
+ @Test
+ @Ignore("Requires actual Java record type - PersonRecord needs to be a real record (Java 17+)")
+ public void mixedGettersAndRecordStyleAccessors() {
+ // PersonRecord has record-style accessors: name(), age(), active()
+ // These should all be included
+ PersonRecord person = new PersonRecord("Mixed Test", 40, true);
+ JSONObject jsonObject = new JSONObject(person);
+
+ assertEquals("Should have all 3 record-style fields", 3, jsonObject.length());
+ assertTrue("Should include name", jsonObject.has("name"));
+ assertTrue("Should include age", jsonObject.has("age"));
+ assertTrue("Should include active", jsonObject.has("active"));
+ }
+
+ /**
+ * Test that methods starting with uppercase are not included (not valid record accessors)
+ *
+ * NOTE: Ignored until PersonRecord is converted to an actual Java record (requires Java 17+)
+ */
+ @Test
+ @Ignore("Requires actual Java record type - PersonRecord needs to be a real record (Java 17+)")
+ public void methodsStartingWithUppercaseShouldNotBeIncluded() {
+ PersonRecord person = new PersonRecord("Test", 50, false);
+ JSONObject jsonObject = new JSONObject(person);
+
+ // Record-style accessors must start with lowercase
+ // Methods like Name(), Age() (uppercase) should not be picked up
+ // Our PersonRecord only has lowercase accessors, which is correct
+
+ assertEquals("Should only have lowercase accessors", 3, jsonObject.length());
+ }
+}
diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java
index 51407f05e..5c1d1a2eb 100644
--- a/src/test/java/org/json/junit/JSONObjectTest.java
+++ b/src/test/java/org/json/junit/JSONObjectTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.assertEquals;
@@ -29,25 +9,20 @@ of this software and associated documentation files (the "Software"), to deal
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.IOException;
+import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
+import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
@@ -56,7 +31,10 @@ of this software and associated documentation files (the "Software"), to deal
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONPointerException;
+import org.json.JSONParserConfiguration;
+import org.json.JSONString;
import org.json.JSONTokener;
+import org.json.ParserConfiguration;
import org.json.XML;
import org.json.junit.data.BrokenToString;
import org.json.junit.data.ExceptionalBean;
@@ -73,9 +51,24 @@ of this software and associated documentation files (the "Software"), to deal
import org.json.junit.data.MyNumber;
import org.json.junit.data.MyNumberContainer;
import org.json.junit.data.MyPublicClass;
+import org.json.junit.data.RecursiveBean;
+import org.json.junit.data.RecursiveBeanEquals;
import org.json.junit.data.Singleton;
import org.json.junit.data.SingletonEnum;
import org.json.junit.data.WeirdList;
+import org.json.junit.data.CustomClass;
+import org.json.junit.data.CustomClassA;
+import org.json.junit.data.CustomClassB;
+import org.json.junit.data.CustomClassC;
+import org.json.junit.data.CustomClassD;
+import org.json.junit.data.CustomClassE;
+import org.json.junit.data.CustomClassF;
+import org.json.junit.data.CustomClassG;
+import org.json.junit.data.CustomClassH;
+import org.json.junit.data.CustomClassI;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Ignore;
import org.junit.Test;
import com.jayway.jsonpath.Configuration;
@@ -94,12 +87,21 @@ public class JSONObjectTest {
*/
static final Pattern NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?");
+ @After
+ public void tearDown() {
+ SingletonEnum.getInstance().setSomeInt(0);
+ SingletonEnum.getInstance().setSomeString(null);
+ Singleton.getInstance().setSomeInt(0);
+ Singleton.getInstance().setSomeString(null);
+ }
+
/**
* Tests that the similar method is working as expected.
*/
@Test
public void verifySimilar() {
final String string1 = "HasSameRef";
+ final String string2 = "HasDifferentRef";
JSONObject obj1 = new JSONObject()
.put("key1", "abc")
.put("key2", 2)
@@ -115,12 +117,33 @@ public void verifySimilar() {
.put("key2", 2)
.put("key3", new String(string1));
- assertFalse("Should eval to false", obj1.similar(obj2));
-
- assertTrue("Should eval to true", obj1.similar(obj3));
+ JSONObject obj4 = new JSONObject()
+ .put("key1", "abc")
+ .put("key2", 2.0)
+ .put("key3", new String(string1));
+
+ JSONObject obj5 = new JSONObject()
+ .put("key1", "abc")
+ .put("key2", 2.0)
+ .put("key3", new String(string2));
+ assertFalse("obj1-obj2 Should eval to false", obj1.similar(obj2));
+ assertTrue("obj1-obj3 Should eval to true", obj1.similar(obj3));
+ assertTrue("obj1-obj4 Should eval to true", obj1.similar(obj4));
+ assertFalse("obj1-obj5 Should eval to false", obj1.similar(obj5));
+ // verify that a double and big decimal are "similar"
+ assertTrue("should eval to true",new JSONObject().put("a",1.1d).similar(new JSONObject("{\"a\":1.1}")));
+ // Confirm #618 is fixed (compare should not exit early if similar numbers are found)
+ // Note that this test may not work if the JSONObject map entry order changes
+ JSONObject first = new JSONObject("{\"a\": 1, \"b\": 2, \"c\": 3}");
+ JSONObject second = new JSONObject("{\"a\": 1, \"b\": 2.0, \"c\": 4}");
+ assertFalse("first-second should eval to false", first.similar(second));
+ List jsonObjects = new ArrayList(
+ Arrays.asList(obj1, obj2, obj3, obj4, obj5)
+ );
+ Util.checkJSONObjectsMaps(jsonObjects);
}
-
+
@Test
public void timeNumberParsing() {
// test data to use
@@ -193,7 +216,9 @@ public void timeNumberParsing() {
*/
@Test(expected=NullPointerException.class)
public void jsonObjectByNullBean() {
- assertNull("Expected an exception",new JSONObject((MyBean)null));
+ JSONObject jsonObject = new JSONObject((MyBean)null);
+ assertNull("Expected an exception", jsonObject);
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -204,13 +229,28 @@ public void jsonObjectByNullBean() {
*/
@Test
public void unquotedText() {
- String str = "{key1:value1, key2:42}";
- JSONObject jsonObject = new JSONObject(str);
- String textStr = jsonObject.toString();
- assertTrue("expected key1", textStr.contains("\"key1\""));
- assertTrue("expected value1", textStr.contains("\"value1\""));
- assertTrue("expected key2", textStr.contains("\"key2\""));
- assertTrue("expected 42", textStr.contains("42"));
+ String str = "{key1:value1, key2:42, 1.2 : 3.4, -7e5 : something!}";
+
+ // Test should fail if default strictMode is true, pass if false
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+ if (jsonParserConfiguration.isStrictMode()) {
+ try {
+ JSONObject jsonObject = new JSONObject(str);
+ assertEquals("Expected to throw exception due to invalid string", true, false);
+ } catch (JSONException e) { }
+ } else {
+ JSONObject jsonObject = new JSONObject(str);
+ String textStr = jsonObject.toString();
+ assertTrue("expected key1", textStr.contains("\"key1\""));
+ assertTrue("expected value1", textStr.contains("\"value1\""));
+ assertTrue("expected key2", textStr.contains("\"key2\""));
+ assertTrue("expected 42", textStr.contains("42"));
+ assertTrue("expected 1.2", textStr.contains("\"1.2\""));
+ assertTrue("expected 3.4", textStr.contains("3.4"));
+ assertTrue("expected -7E+5", textStr.contains("\"-7E+5\""));
+ assertTrue("expected something!", textStr.contains("\"something!\""));
+ Util.checkJSONObjectMaps(jsonObject);
+ }
}
@Test
@@ -228,9 +268,15 @@ public void testLongFromString(){
assert 26315000000253009L == actualLong : "Incorrect key value. Got "
+ actualLong + " expected " + str;
+ final Long actualLongObject = json.optLongObject("key");
+ assert actualLongObject != 0L : "Unable to extract Long value for string " + str;
+ assert Long.valueOf(26315000000253009L).equals(actualLongObject) : "Incorrect key value. Got "
+ + actualLongObject + " expected " + str;
+
final String actualString = json.optString("key");
assert str.equals(actualString) : "Incorrect key value. Got "
+ actualString + " expected " + str;
+ Util.checkJSONObjectMaps(json);
}
/**
@@ -240,6 +286,7 @@ public void testLongFromString(){
public void emptyJsonObject() {
JSONObject jsonObject = new JSONObject();
assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -270,6 +317,7 @@ public void jsonObjectByNames() {
assertTrue("expected \"nullKey\":null", JSONObject.NULL.equals(jsonObjectByName.query("/nullKey")));
assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObjectByName.query("/stringKey")));
assertTrue("expected \"doubleKey\":-23.45e67", new BigDecimal("-23.45e67").equals(jsonObjectByName.query("/doubleKey")));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(jsonObject, jsonObjectByName)));
}
/**
@@ -283,6 +331,7 @@ public void jsonObjectByNullMap() {
Map map = null;
JSONObject jsonObject = new JSONObject(map);
assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -292,12 +341,12 @@ public void jsonObjectByNullMap() {
@Test
public void jsonObjectByMap() {
Map map = new HashMap();
- map.put("trueKey", new Boolean(true));
- map.put("falseKey", new Boolean(false));
+ map.put("trueKey", Boolean.valueOf(true));
+ map.put("falseKey", Boolean.valueOf(false));
map.put("stringKey", "hello world!");
map.put("escapeStringKey", "h\be\tllo w\u1234orld!");
- map.put("intKey", new Long(42));
- map.put("doubleKey", new Double(-23.45e67));
+ map.put("intKey", Long.valueOf(42));
+ map.put("doubleKey", Double.valueOf(-23.45e67));
JSONObject jsonObject = new JSONObject(map);
// validate JSON
@@ -308,6 +357,7 @@ public void jsonObjectByMap() {
assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObject.query("/stringKey")));
assertTrue("expected \"escapeStringKey\":\"h\be\tllo w\u1234orld!\"", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey")));
assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -346,6 +396,9 @@ public void verifyConstructor() {
assertTrue(
"The RAW Collection should give me the same as the Typed Collection",
expected.similar(jaObjObj));
+ Util.checkJSONObjectsMaps(new ArrayList(
+ Arrays.asList(jaRaw, jaStrObj, jaStrInt, jaObjObj))
+ );
}
/**
@@ -363,8 +416,8 @@ public void verifyNumberOutput(){
* The only getter is getNumber (key=number), whose return value is
* BigDecimal(42).
*/
- JSONObject jsonObject = new JSONObject(new MyNumberContainer());
- String actual = jsonObject.toString();
+ JSONObject jsonObject0 = new JSONObject(new MyNumberContainer());
+ String actual = jsonObject0.toString();
String expected = "{\"myNumber\":{\"number\":42}}";
assertEquals("Equal", expected , actual);
@@ -376,9 +429,9 @@ public void verifyNumberOutput(){
* The MyNumber.toString() method is responsible for
* returning a reasonable value: the string '42'.
*/
- jsonObject = new JSONObject();
- jsonObject.put("myNumber", new MyNumber());
- actual = jsonObject.toString();
+ JSONObject jsonObject1 = new JSONObject();
+ jsonObject1.put("myNumber", new MyNumber());
+ actual = jsonObject1.toString();
expected = "{\"myNumber\":42}";
assertEquals("Equal", expected , actual);
@@ -390,8 +443,8 @@ public void verifyNumberOutput(){
* wrap() inserts the value as a string. That is why 42 comes back
* wrapped in quotes.
*/
- jsonObject = new JSONObject(Collections.singletonMap("myNumber", new AtomicInteger(42)));
- actual = jsonObject.toString();
+ JSONObject jsonObject2 = new JSONObject(Collections.singletonMap("myNumber", new AtomicInteger(42)));
+ actual = jsonObject2.toString();
expected = "{\"myNumber\":\"42\"}";
assertEquals("Equal", expected , actual);
@@ -401,9 +454,9 @@ public void verifyNumberOutput(){
* AtomicInteger is recognized as a Number, and converted via
* numberToString() into the unquoted string '42'.
*/
- jsonObject = new JSONObject();
- jsonObject.put("myNumber", new AtomicInteger(42));
- actual = jsonObject.toString();
+ JSONObject jsonObject3 = new JSONObject();
+ jsonObject3.put("myNumber", new AtomicInteger(42));
+ actual = jsonObject3.toString();
expected = "{\"myNumber\":42}";
assertEquals("Equal", expected , actual);
@@ -414,11 +467,11 @@ public void verifyNumberOutput(){
* bean and inserted into a contained JSONObject. It has 2 getters,
* for numerator and denominator.
*/
- jsonObject = new JSONObject(Collections.singletonMap("myNumber", new Fraction(4,2)));
- assertEquals(1, jsonObject.length());
- assertEquals(2, ((JSONObject)(jsonObject.get("myNumber"))).length());
- assertEquals("Numerator", BigInteger.valueOf(4) , jsonObject.query("/myNumber/numerator"));
- assertEquals("Denominator", BigInteger.valueOf(2) , jsonObject.query("/myNumber/denominator"));
+ JSONObject jsonObject4 = new JSONObject(Collections.singletonMap("myNumber", new Fraction(4,2)));
+ assertEquals(1, jsonObject4.length());
+ assertEquals(2, ((JSONObject)(jsonObject4.get("myNumber"))).length());
+ assertEquals("Numerator", BigInteger.valueOf(4) , jsonObject4.query("/myNumber/numerator"));
+ assertEquals("Denominator", BigInteger.valueOf(2) , jsonObject4.query("/myNumber/denominator"));
/**
* JSONObject.put() inserts the Fraction directly into the
@@ -428,11 +481,15 @@ public void verifyNumberOutput(){
* BigDecimal sanity check fails, so writeValue() defaults
* to returning a safe JSON quoted string. Pretty slick!
*/
- jsonObject = new JSONObject();
- jsonObject.put("myNumber", new Fraction(4,2));
- actual = jsonObject.toString();
+ JSONObject jsonObject5 = new JSONObject();
+ jsonObject5.put("myNumber", new Fraction(4,2));
+ actual = jsonObject5.toString();
expected = "{\"myNumber\":\"4/2\"}"; // valid JSON, bug fixed
assertEquals("Equal", expected , actual);
+
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObject0, jsonObject1, jsonObject2, jsonObject3, jsonObject4, jsonObject5
+ )));
}
/**
@@ -467,6 +524,10 @@ public void verifyPutCollection() {
assertTrue(
"The RAW Collection should give me the same as the Typed Collection",
expected.similar(jaInt));
+
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jaRaw, jaObj, jaInt
+ )));
}
@@ -510,6 +571,10 @@ public void verifyPutMap() {
assertTrue(
"The RAW Collection should give me the same as the Typed Collection",
expected.similar(jaObjObj));
+
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jaRaw, jaStrObj, jaStrInt, jaStrObj
+ )));
}
@@ -532,6 +597,7 @@ public void jsonObjectByMapWithUnsupportedValues() {
assertTrue("expected 2 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 2);
assertTrue("expected 0 key1 items", ((Map,?>)(JsonPath.read(doc, "$.key1"))).size() == 0);
assertTrue("expected \"key2\":java.lang.Exception","java.lang.Exception".equals(jsonObject.query("/key2")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -541,13 +607,13 @@ public void jsonObjectByMapWithUnsupportedValues() {
@Test
public void jsonObjectByMapWithNullValue() {
Map map = new HashMap();
- map.put("trueKey", new Boolean(true));
- map.put("falseKey", new Boolean(false));
+ map.put("trueKey", Boolean.valueOf(true));
+ map.put("falseKey", Boolean.valueOf(false));
map.put("stringKey", "hello world!");
map.put("nullKey", null);
map.put("escapeStringKey", "h\be\tllo w\u1234orld!");
- map.put("intKey", new Long(42));
- map.put("doubleKey", new Double(-23.45e67));
+ map.put("intKey", Long.valueOf(42));
+ map.put("doubleKey", Double.valueOf(-23.45e67));
JSONObject jsonObject = new JSONObject(map);
// validate JSON
@@ -559,6 +625,47 @@ public void jsonObjectByMapWithNullValue() {
assertTrue("expected \"escapeStringKey\":\"h\be\tllo w\u1234orld!\"", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey")));
assertTrue("expected \"intKey\":42", Long.valueOf("42").equals(jsonObject.query("/intKey")));
assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey")));
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ @Test
+ public void jsonObjectByMapWithNullValueAndParserConfiguration() {
+ Map map = new HashMap();
+ map.put("nullKey", null);
+
+ // by default, null values are ignored
+ JSONObject obj1 = new JSONObject(map);
+ assertTrue("expected null value to be ignored by default", obj1.isEmpty());
+
+ // if configured, null values are written as such into the JSONObject.
+ JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withUseNativeNulls(true);
+ JSONObject obj2 = new JSONObject(map, parserConfiguration);
+ assertFalse("expected null value to accepted when configured", obj2.isEmpty());
+ assertTrue(obj2.has("nullKey"));
+ assertEquals(JSONObject.NULL, obj2.get("nullKey"));
+ }
+
+ @Test
+ public void jsonObjectByMapWithNestedNullValueAndParserConfiguration() {
+ Map map = new HashMap();
+ Map nestedMap = new HashMap();
+ nestedMap.put("nullKey", null);
+ map.put("nestedMap", nestedMap);
+ List> nestedList = new ArrayList>();
+ nestedList.add(nestedMap);
+ map.put("nestedList", nestedList);
+
+ JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withUseNativeNulls(true);
+ JSONObject jsonObject = new JSONObject(map, parserConfiguration);
+
+ JSONObject nestedObject = jsonObject.getJSONObject("nestedMap");
+ assertTrue(nestedObject.has("nullKey"));
+ assertEquals(JSONObject.NULL, nestedObject.get("nullKey"));
+
+ JSONArray nestedArray = jsonObject.getJSONArray("nestedList");
+ assertEquals(1, nestedArray.length());
+ assertTrue(nestedArray.getJSONObject(0).has("nullKey"));
+ assertEquals(JSONObject.NULL, nestedArray.getJSONObject(0).get("nullKey"));
}
/**
@@ -596,9 +703,10 @@ public void jsonObjectByBean1() {
assertTrue("expected 42", Integer.valueOf("42").equals(jsonObject.query("/intKey")));
assertTrue("expected -23.45e7", Double.valueOf("-23.45e7").equals(jsonObject.query("/doubleKey")));
// sorry, mockito artifact
- assertTrue("expected 2 callbacks items", ((List>)(JsonPath.read(doc, "$.callbacks"))).size() == 2);
- assertTrue("expected 0 handler items", ((Map,?>)(JsonPath.read(doc, "$.callbacks[0].handler"))).size() == 0);
- assertTrue("expected 0 callbacks[1] items", ((Map,?>)(JsonPath.read(doc, "$.callbacks[1]"))).size() == 0);
+ assertTrue("expected 2 mockitoInterceptor items", ((Map,?>)(JsonPath.read(doc, "$.mockitoInterceptor"))).size() == 2);
+ assertTrue("expected 0 mockitoInterceptor.serializationSupport items",
+ ((Map,?>)(JsonPath.read(doc, "$.mockitoInterceptor.serializationSupport"))).size() == 0);
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -634,6 +742,7 @@ public void jsonObjectByBean2() {
// InterfaceField replaces someFloat property name via user-defined annotation
assertTrue("Overridden String field name (InterfaceField) should have been found",
jsonObject.has("InterfaceField"));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -684,6 +793,7 @@ public void jsonObjectByBean3() {
// property name able was replaced by Getable via user-defined annotation
assertTrue("Overridden boolean field name (Getable) should have been found",
jsonObject.has("Getable"));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -704,6 +814,7 @@ public void jsonObjectByObjectAndNames() {
assertTrue("expected 2 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 2);
assertTrue("expected \"publicString\":\"abc\"", "abc".equals(jsonObject.query("/publicString")));
assertTrue("expected \"publicInt\":42", Integer.valueOf(42).equals(jsonObject.query("/publicInt")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -725,6 +836,7 @@ public void jsonObjectByResourceBundle() {
assertTrue("expected 2 farewells items", ((Map,?>)(JsonPath.read(doc, "$.farewells"))).size() == 2);
assertTrue("expected \"later\":\"Later, \"", "Later, ".equals(jsonObject.query("/farewells/later")));
assertTrue("expected \"world\":\"World!\"", "Alligator!".equals(jsonObject.query("/farewells/gator")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -757,6 +869,7 @@ public void jsonObjectAccumulate() {
assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/myArray/3")));
assertTrue("expected 42", Integer.valueOf(42).equals(jsonObject.query("/myArray/4")));
assertTrue("expected -23.45e7", Double.valueOf(-23.45e7).equals(jsonObject.query("/myArray/5")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -788,6 +901,7 @@ public void jsonObjectAppend() {
assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/myArray/3")));
assertTrue("expected 42", Integer.valueOf(42).equals(jsonObject.query("/myArray/4")));
assertTrue("expected -23.45e7", Double.valueOf(-23.45e7).equals(jsonObject.query("/myArray/5")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -798,7 +912,7 @@ public void jsonObjectAppend() {
public void jsonObjectDoubleToString() {
String [] expectedStrs = {"1", "1", "-23.4", "-2.345E68", "null", "null" };
Double [] doubles = { 1.0, 00001.00000, -23.4, -23.45e67,
- Double.NaN, Double.NEGATIVE_INFINITY };
+ Double.NaN, Double.NEGATIVE_INFINITY };
for (int i = 0; i < expectedStrs.length; ++i) {
String actualStr = JSONObject.doubleToString(doubles[i]);
assertTrue("value expected ["+expectedStrs[i]+
@@ -834,9 +948,11 @@ public void jsonObjectValues() {
JSONObject jsonObject = new JSONObject(str);
assertTrue("trueKey should be true", jsonObject.getBoolean("trueKey"));
assertTrue("opt trueKey should be true", jsonObject.optBoolean("trueKey"));
+ assertTrue("opt trueKey should be true", jsonObject.optBooleanObject("trueKey"));
assertTrue("falseKey should be false", !jsonObject.getBoolean("falseKey"));
assertTrue("trueStrKey should be true", jsonObject.getBoolean("trueStrKey"));
assertTrue("trueStrKey should be true", jsonObject.optBoolean("trueStrKey"));
+ assertTrue("trueStrKey should be true", jsonObject.optBooleanObject("trueStrKey"));
assertTrue("falseStrKey should be false", !jsonObject.getBoolean("falseStrKey"));
assertTrue("stringKey should be string",
jsonObject.getString("stringKey").equals("hello world!"));
@@ -852,6 +968,10 @@ public void jsonObjectValues() {
jsonObject.optDouble("doubleKey") == -23.45e7);
assertTrue("opt doubleKey with Default should be double",
jsonObject.optDouble("doubleStrKey", Double.NaN) == 1);
+ assertTrue("opt doubleKey should be Double",
+ Double.valueOf(-23.45e7).equals(jsonObject.optDoubleObject("doubleKey")));
+ assertTrue("opt doubleKey with Default should be Double",
+ Double.valueOf(1).equals(jsonObject.optDoubleObject("doubleStrKey", Double.NaN)));
assertTrue("opt negZeroKey should be a Double",
jsonObject.opt("negZeroKey") instanceof Double);
assertTrue("get negZeroKey should be a Double",
@@ -864,6 +984,10 @@ public void jsonObjectValues() {
Double.compare(jsonObject.optDouble("negZeroKey"), -0.0d) == 0);
assertTrue("opt negZeroStrKey with Default should be double",
Double.compare(jsonObject.optDouble("negZeroStrKey"), -0.0d) == 0);
+ assertTrue("opt negZeroKey should be Double",
+ Double.valueOf(-0.0d).equals(jsonObject.optDoubleObject("negZeroKey")));
+ assertTrue("opt negZeroStrKey with Default should be Double",
+ Double.valueOf(-0.0d).equals(jsonObject.optDoubleObject("negZeroStrKey")));
assertTrue("optNumber negZeroKey should be -0.0",
Double.compare(jsonObject.optNumber("negZeroKey").doubleValue(), -0.0d) == 0);
assertTrue("optNumber negZeroStrKey should be -0.0",
@@ -872,10 +996,18 @@ public void jsonObjectValues() {
jsonObject.optFloat("doubleKey") == -23.45e7f);
assertTrue("optFloat doubleKey with Default should be float",
jsonObject.optFloat("doubleStrKey", Float.NaN) == 1f);
+ assertTrue("optFloat doubleKey should be Float",
+ Float.valueOf(-23.45e7f).equals(jsonObject.optFloatObject("doubleKey")));
+ assertTrue("optFloat doubleKey with Default should be Float",
+ Float.valueOf(1f).equals(jsonObject.optFloatObject("doubleStrKey", Float.NaN)));
assertTrue("intKey should be int",
jsonObject.optInt("intKey") == 42);
assertTrue("opt intKey should be int",
jsonObject.optInt("intKey", 0) == 42);
+ assertTrue("intKey should be Integer",
+ Integer.valueOf(42).equals(jsonObject.optIntegerObject("intKey")));
+ assertTrue("opt intKey should be Integer",
+ Integer.valueOf(42).equals(jsonObject.optIntegerObject("intKey", 0)));
assertTrue("opt intKey with default should be int",
jsonObject.getInt("intKey") == 42);
assertTrue("intStrKey should be int",
@@ -886,6 +1018,10 @@ public void jsonObjectValues() {
jsonObject.optLong("longKey") == 1234567890123456789L);
assertTrue("opt longKey with default should be long",
jsonObject.optLong("longKey", 0) == 1234567890123456789L);
+ assertTrue("opt longKey should be Long",
+ Long.valueOf(1234567890123456789L).equals(jsonObject.optLongObject("longKey")));
+ assertTrue("opt longKey with default should be Long",
+ Long.valueOf(1234567890123456789L).equals(jsonObject.optLongObject("longKey", 0L)));
assertTrue("longStrKey should be long",
jsonObject.getLong("longStrKey") == 987654321098765432L);
assertTrue("optNumber int should return Integer",
@@ -923,6 +1059,7 @@ public void jsonObjectValues() {
JSONObject jsonObjectInner = jsonObject.getJSONObject("objectKey");
assertTrue("objectKey should be JSONObject",
jsonObjectInner.get("myKey").equals("myVal"));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -933,10 +1070,10 @@ public void stringToValueNumbersTest() {
assertTrue("-0 Should be a Double!",JSONObject.stringToValue("-0") instanceof Double);
assertTrue("-0.0 Should be a Double!",JSONObject.stringToValue("-0.0") instanceof Double);
assertTrue("'-' Should be a String!",JSONObject.stringToValue("-") instanceof String);
- assertTrue( "0.2 should be a Double!",
+ assertTrue( "0.2 should be a BigDecimal!",
JSONObject.stringToValue( "0.2" ) instanceof BigDecimal );
assertTrue( "Doubles should be BigDecimal, even when incorrectly converting floats!",
- JSONObject.stringToValue( new Double( "0.2f" ).toString() ) instanceof BigDecimal );
+ JSONObject.stringToValue( Double.valueOf( "0.2f" ).toString() ) instanceof BigDecimal );
/**
* This test documents a need for BigDecimal conversion.
*/
@@ -946,13 +1083,13 @@ public void stringToValueNumbersTest() {
assertTrue( "1 should be an Integer!",
JSONObject.stringToValue( "1" ) instanceof Integer );
assertTrue( "Integer.MAX_VALUE should still be an Integer!",
- JSONObject.stringToValue( new Integer( Integer.MAX_VALUE ).toString() ) instanceof Integer );
+ JSONObject.stringToValue( Integer.valueOf( Integer.MAX_VALUE ).toString() ) instanceof Integer );
assertTrue( "Large integers should be a Long!",
JSONObject.stringToValue( Long.valueOf(((long)Integer.MAX_VALUE) + 1 ) .toString() ) instanceof Long );
assertTrue( "Long.MAX_VALUE should still be an Integer!",
- JSONObject.stringToValue( new Long( Long.MAX_VALUE ).toString() ) instanceof Long );
+ JSONObject.stringToValue( Long.valueOf( Long.MAX_VALUE ).toString() ) instanceof Long );
- String str = new BigInteger( new Long( Long.MAX_VALUE ).toString() ).add( BigInteger.ONE ).toString();
+ String str = new BigInteger( Long.valueOf( Long.MAX_VALUE ).toString() ).add( BigInteger.ONE ).toString();
assertTrue( "Really large integers currently evaluate to BigInteger",
JSONObject.stringToValue(str).equals(new BigInteger("9223372036854775808")));
}
@@ -985,6 +1122,7 @@ public void jsonValidNumberValuesNeitherLongNorIEEE754Compatible() {
obj = jsonObject.get( "largeExponent" );
assertTrue("largeExponent should evaluate as a BigDecimal",
new BigDecimal("-23.45e2327").equals(obj));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -992,47 +1130,58 @@ public void jsonValidNumberValuesNeitherLongNorIEEE754Compatible() {
*/
@Test
public void jsonInvalidNumberValues() {
- // Number-notations supported by Java and invalid as JSON
- String str =
- "{"+
- "\"hexNumber\":-0x123,"+
- "\"tooManyZeros\":00,"+
- "\"negativeInfinite\":-Infinity,"+
- "\"negativeNaN\":-NaN,"+
- "\"negativeFraction\":-.01,"+
- "\"tooManyZerosFraction\":00.001,"+
- "\"negativeHexFloat\":-0x1.fffp1,"+
- "\"hexFloat\":0x1.0P-1074,"+
- "\"floatIdentifier\":0.1f,"+
- "\"doubleIdentifier\":0.1d"+
- "}";
- JSONObject jsonObject = new JSONObject(str);
- Object obj;
- obj = jsonObject.get( "hexNumber" );
- assertFalse( "hexNumber must not be a number (should throw exception!?)",
- obj instanceof Number );
- assertTrue("hexNumber currently evaluates to string",
- obj.equals("-0x123"));
- assertTrue( "tooManyZeros currently evaluates to string",
- jsonObject.get( "tooManyZeros" ).equals("00"));
- obj = jsonObject.get("negativeInfinite");
- assertTrue( "negativeInfinite currently evaluates to string",
- obj.equals("-Infinity"));
- obj = jsonObject.get("negativeNaN");
- assertTrue( "negativeNaN currently evaluates to string",
- obj.equals("-NaN"));
- assertTrue( "negativeFraction currently evaluates to double -0.01",
- jsonObject.get( "negativeFraction" ).equals(BigDecimal.valueOf(-0.01)));
- assertTrue( "tooManyZerosFraction currently evaluates to double 0.001",
- jsonObject.get( "tooManyZerosFraction" ).equals(BigDecimal.valueOf(0.001)));
- assertTrue( "negativeHexFloat currently evaluates to double -3.99951171875",
- jsonObject.get( "negativeHexFloat" ).equals(Double.valueOf(-3.99951171875)));
- assertTrue("hexFloat currently evaluates to double 4.9E-324",
- jsonObject.get("hexFloat").equals(Double.valueOf(4.9E-324)));
- assertTrue("floatIdentifier currently evaluates to double 0.1",
- jsonObject.get("floatIdentifier").equals(Double.valueOf(0.1)));
- assertTrue("doubleIdentifier currently evaluates to double 0.1",
- jsonObject.get("doubleIdentifier").equals(Double.valueOf(0.1)));
+ // Number-notations supported by Java and invalid as JSON
+ String str =
+ "{" +
+ "\"hexNumber\":-0x123," +
+ "\"tooManyZeros\":00," +
+ "\"negativeInfinite\":-Infinity," +
+ "\"negativeNaN\":-NaN," +
+ "\"negativeFraction\":-.01," +
+ "\"tooManyZerosFraction\":00.001," +
+ "\"negativeHexFloat\":-0x1.fffp1," +
+ "\"hexFloat\":0x1.0P-1074," +
+ "\"floatIdentifier\":0.1f," +
+ "\"doubleIdentifier\":0.1d" +
+ "}";
+
+ // Test should fail if default strictMode is true, pass if false
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+ if (jsonParserConfiguration.isStrictMode()) {
+ try {
+ JSONObject jsonObject = new JSONObject(str);
+ assertEquals("Expected to throw exception due to invalid string", true, false);
+ } catch (JSONException e) { }
+ } else {
+ JSONObject jsonObject = new JSONObject(str);
+ Object obj;
+ obj = jsonObject.get("hexNumber");
+ assertFalse("hexNumber must not be a number (should throw exception!?)",
+ obj instanceof Number);
+ assertTrue("hexNumber currently evaluates to string",
+ obj.equals("-0x123"));
+ assertTrue("tooManyZeros currently evaluates to string",
+ jsonObject.get("tooManyZeros").equals("00"));
+ obj = jsonObject.get("negativeInfinite");
+ assertTrue("negativeInfinite currently evaluates to string",
+ obj.equals("-Infinity"));
+ obj = jsonObject.get("negativeNaN");
+ assertTrue("negativeNaN currently evaluates to string",
+ obj.equals("-NaN"));
+ assertTrue("negativeFraction currently evaluates to double -0.01",
+ jsonObject.get("negativeFraction").equals(BigDecimal.valueOf(-0.01)));
+ assertTrue("tooManyZerosFraction currently evaluates to double 0.001",
+ jsonObject.optLong("tooManyZerosFraction") == 0);
+ assertTrue("negativeHexFloat currently evaluates to double -3.99951171875",
+ jsonObject.get("negativeHexFloat").equals(Double.valueOf(-3.99951171875)));
+ assertTrue("hexFloat currently evaluates to double 4.9E-324",
+ jsonObject.get("hexFloat").equals(Double.valueOf(4.9E-324)));
+ assertTrue("floatIdentifier currently evaluates to double 0.1",
+ jsonObject.get("floatIdentifier").equals(Double.valueOf(0.1)));
+ assertTrue("doubleIdentifier currently evaluates to double 0.1",
+ jsonObject.get("doubleIdentifier").equals(Double.valueOf(0.1)));
+ Util.checkJSONObjectMaps(jsonObject);
+ }
}
/**
@@ -1069,7 +1218,7 @@ public void jsonObjectNonAndWrongValues() {
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a Boolean.",
+ "JSONObject[\"stringKey\"] is not a Boolean (class java.lang.String : hello world!).",
e.getMessage());
}
try {
@@ -1085,7 +1234,7 @@ public void jsonObjectNonAndWrongValues() {
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONObject[\"trueKey\"] is not a string.",
+ "JSONObject[\"trueKey\"] is not a string (class java.lang.Boolean : true).",
e.getMessage());
}
try {
@@ -1101,7 +1250,7 @@ public void jsonObjectNonAndWrongValues() {
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a double.",
+ "JSONObject[\"stringKey\"] is not a double (class java.lang.String : hello world!).",
e.getMessage());
}
try {
@@ -1117,7 +1266,7 @@ public void jsonObjectNonAndWrongValues() {
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a float.",
+ "JSONObject[\"stringKey\"] is not a float (class java.lang.String : hello world!).",
e.getMessage());
}
try {
@@ -1133,7 +1282,7 @@ public void jsonObjectNonAndWrongValues() {
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a int.",
+ "JSONObject[\"stringKey\"] is not a int (class java.lang.String : hello world!).",
e.getMessage());
}
try {
@@ -1149,7 +1298,7 @@ public void jsonObjectNonAndWrongValues() {
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a long.",
+ "JSONObject[\"stringKey\"] is not a long (class java.lang.String : hello world!).",
e.getMessage());
}
try {
@@ -1165,7 +1314,7 @@ public void jsonObjectNonAndWrongValues() {
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a JSONArray.",
+ "JSONObject[\"stringKey\"] is not a JSONArray (class java.lang.String : hello world!).",
e.getMessage());
}
try {
@@ -1181,9 +1330,10 @@ public void jsonObjectNonAndWrongValues() {
fail("Expected an exception");
} catch (JSONException e) {
assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a JSONObject.",
+ "JSONObject[\"stringKey\"] is not a JSONObject (class java.lang.String : hello world!).",
e.getMessage());
}
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -1196,8 +1346,8 @@ public void unexpectedDoubleToIntConversion() {
String key30 = "key30";
String key31 = "key31";
JSONObject jsonObject = new JSONObject();
- jsonObject.put(key30, new Double(3.0));
- jsonObject.put(key31, new Double(3.1));
+ jsonObject.put(key30, Double.valueOf(3.0));
+ jsonObject.put(key31, Double.valueOf(3.1));
assertTrue("3.0 should remain a double",
jsonObject.getDouble(key30) == 3);
@@ -1211,6 +1361,7 @@ public void unexpectedDoubleToIntConversion() {
assertTrue("3.0 can still be interpreted as a double",
deserialized.getDouble(key30) == 3.0);
assertTrue("3.1 remains a double", deserialized.getDouble(key31) == 3.1);
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -1226,9 +1377,9 @@ public void bigNumberOperations() {
* value is stored. This should be fixed.
*/
BigInteger bigInteger = new BigInteger("123456789012345678901234567890");
- JSONObject jsonObject = new JSONObject(bigInteger);
- Object obj = jsonObject.get("lowestSetBit");
- assertTrue("JSONObject only has 1 value", jsonObject.length() == 1);
+ JSONObject jsonObject0 = new JSONObject(bigInteger);
+ Object obj = jsonObject0.get("lowestSetBit");
+ assertTrue("JSONObject only has 1 value", jsonObject0.length() == 1);
assertTrue("JSONObject parses BigInteger as the Integer lowestBitSet",
obj instanceof Integer);
assertTrue("this bigInteger lowestBitSet happens to be 1",
@@ -1241,57 +1392,57 @@ public void bigNumberOperations() {
*/
BigDecimal bigDecimal = new BigDecimal(
"123456789012345678901234567890.12345678901234567890123456789");
- jsonObject = new JSONObject(bigDecimal);
- assertTrue("large bigDecimal is not stored", jsonObject.isEmpty());
+ JSONObject jsonObject1 = new JSONObject(bigDecimal);
+ assertTrue("large bigDecimal is not stored", jsonObject1.isEmpty());
/**
* JSONObject put(String, Object) method stores and serializes
* bigInt and bigDec correctly. Nothing needs to change.
*/
- jsonObject = new JSONObject();
- jsonObject.put("bigInt", bigInteger);
+ JSONObject jsonObject2 = new JSONObject();
+ jsonObject2.put("bigInt", bigInteger);
assertTrue("jsonObject.put() handles bigInt correctly",
- jsonObject.get("bigInt").equals(bigInteger));
+ jsonObject2.get("bigInt").equals(bigInteger));
assertTrue("jsonObject.getBigInteger() handles bigInt correctly",
- jsonObject.getBigInteger("bigInt").equals(bigInteger));
+ jsonObject2.getBigInteger("bigInt").equals(bigInteger));
assertTrue("jsonObject.optBigInteger() handles bigInt correctly",
- jsonObject.optBigInteger("bigInt", BigInteger.ONE).equals(bigInteger));
+ jsonObject2.optBigInteger("bigInt", BigInteger.ONE).equals(bigInteger));
assertTrue("jsonObject serializes bigInt correctly",
- jsonObject.toString().equals("{\"bigInt\":123456789012345678901234567890}"));
+ jsonObject2.toString().equals("{\"bigInt\":123456789012345678901234567890}"));
assertTrue("BigInteger as BigDecimal",
- jsonObject.getBigDecimal("bigInt").equals(new BigDecimal(bigInteger)));
+ jsonObject2.getBigDecimal("bigInt").equals(new BigDecimal(bigInteger)));
- jsonObject = new JSONObject();
- jsonObject.put("bigDec", bigDecimal);
+ JSONObject jsonObject3 = new JSONObject();
+ jsonObject3.put("bigDec", bigDecimal);
assertTrue("jsonObject.put() handles bigDec correctly",
- jsonObject.get("bigDec").equals(bigDecimal));
+ jsonObject3.get("bigDec").equals(bigDecimal));
assertTrue("jsonObject.getBigDecimal() handles bigDec correctly",
- jsonObject.getBigDecimal("bigDec").equals(bigDecimal));
+ jsonObject3.getBigDecimal("bigDec").equals(bigDecimal));
assertTrue("jsonObject.optBigDecimal() handles bigDec correctly",
- jsonObject.optBigDecimal("bigDec", BigDecimal.ONE).equals(bigDecimal));
+ jsonObject3.optBigDecimal("bigDec", BigDecimal.ONE).equals(bigDecimal));
assertTrue("jsonObject serializes bigDec correctly",
- jsonObject.toString().equals(
+ jsonObject3.toString().equals(
"{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}"));
assertTrue("BigDecimal as BigInteger",
- jsonObject.getBigInteger("bigDec").equals(bigDecimal.toBigInteger()));
+ jsonObject3.getBigInteger("bigDec").equals(bigDecimal.toBigInteger()));
/**
* exercise some exceptions
*/
try {
// bigInt key does not exist
- jsonObject.getBigDecimal("bigInt");
+ jsonObject3.getBigDecimal("bigInt");
fail("expected an exeption");
} catch (JSONException ignored) {}
- obj = jsonObject.optBigDecimal("bigInt", BigDecimal.ONE);
+ obj = jsonObject3.optBigDecimal("bigInt", BigDecimal.ONE);
assertTrue("expected BigDecimal", obj.equals(BigDecimal.ONE));
- jsonObject.put("stringKey", "abc");
+ jsonObject3.put("stringKey", "abc");
try {
- jsonObject.getBigDecimal("stringKey");
+ jsonObject3.getBigDecimal("stringKey");
fail("expected an exeption");
} catch (JSONException ignored) {}
- obj = jsonObject.optBigInteger("bigDec", BigInteger.ONE);
+ obj = jsonObject3.optBigInteger("bigDec", BigInteger.ONE);
assertTrue("expected BigInteger", obj instanceof BigInteger);
assertEquals(bigDecimal.toBigInteger(), obj);
@@ -1324,79 +1475,79 @@ public void bigNumberOperations() {
// bigInt map ctor
Map map = new HashMap();
map.put("bigInt", bigInteger);
- jsonObject = new JSONObject(map);
- String actualFromMapStr = jsonObject.toString();
+ JSONObject jsonObject4 = new JSONObject(map);
+ String actualFromMapStr = jsonObject4.toString();
assertTrue("bigInt in map (or array or bean) is a string",
actualFromMapStr.equals(
"{\"bigInt\":123456789012345678901234567890}"));
// bigInt put
- jsonObject = new JSONObject();
- jsonObject.put("bigInt", bigInteger);
- String actualFromPutStr = jsonObject.toString();
+ JSONObject jsonObject5 = new JSONObject();
+ jsonObject5.put("bigInt", bigInteger);
+ String actualFromPutStr = jsonObject5.toString();
assertTrue("bigInt from put is a number",
actualFromPutStr.equals(
"{\"bigInt\":123456789012345678901234567890}"));
// bigDec map ctor
map = new HashMap();
map.put("bigDec", bigDecimal);
- jsonObject = new JSONObject(map);
- actualFromMapStr = jsonObject.toString();
+ JSONObject jsonObject6 = new JSONObject(map);
+ actualFromMapStr = jsonObject6.toString();
assertTrue("bigDec in map (or array or bean) is a bigDec",
actualFromMapStr.equals(
"{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}"));
// bigDec put
- jsonObject = new JSONObject();
- jsonObject.put("bigDec", bigDecimal);
- actualFromPutStr = jsonObject.toString();
+ JSONObject jsonObject7 = new JSONObject();
+ jsonObject7.put("bigDec", bigDecimal);
+ actualFromPutStr = jsonObject7.toString();
assertTrue("bigDec from put is a number",
actualFromPutStr.equals(
"{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}"));
// bigInt,bigDec put
- JSONArray jsonArray = new JSONArray();
- jsonArray.put(bigInteger);
- jsonArray.put(bigDecimal);
- actualFromPutStr = jsonArray.toString();
+ JSONArray jsonArray0 = new JSONArray();
+ jsonArray0.put(bigInteger);
+ jsonArray0.put(bigDecimal);
+ actualFromPutStr = jsonArray0.toString();
assertTrue("bigInt, bigDec from put is a number",
actualFromPutStr.equals(
"[123456789012345678901234567890,123456789012345678901234567890.12345678901234567890123456789]"));
- assertTrue("getBigInt is bigInt", jsonArray.getBigInteger(0).equals(bigInteger));
- assertTrue("getBigDec is bigDec", jsonArray.getBigDecimal(1).equals(bigDecimal));
- assertTrue("optBigInt is bigInt", jsonArray.optBigInteger(0, BigInteger.ONE).equals(bigInteger));
- assertTrue("optBigDec is bigDec", jsonArray.optBigDecimal(1, BigDecimal.ONE).equals(bigDecimal));
- jsonArray.put(Boolean.TRUE);
+ assertTrue("getBigInt is bigInt", jsonArray0.getBigInteger(0).equals(bigInteger));
+ assertTrue("getBigDec is bigDec", jsonArray0.getBigDecimal(1).equals(bigDecimal));
+ assertTrue("optBigInt is bigInt", jsonArray0.optBigInteger(0, BigInteger.ONE).equals(bigInteger));
+ assertTrue("optBigDec is bigDec", jsonArray0.optBigDecimal(1, BigDecimal.ONE).equals(bigDecimal));
+ jsonArray0.put(Boolean.TRUE);
try {
- jsonArray.getBigInteger(2);
+ jsonArray0.getBigInteger(2);
fail("should not be able to get big int");
} catch (Exception ignored) {}
try {
- jsonArray.getBigDecimal(2);
+ jsonArray0.getBigDecimal(2);
fail("should not be able to get big dec");
} catch (Exception ignored) {}
- assertTrue("optBigInt is default", jsonArray.optBigInteger(2, BigInteger.ONE).equals(BigInteger.ONE));
- assertTrue("optBigDec is default", jsonArray.optBigDecimal(2, BigDecimal.ONE).equals(BigDecimal.ONE));
+ assertTrue("optBigInt is default", jsonArray0.optBigInteger(2, BigInteger.ONE).equals(BigInteger.ONE));
+ assertTrue("optBigDec is default", jsonArray0.optBigDecimal(2, BigDecimal.ONE).equals(BigDecimal.ONE));
// bigInt,bigDec list ctor
List list = new ArrayList();
list.add(bigInteger);
list.add(bigDecimal);
- jsonArray = new JSONArray(list);
- String actualFromListStr = jsonArray.toString();
+ JSONArray jsonArray1 = new JSONArray(list);
+ String actualFromListStr = jsonArray1.toString();
assertTrue("bigInt, bigDec in list is a bigInt, bigDec",
actualFromListStr.equals(
"[123456789012345678901234567890,123456789012345678901234567890.12345678901234567890123456789]"));
// bigInt bean ctor
MyBigNumberBean myBigNumberBean = mock(MyBigNumberBean.class);
when(myBigNumberBean.getBigInteger()).thenReturn(new BigInteger("123456789012345678901234567890"));
- jsonObject = new JSONObject(myBigNumberBean);
- String actualFromBeanStr = jsonObject.toString();
+ JSONObject jsonObject8 = new JSONObject(myBigNumberBean);
+ String actualFromBeanStr = jsonObject8.toString();
// can't do a full string compare because mockery adds an extra key/value
assertTrue("bigInt from bean ctor is a bigInt",
actualFromBeanStr.contains("123456789012345678901234567890"));
// bigDec bean ctor
myBigNumberBean = mock(MyBigNumberBean.class);
when(myBigNumberBean.getBigDecimal()).thenReturn(new BigDecimal("123456789012345678901234567890.12345678901234567890123456789"));
- jsonObject = new JSONObject(myBigNumberBean);
- actualFromBeanStr = jsonObject.toString();
+ jsonObject8 = new JSONObject(myBigNumberBean);
+ actualFromBeanStr = jsonObject8.toString();
// can't do a full string compare because mockery adds an extra key/value
assertTrue("bigDec from bean ctor is a bigDec",
actualFromBeanStr.contains("123456789012345678901234567890.12345678901234567890123456789"));
@@ -1405,7 +1556,12 @@ public void bigNumberOperations() {
assertTrue("wrap() returns big num",obj.equals(bigInteger));
obj = JSONObject.wrap(bigDecimal);
assertTrue("wrap() returns string",obj.equals(bigDecimal));
-
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObject0, jsonObject1, jsonObject2, jsonObject3, jsonObject4,
+ jsonObject5, jsonObject6, jsonObject7, jsonObject8
+ )));
+ Util.checkJSONArrayMaps(jsonArray0, jsonObject0.getMapType());
+ Util.checkJSONArrayMaps(jsonArray1, jsonObject0.getMapType());
}
/**
@@ -1417,7 +1573,6 @@ public void bigNumberOperations() {
*/
@Test
public void jsonObjectNames() {
- JSONObject jsonObject;
// getNames() from null JSONObject
assertTrue("null names from null Object",
@@ -1428,31 +1583,31 @@ public void jsonObjectNames() {
null == JSONObject.getNames(new MyJsonString()));
// getNames from new JSONOjbect
- jsonObject = new JSONObject();
- String [] names = JSONObject.getNames(jsonObject);
+ JSONObject jsonObject0 = new JSONObject();
+ String [] names = JSONObject.getNames(jsonObject0);
assertTrue("names should be null", names == null);
// getNames() from empty JSONObject
String emptyStr = "{}";
- jsonObject = new JSONObject(emptyStr);
+ JSONObject jsonObject1 = new JSONObject(emptyStr);
assertTrue("empty JSONObject should have null names",
- null == JSONObject.getNames(jsonObject));
+ null == JSONObject.getNames(jsonObject1));
// getNames() from JSONObject
String str =
"{"+
"\"trueKey\":true,"+
"\"falseKey\":false,"+
- "\"stringKey\":\"hello world!\","+
+ "\"stringKey\":\"hello world!\""+
"}";
- jsonObject = new JSONObject(str);
- names = JSONObject.getNames(jsonObject);
- JSONArray jsonArray = new JSONArray(names);
+ JSONObject jsonObject2 = new JSONObject(str);
+ names = JSONObject.getNames(jsonObject2);
+ JSONArray jsonArray0 = new JSONArray(names);
// validate JSON
Object doc = Configuration.defaultConfiguration().jsonProvider()
- .parse(jsonArray.toString());
+ .parse(jsonArray0.toString());
List> docList = JsonPath.read(doc, "$");
assertTrue("expected 3 items", docList.size() == 3);
assertTrue(
@@ -1473,9 +1628,9 @@ public void jsonObjectNames() {
names = JSONObject.getNames(myEnumField);
// validate JSON
- jsonArray = new JSONArray(names);
+ JSONArray jsonArray1 = new JSONArray(names);
doc = Configuration.defaultConfiguration().jsonProvider()
- .parse(jsonArray.toString());
+ .parse(jsonArray1.toString());
docList = JsonPath.read(doc, "$");
assertTrue("expected 3 items", docList.size() == 3);
assertTrue(
@@ -1497,9 +1652,9 @@ public void jsonObjectNames() {
names = JSONObject.getNames(myPublicClass);
// validate JSON
- jsonArray = new JSONArray(names);
+ JSONArray jsonArray2 = new JSONArray(names);
doc = Configuration.defaultConfiguration().jsonProvider()
- .parse(jsonArray.toString());
+ .parse(jsonArray2.toString());
docList = JsonPath.read(doc, "$");
assertTrue("expected 2 items", docList.size() == 2);
assertTrue(
@@ -1508,6 +1663,12 @@ public void jsonObjectNames() {
assertTrue(
"expected to find publicInt",
((List>) JsonPath.read(doc, "$[?(@=='publicInt')]")).size() == 1);
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObject0, jsonObject1, jsonObject2
+ )));
+ Util.checkJSONArrayMaps(jsonArray0, jsonObject0.getMapType());
+ Util.checkJSONArrayMaps(jsonArray1, jsonObject0.getMapType());
+ Util.checkJSONArrayMaps(jsonArray2, jsonObject0.getMapType());
}
/**
@@ -1519,6 +1680,8 @@ public void emptyJsonObjectNamesToJsonAray() {
JSONObject jsonObject = new JSONObject();
JSONArray jsonArray = jsonObject.names();
assertTrue("jsonArray should be null", jsonArray == null);
+ Util.checkJSONObjectMaps(jsonObject);
+ Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
}
/**
@@ -1531,7 +1694,7 @@ public void jsonObjectNamesToJsonAray() {
"{"+
"\"trueKey\":true,"+
"\"falseKey\":false,"+
- "\"stringKey\":\"hello world!\","+
+ "\"stringKey\":\"hello world!\""+
"}";
JSONObject jsonObject = new JSONObject(str);
@@ -1543,6 +1706,8 @@ public void jsonObjectNamesToJsonAray() {
assertTrue("expected to find trueKey", ((List>) JsonPath.read(doc, "$[?(@=='trueKey')]")).size() == 1);
assertTrue("expected to find falseKey", ((List>) JsonPath.read(doc, "$[?(@=='falseKey')]")).size() == 1);
assertTrue("expected to find stringKey", ((List>) JsonPath.read(doc, "$[?(@=='stringKey')]")).size() == 1);
+ Util.checkJSONObjectMaps(jsonObject);
+ Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
}
/**
@@ -1635,19 +1800,19 @@ public void jsonObjectIncrement() {
*/
assertFalse("Document unexpected behaviour with explicit type-casting float as double!", (double)0.2f == 0.2d );
assertFalse("Document unexpected behaviour with implicit type-cast!", 0.2f == 0.2d );
- Double d1 = new Double( 1.1f );
- Double d2 = new Double( "1.1f" );
+ Double d1 = Double.valueOf( 1.1f );
+ Double d2 = Double.valueOf( "1.1f" );
assertFalse( "Document implicit type cast from float to double before calling Double(double d) constructor", d1.equals( d2 ) );
- assertTrue( "Correctly converting float to double via base10 (string) representation!", new Double( 3.1d ).equals( new Double( new Float( 3.1f ).toString() ) ) );
+ assertTrue( "Correctly converting float to double via base10 (string) representation!", Double.valueOf( 3.1d ).equals( Double.valueOf( Float.valueOf( 3.1f ).toString() ) ) );
// Pinpointing the not so obvious "buggy" conversion from float to double in JSONObject
JSONObject jo = new JSONObject();
jo.put( "bug", 3.1f ); // will call put( String key, double value ) with implicit and "buggy" type-cast from float to double
- assertFalse( "The java-compiler did add some zero bits for you to the mantissa (unexpected, but well documented)", jo.get( "bug" ).equals( new Double( 3.1d ) ) );
+ assertFalse( "The java-compiler did add some zero bits for you to the mantissa (unexpected, but well documented)", jo.get( "bug" ).equals( Double.valueOf( 3.1d ) ) );
JSONObject inc = new JSONObject();
- inc.put( "bug", new Float( 3.1f ) ); // This will put in instance of Float into JSONObject, i.e. call put( String key, Object value )
+ inc.put( "bug", Float.valueOf( 3.1f ) ); // This will put in instance of Float into JSONObject, i.e. call put( String key, Object value )
assertTrue( "Everything is ok here!", inc.get( "bug" ) instanceof Float );
inc.increment( "bug" ); // after adding 1, increment will call put( String key, double value ) with implicit and "buggy" type-cast from float to double!
// this.put(key, (Float) value + 1);
@@ -1660,8 +1825,10 @@ public void jsonObjectIncrement() {
// correct implementation (with change of behavior) would be:
// this.put(key, new Float((Float) value + 1));
// Probably it would be better to deprecate the method and remove some day, while convenient processing the "payload" is not
- // really in the the scope of a JSON-library (IMHO.)
-
+ // really in the scope of a JSON-library (IMHO.)
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObject, inc
+ )));
}
/**
@@ -1759,6 +1926,12 @@ public void jsonObjectPut() {
JSONObject bCompareArrayJsonObject = new JSONObject(bCompareArrayStr);
assertTrue("different nested JSONArrays should not be similar",
!aCompareArrayJsonObject.similar(bCompareArrayJsonObject));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObject, expectedJsonObject, aCompareValueJsonObject,
+ aCompareArrayJsonObject, aCompareObjectJsonObject, aCompareArrayJsonObject,
+ bCompareValueJsonObject, bCompareArrayJsonObject, bCompareObjectJsonObject,
+ bCompareArrayJsonObject
+ )));
}
/**
@@ -1794,6 +1967,7 @@ public void jsonObjectToString() {
assertTrue("expected myVal2", "myVal2".equals(jsonObject.query("/objectKey/myKey2")));
assertTrue("expected myVal3", "myVal3".equals(jsonObject.query("/objectKey/myKey3")));
assertTrue("expected myVal4", "myVal4".equals(jsonObject.query("/objectKey/myKey4")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -1867,6 +2041,9 @@ public void jsonObjectToStringIndent() {
JSONObject jo = new JSONObject().put("TABLE", new JSONObject().put("yhoo", new JSONObject()));
assertEquals("toString(2)","{\"TABLE\": {\"yhoo\": {}}}", jo.toString(2));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObject, jo
+ )));
}
/**
@@ -1879,7 +2056,7 @@ public void jsonObjectToStringIndent() {
@Test
public void jsonObjectToStringSuppressWarningOnCastToMap() {
JSONObject jsonObject = new JSONObject();
- Map map = new HashMap();
+ Map map = new HashMap<>();
map.put("abc", "def");
jsonObject.put("key", map);
@@ -1888,6 +2065,7 @@ public void jsonObjectToStringSuppressWarningOnCastToMap() {
assertTrue("expected 1 top level item", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 1);
assertTrue("expected 1 key item", ((Map,?>)(JsonPath.read(doc, "$.key"))).size() == 1);
assertTrue("expected def", "def".equals(jsonObject.query("/key/abc")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -1910,6 +2088,7 @@ public void jsonObjectToStringSuppressWarningOnCastToCollection() {
assertTrue("expected 1 top level item", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 1);
assertTrue("expected 1 key item", ((List>)(JsonPath.read(doc, "$.key"))).size() == 1);
assertTrue("expected abc", "abc".equals(jsonObject.query("/key/0")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -1935,7 +2114,9 @@ public void valueToString() {
"}";
JSONObject jsonObject = new JSONObject(jsonObjectStr);
assertTrue("jsonObject valueToString() incorrect",
- JSONObject.valueToString(jsonObject).equals(jsonObject.toString()));
+ new JSONObject(JSONObject.valueToString(jsonObject))
+ .similar(new JSONObject(jsonObject.toString()))
+ );
String jsonArrayStr =
"[1,2,3]";
JSONArray jsonArray = new JSONArray(jsonArrayStr);
@@ -1946,18 +2127,21 @@ public void valueToString() {
map.put("key2", "val2");
map.put("key3", "val3");
assertTrue("map valueToString() incorrect",
- jsonObject.toString().equals(JSONObject.valueToString(map)));
+ new JSONObject(jsonObject.toString())
+ .similar(new JSONObject(JSONObject.valueToString(map))));
Collection collection = new ArrayList();
- collection.add(new Integer(1));
- collection.add(new Integer(2));
- collection.add(new Integer(3));
+ collection.add(Integer.valueOf(1));
+ collection.add(Integer.valueOf(2));
+ collection.add(Integer.valueOf(3));
assertTrue("collection valueToString() expected: "+
jsonArray.toString()+ " actual: "+
JSONObject.valueToString(collection),
jsonArray.toString().equals(JSONObject.valueToString(collection)));
- Integer[] array = { new Integer(1), new Integer(2), new Integer(3) };
+ Integer[] array = { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3) };
assertTrue("array valueToString() incorrect",
- jsonArray.toString().equals(JSONObject.valueToString(array)));
+ jsonArray.toString().equals(JSONObject.valueToString(array)));
+ Util.checkJSONObjectMaps(jsonObject);
+ Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
}
/**
@@ -1991,7 +2175,7 @@ public void wrapObject() {
JSONObject.NULL == JSONObject.wrap(null));
// wrap(Integer) returns Integer
- Integer in = new Integer(1);
+ Integer in = Integer.valueOf(1);
assertTrue("Integer wrap() incorrect",
in == JSONObject.wrap(in));
@@ -2018,9 +2202,9 @@ public void wrapObject() {
// wrap collection returns JSONArray
Collection collection = new ArrayList();
- collection.add(new Integer(1));
- collection.add(new Integer(2));
- collection.add(new Integer(3));
+ collection.add(Integer.valueOf(1));
+ collection.add(Integer.valueOf(2));
+ collection.add(Integer.valueOf(3));
JSONArray jsonArray = (JSONArray) (JSONObject.wrap(collection));
// validate JSON
@@ -2031,7 +2215,7 @@ public void wrapObject() {
assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2")));
// wrap Array returns JSONArray
- Integer[] array = { new Integer(1), new Integer(2), new Integer(3) };
+ Integer[] array = { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3) };
JSONArray integerArrayJsonArray = (JSONArray)(JSONObject.wrap(array));
// validate JSON
@@ -2061,6 +2245,11 @@ public void wrapObject() {
assertTrue("expected val1", "val1".equals(mapJsonObject.query("/key1")));
assertTrue("expected val2", "val2".equals(mapJsonObject.query("/key2")));
assertTrue("expected val3", "val3".equals(mapJsonObject.query("/key3")));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObject, mapJsonObject
+ )));
+ Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
+ Util.checkJSONArrayMaps(integerArrayJsonArray, jsonObject.getMapType());
}
@@ -2075,6 +2264,7 @@ public void jsonObjectParseControlCharacters(){
try {
JSONObject jo = new JSONObject(source);
assertTrue("Expected "+charString+"("+i+") in the JSON Object but did not find it.",charString.equals(jo.getString("key")));
+ Util.checkJSONObjectMaps(jo);
} catch (JSONException ex) {
assertTrue("Only \\0 (U+0000), \\n (U+000A), and \\r (U+000D) should cause an error. Instead "+charString+"("+i+") caused an error",
i=='\0' || i=='\n' || i=='\r'
@@ -2083,122 +2273,279 @@ public void jsonObjectParseControlCharacters(){
}
}
- /**
- * Explore how JSONObject handles parsing errors.
- */
- @SuppressWarnings({"boxing", "unused"})
@Test
- public void jsonObjectParsingErrors() {
+ public void jsonObjectParseControlCharacterEOFAssertExceptionMessage(){
+ char c = '\0';
+ final String source = "{\"key\":\"" + c + "\"}";
try {
- // does not start with '{'
- String str = "abc";
- assertNull("Expected an exception",new JSONObject(str));
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "A JSONObject text must begin with '{' at 1 [character 2 line 1]",
- e.getMessage());
+ JSONObject jo = new JSONObject(source);
+ fail("JSONException should be thrown");
+ } catch (JSONException ex) {
+ assertEquals("Unterminated string. " + "Character with int code 0" +
+ " is not allowed within a quoted string. at 8 [character 9 line 1]", ex.getMessage());
+ }
+ }
+
+ @Test
+ public void jsonObjectParseControlCharacterNewLineAssertExceptionMessage(){
+ char[] chars = {'\n', '\r'};
+ for( char c : chars) {
+ final String source = "{\"key\":\"" + c + "\"}";
+ try {
+ JSONObject jo = new JSONObject(source);
+ fail("JSONException should be thrown");
+ } catch (JSONException ex) {
+ assertEquals("Unterminated string. " + "Character with int code " + (int) c +
+ " is not allowed within a quoted string. at 9 [character 0 line 2]", ex.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void jsonObjectParseUTF8EncodingAssertExceptionMessage(){
+ String c = "\\u123x";
+ final String source = "{\"key\":\"" + c + "\"}";
+ try {
+ JSONObject jo = new JSONObject(source);
+ fail("JSONException should be thrown");
+ } catch (JSONException ex) {
+ assertEquals("Illegal escape. \\u must be followed by a 4 digit hexadecimal number. " +
+ "\\123x is not valid. at 14 [character 15 line 1]", ex.getMessage());
}
+ }
+
+ @Test
+ public void jsonObjectParseIllegalEscapeAssertExceptionMessage(){
+ String c = "\\x";
+ final String source = "{\"key\":\"" + c + "\"}";
+ try {
+ JSONObject jo = new JSONObject(source);
+ fail("JSONException should be thrown");
+ } catch (JSONException ex) {
+ assertEquals("Illegal escape. Escape sequence " + c + " is not valid." +
+ " at 10 [character 11 line 1]", ex.getMessage());
+ }
+ }
+
+ @Test
+ public void parsingErrorTrailingCurlyBrace () {
try {
// does not end with '}'
String str = "{";
- assertNull("Expected an exception",new JSONObject(str));
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
+ assertNull("Expected an exception", new JSONObject(str));
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
"A JSONObject text must end with '}' at 1 [character 2 line 1]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorInitialCurlyBrace() {
+ try {
+ // does not start with '{'
+ String str = "abc";
+ assertNull("Expected an exception", new JSONObject(str));
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "A JSONObject text must begin with '{' at 1 [character 2 line 1]",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void parsingErrorNoColon() {
try {
// key with no ':'
String str = "{\"myKey\" = true}";
- assertNull("Expected an exception",new JSONObject(str));
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
+ assertNull("Expected an exception", new JSONObject(str));
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
"Expected a ':' after a key at 10 [character 11 line 1]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorNoCommaSeparator() {
try {
// entries with no ',' separator
String str = "{\"myKey\":true \"myOtherKey\":false}";
- assertNull("Expected an exception",new JSONObject(str));
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
+ assertNull("Expected an exception", new JSONObject(str));
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
"Expected a ',' or '}' at 15 [character 16 line 1]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorKeyIsNestedMap() {
+ try {
+ // key is a nested map
+ String str = "{{\"foo\": \"bar\"}: \"baz\"}";
+ assertNull("Expected an exception", new JSONObject(str));
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Missing value at 1 [character 2 line 1]",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void parsingErrorKeyIsNestedArrayWithMap() {
+ try {
+ // key is a nested array containing a map
+ String str = "{\"a\": 1, [{\"foo\": \"bar\"}]: \"baz\"}";
+ assertNull("Expected an exception", new JSONObject(str));
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "Missing value at 9 [character 10 line 1]",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void parsingErrorKeyContainsCurlyBrace() {
+ try {
+ // key contains }
+ String str = "{foo}: 2}";
+ assertNull("Expected an exception", new JSONObject(str));
+ } catch (JSONException e) {
+// assertEquals("Expecting an exception message",
+// "Expected a ':' after a key at 5 [character 6 line 1]",
+// e.getMessage());
+ }
+ }
+
+ @Test
+ public void parsingErrorKeyContainsSquareBrace() {
+ try {
+ // key contains ]
+ String str = "{foo]: 2}";
+ assertNull("Expected an exception", new JSONObject(str));
+ } catch (JSONException e) {
+// assertEquals("Expecting an exception message",
+// "Expected a ':' after a key at 5 [character 6 line 1]",
+// e.getMessage());
+ }
+ }
+
+ @Test
+ public void parsingErrorKeyContainsBinaryZero() {
+ try {
+ // \0 after ,
+ String str = "{\"myKey\":true, \0\"myOtherKey\":false}";
+ assertNull("Expected an exception", new JSONObject(str));
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "A JSONObject text must end with '}' at 15 [character 16 line 1]",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void parsingErrorAppendToWrongValue() {
try {
- // append to wrong key
+ // append to wrong value
String str = "{\"myKey\":true, \"myOtherKey\":false}";
JSONObject jsonObject = new JSONObject(str);
jsonObject.append("myKey", "hello");
fail("Expected an exception");
- } catch (JSONException e) {
+ } catch (JSONException e) {
assertEquals("Expecting an exception message",
"JSONObject[\"myKey\"] is not a JSONArray (null).",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorIncrementWrongValue() {
try {
- // increment wrong key
+ // increment wrong value
String str = "{\"myKey\":true, \"myOtherKey\":false}";
JSONObject jsonObject = new JSONObject(str);
jsonObject.increment("myKey");
fail("Expected an exception");
- } catch (JSONException e) {
+ } catch (JSONException e) {
assertEquals("Expecting an exception message",
"Unable to increment [\"myKey\"].",
e.getMessage());
}
+ }
+ @Test
+ public void parsingErrorInvalidKey() {
try {
// invalid key
String str = "{\"myKey\":true, \"myOtherKey\":false}";
JSONObject jsonObject = new JSONObject(str);
jsonObject.get(null);
fail("Expected an exception");
- } catch (JSONException e) {
+ } catch (JSONException e) {
assertEquals("Expecting an exception message",
"Null key.",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorNumberToString() {
try {
// invalid numberToString()
- JSONObject.numberToString((Number)null);
+ JSONObject.numberToString((Number) null);
fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
"Null pointer",
e.getMessage());
}
+ }
+ @Test
+ public void parsingErrorPutOnceDuplicateKey() {
try {
- // multiple putOnce key
+ // multiple putOnce key
JSONObject jsonObject = new JSONObject("{}");
jsonObject.putOnce("hello", "world");
jsonObject.putOnce("hello", "world!");
fail("Expected an exception");
- } catch (JSONException e) {
+ } catch (JSONException e) {
assertTrue("", true);
}
+ }
+
+ @Test
+ public void parsingErrorInvalidDouble() {
try {
- // test validity of invalid double
+ // test validity of invalid double
JSONObject.testValidity(Double.NaN);
fail("Expected an exception");
- } catch (JSONException e) {
+ } catch (JSONException e) {
assertTrue("", true);
}
+ }
+
+ @Test
+ public void parsingErrorInvalidFloat() {
try {
- // test validity of invalid float
+ // test validity of invalid float
JSONObject.testValidity(Float.NEGATIVE_INFINITY);
fail("Expected an exception");
- } catch (JSONException e) {
+ } catch (JSONException e) {
assertTrue("", true);
}
+ }
+
+ @Test
+ public void parsingErrorDuplicateKeyException() {
try {
// test exception message when including a duplicate key (level 0)
String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr03\":\"value-04\"\n"
- + "}";
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\":\"value-02\",\n"
+ + " \"attr03\":\"value-03\",\n"
+ + " \"attr03\":\"value-04\"\n"
+ + "}";
new JSONObject(str);
fail("Expected an exception");
} catch (JSONException e) {
@@ -2206,18 +2553,22 @@ public void jsonObjectParsingErrors() {
"Duplicate key \"attr03\" at 90 [character 13 line 5]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorNestedDuplicateKeyException() {
try {
// test exception message when including a duplicate key (level 0) holding an object
String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr03\": {"
- +" \"attr04-01\":\"value-04-01\",n"
- +" \"attr04-02\":\"value-04-02\",n"
- +" \"attr04-03\":\"value-04-03\"n"
- + " }\n"
- + "}";
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\":\"value-02\",\n"
+ + " \"attr03\":\"value-03\",\n"
+ + " \"attr03\": {"
+ + " \"attr04-01\":\"value-04-01\",n"
+ + " \"attr04-02\":\"value-04-02\",n"
+ + " \"attr04-03\":\"value-04-03\"n"
+ + " }\n"
+ + "}";
new JSONObject(str);
fail("Expected an exception");
} catch (JSONException e) {
@@ -2225,20 +2576,24 @@ public void jsonObjectParsingErrors() {
"Duplicate key \"attr03\" at 90 [character 13 line 5]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorNestedDuplicateKeyWithArrayException() {
try {
// test exception message when including a duplicate key (level 0) holding an array
String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr03\": [\n"
- +" {"
- +" \"attr04-01\":\"value-04-01\",n"
- +" \"attr04-02\":\"value-04-02\",n"
- +" \"attr04-03\":\"value-04-03\"n"
- +" }\n"
- + " ]\n"
- + "}";
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\":\"value-02\",\n"
+ + " \"attr03\":\"value-03\",\n"
+ + " \"attr03\": [\n"
+ + " {"
+ + " \"attr04-01\":\"value-04-01\",n"
+ + " \"attr04-02\":\"value-04-02\",n"
+ + " \"attr04-03\":\"value-04-03\"n"
+ + " }\n"
+ + " ]\n"
+ + "}";
new JSONObject(str);
fail("Expected an exception");
} catch (JSONException e) {
@@ -2246,19 +2601,23 @@ public void jsonObjectParsingErrors() {
"Duplicate key \"attr03\" at 90 [character 13 line 5]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorDuplicateKeyWithinNestedDictExceptionMessage() {
try {
// test exception message when including a duplicate key (level 1)
String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr04\": {\n"
- +" \"attr04-01\":\"value04-01\",\n"
- +" \"attr04-02\":\"value04-02\",\n"
- +" \"attr04-03\":\"value04-03\",\n"
- +" \"attr04-03\":\"value04-04\"\n"
- + " }\n"
- + "}";
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\":\"value-02\",\n"
+ + " \"attr03\":\"value-03\",\n"
+ + " \"attr04\": {\n"
+ + " \"attr04-01\":\"value04-01\",\n"
+ + " \"attr04-02\":\"value04-02\",\n"
+ + " \"attr04-03\":\"value04-03\",\n"
+ + " \"attr04-03\":\"value04-04\"\n"
+ + " }\n"
+ + "}";
new JSONObject(str);
fail("Expected an exception");
} catch (JSONException e) {
@@ -2266,23 +2625,28 @@ public void jsonObjectParsingErrors() {
"Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorDuplicateKeyDoubleNestedDictExceptionMessage() {
try {
- // test exception message when including a duplicate key (level 1) holding an object
+ // test exception message when including a duplicate key (level 1) holding an
+ // object
String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr04\": {\n"
- +" \"attr04-01\":\"value04-01\",\n"
- +" \"attr04-02\":\"value04-02\",\n"
- +" \"attr04-03\":\"value04-03\",\n"
- +" \"attr04-03\": {\n"
- +" \"attr04-04-01\":\"value04-04-01\",\n"
- +" \"attr04-04-02\":\"value04-04-02\",\n"
- +" \"attr04-04-03\":\"value04-04-03\",\n"
- +" }\n"
- +" }\n"
- + "}";
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\":\"value-02\",\n"
+ + " \"attr03\":\"value-03\",\n"
+ + " \"attr04\": {\n"
+ + " \"attr04-01\":\"value04-01\",\n"
+ + " \"attr04-02\":\"value04-02\",\n"
+ + " \"attr04-03\":\"value04-03\",\n"
+ + " \"attr04-03\": {\n"
+ + " \"attr04-04-01\":\"value04-04-01\",\n"
+ + " \"attr04-04-02\":\"value04-04-02\",\n"
+ + " \"attr04-04-03\":\"value04-04-03\",\n"
+ + " }\n"
+ + " }\n"
+ + "}";
new JSONObject(str);
fail("Expected an exception");
} catch (JSONException e) {
@@ -2290,25 +2654,30 @@ public void jsonObjectParsingErrors() {
"Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorDuplicateKeyNestedWithArrayExceptionMessage() {
try {
- // test exception message when including a duplicate key (level 1) holding an array
+ // test exception message when including a duplicate key (level 1) holding an
+ // array
String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr04\": {\n"
- +" \"attr04-01\":\"value04-01\",\n"
- +" \"attr04-02\":\"value04-02\",\n"
- +" \"attr04-03\":\"value04-03\",\n"
- +" \"attr04-03\": [\n"
- +" {\n"
- +" \"attr04-04-01\":\"value04-04-01\",\n"
- +" \"attr04-04-02\":\"value04-04-02\",\n"
- +" \"attr04-04-03\":\"value04-04-03\",\n"
- +" }\n"
- +" ]\n"
- +" }\n"
- + "}";
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\":\"value-02\",\n"
+ + " \"attr03\":\"value-03\",\n"
+ + " \"attr04\": {\n"
+ + " \"attr04-01\":\"value04-01\",\n"
+ + " \"attr04-02\":\"value04-02\",\n"
+ + " \"attr04-03\":\"value04-03\",\n"
+ + " \"attr04-03\": [\n"
+ + " {\n"
+ + " \"attr04-04-01\":\"value04-04-01\",\n"
+ + " \"attr04-04-02\":\"value04-04-02\",\n"
+ + " \"attr04-04-03\":\"value04-04-03\",\n"
+ + " }\n"
+ + " ]\n"
+ + " }\n"
+ + "}";
new JSONObject(str);
fail("Expected an exception");
} catch (JSONException e) {
@@ -2316,18 +2685,23 @@ public void jsonObjectParsingErrors() {
"Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorDuplicateKeyWithinArrayExceptionMessage() {
try {
- // test exception message when including a duplicate key in object (level 0) within an array
+ // test exception message when including a duplicate key in object (level 0)
+ // within an array
String str = "[\n"
- +" {\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\"\n"
- +" },\n"
- +" {\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr01\":\"value-02\"\n"
- +" }\n"
- + "]";
+ + " {\n"
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\":\"value-02\"\n"
+ + " },\n"
+ + " {\n"
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr01\":\"value-02\"\n"
+ + " }\n"
+ + "]";
new JSONArray(str);
fail("Expected an exception");
} catch (JSONException e) {
@@ -2335,24 +2709,29 @@ public void jsonObjectParsingErrors() {
"Duplicate key \"attr01\" at 124 [character 17 line 8]",
e.getMessage());
}
+ }
+
+ @Test
+ public void parsingErrorDuplicateKeyDoubleNestedWithinArrayExceptionMessage() {
try {
- // test exception message when including a duplicate key in object (level 1) within an array
+ // test exception message when including a duplicate key in object (level 1)
+ // within an array
String str = "[\n"
- +" {\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\": {\n"
- +" \"attr02-01\":\"value-02-01\",\n"
- +" \"attr02-02\":\"value-02-02\"\n"
- +" }\n"
- +" },\n"
- +" {\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\": {\n"
- +" \"attr02-01\":\"value-02-01\",\n"
- +" \"attr02-01\":\"value-02-02\"\n"
- +" }\n"
- +" }\n"
- + "]";
+ + " {\n"
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\": {\n"
+ + " \"attr02-01\":\"value-02-01\",\n"
+ + " \"attr02-02\":\"value-02-02\"\n"
+ + " }\n"
+ + " },\n"
+ + " {\n"
+ + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\": {\n"
+ + " \"attr02-01\":\"value-02-01\",\n"
+ + " \"attr02-01\":\"value-02-02\"\n"
+ + " }\n"
+ + " }\n"
+ + "]";
new JSONArray(str);
fail("Expected an exception");
} catch (JSONException e) {
@@ -2374,6 +2753,7 @@ public void jsonObjectPutOnceNull() {
assertTrue("jsonObject should be empty", jsonObject.isEmpty());
jsonObject.putOnce(null, "");
assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -2391,24 +2771,37 @@ public void jsonObjectOptDefault() {
BigInteger.TEN.compareTo(jsonObject.optBigInteger("myKey",BigInteger.TEN ))==0);
assertTrue("optBoolean() should return default boolean",
jsonObject.optBoolean("myKey", true));
+ assertTrue("optBooleanObject() should return default Boolean",
+ jsonObject.optBooleanObject("myKey", true));
assertTrue("optInt() should return default int",
42 == jsonObject.optInt("myKey", 42));
+ assertTrue("optIntegerObject() should return default Integer",
+ Integer.valueOf(42).equals(jsonObject.optIntegerObject("myKey", 42)));
assertTrue("optEnum() should return default Enum",
MyEnum.VAL1.equals(jsonObject.optEnum(MyEnum.class, "myKey", MyEnum.VAL1)));
assertTrue("optJSONArray() should return null ",
null==jsonObject.optJSONArray("myKey"));
- assertTrue("optJSONObject() should return null ",
- null==jsonObject.optJSONObject("myKey"));
+ assertTrue("optJSONArray() should return default JSONArray",
+ "value".equals(jsonObject.optJSONArray("myKey", new JSONArray("[\"value\"]")).getString(0)));
+ assertTrue("optJSONObject() should return default JSONObject ",
+ jsonObject.optJSONObject("myKey", new JSONObject("{\"testKey\":\"testValue\"}")).getString("testKey").equals("testValue"));
assertTrue("optLong() should return default long",
42l == jsonObject.optLong("myKey", 42l));
+ assertTrue("optLongObject() should return default Long",
+ Long.valueOf(42l).equals(jsonObject.optLongObject("myKey", 42l)));
assertTrue("optDouble() should return default double",
42.3d == jsonObject.optDouble("myKey", 42.3d));
+ assertTrue("optDoubleObject() should return default Double",
+ Double.valueOf(42.3d).equals(jsonObject.optDoubleObject("myKey", 42.3d)));
assertTrue("optFloat() should return default float",
42.3f == jsonObject.optFloat("myKey", 42.3f));
+ assertTrue("optFloatObject() should return default Float",
+ Float.valueOf(42.3f).equals(jsonObject.optFloatObject("myKey", 42.3f)));
assertTrue("optNumber() should return default Number",
42l == jsonObject.optNumber("myKey", Long.valueOf(42)).longValue());
assertTrue("optString() should return default string",
"hi".equals(jsonObject.optString("hiKey", "hi")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -2427,24 +2820,37 @@ public void jsonObjectOptNoKey() {
BigInteger.TEN.compareTo(jsonObject.optBigInteger("myKey",BigInteger.TEN ))==0);
assertTrue("optBoolean() should return default boolean",
jsonObject.optBoolean("myKey", true));
+ assertTrue("optBooleanObject() should return default Boolean",
+ jsonObject.optBooleanObject("myKey", true));
assertTrue("optInt() should return default int",
42 == jsonObject.optInt("myKey", 42));
+ assertTrue("optIntegerObject() should return default Integer",
+ Integer.valueOf(42).equals(jsonObject.optIntegerObject("myKey", 42)));
assertTrue("optEnum() should return default Enum",
MyEnum.VAL1.equals(jsonObject.optEnum(MyEnum.class, "myKey", MyEnum.VAL1)));
+ assertTrue("optJSONArray() should return default JSONArray",
+ "value".equals(jsonObject.optJSONArray("myKey", new JSONArray("[\"value\"]")).getString(0)));
assertTrue("optJSONArray() should return null ",
null==jsonObject.optJSONArray("myKey"));
- assertTrue("optJSONObject() should return null ",
- null==jsonObject.optJSONObject("myKey"));
+ assertTrue("optJSONObject() should return default JSONObject ",
+ jsonObject.optJSONObject("myKey", new JSONObject("{\"testKey\":\"testValue\"}")).getString("testKey").equals("testValue"));
assertTrue("optLong() should return default long",
42l == jsonObject.optLong("myKey", 42l));
+ assertTrue("optLongObject() should return default Long",
+ Long.valueOf(42l).equals(jsonObject.optLongObject("myKey", 42l)));
assertTrue("optDouble() should return default double",
42.3d == jsonObject.optDouble("myKey", 42.3d));
+ assertTrue("optDoubleObject() should return default Double",
+ Double.valueOf(42.3d).equals(jsonObject.optDoubleObject("myKey", 42.3d)));
assertTrue("optFloat() should return default float",
42.3f == jsonObject.optFloat("myKey", 42.3f));
+ assertTrue("optFloatObject() should return default Float",
+ Float.valueOf(42.3f).equals(jsonObject.optFloatObject("myKey", 42.3f)));
assertTrue("optNumber() should return default Number",
42l == jsonObject.optNumber("myKey", Long.valueOf(42)).longValue());
assertTrue("optString() should return default string",
"hi".equals(jsonObject.optString("hiKey", "hi")));
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -2454,15 +2860,22 @@ public void jsonObjectOptNoKey() {
public void jsonObjectOptStringConversion() {
JSONObject jo = new JSONObject("{\"int\":\"123\",\"true\":\"true\",\"false\":\"false\"}");
assertTrue("unexpected optBoolean value",jo.optBoolean("true",false)==true);
+ assertTrue("unexpected optBooleanObject value",Boolean.valueOf(true).equals(jo.optBooleanObject("true",false)));
assertTrue("unexpected optBoolean value",jo.optBoolean("false",true)==false);
+ assertTrue("unexpected optBooleanObject value",Boolean.valueOf(false).equals(jo.optBooleanObject("false",true)));
assertTrue("unexpected optInt value",jo.optInt("int",0)==123);
+ assertTrue("unexpected optIntegerObject value",Integer.valueOf(123).equals(jo.optIntegerObject("int",0)));
assertTrue("unexpected optLong value",jo.optLong("int",0)==123l);
+ assertTrue("unexpected optLongObject value",Long.valueOf(123l).equals(jo.optLongObject("int",0L)));
assertTrue("unexpected optDouble value",jo.optDouble("int",0.0d)==123.0d);
+ assertTrue("unexpected optDoubleObject value",Double.valueOf(123.0d).equals(jo.optDoubleObject("int",0.0d)));
assertTrue("unexpected optFloat value",jo.optFloat("int",0.0f)==123.0f);
+ assertTrue("unexpected optFloatObject value",Float.valueOf(123.0f).equals(jo.optFloatObject("int",0.0f)));
assertTrue("unexpected optBigInteger value",jo.optBigInteger("int",BigInteger.ZERO).compareTo(new BigInteger("123"))==0);
assertTrue("unexpected optBigDecimal value",jo.optBigDecimal("int",BigDecimal.ZERO).compareTo(new BigDecimal("123"))==0);
assertTrue("unexpected optBigDecimal value",jo.optBigDecimal("int",BigDecimal.ZERO).compareTo(new BigDecimal("123"))==0);
assertTrue("unexpected optNumber value",jo.optNumber("int",BigInteger.ZERO).longValue()==123l);
+ Util.checkJSONObjectMaps(jo);
}
/**
@@ -2478,25 +2891,38 @@ public void jsonObjectOptCoercion() {
assertEquals(new BigDecimal("19007199254740993.35481234487103587486413587843213584"), jo.optBigDecimal("largeNumber",null));
assertEquals(new BigInteger("19007199254740993"), jo.optBigInteger("largeNumber",null));
assertEquals(1.9007199254740992E16, jo.optDouble("largeNumber"),0.0);
+ assertEquals(1.9007199254740992E16, jo.optDoubleObject("largeNumber"),0.0);
assertEquals(1.90071995E16f, jo.optFloat("largeNumber"),0.0f);
+ assertEquals(1.90071995E16f, jo.optFloatObject("largeNumber"),0.0f);
assertEquals(19007199254740993l, jo.optLong("largeNumber"));
+ assertEquals(Long.valueOf(19007199254740993l), jo.optLongObject("largeNumber"));
assertEquals(1874919425, jo.optInt("largeNumber"));
+ assertEquals(Integer.valueOf(1874919425), jo.optIntegerObject("largeNumber"));
// conversion from a string
assertEquals(new BigDecimal("19007199254740993.35481234487103587486413587843213584"), jo.optBigDecimal("largeNumberStr",null));
assertEquals(new BigInteger("19007199254740993"), jo.optBigInteger("largeNumberStr",null));
assertEquals(1.9007199254740992E16, jo.optDouble("largeNumberStr"),0.0);
+ assertEquals(1.9007199254740992E16, jo.optDoubleObject("largeNumberStr"),0.0);
assertEquals(1.90071995E16f, jo.optFloat("largeNumberStr"),0.0f);
+ assertEquals(1.90071995E16f, jo.optFloatObject("largeNumberStr"),0.0f);
assertEquals(19007199254740993l, jo.optLong("largeNumberStr"));
+ assertEquals(Long.valueOf(19007199254740993l), jo.optLongObject("largeNumberStr"));
assertEquals(1874919425, jo.optInt("largeNumberStr"));
+ assertEquals(Integer.valueOf(1874919425), jo.optIntegerObject("largeNumberStr"));
// the integer portion of the actual value is larger than a double can hold.
assertNotEquals((long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optLong("largeNumber"));
+ assertNotEquals(Long.valueOf((long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584")), jo.optLongObject("largeNumber"));
assertNotEquals((int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optInt("largeNumber"));
+ assertNotEquals(Integer.valueOf((int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584")), jo.optIntegerObject("largeNumber"));
assertNotEquals((long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optLong("largeNumberStr"));
+ assertNotEquals(Long.valueOf((long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584")), jo.optLongObject("largeNumberStr"));
assertNotEquals((int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optInt("largeNumberStr"));
+ assertNotEquals(Integer.valueOf((int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584")), jo.optIntegerObject("largeNumberStr"));
assertEquals(19007199254740992l, (long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"));
assertEquals(2147483647, (int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"));
+ Util.checkJSONObjectMaps(jo);
}
/**
@@ -2519,6 +2945,7 @@ public void jsonObjectOptBigDecimal() {
assertNull(jo.optBigDecimal("nullVal", null));
assertEquals(jo.optBigDecimal("float", null),jo.getBigDecimal("float"));
assertEquals(jo.optBigDecimal("double", null),jo.getBigDecimal("double"));
+ Util.checkJSONObjectMaps(jo);
}
/**
@@ -2539,6 +2966,7 @@ public void jsonObjectOptBigInteger() {
assertEquals(new BigInteger("1234"),jo.optBigInteger("bigInteger", null));
assertEquals(new BigInteger("1234"),jo.optBigInteger("bigDecimal", null));
assertNull(jo.optBigDecimal("nullVal", null));
+ Util.checkJSONObjectMaps(jo);
}
/**
@@ -2556,8 +2984,9 @@ public void jsonObjectputNull() {
JSONObject jsonObjectPutNull = new JSONObject(str);
jsonObjectPutNull.put("myKey", (Object) null);
assertTrue("jsonObject should be empty", jsonObjectPutNull.isEmpty());
-
-
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObjectRemove, jsonObjectPutNull
+ )));
}
/**
@@ -2642,6 +3071,7 @@ public void write() throws IOException {
} finally {
stringWriter.close();
}
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -2687,12 +3117,13 @@ public void testJSONWriterException() {
// test a more complex object
writer = new StringWriter();
- try {
- new JSONObject()
+
+ JSONObject object = new JSONObject()
.put("somethingElse", "a value")
.put("someKey", new JSONArray()
- .put(new JSONObject().put("key1", new BrokenToString())))
- .write(writer).toString();
+ .put(new JSONObject().put("key1", new BrokenToString())));
+ try {
+ object.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());
@@ -2703,17 +3134,18 @@ public void testJSONWriterException() {
writer.close();
} catch (Exception e) {}
}
-
+
// test a more slightly complex object
writer = new StringWriter();
- try {
- new JSONObject()
+
+ object = new JSONObject()
.put("somethingElse", "a value")
.put("someKey", new JSONArray()
.put(new JSONObject().put("key1", new BrokenToString()))
.put(12345)
- )
- .write(writer).toString();
+ );
+ try {
+ object.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());
@@ -2724,7 +3156,7 @@ public void testJSONWriterException() {
writer.close();
} catch (Exception e) {}
}
-
+ Util.checkJSONObjectMaps(jsonObject);
}
@@ -2792,6 +3224,7 @@ public void write3Param() throws IOException {
stringWriter.close();
} catch (Exception e) {}
}
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -2834,6 +3267,7 @@ public void equals() {
JSONObject aJsonObject = new JSONObject(str);
assertTrue("Same JSONObject should be equal to itself",
aJsonObject.equals(aJsonObject));
+ Util.checkJSONObjectMaps(aJsonObject);
}
/**
@@ -2919,6 +3353,9 @@ public void jsonObjectNullOperations() {
"null ".equals(sJONull));
String sNull = XML.toString(jsonObjectNull);
assertTrue("null should emit an empty string", "".equals(sNull));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jsonObjectJONull, jsonObjectNull
+ )));
}
@Test(expected = JSONPointerException.class)
@@ -3016,6 +3453,7 @@ public void toMap() {
// assert that the new map is mutable
assertTrue("Removing a key should succeed", map.remove("key3") != null);
assertTrue("Map should have 2 elements", map.size() == 2);
+ Util.checkJSONObjectMaps(jsonObject);
}
/**
@@ -3040,6 +3478,9 @@ public void testSingletonBean() {
// ensure our original jo hasn't changed.
assertEquals(0, jo.get("someInt"));
assertEquals(null, jo.opt("someString"));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jo, jo2
+ )));
}
/**
@@ -3064,6 +3505,9 @@ public void testSingletonEnumBean() {
// ensure our original jo hasn't changed.
assertEquals(0, jo.get("someInt"));
assertEquals(null, jo.opt("someString"));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ jo, jo2
+ )));
}
/**
@@ -3072,13 +3516,14 @@ public void testSingletonEnumBean() {
@SuppressWarnings("boxing")
@Test
public void testGenericBean() {
- GenericBean bean = new GenericBean(42);
+ GenericBean bean = new GenericBean<>(42);
final JSONObject jo = new JSONObject(bean);
assertEquals(jo.keySet().toString(), 8, jo.length());
assertEquals(42, jo.get("genericValue"));
assertEquals("Expected the getter to only be called once",
1, bean.genericGetCounter);
assertEquals(0, bean.genericSetCounter);
+ Util.checkJSONObjectMaps(jo);
}
/**
@@ -3094,6 +3539,7 @@ public void testGenericIntBean() {
assertEquals("Expected the getter to only be called once",
1, bean.genericGetCounter);
assertEquals(0, bean.genericSetCounter);
+ Util.checkJSONObjectMaps(jo);
}
/**
@@ -3112,12 +3558,14 @@ public void testWierdListBean() {
assertEquals("Expected 1 key to be mapped. Instead found: "+jo.keySet().toString(),
1, jo.length());
assertNotNull(jo.get("ALL"));
+ Util.checkJSONObjectMaps(jo);
}
/**
* Sample test case from https://github.com/stleary/JSON-java/issues/531
* which verifies that no regression in double/BigDecimal support is present.
*/
+ @Test
public void testObjectToBigDecimal() {
double value = 1412078745.01074;
Reader reader = new StringReader("[{\"value\": " + value + "}]");
@@ -3129,6 +3577,8 @@ public void testObjectToBigDecimal() {
BigDecimal wantedValue = BigDecimal.valueOf(value);
assertEquals(current, wantedValue);
+ Util.checkJSONObjectMaps(jsonObject);
+ Util.checkJSONArrayMaps(array, jsonObject.getMapType());
}
/**
@@ -3142,6 +3592,7 @@ public void testExceptionalBean() {
1, jo.length());
assertTrue(jo.get("closeable") instanceof JSONObject);
assertTrue(jo.getJSONObject("closeable").has("string"));
+ Util.checkJSONObjectMaps(jo);
}
@Test(expected=NullPointerException.class)
@@ -3200,6 +3651,122 @@ public void testPutNullObject() {
jsonObject.put(null, new Object());
fail("Expected an exception");
}
+ @Test(expected=JSONException.class)
+ public void testSelfRecursiveObject() {
+ // A -> A ...
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ ObjA.setRef(ObjA);
+ new JSONObject(ObjA);
+ fail("Expected an exception");
+ }
+ @Test(expected=JSONException.class)
+ public void testLongSelfRecursiveObject() {
+ // B -> A -> A ...
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ ObjB.setRef(ObjA);
+ ObjA.setRef(ObjA);
+ new JSONObject(ObjB);
+ fail("Expected an exception");
+ }
+ @Test(expected=JSONException.class)
+ public void testSimpleRecursiveObject() {
+ // B -> A -> B ...
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ ObjB.setRef(ObjA);
+ ObjA.setRef(ObjB);
+ new JSONObject(ObjA);
+ fail("Expected an exception");
+ }
+ @Test(expected=JSONException.class)
+ public void testLongRecursiveObject() {
+ // D -> C -> B -> A -> D ...
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ RecursiveBean ObjC = new RecursiveBean("ObjC");
+ RecursiveBean ObjD = new RecursiveBean("ObjD");
+ ObjC.setRef(ObjB);
+ ObjB.setRef(ObjA);
+ ObjD.setRef(ObjC);
+ ObjA.setRef(ObjD);
+ new JSONObject(ObjB);
+ fail("Expected an exception");
+ }
+ @Test(expected=JSONException.class)
+ public void testRepeatObjectRecursive() {
+ // C -> B -> A -> D -> C ...
+ // -> D -> C ...
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ RecursiveBean ObjC = new RecursiveBean("ObjC");
+ RecursiveBean ObjD = new RecursiveBean("ObjD");
+ ObjC.setRef(ObjB);
+ ObjB.setRef(ObjA);
+ ObjB.setRef2(ObjD);
+ ObjA.setRef(ObjD);
+ ObjD.setRef(ObjC);
+ new JSONObject(ObjC);
+ fail("Expected an exception");
+ }
+ @Test
+ public void testRepeatObjectNotRecursive() {
+ // C -> B -> A
+ // -> A
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ RecursiveBean ObjC = new RecursiveBean("ObjC");
+ ObjC.setRef(ObjA);
+ ObjB.setRef(ObjA);
+ ObjB.setRef2(ObjA);
+ JSONObject j0 = new JSONObject(ObjC);
+ JSONObject j1 = new JSONObject(ObjB);
+ JSONObject j2 = new JSONObject(ObjA);
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ j0, j1, j2
+ )));
+ }
+ @Test
+ public void testLongRepeatObjectNotRecursive() {
+ // C -> B -> A -> D -> E
+ // -> D -> E
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ RecursiveBean ObjC = new RecursiveBean("ObjC");
+ RecursiveBean ObjD = new RecursiveBean("ObjD");
+ RecursiveBean ObjE = new RecursiveBean("ObjE");
+ ObjC.setRef(ObjB);
+ ObjB.setRef(ObjA);
+ ObjB.setRef2(ObjD);
+ ObjA.setRef(ObjD);
+ ObjD.setRef(ObjE);
+ JSONObject j0 = new JSONObject(ObjC);
+ JSONObject j1 = new JSONObject(ObjB);
+ JSONObject j2 = new JSONObject(ObjA);
+ JSONObject j3 = new JSONObject(ObjD);
+ JSONObject j4 = new JSONObject(ObjE);
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
+ j0, j1, j2, j3, j4
+ )));
+ }
+ @Test(expected=JSONException.class)
+ public void testRecursiveEquals() {
+ RecursiveBeanEquals a = new RecursiveBeanEquals("same");
+ a.setRef(a);
+ JSONObject j0 = new JSONObject(a);
+ Util.checkJSONObjectMaps(j0);
+ }
+ @Test
+ public void testNotRecursiveEquals() {
+ RecursiveBeanEquals a = new RecursiveBeanEquals("same");
+ RecursiveBeanEquals b = new RecursiveBeanEquals("same");
+ RecursiveBeanEquals c = new RecursiveBeanEquals("same");
+ a.setRef(b);
+ b.setRef(c);
+ JSONObject j0 = new JSONObject(a);
+ Util.checkJSONObjectMaps(j0);
+ }
+
@Test
public void testIssue548ObjectWithEmptyJsonArray() {
@@ -3207,5 +3774,462 @@ public void testIssue548ObjectWithEmptyJsonArray() {
assertTrue("missing expected key 'empty_json_array'", jsonObject.has("empty_json_array"));
assertNotNull("'empty_json_array' should be an array", jsonObject.getJSONArray("empty_json_array"));
assertEquals("'empty_json_array' should have a length of 0", 0, jsonObject.getJSONArray("empty_json_array").length());
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Tests if calling JSONObject clear() method actually makes the JSONObject empty
+ */
+ @Test(expected = JSONException.class)
+ public void jsonObjectClearMethodTest() {
+ //Adds random stuff to the JSONObject
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("key1", 123);
+ jsonObject.put("key2", "456");
+ jsonObject.put("key3", new JSONObject());
+ jsonObject.clear(); //Clears the JSONObject
+ assertTrue("expected jsonObject.length() == 0", jsonObject.length() == 0); //Check if its length is 0
+ jsonObject.getInt("key1"); //Should throws org.json.JSONException: JSONObject["asd"] not found
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Tests for stack overflow. See https://github.com/stleary/JSON-java/issues/654
+ */
+ @Test(expected = JSONException.class)
+ public void issue654StackOverflowInput() {
+ //String base64Bytes ="eyJHWiI6Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMCkwLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMCkwLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sJe3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTApMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sJe3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTApMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMCkwLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMCkwLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sJe3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTApMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sJe3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTApMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7c3t7e3t7e3vPAAAAAAAAAHt7e3t7e3t7e3t7e3t7e3t7e3t7e1ste3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e88AAAAAAAAAe3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7f3syMv//e3t7e3t7e3t7e3t7e3sx//////8=";
+ //String input = new String(java.util.Base64.getDecoder().decode(base64Bytes));
+ String input = "{\"GZ\":[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{s{{{{{{{";
+ JSONObject json_input = new JSONObject(input);
+ assertNotNull(json_input);
+ fail("Excepected Exception.");
+ Util.checkJSONObjectMaps(json_input);
+ }
+
+ /**
+ * Tests for incorrect object/array nesting. See https://github.com/stleary/JSON-java/issues/654
+ */
+ @Test(expected = JSONException.class)
+ public void issue654IncorrectNestingNoKey1() {
+ JSONObject json_input = new JSONObject("{{\"a\":0}}");
+ assertNotNull(json_input);
+ fail("Expected Exception.");
+ }
+
+ /**
+ * Tests for incorrect object/array nesting. See https://github.com/stleary/JSON-java/issues/654
+ */
+ @Test(expected = JSONException.class)
+ public void issue654IncorrectNestingNoKey2() {
+ JSONObject json_input = new JSONObject("{[\"a\"]}");
+ assertNotNull(json_input);
+ fail("Excepected Exception.");
+ }
+
+ /**
+ * Tests for stack overflow. See https://github.com/stleary/JSON-java/issues/654
+ */
+ @Ignore("This test relies on system constraints and may not always pass. See: https://github.com/stleary/JSON-java/issues/821")
+ @Test(expected = JSONException.class)
+ public void issue654StackOverflowInputWellFormed() {
+ //String input = new String(java.util.Base64.getDecoder().decode(base64Bytes));
+ final InputStream resourceAsStream = JSONObjectTest.class.getClassLoader().getResourceAsStream("Issue654WellFormedObject.json");
+ JSONTokener tokener = new JSONTokener(resourceAsStream);
+ JSONObject json_input = new JSONObject(tokener);
+ assertNotNull(json_input);
+ fail("Excepected Exception due to stack overflow.");
+ }
+
+ @Test
+ public void testIssue682SimilarityOfJSONString() {
+ JSONObject jo1 = new JSONObject()
+ .put("a", new MyJsonString())
+ .put("b", 2);
+ JSONObject jo2 = new JSONObject()
+ .put("a", new MyJsonString())
+ .put("b", 2);
+ assertTrue(jo1.similar(jo2));
+
+ JSONObject jo3 = new JSONObject()
+ .put("a", new JSONString() {
+ @Override
+ public String toJSONString() {
+ return "\"different value\"";
+ }
+ })
+ .put("b", 2);
+ assertFalse(jo1.similar(jo3));
+ }
+
+ private static final Number[] NON_FINITE_NUMBERS = { Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN,
+ Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NaN };
+
+ @Test
+ public void issue713MapConstructorWithNonFiniteNumbers() {
+ for (Number nonFinite : NON_FINITE_NUMBERS) {
+ Map map = new HashMap<>();
+ map.put("a", nonFinite);
+
+ assertThrows(JSONException.class, () -> new JSONObject(map));
+ }
+ }
+
+ @Test
+ public void issue713BeanConstructorWithNonFiniteNumbers() {
+ for (Number nonFinite : NON_FINITE_NUMBERS) {
+ GenericBean bean = new GenericBean<>(nonFinite);
+ assertThrows(JSONException.class, () -> new JSONObject(bean));
+ }
+ }
+
+ @Test(expected = JSONException.class)
+ public void issue743SerializationMap() {
+ HashMap map = new HashMap<>();
+ map.put("t", map);
+ JSONObject object = new JSONObject(map);
+ String jsonString = object.toString();
+ }
+
+ @Test(expected = JSONException.class)
+ public void testCircularReferenceMultipleLevel() {
+ HashMap inside = new HashMap<>();
+ HashMap jsonObject = new HashMap<>();
+ inside.put("inside", jsonObject);
+ jsonObject.put("test", inside);
+ new JSONObject(jsonObject);
+ }
+
+ @Test
+ public void issue743SerializationMapWith512Objects() {
+ HashMap map = buildNestedMap(ParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
+ JSONObject object = new JSONObject(map);
+ String jsonString = object.toString();
+ }
+
+ @Test
+ public void issue743SerializationMapWith500Objects() {
+ // TODO: find out why 1000 objects no longer works
+ HashMap map = buildNestedMap(500);
+ JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withMaxNestingDepth(500);
+ JSONObject object = new JSONObject(map, parserConfiguration);
+ String jsonString = object.toString();
+ }
+
+ @Test(expected = JSONException.class)
+ public void issue743SerializationMapWith1001Objects() {
+ HashMap map = buildNestedMap(1001);
+ JSONObject object = new JSONObject(map);
+ String jsonString = object.toString();
+ }
+
+ @Test(expected = JSONException.class)
+ public void testCircleReferenceFirstLevel() {
+ Map jsonObject = new HashMap<>();
+
+ jsonObject.put("test", jsonObject);
+
+ new JSONObject(jsonObject, new JSONParserConfiguration());
+ }
+
+ @Test(expected = StackOverflowError.class)
+ public void testCircleReferenceMultiplyLevel_notConfigured_expectedStackOverflow() {
+ Map inside = new HashMap<>();
+
+ Map jsonObject = new HashMap<>();
+ inside.put("test", jsonObject);
+ jsonObject.put("test", inside);
+
+ new JSONObject(jsonObject, new JSONParserConfiguration().withMaxNestingDepth(99999));
+ }
+
+ @Test(expected = JSONException.class)
+ public void testCircleReferenceMultiplyLevel_configured_expectedJSONException() {
+ Map inside = new HashMap<>();
+
+ Map jsonObject = new HashMap<>();
+ inside.put("test", jsonObject);
+ jsonObject.put("test", inside);
+
+ new JSONObject(jsonObject, new JSONParserConfiguration());
+ }
+
+ @Test
+ public void testDifferentKeySameInstanceNotACircleReference() {
+ Map map1 = new HashMap<>();
+ Map map2 = new HashMap<>();
+
+ map1.put("test1", map2);
+ map1.put("test2", map2);
+
+ new JSONObject(map1);
+ }
+
+ @Test
+ public void clarifyCurrentBehavior() {
+ // Behavior documented in #653 optLong vs getLong inconsistencies
+ // This problem still exists.
+ // Internally, both number_1 and number_2 are stored as strings. This is reasonable since they are parsed as strings.
+ // However, getLong and optLong should return similar results
+ JSONObject json = new JSONObject("{\"number_1\":\"01234\", \"number_2\": \"332211\"}");
+ assertEquals(json.getLong("number_1"), 1234L);
+ assertEquals(json.optLong("number_1"), 0); //THIS VALUE IS NOT RETURNED AS A NUMBER
+ assertEquals(json.getLong("number_2"), 332211L);
+ assertEquals(json.optLong("number_2"), 332211L);
+
+ // Behavior documented in #826 JSONObject parsing 0-led numeric strings as ints
+ // After reverting the code, personId is stored as a string, and the behavior is as expected
+ String personId = "\"0123\"";
+ JSONObject j1 = new JSONObject("{\"personId\": " + personId + "}");
+ assertEquals(j1.getString("personId"), "0123");
+
+ // Also #826. Here is input with missing quotes. Because of the leading zero, it should not be parsed as a number.
+ // This example was mentioned in the same ticket
+ // After reverting the code, personId is stored as a string, and the behavior is as expected
+ JSONObject j2 = new JSONObject("{\"personId\":\"0123\"}");
+ assertEquals(j2.getString("personId"), "0123");
+
+ // Behavior uncovered while working on the code
+ // All of the values are stored as strings except for hex4, which is stored as a number. This is probably incorrect
+ JSONObject j3 = new JSONObject("{ " +
+ "\"hex1\": \"010e4\", \"hex2\": \"00f0\", \"hex3\": \"0011\", " +
+ "\"hex4\": 00e0, \"hex5\": \"00f0\", \"hex6\": \"0011\" }");
+ assertEquals(j3.getString("hex1"), "010e4");
+ assertEquals(j3.getString("hex2"), "00f0");
+ assertEquals(j3.getString("hex3"), "0011");
+ assertEquals(j3.getLong("hex4"), 0, .1);
+ assertEquals(j3.getString("hex5"), "00f0");
+ assertEquals(j3.getString("hex6"), "0011");
+ }
+
+
+ @Test
+ public void testStrictModeJSONTokener_expectException(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
+ JSONTokener tokener = new JSONTokener("{\"key\":\"value\"}invalidCharacters", jsonParserConfiguration);
+
+ assertThrows(JSONException.class, () -> { new JSONObject(tokener); });
+ }
+
+ @Test
+ public void test_strictModeWithMisCasedBooleanOrNullValue(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
+ try{
+ new JSONObject("{\"a\":True}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+ try{
+ new JSONObject("{\"a\":TRUE}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+ try{
+ new JSONObject("{\"a\":nUlL}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+ }
+
+ @Test
+ public void test_strictModeWithInappropriateKey(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
+
+ // Parsing the following objects should fail
+ try{
+ new JSONObject("{true : 3}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+ try{
+ new JSONObject("{TRUE : 3}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+ try{
+ new JSONObject("{1 : 3}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+
+ }
+
+
+ /**
+ * Method to build nested map of max maxDepth
+ *
+ * @param maxDepth
+ * @return
+ */
+ public static HashMap buildNestedMap(int maxDepth) {
+ if (maxDepth <= 0) {
+ return new HashMap<>();
+ }
+ HashMap nestedMap = new HashMap<>();
+ nestedMap.put("t", buildNestedMap(maxDepth - 1));
+ return nestedMap;
+ }
+
+
+ /**
+ * Tests the behavior of the {@link JSONObject} when parsing a bean with null fields
+ * using a custom {@link JSONParserConfiguration} that enables the use of native nulls.
+ *
+ * This test ensures that uninitialized fields in the bean are serialized correctly
+ * into the resulting JSON object, and their keys are present in the JSON string output.
+ */
+ @Test
+ public void jsonObjectParseNullFieldsWithParserConfiguration() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+ RecursiveBean bean = new RecursiveBean(null);
+ JSONObject jsonObject = new JSONObject(bean, jsonParserConfiguration.withUseNativeNulls(true));
+ assertTrue("name key should be present", jsonObject.has("name"));
+ assertTrue("ref key should be present", jsonObject.has("ref"));
+ assertTrue("ref2 key should be present", jsonObject.has("ref2"));
+ }
+
+ /**
+ * Tests the behavior of the {@link JSONObject} when parsing a bean with null fields
+ * without using a custom {@link JSONParserConfiguration}.
+ *
+ * This test ensures that uninitialized fields in the bean are not serialized
+ * into the resulting JSON object, and the object remains empty.
+ */
+ @Test
+ public void jsonObjectParseNullFieldsWithoutParserConfiguration() {
+ RecursiveBean bean = new RecursiveBean(null);
+ JSONObject jsonObject = new JSONObject(bean);
+ assertTrue("JSONObject should be empty", jsonObject.isEmpty());
+ }
+
+
+ @Test
+ public void jsonObjectParseFromJson_0() {
+ JSONObject object = new JSONObject();
+ object.put("number", 12);
+ object.put("name", "Alex");
+ object.put("longNumber", 1500000000L);
+ CustomClass customClass = object.fromJson(CustomClass.class);
+ CustomClass compareClass = new CustomClass(12, "Alex", 1500000000L);
+ assertEquals(customClass, compareClass);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_1() {
+ JSONObject object = new JSONObject();
+
+ BigInteger largeInt = new BigInteger("123");
+ object.put("largeInt", largeInt.toString());
+ CustomClassA customClassA = object.fromJson(CustomClassA.class);
+ CustomClassA compareClassClassA = new CustomClassA(largeInt);
+ assertEquals(customClassA, compareClassClassA);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_2() {
+ JSONObject object = new JSONObject();
+ object.put("number", 12);
+
+ JSONObject classC = new JSONObject();
+ classC.put("stringName", "Alex");
+ classC.put("longNumber", 123456L);
+
+ object.put("classC", classC);
+
+ CustomClassB customClassB = object.fromJson(CustomClassB.class);
+ CustomClassC classCObject = new CustomClassC("Alex", 123456L);
+ CustomClassB compareClassB = new CustomClassB(12, classCObject);
+ assertEquals(customClassB, compareClassB);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_3() {
+ JSONObject object = new JSONObject();
+ JSONArray array = new JSONArray();
+ array.put("test1");
+ array.put("test2");
+ array.put("test3");
+ object.put("stringList", array);
+
+ CustomClassD customClassD = object.fromJson(CustomClassD.class);
+ CustomClassD compareClassD = new CustomClassD(Arrays.asList("test1", "test2", "test3"));
+ assertEquals(customClassD, compareClassD);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_4() {
+ JSONObject object = new JSONObject();
+ JSONArray array = new JSONArray();
+ array.put(new CustomClassC("test1", 1L).toJSON());
+ array.put(new CustomClassC("test2", 2L).toJSON());
+ object.put("listClassC", array);
+
+ CustomClassE customClassE = object.fromJson(CustomClassE.class);
+ CustomClassE compareClassE = new CustomClassE(java.util.Arrays.asList(
+ new CustomClassC("test1", 1L),
+ new CustomClassC("test2", 2L)));
+ assertEquals(customClassE, compareClassE);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_5() {
+ JSONObject object = new JSONObject();
+ JSONArray array = new JSONArray();
+ array.put(Arrays.asList("A", "B", "C"));
+ array.put(Arrays.asList("D", "E"));
+ object.put("listOfString", array);
+
+ CustomClassF customClassF = object.fromJson(CustomClassF.class);
+ List> listOfString = new ArrayList<>();
+ listOfString.add(Arrays.asList("A", "B", "C"));
+ listOfString.add(Arrays.asList("D", "E"));
+ CustomClassF compareClassF = new CustomClassF(listOfString);
+ assertEquals(customClassF, compareClassF);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_6() {
+ JSONObject object = new JSONObject();
+ Map dataList = new HashMap<>();
+ dataList.put("A", "Aa");
+ dataList.put("B", "Bb");
+ dataList.put("C", "Cc");
+ object.put("dataList", dataList);
+
+ CustomClassG customClassG = object.fromJson(CustomClassG.class);
+ CustomClassG compareClassG = new CustomClassG(dataList);
+ assertEquals(customClassG, compareClassG);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_7() {
+ JSONObject object = new JSONObject();
+ Map> dataList = new HashMap<>();
+ dataList.put("1", Arrays.asList(1, 2, 3, 4));
+ dataList.put("2", Arrays.asList(2, 3, 4, 5));
+ object.put("integerMap", dataList);
+
+ CustomClassH customClassH = object.fromJson(CustomClassH.class);
+ CustomClassH compareClassH = new CustomClassH(dataList);
+ assertEquals(customClassH.integerMap.toString(), compareClassH.integerMap.toString());
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_8() {
+ JSONObject object = new JSONObject();
+ Map> dataList = new HashMap<>();
+ dataList.put("1", Collections.singletonMap("1", 1));
+ dataList.put("2", Collections.singletonMap("2", 2));
+ object.put("integerMap", dataList);
+
+ CustomClassI customClassI = object.fromJson(CustomClassI.class);
+ CustomClassI compareClassI = new CustomClassI(dataList);
+ assertEquals(customClassI.integerMap.toString(), compareClassI.integerMap.toString());
}
}
diff --git a/src/test/java/org/json/junit/JSONParserConfigurationTest.java b/src/test/java/org/json/junit/JSONParserConfigurationTest.java
new file mode 100644
index 000000000..926c49f41
--- /dev/null
+++ b/src/test/java/org/json/junit/JSONParserConfigurationTest.java
@@ -0,0 +1,624 @@
+package org.json.junit;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONParserConfiguration;
+import org.json.JSONTokener;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class JSONParserConfigurationTest {
+ private static final String TEST_SOURCE = "{\"key\": \"value1\", \"key\": \"value2\"}";
+
+ @Test(expected = JSONException.class)
+ public void testThrowException() {
+ new JSONObject(TEST_SOURCE);
+ }
+
+ @Test
+ public void testOverwrite() {
+ JSONObject jsonObject = new JSONObject(TEST_SOURCE,
+ new JSONParserConfiguration().withOverwriteDuplicateKey(true));
+
+ assertEquals("duplicate key should be overwritten", "value2", jsonObject.getString("key"));
+ }
+
+ @Test
+ public void strictModeIsCloned(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true)
+ .withMaxNestingDepth(12);
+
+ assertTrue(jsonParserConfiguration.isStrictMode());
+ }
+
+ @Test
+ public void maxNestingDepthIsCloned(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withKeepStrings(true)
+ .withStrictMode(true);
+
+ assertTrue(jsonParserConfiguration.isKeepStrings());
+ }
+
+ @Test
+ public void useNativeNullsIsCloned() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withUseNativeNulls(true)
+ .withStrictMode(true);
+ assertTrue(jsonParserConfiguration.isUseNativeNulls());
+ }
+
+ @Test
+ public void verifyDuplicateKeyThenMaxDepth() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withOverwriteDuplicateKey(true)
+ .withMaxNestingDepth(42);
+
+ assertEquals(42, jsonParserConfiguration.getMaxNestingDepth());
+ assertTrue(jsonParserConfiguration.isOverwriteDuplicateKey());
+ }
+
+ @Test
+ public void verifyMaxDepthThenDuplicateKey() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withMaxNestingDepth(42)
+ .withOverwriteDuplicateKey(true);
+
+ assertTrue(jsonParserConfiguration.isOverwriteDuplicateKey());
+ assertEquals(42, jsonParserConfiguration.getMaxNestingDepth());
+ }
+
+ @Test
+ public void givenInvalidInput_testStrictModeTrue_shouldThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ List strictModeInputTestCases = getNonCompliantJSONArrayList();
+ // this is a lot easier to debug when things stop working
+ for (int i = 0; i < strictModeInputTestCases.size(); ++i) {
+ String testCase = strictModeInputTestCases.get(i);
+ try {
+ JSONArray jsonArray = new JSONArray(testCase, jsonParserConfiguration);
+ String s = jsonArray.toString();
+ String msg = "Expected an exception, but got: " + s + " Noncompliant Array index: " + i;
+ fail(msg);
+ } catch (Exception e) {
+ // its all good
+ }
+ }
+ }
+
+ @Test
+ public void givenInvalidInputObjects_testStrictModeTrue_shouldThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ List strictModeInputTestCases = getNonCompliantJSONObjectList();
+ // this is a lot easier to debug when things stop working
+ for (int i = 0; i < strictModeInputTestCases.size(); ++i) {
+ String testCase = strictModeInputTestCases.get(i);
+ try {
+ JSONObject jsonObject = new JSONObject(testCase, jsonParserConfiguration);
+ String s = jsonObject.toString();
+ String msg = "Expected an exception, but got: " + s + " Noncompliant Array index: " + i;
+ fail(msg);
+ } catch (Exception e) {
+ // its all good
+ }
+ }
+ }
+
+ @Test
+ public void givenEmptyArray_testStrictModeTrue_shouldNotThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "[]";
+ JSONArray jsonArray = new JSONArray(testCase, jsonParserConfiguration);
+ assertEquals(testCase, jsonArray.toString());
+ }
+
+ @Test
+ public void givenEmptyObject_testStrictModeTrue_shouldNotThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "{}";
+ JSONObject jsonObject = new JSONObject(testCase, jsonParserConfiguration);
+ assertEquals(testCase, jsonObject.toString());
+ }
+
+ @Test
+ public void givenValidNestedArray_testStrictModeTrue_shouldNotThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+
+ String testCase = "[[\"c\"], [10.2], [true, false, true]]";
+
+ JSONArray jsonArray = new JSONArray(testCase, jsonParserConfiguration);
+ JSONArray arrayShouldContainStringAt0 = jsonArray.getJSONArray(0);
+ JSONArray arrayShouldContainNumberAt0 = jsonArray.getJSONArray(1);
+ JSONArray arrayShouldContainBooleanAt0 = jsonArray.getJSONArray(2);
+
+ assertTrue(arrayShouldContainStringAt0.get(0) instanceof String);
+ assertTrue(arrayShouldContainNumberAt0.get(0) instanceof Number);
+ assertTrue(arrayShouldContainBooleanAt0.get(0) instanceof Boolean);
+ }
+
+ @Test
+ public void givenValidNestedObject_testStrictModeTrue_shouldNotThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+
+ String testCase = "{\"a0\":[\"c\"], \"a1\":[10.2], \"a2\":[true, false, true]}";
+
+ JSONObject jsonObject = new JSONObject(testCase, jsonParserConfiguration);
+ JSONArray arrayShouldContainStringAt0 = jsonObject.getJSONArray("a0");
+ JSONArray arrayShouldContainNumberAt0 = jsonObject.getJSONArray("a1");
+ JSONArray arrayShouldContainBooleanAt0 = jsonObject.getJSONArray("a2");
+
+ assertTrue(arrayShouldContainStringAt0.get(0) instanceof String);
+ assertTrue(arrayShouldContainNumberAt0.get(0) instanceof Number);
+ assertTrue(arrayShouldContainBooleanAt0.get(0) instanceof Boolean);
+ }
+
+ @Test
+ public void givenValidEmptyArrayInsideArray_testStrictModeTrue_shouldNotThrowJsonException(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "[[]]";
+ JSONArray jsonArray = new JSONArray(testCase, jsonParserConfiguration);
+ assertEquals(testCase, jsonArray.toString());
+ }
+
+ @Test
+ public void givenValidEmptyArrayInsideObject_testStrictModeTrue_shouldNotThrowJsonException(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "{\"a0\":[]}";
+ JSONObject jsonObject = new JSONObject(testCase, jsonParserConfiguration);
+ assertEquals(testCase, jsonObject.toString());
+ }
+
+ @Test
+ public void givenValidEmptyArrayInsideArray_testStrictModeFalse_shouldNotThrowJsonException(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(false);
+ String testCase = "[[]]";
+ JSONArray jsonArray = new JSONArray(testCase, jsonParserConfiguration);
+ assertEquals(testCase, jsonArray.toString());
+ }
+
+ @Test
+ public void givenValidEmptyArrayInsideObject_testStrictModeFalse_shouldNotThrowJsonException(){
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(false);
+ String testCase = "{\"a0\":[]}";
+ JSONObject jsonObject = new JSONObject(testCase, jsonParserConfiguration);
+ assertEquals(testCase, jsonObject.toString());
+ }
+
+ @Test
+ public void givenInvalidStringArray_testStrictModeTrue_shouldThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "[badString]";
+ JSONException je = assertThrows(JSONException.class, () -> new JSONArray(testCase, jsonParserConfiguration));
+ assertEquals("Strict mode error: Value 'badString' is not surrounded by quotes at 10 [character 11 line 1]",
+ je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidStringObject_testStrictModeTrue_shouldThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "{\"a0\":badString}";
+ JSONException je = assertThrows(JSONException.class, () -> new JSONObject(testCase, jsonParserConfiguration));
+ assertEquals("Strict mode error: Value 'badString' is not surrounded by quotes at 15 [character 16 line 1]",
+ je.getMessage());
+ }
+
+ @Test
+ public void allowNullArrayInStrictMode() {
+ String expected = "[null]";
+ JSONArray jsonArray = new JSONArray(expected, new JSONParserConfiguration().withStrictMode(true));
+ assertEquals(expected, jsonArray.toString());
+ }
+
+ @Test
+ public void allowNullObjectInStrictMode() {
+ String expected = "{\"a0\":null}";
+ JSONObject jsonObject = new JSONObject(expected, new JSONParserConfiguration().withStrictMode(true));
+ assertEquals(expected, jsonObject.toString());
+ }
+
+ @Test
+ public void shouldHandleNumericArray() {
+ String expected = "[10]";
+ JSONArray jsonArray = new JSONArray(expected, new JSONParserConfiguration().withStrictMode(true));
+ assertEquals(expected, jsonArray.toString());
+ }
+
+ @Test
+ public void shouldHandleNumericObject() {
+ String expected = "{\"a0\":10}";
+ JSONObject jsonObject = new JSONObject(expected, new JSONParserConfiguration().withStrictMode(true));
+ assertEquals(expected, jsonObject.toString());
+ }
+ @Test
+ public void givenCompliantJSONArrayFile_testStrictModeTrue_shouldNotThrowAnyException() throws IOException {
+ try (Stream lines = Files.lines(Paths.get("src/test/resources/compliantJsonArray.json"))) {
+ String compliantJsonArrayAsString = lines.collect(Collectors.joining());
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ new JSONArray(compliantJsonArrayAsString, jsonParserConfiguration);
+ }
+ }
+
+ @Test
+ public void givenCompliantJSONObjectFile_testStrictModeTrue_shouldNotThrowAnyException() throws IOException {
+ try (Stream lines = Files.lines(Paths.get("src/test/resources/compliantJsonObject.json"))) {
+ String compliantJsonObjectAsString = lines.collect(Collectors.joining());
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+
+ new JSONObject(compliantJsonObjectAsString, jsonParserConfiguration);
+ }
+ }
+
+ @Test
+ public void givenInvalidInputArrays_testStrictModeFalse_shouldNotThrowAnyException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(false);
+
+ List strictModeInputTestCases = getNonCompliantJSONArrayList();
+
+ // this is a lot easier to debug when things stop working
+ for (int i = 0; i < strictModeInputTestCases.size(); ++i) {
+ String testCase = strictModeInputTestCases.get(i);
+ try {
+ JSONArray jsonArray = new JSONArray(testCase, jsonParserConfiguration);
+ } catch (Exception e) {
+ System.out.println("Unexpected exception: " + e.getMessage() + " Noncompliant Array index: " + i);
+ fail(String.format("Noncompliant array index: %d", i));
+ }
+ }
+ }
+
+ @Test
+ public void givenInvalidInputObjects_testStrictModeFalse_shouldNotThrowAnyException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(false);
+
+ List strictModeInputTestCases = getNonCompliantJSONObjectList();
+
+ // this is a lot easier to debug when things stop working
+ for (int i = 0; i < strictModeInputTestCases.size(); ++i) {
+ String testCase = strictModeInputTestCases.get(i);
+ try {
+ JSONObject jsonObject = new JSONObject(testCase, jsonParserConfiguration);
+ } catch (Exception e) {
+ System.out.println("Unexpected exception: " + e.getMessage() + " Noncompliant Array index: " + i);
+ fail(String.format("Noncompliant array index: %d", i));
+ }
+ }
+ }
+
+ @Test
+ public void givenInvalidInputArray_testStrictModeTrue_shouldThrowInvalidCharacterErrorMessage() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "[1,2];[3,4]";
+ JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
+ JSONException.class, () -> new JSONArray(testCase, jsonParserConfiguration));
+ assertEquals("Strict mode error: Unparsed characters found at end of input text at 6 [character 7 line 1]",
+ je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputObject_testStrictModeTrue_shouldThrowInvalidCharacterErrorMessage() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "{\"a0\":[1,2];\"a1\":[3,4]}";
+ JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
+ JSONException.class, () -> new JSONObject(testCase, jsonParserConfiguration));
+ assertEquals("Strict mode error: Invalid character ';' found at 12 [character 13 line 1]", je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputArrayWithNumericStrings_testStrictModeTrue_shouldThrowInvalidCharacterErrorMessage() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "[\"1\",\"2\"];[3,4]";
+ JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
+ JSONException.class, () -> new JSONArray(testCase, jsonParserConfiguration));
+ assertEquals("Strict mode error: Unparsed characters found at end of input text at 10 [character 11 line 1]",
+ je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputObjectWithNumericStrings_testStrictModeTrue_shouldThrowInvalidCharacterErrorMessage() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "{\"a0\":[\"1\",\"2\"];\"a1\":[3,4]}";
+ JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
+ JSONException.class, () -> new JSONObject(testCase, jsonParserConfiguration));
+ assertEquals("Strict mode error: Invalid character ';' found at 16 [character 17 line 1]", je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputArray_testStrictModeTrue_shouldThrowValueNotSurroundedByQuotesErrorMessage() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "[{\"test\": implied}]";
+ JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
+ JSONException.class, () -> new JSONArray(testCase, jsonParserConfiguration));
+ assertEquals("Strict mode error: Value 'implied' is not surrounded by quotes at 17 [character 18 line 1]",
+ je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputObject_testStrictModeTrue_shouldThrowValueNotSurroundedByQuotesErrorMessage() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+ String testCase = "{\"a0\":{\"test\": implied}]}";
+ JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
+ JSONException.class, () -> new JSONObject(testCase, jsonParserConfiguration));
+ assertEquals("Strict mode error: Value 'implied' is not surrounded by quotes at 22 [character 23 line 1]",
+ je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputArray_testStrictModeFalse_shouldNotThrowAnyException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(false);
+ String testCase = "[{\"test\": implied}]";
+ new JSONArray(testCase, jsonParserConfiguration);
+ }
+
+ @Test
+ public void givenInvalidInputObject_testStrictModeFalse_shouldNotThrowAnyException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(false);
+ String testCase = "{\"a0\":{\"test\": implied}}";
+ new JSONObject(testCase, jsonParserConfiguration);
+ }
+
+ @Test
+ public void givenNonCompliantQuotesArray_testStrictModeTrue_shouldThrowJsonExceptionWithConcreteErrorDescription() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+
+ String testCaseOne = "[\"abc', \"test\"]";
+ String testCaseTwo = "['abc\", \"test\"]";
+ String testCaseThree = "['abc']";
+ String testCaseFour = "[{'testField': \"testValue\"}]";
+
+ JSONException jeOne = assertThrows(JSONException.class,
+ () -> new JSONArray(testCaseOne, jsonParserConfiguration));
+ JSONException jeTwo = assertThrows(JSONException.class,
+ () -> new JSONArray(testCaseTwo, jsonParserConfiguration));
+ JSONException jeThree = assertThrows(JSONException.class,
+ () -> new JSONArray(testCaseThree, jsonParserConfiguration));
+ JSONException jeFour = assertThrows(JSONException.class,
+ () -> new JSONArray(testCaseFour, jsonParserConfiguration));
+
+ assertEquals(
+ "Expected a ',' or ']' at 10 [character 11 line 1]",
+ jeOne.getMessage());
+ assertEquals(
+ "Strict mode error: Single quoted strings are not allowed at 2 [character 3 line 1]",
+ jeTwo.getMessage());
+ assertEquals(
+ "Strict mode error: Single quoted strings are not allowed at 2 [character 3 line 1]",
+ jeThree.getMessage());
+ assertEquals(
+ "Strict mode error: Single quoted strings are not allowed at 3 [character 4 line 1]",
+ jeFour.getMessage());
+ }
+
+ @Test
+ public void givenNonCompliantQuotesObject_testStrictModeTrue_shouldThrowJsonExceptionWithConcreteErrorDescription() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+
+ String testCaseOne = "{\"abc': \"test\"}";
+ String testCaseTwo = "{'abc\": \"test\"}";
+ String testCaseThree = "{\"a\":'abc'}";
+ String testCaseFour = "{'testField': \"testValue\"}";
+
+ JSONException jeOne = assertThrows(JSONException.class,
+ () -> new JSONObject(testCaseOne, jsonParserConfiguration));
+ JSONException jeTwo = assertThrows(JSONException.class,
+ () -> new JSONObject(testCaseTwo, jsonParserConfiguration));
+ JSONException jeThree = assertThrows(JSONException.class,
+ () -> new JSONObject(testCaseThree, jsonParserConfiguration));
+ JSONException jeFour = assertThrows(JSONException.class,
+ () -> new JSONObject(testCaseFour, jsonParserConfiguration));
+
+ assertEquals(
+ "Expected a ':' after a key at 10 [character 11 line 1]",
+ jeOne.getMessage());
+ assertEquals(
+ "Strict mode error: Single quoted strings are not allowed at 2 [character 3 line 1]",
+ jeTwo.getMessage());
+ assertEquals(
+ "Strict mode error: Single quoted strings are not allowed at 6 [character 7 line 1]",
+ jeThree.getMessage());
+ assertEquals(
+ "Strict mode error: Single quoted strings are not allowed at 2 [character 3 line 1]",
+ jeFour.getMessage());
+ }
+
+ @Test
+ public void givenUnbalancedQuotesArray_testStrictModeFalse_shouldThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(false);
+
+ String testCaseOne = "[\"abc', \"test\"]";
+ String testCaseTwo = "['abc\", \"test\"]";
+
+ JSONException jeOne = assertThrows(JSONException.class,
+ () -> new JSONArray(testCaseOne, jsonParserConfiguration));
+ JSONException jeTwo = assertThrows(JSONException.class,
+ () -> new JSONArray(testCaseTwo, jsonParserConfiguration));
+
+ assertEquals("Expected a ',' or ']' at 10 [character 11 line 1]", jeOne.getMessage());
+ assertEquals("Unterminated string. Character with int code 0 is not allowed within a quoted string. at 15 [character 16 line 1]", jeTwo.getMessage());
+ }
+
+ @Test
+ public void givenUnbalancedQuotesObject_testStrictModeFalse_shouldThrowJsonException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(false);
+
+ String testCaseOne = "{\"abc': \"test\"}";
+ String testCaseTwo = "{'abc\": \"test\"}";
+
+ JSONException jeOne = assertThrows(JSONException.class,
+ () -> new JSONObject(testCaseOne, jsonParserConfiguration));
+ JSONException jeTwo = assertThrows(JSONException.class,
+ () -> new JSONObject(testCaseTwo, jsonParserConfiguration));
+
+ assertEquals("Expected a ':' after a key at 10 [character 11 line 1]", jeOne.getMessage());
+ assertEquals("Unterminated string. Character with int code 0 is not allowed within a quoted string. at 15 [character 16 line 1]", jeTwo.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputArray_testStrictModeTrue_shouldThrowKeyNotSurroundedByQuotesErrorMessage() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+
+ String testCase = "[{test: implied}]";
+ JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
+ JSONException.class, () -> new JSONArray(testCase, jsonParserConfiguration));
+
+ assertEquals("Strict mode error: Value 'test' is not surrounded by quotes at 6 [character 7 line 1]",
+ je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputObject_testStrictModeTrue_shouldThrowKeyNotSurroundedByQuotesErrorMessage() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
+ .withStrictMode(true);
+
+ String testCase = "{test: implied}";
+ JSONException je = assertThrows("expected non-compliant json but got instead: " + testCase,
+ JSONException.class, () -> new JSONObject(testCase, jsonParserConfiguration));
+
+ assertEquals("Strict mode error: Value 'test' is not surrounded by quotes at 5 [character 6 line 1]",
+ je.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputObject_testStrictModeTrue_JSONObjectUsingJSONTokener_shouldThrowJSONException() {
+ JSONException exception = assertThrows(JSONException.class, () -> {
+ new JSONObject(new JSONTokener("{\"key\":\"value\"} invalid trailing text"), new JSONParserConfiguration().withStrictMode(true));
+ });
+
+ assertEquals("Strict mode error: Unparsed characters found at end of input text at 17 [character 18 line 1]", exception.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputObject_testStrictModeTrue_JSONObjectUsingString_shouldThrowJSONException() {
+ JSONException exception = assertThrows(JSONException.class, () -> {
+ new JSONObject("{\"key\":\"value\"} invalid trailing text", new JSONParserConfiguration().withStrictMode(true));
+ });
+ assertEquals("Strict mode error: Unparsed characters found at end of input text at 17 [character 18 line 1]", exception.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputObject_testStrictModeTrue_JSONArrayUsingJSONTokener_shouldThrowJSONException() {
+ JSONException exception = assertThrows(JSONException.class, () -> {
+ new JSONArray(new JSONTokener("[\"value\"] invalid trailing text"), new JSONParserConfiguration().withStrictMode(true));
+ });
+
+ assertEquals("Strict mode error: Unparsed characters found at end of input text at 11 [character 12 line 1]", exception.getMessage());
+ }
+
+ @Test
+ public void givenInvalidInputObject_testStrictModeTrue_JSONArrayUsingString_shouldThrowJSONException() {
+ JSONException exception = assertThrows(JSONException.class, () -> {
+ new JSONArray("[\"value\"] invalid trailing text", new JSONParserConfiguration().withStrictMode(true));
+ });
+ assertEquals("Strict mode error: Unparsed characters found at end of input text at 11 [character 12 line 1]", exception.getMessage());
+ }
+
+ /**
+ * This method contains short but focused use-case samples and is exclusively used to test strictMode unit tests in
+ * this class.
+ *
+ * @return List with JSON strings.
+ */
+ private List getNonCompliantJSONArrayList() {
+ return Arrays.asList(
+ "[1],",
+ "[1,]",
+ "[,]",
+ "[,,]",
+ "[[1],\"sa\",[2]]a",
+ "[1],\"dsa\": \"test\"",
+ "[[a]]",
+ "[]asdf",
+ "[]]",
+ "[]}",
+ "[][",
+ "[]{",
+ "[],",
+ "[]:",
+ "[],[",
+ "[],{",
+ "[1,2];[3,4]",
+ "[test]",
+ "[{'testSingleQuote': 'testSingleQuote'}]",
+ "[1, 2,3]:[4,5]",
+ "[{test: implied}]",
+ "[{\"test\": implied}]",
+ "[{\"number\":\"7990154836330\",\"color\":'c'},{\"number\":8784148854580,\"color\":RosyBrown},{\"number\":\"5875770107113\",\"color\":\"DarkSeaGreen\"}]",
+ "[{test: \"implied\"}]");
+ }
+
+ /**
+ * This method contains short but focused use-case samples and is exclusively used to test strictMode unit tests in
+ * this class.
+ *
+ * @return List with JSON strings.
+ */
+ private List getNonCompliantJSONObjectList() {
+ return Arrays.asList(
+ "{\"a\":1},",
+ "{\"a\":1,}",
+ "{\"a0\":[1],\"a1\":\"sa\",\"a2\":[2]}a",
+ "{\"a\":1},\"dsa\": \"test\"",
+ "{\"a\":[a]}",
+ "{}asdf",
+ "{}}",
+ "{}]",
+ "{}{",
+ "{}[",
+ "{},",
+ "{}:",
+ "{},{",
+ "{},[",
+ "{\"a0\":[1,2];\"a1\":[3,4]}",
+ "{\"a\":test}",
+ "{a:{'testSingleQuote': 'testSingleQuote'}}",
+ "{\"a0\":1, \"a1\":2,\"a2\":3}:{\"a3\":4,\"a4\":5}",
+ "{\"a\":{test: implied}}",
+ "{a:{\"test\": implied}}",
+ "{a:[{\"number\":\"7990154836330\",\"color\":'c'},{\"number\":8784148854580,\"color\":RosyBrown},{\"number\":\"5875770107113\",\"color\":\"DarkSeaGreen\"}]}",
+ "{a:{test: \"implied\"}}"
+ );
+ }
+
+}
diff --git a/src/test/java/org/json/junit/JSONPointerTest.java b/src/test/java/org/json/junit/JSONPointerTest.java
index e06851eb7..a420b297f 100644
--- a/src/test/java/org/json/junit/JSONPointerTest.java
+++ b/src/test/java/org/json/junit/JSONPointerTest.java
@@ -1,31 +1,10 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -41,7 +20,12 @@ of this software and associated documentation files (the "Software"), to deal
public class JSONPointerTest {
private static final JSONObject document;
+ private static final String EXPECTED_COMPLETE_DOCUMENT = "{\"\":0,\" \":7,\"g|h\":4,\"c%d\":2,\"k\\\"l\":6,\"a/b\":1,\"i\\\\j\":5," +
+ "\"obj\":{\"\":{\"\":\"empty key of an object with an empty key\",\"subKey\":\"Some other value\"}," +
+ "\"other~key\":{\"another/key\":[\"val\"]},\"key\":\"value\"},\"foo\":[\"bar\",\"baz\"],\"e^f\":3," +
+ "\"m~n\":8}";
+
static {
@SuppressWarnings("resource")
InputStream resourceAsStream = JSONPointerTest.class.getClassLoader().getResourceAsStream("jsonpointer-testdoc.json");
@@ -57,7 +41,7 @@ private Object query(String pointer) {
@Test
public void emptyPointer() {
- assertSame(document, query(""));
+ assertTrue(new JSONObject(EXPECTED_COMPLETE_DOCUMENT).similar(query("")));
}
@SuppressWarnings("unused")
@@ -68,12 +52,12 @@ public void nullPointer() {
@Test
public void objectPropertyQuery() {
- assertSame(document.get("foo"), query("/foo"));
+ assertEquals("[\"bar\",\"baz\"]", query("/foo").toString());
}
@Test
public void arrayIndexQuery() {
- assertSame(document.getJSONArray("foo").get(0), query("/foo/0"));
+ assertEquals("bar", query("/foo/0"));
}
@Test(expected = JSONPointerException.class)
@@ -83,71 +67,78 @@ public void stringPropOfArrayFailure() {
@Test
public void queryByEmptyKey() {
- assertSame(document.get(""), query("/"));
+ assertEquals(0, query("/"));
}
@Test
public void queryByEmptyKeySubObject() {
- assertSame(document.getJSONObject("obj").getJSONObject(""), query("/obj/"));
+ JSONObject json = new JSONObject("{\"\":\"empty key of an object with an empty key\",\"subKey\":\"Some" +
+ " other value\"}");
+ JSONObject obj = (JSONObject) query("/obj/");
+ assertTrue(json.similar(obj));
}
@Test
public void queryByEmptyKeySubObjectSubOject() {
- assertSame(
- document.getJSONObject("obj").getJSONObject("").get(""),
- query("/obj//")
- );
+ assertEquals("empty key of an object with an empty key", query("/obj//"));
}
@Test
public void queryByEmptyKeySubObjectValue() {
- assertSame(
- document.getJSONObject("obj").getJSONObject("").get("subKey"),
- query("/obj//subKey")
- );
+ assertEquals("Some other value", query("/obj//subKey"));
}
@Test
public void slashEscaping() {
- assertSame(document.get("a/b"), query("/a~1b"));
+ assertEquals(1, query("/a~1b"));
}
@Test
public void tildeEscaping() {
- assertSame(document.get("m~n"), query("/m~0n"));
+ assertEquals(8, query("/m~0n"));
}
+ /**
+ * We pass backslashes as-is
+ *
+ * @see rfc6901 section 3
+ */
@Test
- public void backslashEscaping() {
- assertSame(document.get("i\\j"), query("/i\\\\j"));
+ public void backslashHandling() {
+ assertEquals(5, query("/i\\j"));
}
-
+
+ /**
+ * We pass quotations as-is
+ *
+ * @see rfc6901 section 3
+ */
@Test
- public void quotationEscaping() {
- assertSame(document.get("k\"l"), query("/k\\\\\\\"l"));
+ public void quotationHandling() {
+ assertEquals(6, query("/k\"l"));
}
-
+
@Test
public void whitespaceKey() {
- assertSame(document.get(" "), query("/ "));
+ assertEquals(7, query("/ "));
}
@Test
public void uriFragmentNotation() {
- assertSame(document.get("foo"), query("#/foo"));
+ assertEquals("[\"bar\",\"baz\"]", query("#/foo").toString());
}
@Test
public void uriFragmentNotationRoot() {
- assertSame(document, query("#"));
+ assertTrue(new JSONObject(EXPECTED_COMPLETE_DOCUMENT).similar(query("#")));
}
@Test
public void uriFragmentPercentHandling() {
- assertSame(document.get("c%d"), query("#/c%25d"));
- assertSame(document.get("e^f"), query("#/e%5Ef"));
- assertSame(document.get("g|h"), query("#/g%7Ch"));
- assertSame(document.get("m~n"), query("#/m~0n"));
+ assertEquals(2, query("#/c%25d"));
+ assertEquals(3, query("#/e%5Ef"));
+ assertEquals(4, query("#/g%7Ch"));
+ assertEquals(8, query("#/m~0n"));
}
@SuppressWarnings("unused")
@@ -189,7 +180,7 @@ public void toStringEscaping() {
.append("\"")
.append(0)
.build();
- assertEquals("/obj/other~0key/another~1key/\\\"/0", pointer.toString());
+ assertEquals("/obj/other~0key/another~1key/\"/0", pointer.toString());
}
@Test
@@ -381,4 +372,27 @@ public void optQueryFromJSONArrayUsingPointer() {
obj = jsonArray.optQuery(new JSONPointer("/a/b/c"));
assertTrue("Expected null", obj == null);
}
+
+ /**
+ * When creating a jsonObject we need to parse escaped characters "\\\\"
+ * --> it's the string representation of "\\", so when query'ing via the JSONPointer
+ * we DON'T escape them
+ *
+ */
+ @Test
+ public void queryFromJSONObjectUsingPointer0() {
+ String str = "{"+
+ "\"string\\\\\\\\Key\":\"hello world!\","+
+
+ "\"\\\\\":\"slash test\"" +
+ "}";
+ JSONObject jsonObject = new JSONObject(str);
+ //Summary of issue: When a KEY in the jsonObject is "\\\\" --> it's held
+ // as "\\" which means when querying, we need to use "\\"
+ Object twoBackslahObj = jsonObject.optQuery(new JSONPointer("/\\"));
+ assertEquals("slash test", twoBackslahObj);
+
+ Object fourBackslashObj = jsonObject.optQuery(new JSONPointer("/string\\\\Key"));
+ assertEquals("hello world!", fourBackslashObj);
+ }
}
diff --git a/src/test/java/org/json/junit/JSONStringTest.java b/src/test/java/org/json/junit/JSONStringTest.java
index a19961103..235df1806 100644
--- a/src/test/java/org/json/junit/JSONStringTest.java
+++ b/src/test/java/org/json/junit/JSONStringTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
@@ -339,6 +319,22 @@ public void testNullStringValue() throws Exception {
}
}
+ @Test
+ public void testEnumJSONString() {
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("key", MyEnum.MY_ENUM);
+ assertEquals("{\"key\":\"myJsonString\"}", jsonObject.toString());
+ }
+
+ private enum MyEnum implements JSONString {
+ MY_ENUM;
+
+ @Override
+ public String toJSONString() {
+ return "\"myJsonString\"";
+ }
+ }
+
/**
* A JSONString that returns a valid JSON string value.
*/
diff --git a/src/test/java/org/json/junit/JSONStringerTest.java b/src/test/java/org/json/junit/JSONStringerTest.java
index a99db3b8c..0ecb9d662 100644
--- a/src/test/java/org/json/junit/JSONStringerTest.java
+++ b/src/test/java/org/json/junit/JSONStringerTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
diff --git a/src/test/java/org/json/junit/JSONTokenerTest.java b/src/test/java/org/json/junit/JSONTokenerTest.java
index e8e0f98a9..b0b45cb7c 100644
--- a/src/test/java/org/json/junit/JSONTokenerTest.java
+++ b/src/test/java/org/json/junit/JSONTokenerTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.assertEquals;
@@ -36,10 +16,7 @@ of this software and associated documentation files (the "Software"), to deal
import java.io.Reader;
import java.io.StringReader;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONTokener;
+import org.json.*;
import org.junit.Test;
/**
@@ -118,7 +95,17 @@ public void testValid() {
checkValid(" [] ",JSONArray.class);
checkValid("[1,2]",JSONArray.class);
checkValid("\n\n[1,2]\n\n",JSONArray.class);
- checkValid("1 2", String.class);
+
+ // Test should fail if default strictMode is true, pass if false
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+ if (jsonParserConfiguration.isStrictMode()) {
+ try {
+ checkValid("1 2", String.class);
+ assertEquals("Expected to throw exception due to invalid string", true, false);
+ } catch (JSONException e) { }
+ } else {
+ checkValid("1 2", String.class);
+ }
}
@Test
@@ -333,4 +320,54 @@ public void testNextBackComboWithNewLines() {
assertEquals(0, t2.next());
assertFalse(t2.more());
}
+
+ @Test
+ public void testAutoClose(){
+ Reader reader = new StringReader("some test string");
+ try {
+ JSONTokener tokener = new JSONTokener(reader);
+ tokener.close();
+ tokener.next();
+ } catch (Exception exception){
+ assertEquals("Stream closed", exception.getMessage());
+ }
+ }
+
+ @Test
+ public void testInvalidInput_JSONObject_withoutStrictModel_shouldParseInput() {
+ String input = "{\"invalidInput\": [],}";
+ JSONTokener tokener = new JSONTokener(input);
+
+ // Test should fail if default strictMode is true, pass if false
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+ if (jsonParserConfiguration.isStrictMode()) {
+ try {
+ Object value = tokener.nextValue();
+ assertEquals(new JSONObject(input).toString(), value.toString());
+ assertEquals("Expected to throw exception due to invalid string", true, false);
+ } catch (JSONException e) { }
+ } else {
+ Object value = tokener.nextValue();
+ assertEquals(new JSONObject(input).toString(), value.toString());
+ }
+ }
+
+ @Test
+ public void testInvalidInput_JSONArray_withoutStrictModel_shouldParseInput() {
+ String input = "[\"invalidInput\",]";
+ JSONTokener tokener = new JSONTokener(input);
+
+ // Test should fail if default strictMode is true, pass if false
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+ if (jsonParserConfiguration.isStrictMode()) {
+ try {
+ Object value = tokener.nextValue();
+ assertEquals(new JSONArray(input).toString(), value.toString());
+ assertEquals("Expected to throw exception due to invalid string", true, false);
+ } catch (JSONException e) { }
+ } else {
+ Object value = tokener.nextValue();
+ assertEquals(new JSONArray(input).toString(), value.toString());
+ }
+ }
}
diff --git a/src/test/java/org/json/junit/PropertyTest.java b/src/test/java/org/json/junit/PropertyTest.java
index e1a9b8dcf..eee482fbf 100644
--- a/src/test/java/org/json/junit/PropertyTest.java
+++ b/src/test/java/org/json/junit/PropertyTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import java.util.*;
diff --git a/src/test/java/org/json/junit/StringBuilderWriterTest.java b/src/test/java/org/json/junit/StringBuilderWriterTest.java
new file mode 100644
index 000000000..b12f5db0c
--- /dev/null
+++ b/src/test/java/org/json/junit/StringBuilderWriterTest.java
@@ -0,0 +1,60 @@
+package org.json.junit;
+
+import static org.junit.Assert.assertEquals;
+
+import org.json.StringBuilderWriter;
+import org.junit.Before;
+import org.junit.Test;
+
+public class StringBuilderWriterTest {
+ private StringBuilderWriter writer;
+
+ @Before
+ public void setUp() {
+ writer = new StringBuilderWriter();
+ }
+
+ @Test
+ public void testWriteChar() {
+ writer.write('a');
+ assertEquals("a", writer.toString());
+ }
+
+ @Test
+ public void testWriteCharArray() {
+ char[] chars = {'a', 'b', 'c'};
+ writer.write(chars, 0, 3);
+ assertEquals("abc", writer.toString());
+ }
+
+ @Test
+ public void testWriteString() {
+ writer.write("hello");
+ assertEquals("hello", writer.toString());
+ }
+
+ @Test
+ public void testWriteStringWithOffsetAndLength() {
+ writer.write("hello world", 6, 5);
+ assertEquals("world", writer.toString());
+ }
+
+ @Test
+ public void testAppendCharSequence() {
+ writer.append("hello");
+ assertEquals("hello", writer.toString());
+ }
+
+ @Test
+ public void testAppendCharSequenceWithStartAndEnd() {
+ CharSequence csq = "hello world";
+ writer.append(csq, 6, 11);
+ assertEquals("world", writer.toString());
+ }
+
+ @Test
+ public void testAppendChar() {
+ writer.append('a');
+ assertEquals("a", writer.toString());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/json/junit/Util.java b/src/test/java/org/json/junit/Util.java
index 8dc27ddfa..b676045b8 100644
--- a/src/test/java/org/json/junit/Util.java
+++ b/src/test/java/org/json/junit/Util.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.*;
@@ -78,7 +58,6 @@ public static void compareActualVsExpectedJsonObjects(
* or something else.
* @param value created by the code to be tested
* @param expectedValue created specifically for comparing
- * @param key key to the jsonObject entry to be compared
*/
private static void compareActualVsExpectedObjects(Object value,
Object expectedValue) {
@@ -117,4 +96,106 @@ private static void compareActualVsExpectedObjects(Object value,
);
}
}
+
+ /**
+ * Asserts that all JSONObject maps are the same as the default ctor
+ * @param jsonObjects list of objects to be tested
+ */
+ public static void checkJSONObjectsMaps(List jsonObjects) {
+ if (jsonObjects == null || jsonObjects.size() == 0) {
+ return;
+ }
+ Class extends Map> mapType = new JSONObject().getMapType();
+ for (JSONObject jsonObject : jsonObjects) {
+ if (jsonObject != null) {
+ assertTrue(mapType == jsonObject.getMapType());
+ checkJSONObjectMaps(jsonObject, mapType);
+ }
+ }
+ }
+
+ /**
+ * Asserts that all JSONObject maps are the same as the default ctor
+ * @param jsonObject the object to be tested
+ */
+ public static void checkJSONObjectMaps(JSONObject jsonObject) {
+ if (jsonObject != null) {
+ checkJSONObjectMaps(jsonObject, jsonObject.getMapType());
+ }
+ }
+
+ /**
+ * Asserts that all JSONObject maps are the same as mapType
+ * @param jsonObject object to be tested
+ * @param mapType mapType to test against
+ */
+ public static void checkJSONObjectMaps(JSONObject jsonObject, Class extends Map> mapType) {
+ if (mapType == null) {
+ mapType = new JSONObject().getMapType();
+ }
+ Set keys = jsonObject.keySet();
+ for (String key : keys) {
+ Object val = jsonObject.get(key);
+ if (val instanceof JSONObject) {
+ JSONObject jsonObjectVal = (JSONObject) val;
+ assertTrue(mapType == ((JSONObject) val).getMapType());
+ checkJSONObjectMaps(jsonObjectVal, mapType);
+ } else if (val instanceof JSONArray) {
+ JSONArray jsonArrayVal = (JSONArray)val;
+ checkJSONArrayMaps(jsonArrayVal, mapType);
+ }
+ }
+ }
+
+ /**
+ * Asserts that all JSONObject maps in the JSONArray object match the default map
+ * @param jsonArrays list of JSONArray objects to be tested
+ */
+ public static void checkJSONArraysMaps(List jsonArrays) {
+ if (jsonArrays == null || jsonArrays.size() == 0) {
+ return;
+ }
+ Class extends Map> mapType = new JSONObject().getMapType();
+ for (JSONArray jsonArray : jsonArrays) {
+ if (jsonArray != null) {
+ checkJSONArrayMaps(jsonArray, mapType);
+ }
+ }
+ }
+
+ /**
+ * Asserts that all JSONObject maps in the JSONArray object match mapType
+ * @param jsonArray object to be tested
+ * @param mapType map type to be tested against
+ */
+ public static void checkJSONArrayMaps(JSONArray jsonArray, Class extends Map> mapType) {
+ if (jsonArray == null) {
+ return;
+ }
+ if (mapType == null) {
+ mapType = new JSONObject().getMapType();
+ }
+ Iterator it = jsonArray.iterator();
+ while (it.hasNext()) {
+ Object val = it.next();
+ if (val instanceof JSONObject) {
+ JSONObject jsonObjectVal = (JSONObject)val;
+ checkJSONObjectMaps(jsonObjectVal, mapType);
+ } else if (val instanceof JSONArray) {
+ JSONArray jsonArrayVal = (JSONArray)val;
+ checkJSONArrayMaps(jsonArrayVal, mapType);
+ }
+ }
+ }
+
+ /**
+ * Asserts that all JSONObject maps nested in the JSONArray match
+ * the default mapType
+ * @param jsonArray the object to be tested
+ */
+ public static void checkJSONArrayMaps(JSONArray jsonArray) {
+ if (jsonArray != null) {
+ checkJSONArrayMaps(jsonArray, null);
+ }
+ }
}
diff --git a/src/test/java/org/json/junit/XMLConfigurationTest.java b/src/test/java/org/json/junit/XMLConfigurationTest.java
index 28b20ddfd..ca1980c8a 100755
--- a/src/test/java/org/json/junit/XMLConfigurationTest.java
+++ b/src/test/java/org/json/junit/XMLConfigurationTest.java
@@ -1,40 +1,17 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
+import java.util.HashSet;
+import java.util.Set;
import org.json.JSONArray;
import org.json.JSONException;
@@ -45,6 +22,8 @@ of this software and associated documentation files (the "Software"), to deal
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import static org.junit.Assert.*;
+
/**
* Tests for JSON-Java XML.java with XMLParserConfiguration.java
@@ -291,9 +270,9 @@ public void shouldHandleSimpleXML() {
String expectedStr =
"{\"addresses\":{\"address\":{\"street\":\"[CDATA[Baker street 5]\","+
- "\"name\":\"Joe Tester\",\"NothingHere\":\"\",TrueValue:true,\n"+
+ "\"name\":\"Joe Tester\",\"NothingHere\":\"\",\"TrueValue\":true,\n"+
"\"FalseValue\":false,\"NullValue\":null,\"PositiveValue\":42,\n"+
- "\"NegativeValue\":-23,\"DoubleValue\":-23.45,\"Nan\":-23x.45,\n"+
+ "\"NegativeValue\":-23,\"DoubleValue\":-23.45,\"Nan\":\"-23x.45\",\n"+
"\"ArrayOfNum\":\"1, 2, 3, 4.1, 5.2\"\n"+
"},\"xsi:noNamespaceSchemaLocation\":"+
"\"test.xsd\",\"xmlns:xsi\":\"http://www.w3.org/2001/"+
@@ -575,6 +554,40 @@ public void shouldHandleNullNodeValue()
assertEquals(actualXML, resultXML);
}
+ @Test
+ public void shouldHandleEmptyNodeValue()
+ {
+ JSONObject inputJSON = new JSONObject();
+ inputJSON.put("Emptyness", "");
+ String expectedXmlWithoutExplicitEndTag = " ";
+ String expectedXmlWithExplicitEndTag = " ";
+ assertEquals(expectedXmlWithoutExplicitEndTag, XML.toString(inputJSON, null,
+ new XMLParserConfiguration().withCloseEmptyTag(false)));
+ assertEquals(expectedXmlWithExplicitEndTag, XML.toString(inputJSON, null,
+ new XMLParserConfiguration().withCloseEmptyTag(true)));
+ }
+
+ @Test
+ public void shouldKeepConfigurationIntactAndUpdateCloseEmptyTagChoice()
+ {
+ XMLParserConfiguration keepStrings = XMLParserConfiguration.KEEP_STRINGS;
+ XMLParserConfiguration keepStringsAndCloseEmptyTag = keepStrings.withCloseEmptyTag(true);
+ XMLParserConfiguration keepDigits = keepStringsAndCloseEmptyTag.withKeepStrings(false);
+ XMLParserConfiguration keepDigitsAndNoCloseEmptyTag = keepDigits.withCloseEmptyTag(false);
+ assertTrue(keepStrings.isKeepNumberAsString());
+ assertTrue(keepStrings.isKeepBooleanAsString());
+ assertFalse(keepStrings.isCloseEmptyTag());
+ assertTrue(keepStringsAndCloseEmptyTag.isKeepNumberAsString());
+ assertTrue(keepStringsAndCloseEmptyTag.isKeepBooleanAsString());
+ assertTrue(keepStringsAndCloseEmptyTag.isCloseEmptyTag());
+ assertFalse(keepDigits.isKeepNumberAsString());
+ assertFalse(keepDigits.isKeepBooleanAsString());
+ assertTrue(keepDigits.isCloseEmptyTag());
+ assertFalse(keepDigitsAndNoCloseEmptyTag.isKeepNumberAsString());
+ assertFalse(keepDigitsAndNoCloseEmptyTag.isKeepBooleanAsString());
+ assertFalse(keepDigitsAndNoCloseEmptyTag.isCloseEmptyTag());
+ }
+
/**
* Investigate exactly how the "content" keyword works
*/
@@ -757,6 +770,67 @@ public void testToJSONArray_jsonOutput() {
Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);
}
+ /**
+ * JSON string lost leading zero and converted "True" to true.
+ */
+ @Test
+ public void testToJSONArray_jsonOutput_withKeepNumberAsString() {
+ final String originalXml = "01 1 00 0 null True ";
+ final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\",null],\"title\":true}}");
+ final JSONObject actualJsonOutput = XML.toJSONObject(originalXml,
+ new XMLParserConfiguration().withKeepNumberAsString(true));
+ Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);
+ }
+
+ /**
+ * JSON string lost leading zero and converted "True" to true.
+ */
+ @Test
+ public void testToJSONArray_jsonOutput_withKeepBooleanAsString() {
+ final String originalXml = "01 1 00 0 null True ";
+ final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0,null],\"title\":\"True\"}}");
+ final JSONObject actualJsonOutput = XML.toJSONObject(originalXml,
+ new XMLParserConfiguration().withKeepBooleanAsString(true));
+ Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);
+ }
+
+ /**
+ * null is "null" when keepStrings == true
+ */
+ @Test
+ public void testToJSONArray_jsonOutput_null_withKeepString() {
+ final String originalXml = "01 1 00 0 null ";
+ final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\"],\"title\":\"null\"}}");
+ final JSONObject actualJsonOutput = XML.toJSONObject(originalXml,
+ new XMLParserConfiguration().withKeepStrings(true));
+ Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expected);
+ }
+
+ /**
+ * Test keepStrings behavior when setting keepBooleanAsString, keepNumberAsString
+ */
+ @Test
+ public void test_keepStringBehavior() {
+ XMLParserConfiguration xpc = new XMLParserConfiguration().withKeepStrings(true);
+ assertEquals(xpc.isKeepStrings(), true);
+
+ xpc = xpc.withKeepBooleanAsString(true);
+ xpc = xpc.withKeepNumberAsString(false);
+ assertEquals(xpc.isKeepStrings(), false);
+
+ xpc = xpc.withKeepBooleanAsString(false);
+ xpc = xpc.withKeepNumberAsString(true);
+ assertEquals(xpc.isKeepStrings(), false);
+
+ xpc = xpc.withKeepBooleanAsString(true);
+ xpc = xpc.withKeepNumberAsString(true);
+ assertEquals(xpc.isKeepStrings(), true);
+
+ xpc = xpc.withKeepBooleanAsString(false);
+ xpc = xpc.withKeepNumberAsString(false);
+ assertEquals(xpc.isKeepStrings(), false);
+ }
+
/**
* JSON string cannot be reverted to original xml.
*/
@@ -903,7 +977,195 @@ public void testConfig() {
Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
}
-
+
+ /**
+ * Test forceList parameter
+ */
+ @Test
+ public void testSimpleForceList() {
+ String xmlStr =
+ "\n"+
+ "\n"+
+ " \n"+
+ " Sherlock Holmes \n"+
+ " \n"+
+ " ";
+
+ String expectedStr =
+ "{\"addresses\":[{\"address\":{\"name\":\"Sherlock Holmes\"}}]}";
+
+ Set forceList = new HashSet();
+ forceList.add("addresses");
+
+ XMLParserConfiguration config =
+ new XMLParserConfiguration()
+ .withForceList(forceList);
+ JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
+ JSONObject expetedJsonObject = new JSONObject(expectedStr);
+
+ Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
+ }
+ @Test
+ public void testLongForceList() {
+ String xmlStr =
+ ""+
+ ""+
+ "host1 "+
+ "Linux "+
+ ""+
+ ""+
+ "em0 "+
+ "10.0.0.1 "+
+ " "+
+ " "+
+ " "+
+ " ";
+
+ String expectedStr =
+ "{"+
+ "\"servers\": ["+
+ "{"+
+ "\"server\": {"+
+ "\"name\": \"host1\","+
+ "\"os\": \"Linux\","+
+ "\"interfaces\": ["+
+ "{"+
+ "\"interface\": {"+
+ "\"name\": \"em0\","+
+ "\"ip_address\": \"10.0.0.1\""+
+ "}}]}}]}";
+
+ Set forceList = new HashSet();
+ forceList.add("servers");
+ forceList.add("interfaces");
+
+ XMLParserConfiguration config =
+ new XMLParserConfiguration()
+ .withForceList(forceList);
+ JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
+ JSONObject expetedJsonObject = new JSONObject(expectedStr);
+
+ Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
+ }
+ @Test
+ public void testMultipleTagForceList() {
+ String xmlStr =
+ "\n"+
+ " \n"+
+ " Sherlock Holmes \n"+
+ " John H. Watson \n"+
+ " \n"+
+ " ";
+
+ String expectedStr =
+ "{"+
+ "\"addresses\":["+
+ "{"+
+ "\"address\":["+
+ "{"+
+ "\"name\":["+
+ "\"Sherlock Holmes\","+
+ "\"John H. Watson\""+
+ "]"+
+ "}"+
+ "]"+
+ "}"+
+ "]"+
+ "}";
+
+ Set forceList = new HashSet();
+ forceList.add("addresses");
+ forceList.add("address");
+ forceList.add("name");
+
+ XMLParserConfiguration config =
+ new XMLParserConfiguration()
+ .withForceList(forceList);
+ JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
+ JSONObject expetedJsonObject = new JSONObject(expectedStr);
+
+ Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
+ }
+ @Test
+ public void testEmptyForceList() {
+ String xmlStr =
+ " ";
+
+ String expectedStr =
+ "{\"addresses\":[]}";
+
+ Set forceList = new HashSet();
+ forceList.add("addresses");
+
+ XMLParserConfiguration config =
+ new XMLParserConfiguration()
+ .withForceList(forceList);
+ JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
+ JSONObject expetedJsonObject = new JSONObject(expectedStr);
+
+ Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
+ }
+ @Test
+ public void testContentForceList() {
+ String xmlStr =
+ "Baker Street ";
+
+ String expectedStr =
+ "{\"addresses\":[\"Baker Street\"]}";
+
+ Set forceList = new HashSet();
+ forceList.add("addresses");
+
+ XMLParserConfiguration config =
+ new XMLParserConfiguration()
+ .withForceList(forceList);
+ JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
+ JSONObject expetedJsonObject = new JSONObject(expectedStr);
+
+ Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
+ }
+ @Test
+ public void testEmptyTagForceList() {
+ String xmlStr =
+ " ";
+
+ String expectedStr =
+ "{\"addresses\":[]}";
+
+ Set forceList = new HashSet();
+ forceList.add("addresses");
+
+ XMLParserConfiguration config =
+ new XMLParserConfiguration()
+ .withForceList(forceList);
+ JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
+ JSONObject expetedJsonObject = new JSONObject(expectedStr);
+
+ Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
+ }
+
+ @Test
+ public void testMaxNestingDepthIsSet() {
+ XMLParserConfiguration xmlParserConfiguration = XMLParserConfiguration.ORIGINAL;
+
+ assertEquals(xmlParserConfiguration.getMaxNestingDepth(), XMLParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
+
+ xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(42);
+
+ assertEquals(xmlParserConfiguration.getMaxNestingDepth(), 42);
+
+ xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(0);
+
+ assertEquals(xmlParserConfiguration.getMaxNestingDepth(), 0);
+
+ xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(-31415926);
+
+ assertEquals(xmlParserConfiguration.getMaxNestingDepth(), XMLParserConfiguration.UNDEFINED_MAXIMUM_NESTING_DEPTH);
+
+ xmlParserConfiguration = xmlParserConfiguration.withMaxNestingDepth(Integer.MIN_VALUE);
+
+ assertEquals(xmlParserConfiguration.getMaxNestingDepth(), XMLParserConfiguration.UNDEFINED_MAXIMUM_NESTING_DEPTH);
+ }
/**
* Convenience method, given an input string and expected result,
@@ -983,4 +1245,4 @@ private void compareFileToJSONObject(String xmlStr, String expectedStr) {
assertTrue("Error: " +e.getMessage(), false);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java
index 62ee516b2..2fa5daeea 100644
--- a/src/test/java/org/json/junit/XMLTest.java
+++ b/src/test/java/org/json/junit/XMLTest.java
@@ -1,27 +1,7 @@
package org.json.junit;
/*
-Copyright (c) 2020 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Public Domain.
*/
import static org.junit.Assert.assertEquals;
@@ -38,16 +18,11 @@ of this software and associated documentation files (the "Software"), to deal
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
+import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONTokener;
-import org.json.XML;
-import org.json.XMLParserConfiguration;
-import org.json.XMLXsiTypeConverter;
+import org.json.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -65,6 +40,7 @@ public class XMLTest {
@Rule
public TemporaryFolder testFolder = new TemporaryFolder();
+
/**
* JSONObject from a null XML string.
* Expects a NullPointerException
@@ -291,9 +267,9 @@ public void shouldHandleSimpleXML() {
String expectedStr =
"{\"addresses\":{\"address\":{\"street\":\"[CDATA[Baker street 5]\","+
- "\"name\":\"Joe Tester\",\"NothingHere\":\"\",TrueValue:true,\n"+
+ "\"name\":\"Joe Tester\",\"NothingHere\":\"\",\"TrueValue\":true,\n"+
"\"FalseValue\":false,\"NullValue\":null,\"PositiveValue\":42,\n"+
- "\"NegativeValue\":-23,\"DoubleValue\":-23.45,\"Nan\":-23x.45,\n"+
+ "\"NegativeValue\":-23,\"DoubleValue\":-23.45,\"Nan\":\"-23x.45\",\n"+
"\"ArrayOfNum\":\"1, 2, 3, 4.1, 5.2\"\n"+
"},\"xsi:noNamespaceSchemaLocation\":"+
"\"test.xsd\",\"xmlns:xsi\":\"http://www.w3.org/2001/"+
@@ -940,7 +916,7 @@ public void testIssue537CaseSensitiveHexEscapeFullFile(){
InputStream xmlStream = null;
try {
xmlStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue537.xml");
- Reader xmlReader = new InputStreamReader(xmlStream);
+ Reader xmlReader = new InputStreamReader(xmlStream, Charset.forName("UTF-8"));
JSONObject actual = XML.toJSONObject(xmlReader, true);
InputStream jsonStream = null;
try {
@@ -1068,4 +1044,389 @@ public void testXSITypeMapNotModifiable() {
fail("Expected to be unable to modify the config");
} catch (Exception ignored) { }
}
+
+ @Test
+ public void testIndentComplicatedJsonObject(){
+ String str = "{\n" +
+ " \"success\": true,\n" +
+ " \"error\": null,\n" +
+ " \"response\": [\n" +
+ " {\n" +
+ " \"timestamp\": 1664917200,\n" +
+ " \"dateTimeISO\": \"2022-10-05T00:00:00+03:00\",\n" +
+ " \"loc\": {\n" +
+ " \"lat\": 39.91987,\n" +
+ " \"long\": 32.85427\n" +
+ " },\n" +
+ " \"place\": {\n" +
+ " \"name\": \"ankara\",\n" +
+ " \"state\": \"an\",\n" +
+ " \"country\": \"tr\"\n" +
+ " },\n" +
+ " \"profile\": {\n" +
+ " \"tz\": \"Europe/Istanbul\"\n" +
+ " },\n" +
+ " \"sun\": {\n" +
+ " \"rise\": 1664941721,\n" +
+ " \"riseISO\": \"2022-10-05T06:48:41+03:00\",\n" +
+ " \"set\": 1664983521,\n" +
+ " \"setISO\": \"2022-10-05T18:25:21+03:00\",\n" +
+ " \"transit\": 1664962621,\n" +
+ " \"transitISO\": \"2022-10-05T12:37:01+03:00\",\n" +
+ " \"midnightSun\": false,\n" +
+ " \"polarNight\": false,\n" +
+ " \"twilight\": {\n" +
+ " \"civilBegin\": 1664940106,\n" +
+ " \"civilBeginISO\": \"2022-10-05T06:21:46+03:00\",\n" +
+ " \"civilEnd\": 1664985136,\n" +
+ " \"civilEndISO\": \"2022-10-05T18:52:16+03:00\",\n" +
+ " \"nauticalBegin\": 1664938227,\n" +
+ " \"nauticalBeginISO\": \"2022-10-05T05:50:27+03:00\",\n" +
+ " \"nauticalEnd\": 1664987015,\n" +
+ " \"nauticalEndISO\": \"2022-10-05T19:23:35+03:00\",\n" +
+ " \"astronomicalBegin\": 1664936337,\n" +
+ " \"astronomicalBeginISO\": \"2022-10-05T05:18:57+03:00\",\n" +
+ " \"astronomicalEnd\": 1664988905,\n" +
+ " \"astronomicalEndISO\": \"2022-10-05T19:55:05+03:00\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"moon\": {\n" +
+ " \"rise\": 1664976480,\n" +
+ " \"riseISO\": \"2022-10-05T16:28:00+03:00\",\n" +
+ " \"set\": 1664921520,\n" +
+ " \"setISO\": \"2022-10-05T01:12:00+03:00\",\n" +
+ " \"transit\": 1664994240,\n" +
+ " \"transitISO\": \"2022-10-05T21:24:00+03:00\",\n" +
+ " \"underfoot\": 1664949360,\n" +
+ " \"underfootISO\": \"2022-10-05T08:56:00+03:00\",\n" +
+ " \"phase\": {\n" +
+ " \"phase\": 0.3186,\n" +
+ " \"name\": \"waxing gibbous\",\n" +
+ " \"illum\": 71,\n" +
+ " \"age\": 9.41,\n" +
+ " \"angle\": 0.55\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}" ;
+ JSONObject jsonObject = new JSONObject(str);
+ String actualIndentedXmlString = XML.toString(jsonObject, 1);
+ JSONObject actualJsonObject = XML.toJSONObject(actualIndentedXmlString);
+ String expected = "true \n" +
+ "\n" +
+ " 2022-10-05T00:00:00+03:00 \n" +
+ " \n" +
+ " 39.91987 \n" +
+ " 32.85427 \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " 0.3186 \n" +
+ " waxing gibbous \n" +
+ " 0.55 \n" +
+ " 71 \n" +
+ " 9.41 \n" +
+ " \n" +
+ " 2022-10-05T01:12:00+03:00 \n" +
+ " 1664949360 \n" +
+ " 1664921520 \n" +
+ " 1664994240 \n" +
+ " 2022-10-05T21:24:00+03:00 \n" +
+ " 2022-10-05T16:28:00+03:00 \n" +
+ " 1664976480 \n" +
+ " 2022-10-05T08:56:00+03:00 \n" +
+ " \n" +
+ " \n" +
+ " Europe/Istanbul \n" +
+ " \n" +
+ " \n" +
+ " tr \n" +
+ " ankara \n" +
+ " an \n" +
+ " \n" +
+ " \n" +
+ " 2022-10-05T18:25:21+03:00 \n" +
+ " false \n" +
+ " 1664983521 \n" +
+ " 1664962621 \n" +
+ " false \n" +
+ " 2022-10-05T12:37:01+03:00 \n" +
+ " 2022-10-05T06:48:41+03:00 \n" +
+ " 1664941721 \n" +
+ " \n" +
+ " 1664985136 \n" +
+ " 1664936337 \n" +
+ " 1664988905 \n" +
+ " 2022-10-05T05:18:57+03:00 \n" +
+ " 1664940106 \n" +
+ " 2022-10-05T19:23:35+03:00 \n" +
+ " 2022-10-05T19:55:05+03:00 \n" +
+ " 1664938227 \n" +
+ " 1664987015 \n" +
+ " 2022-10-05T05:50:27+03:00 \n" +
+ " 2022-10-05T06:21:46+03:00 \n" +
+ " 2022-10-05T18:52:16+03:00 \n" +
+ " \n" +
+ " \n" +
+ " 1664917200 \n" +
+ " \n" +
+ "null \n";
+ JSONObject expectedJsonObject = XML.toJSONObject(expected);
+ assertTrue(expectedJsonObject.similar(actualJsonObject));
+
+
+ }
+
+ @Test
+ public void shouldCreateExplicitEndTagWithEmptyValueWhenConfigured(){
+ String jsonString = "{\"outer\":{\"innerOne\":\"\", \"innerTwo\":\"two\"}}";
+ JSONObject jsonObject = new JSONObject(jsonString);
+ String expectedXmlString = "two ";
+ String xmlForm = XML.toString(jsonObject,"encloser", new XMLParserConfiguration().withCloseEmptyTag(true));
+ JSONObject actualJsonObject = XML.toJSONObject(xmlForm);
+ JSONObject expectedJsonObject = XML.toJSONObject(expectedXmlString);
+ assertTrue(expectedJsonObject.similar(actualJsonObject));
+ }
+
+ @Test
+ public void shouldNotCreateExplicitEndTagWithEmptyValueWhenNotConfigured(){
+ String jsonString = "{\"outer\":{\"innerOne\":\"\", \"innerTwo\":\"two\"}}";
+ JSONObject jsonObject = new JSONObject(jsonString);
+ String expectedXmlString = "two ";
+ String xmlForm = XML.toString(jsonObject,"encloser", new XMLParserConfiguration().withCloseEmptyTag(false));
+ JSONObject actualJsonObject = XML.toJSONObject(xmlForm);
+ JSONObject expectedJsonObject = XML.toJSONObject(expectedXmlString);
+ assertTrue(expectedJsonObject.similar(actualJsonObject));
+ }
+
+
+ @Test
+ public void testIndentSimpleJsonObject(){
+ String str = "{ \"employee\": { \n" +
+ " \"name\": \"sonoo\", \n" +
+ " \"salary\": 56000, \n" +
+ " \"married\": true \n" +
+ " }}";
+ JSONObject jsonObject = new JSONObject(str);
+ String actual = XML.toString(jsonObject, "Test", 2);
+ JSONObject actualJsonObject = XML.toJSONObject(actual);
+ String expected = "\n" +
+ " \n" +
+ " sonoo \n" +
+ " 56000 \n" +
+ " true \n" +
+ " \n" +
+ " \n";
+ JSONObject expectedJsonObject = XML.toJSONObject(expected);
+ assertTrue(expectedJsonObject.similar(actualJsonObject));
+ }
+
+ @Test
+ public void testIndentSimpleJsonArray(){
+ String str = "[ \n" +
+ " {\"name\":\"Ram\", \"email\":\"Ram@gmail.com\"}, \n" +
+ " {\"name\":\"Bob\", \"email\":\"bob32@gmail.com\"} \n" +
+ "] ";
+ JSONArray jsonObject = new JSONArray(str);
+ String actual = XML.toString(jsonObject, 2);
+ JSONObject actualJsonObject = XML.toJSONObject(actual);
+ String expected = "\n" +
+ " Ram \n" +
+ " Ram@gmail.com \n" +
+ " \n" +
+ "\n" +
+ " Bob \n" +
+ " bob32@gmail.com \n" +
+ " \n";
+ JSONObject expectedJsonObject = XML.toJSONObject(expected);
+ assertTrue(expectedJsonObject.similar(actualJsonObject));
+
+
+ }
+
+ @Test
+ public void testIndentComplicatedJsonObjectWithArrayAndWithConfig(){
+ try (InputStream jsonStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue593.json")) {
+ final JSONObject object = new JSONObject(new JSONTokener(jsonStream));
+ String actualString = XML.toString(object, null, XMLParserConfiguration.KEEP_STRINGS, 2);
+ try (InputStream xmlStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue593.xml")) {
+ int bufferSize = 1024;
+ char[] buffer = new char[bufferSize];
+ StringBuilder expected = new StringBuilder();
+ Reader in = new InputStreamReader(xmlStream, "UTF-8");
+ for (int numRead; (numRead = in.read(buffer, 0, buffer.length)) > 0; ) {
+ expected.append(buffer, 0, numRead);
+ }
+ assertTrue(XML.toJSONObject(expected.toString()).similar(XML.toJSONObject(actualString)));
+ }
+ } catch (IOException e) {
+ fail("file writer error: " +e.getMessage());
+ }
+ }
+
+ @Test
+ public void testMaxNestingDepthOf42IsRespected() {
+ final String wayTooLongMalformedXML = new String(new char[6000]).replace("\0", "");
+
+ final int maxNestingDepth = 42;
+
+ try {
+ XML.toJSONObject(wayTooLongMalformedXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
+ e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
+ }
+ }
+
+ @Test
+ public void testMaxNestingDepthIsRespectedWithValidXML() {
+ final String perfectlyFineXML = "\n" +
+ " \n" +
+ " sonoo \n" +
+ " 56000 \n" +
+ " true \n" +
+ " \n" +
+ " \n";
+
+ final int maxNestingDepth = 1;
+
+ try {
+ XML.toJSONObject(perfectlyFineXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+
+ fail("Expecting a JSONException");
+ } catch (JSONException e) {
+ assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
+ e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
+ }
+ }
+
+ @Test
+ public void testMaxNestingDepthWithValidFittingXML() {
+ final String perfectlyFineXML = "\n" +
+ " \n" +
+ " sonoo \n" +
+ " 56000 \n" +
+ " true \n" +
+ " \n" +
+ " \n";
+
+ final int maxNestingDepth = 3;
+
+ try {
+ XML.toJSONObject(perfectlyFineXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ fail("XML document should be parsed as its maximum depth fits the maxNestingDepth " +
+ "parameter of the XMLParserConfiguration used");
+ }
+ }
+ @Test
+ public void testWithWhitespaceTrimmingDisabled() {
+ String originalXml = " Test Whitespace String \t ";
+
+ JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(false));
+ String expectedJsonString = "{\"testXml\":\" Test Whitespace String \t \"}";
+ JSONObject expectedJson = new JSONObject(expectedJsonString);
+ Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
+ }
+ @Test
+ public void testNestedWithWhitespaceTrimmingDisabled() {
+ String originalXml =
+ "\n"+
+ "\n"+
+ " \n"+
+ " Sherlock Holmes \n"+
+ " \n"+
+ " ";
+
+ JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(false));
+ String expectedJsonString = "{\"addresses\":{\"address\":{\"name\":\" Sherlock Holmes \"}}}";
+ JSONObject expectedJson = new JSONObject(expectedJsonString);
+ Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
+ }
+ @Test
+ public void shouldTrimWhitespaceDoesNotSupportTagsEqualingCDataTagName() {
+ // When using withShouldTrimWhitespace = true, input containing tags with same name as cDataTagName is unsupported and should not be used in conjunction
+ String originalXml =
+ "\n"+
+ "\n"+
+ " \n"+
+ " Sherlock Holmes \n"+
+ " \n"+
+ " ";
+
+ JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(false).withcDataTagName("content"));
+ String expectedJsonString = "{\"addresses\":{\"address\":[[\"\\n \",\" Sherlock Holmes \",\"\\n \"]]}}";
+ JSONObject expectedJson = new JSONObject(expectedJsonString);
+ Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
+ }
+ @Test
+ public void shouldTrimWhitespaceEnabledDropsTagsEqualingCDataTagNameButValueRemains() {
+ String originalXml =
+ "\n"+
+ "\n"+
+ " \n"+
+ " Sherlock Holmes \n"+
+ " \n"+
+ " ";
+
+ JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(true).withcDataTagName("content"));
+ String expectedJsonString = "{\"addresses\":{\"address\":\"Sherlock Holmes\"}}";
+ JSONObject expectedJson = new JSONObject(expectedJsonString);
+ Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
+ }
+ @Test
+ public void testWithWhitespaceTrimmingEnabled() {
+ String originalXml = " Test Whitespace String \t ";
+
+ JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(true));
+ String expectedJsonString = "{\"testXml\":\"Test Whitespace String\"}";
+ JSONObject expectedJson = new JSONObject(expectedJsonString);
+ Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
+ }
+ @Test
+ public void testWithWhitespaceTrimmingEnabledByDefault() {
+ String originalXml = " Test Whitespace String \t ";
+
+ JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration());
+ String expectedJsonString = "{\"testXml\":\"Test Whitespace String\"}";
+ JSONObject expectedJson = new JSONObject(expectedJsonString);
+ Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
+ }
+
+ @Test
+ public void clarifyCurrentBehavior() {
+
+ // Behavior documented in #826
+ // After reverting the code, amount is stored as numeric, and phone is stored as string
+ String str1 =
+ " \n" +
+ " 0123456789 \n" +
+ " 0.1230 \n" +
+ " true \n" +
+ " ";
+ JSONObject jsonObject1 = XML.toJSONObject(str1,
+ new XMLParserConfiguration().withKeepStrings(false));
+ assertEquals(jsonObject1.getJSONObject("datatypes").getFloat("amount"), 0.123, .1);
+ assertEquals(jsonObject1.getJSONObject("datatypes").getString("telephone"), "0123456789");
+
+
+ // Behavior documented in #852
+ // After reverting the code, value is still stored as a number. This is due to how XML.isDecimalNotation() works
+ // and is probably a bug. JSONObject has a similar problem.
+ String str2 = " primary 008E97 ";
+ JSONObject jsonObject2 = XML.toJSONObject(str2);
+ assertEquals(jsonObject2.getJSONObject("color").getLong("value"), 0e897, .1);
+
+ // Workaround for now is to use keepStrings
+ JSONObject jsonObject3 = XML.toJSONObject(str2, new XMLParserConfiguration().withKeepStrings(true));
+ assertEquals(jsonObject3.getJSONObject("color").getString("value"), "008E97");
+ }
+
}
+
+
+
diff --git a/src/test/java/org/json/junit/XMLTokenerTest.java b/src/test/java/org/json/junit/XMLTokenerTest.java
new file mode 100644
index 000000000..ca2f2075e
--- /dev/null
+++ b/src/test/java/org/json/junit/XMLTokenerTest.java
@@ -0,0 +1,81 @@
+package org.json.junit;
+
+import org.json.XMLTokener;
+import org.junit.Test;
+
+import java.io.StringReader;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for JSON-Java XMLTokener.java
+ */
+public class XMLTokenerTest {
+
+ /**
+ * Tests that nextCDATA() correctly extracts content from within a CDATA section.
+ */
+ @Test
+ public void testNextCDATA() {
+ String xml = "This is content ]]> after";
+ XMLTokener tokener = new XMLTokener(new StringReader(xml));
+ tokener.skipPast(" content ", cdata);
+ }
+
+ /**
+ * Tests that nextContent() returns plain text content before a tag.
+ */
+ @Test
+ public void testNextContentWithText() {
+ String xml = "Some content";
+ XMLTokener tokener = new XMLTokener(xml);
+ Object content = tokener.nextContent();
+ assertEquals("Some content", content);
+ }
+
+ /**
+ * Tests that nextContent() returns '<' character when starting with a tag.
+ */
+ @Test
+ public void testNextContentWithTag() {
+ String xml = "";
+ XMLTokener tokener = new XMLTokener(xml);
+ Object content = tokener.nextContent();
+ assertEquals('<', content);
+ }
+
+ /**
+ * Tests that nextEntity() resolves a known entity like & correctly.
+ */
+ @Test
+ public void testNextEntityKnown() {
+ XMLTokener tokener = new XMLTokener("amp;");
+ Object result = tokener.nextEntity('&');
+ assertEquals("&", result);
+ }
+
+ /**
+ * Tests that nextEntity() preserves unknown entities by returning them unchanged.
+ */
+ @Test
+ public void testNextEntityUnknown() {
+ XMLTokener tokener = new XMLTokener("unknown;");
+ tokener.next(); // skip 'u'
+ Object result = tokener.nextEntity('&');
+ assertEquals("&nknown;", result); // malformed start to simulate unknown
+ }
+
+ /**
+ * Tests skipPast() to ensure the cursor moves past the specified string.
+ */
+ @Test
+ public void testSkipPast() {
+ String xml = "Ignore this... endHere more text";
+ XMLTokener tokener = new XMLTokener(xml);
+ tokener.skipPast("endHere");
+ assertEquals(' ', tokener.next()); // should be the space after "endHere"
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/json/junit/data/CustomClass.java b/src/test/java/org/json/junit/data/CustomClass.java
new file mode 100644
index 000000000..9ae405597
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClass.java
@@ -0,0 +1,23 @@
+package org.json.junit.data;
+
+public class CustomClass {
+ public int number;
+ public String name;
+ public Long longNumber;
+
+ public CustomClass() {}
+ public CustomClass (int number, String name, Long longNumber) {
+ this.number = number;
+ this.name = name;
+ this.longNumber = longNumber;
+ }
+ @Override
+ public boolean equals(Object o) {
+ CustomClass customClass = (CustomClass) o;
+
+ return (this.number == customClass.number
+ && this.name.equals(customClass.name)
+ && this.longNumber.equals(customClass.longNumber));
+ }
+}
+
diff --git a/src/test/java/org/json/junit/data/CustomClassA.java b/src/test/java/org/json/junit/data/CustomClassA.java
new file mode 100644
index 000000000..08a99d333
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassA.java
@@ -0,0 +1,19 @@
+package org.json.junit.data;
+
+import java.math.BigInteger;
+
+public class CustomClassA {
+ public BigInteger largeInt;
+
+ public CustomClassA() {}
+ public CustomClassA(BigInteger largeInt) {
+ this.largeInt = largeInt;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ CustomClassA classA = (CustomClassA) o;
+ return this.largeInt.equals(classA.largeInt);
+ }
+}
+
diff --git a/src/test/java/org/json/junit/data/CustomClassB.java b/src/test/java/org/json/junit/data/CustomClassB.java
new file mode 100644
index 000000000..688997ec4
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassB.java
@@ -0,0 +1,20 @@
+package org.json.junit.data;
+
+public class CustomClassB {
+ public int number;
+ public CustomClassC classC;
+
+ public CustomClassB() {}
+ public CustomClassB(int number, CustomClassC classC) {
+ this.number = number;
+ this.classC = classC;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ CustomClassB classB = (CustomClassB) o;
+ return this.number == classB.number
+ && this.classC.equals(classB.classC);
+ }
+}
+
diff --git a/src/test/java/org/json/junit/data/CustomClassC.java b/src/test/java/org/json/junit/data/CustomClassC.java
new file mode 100644
index 000000000..9d20aa392
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassC.java
@@ -0,0 +1,34 @@
+package org.json.junit.data;
+
+import org.json.JSONObject;
+
+public class CustomClassC {
+ public String stringName;
+ public Long longNumber;
+
+ public CustomClassC() {}
+ public CustomClassC(String stringName, Long longNumber) {
+ this.stringName = stringName;
+ this.longNumber = longNumber;
+ }
+
+ public JSONObject toJSON() {
+ JSONObject object = new JSONObject();
+ object.put("stringName", this.stringName);
+ object.put("longNumber", this.longNumber);
+ return object;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ CustomClassC classC = (CustomClassC) o;
+ return this.stringName.equals(classC.stringName)
+ && this.longNumber.equals(classC.longNumber);
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(stringName, longNumber);
+ }
+}
+
diff --git a/src/test/java/org/json/junit/data/CustomClassD.java b/src/test/java/org/json/junit/data/CustomClassD.java
new file mode 100644
index 000000000..4a858058c
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassD.java
@@ -0,0 +1,19 @@
+package org.json.junit.data;
+
+import java.util.List;
+
+public class CustomClassD {
+ public List stringList;
+
+ public CustomClassD() {}
+ public CustomClassD(List stringList) {
+ this.stringList = stringList;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ CustomClassD classD = (CustomClassD) o;
+ return this.stringList.equals(classD.stringList);
+ }
+}
+
diff --git a/src/test/java/org/json/junit/data/CustomClassE.java b/src/test/java/org/json/junit/data/CustomClassE.java
new file mode 100644
index 000000000..807dc5540
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassE.java
@@ -0,0 +1,18 @@
+package org.json.junit.data;
+
+import java.util.List;
+
+public class CustomClassE {
+ public List listClassC;
+
+ public CustomClassE() {}
+ public CustomClassE(List listClassC) {
+ this.listClassC = listClassC;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ CustomClassE classE = (CustomClassE) o;
+ return this.listClassC.equals(classE.listClassC);
+ }
+}
diff --git a/src/test/java/org/json/junit/data/CustomClassF.java b/src/test/java/org/json/junit/data/CustomClassF.java
new file mode 100644
index 000000000..d85861036
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassF.java
@@ -0,0 +1,19 @@
+package org.json.junit.data;
+
+import java.util.List;
+
+public class CustomClassF {
+ public List> listOfString;
+
+ public CustomClassF() {}
+ public CustomClassF(List> listOfString) {
+ this.listOfString = listOfString;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ CustomClassF classF = (CustomClassF) o;
+ return this.listOfString.equals(classF.listOfString);
+ }
+}
+
diff --git a/src/test/java/org/json/junit/data/CustomClassG.java b/src/test/java/org/json/junit/data/CustomClassG.java
new file mode 100644
index 000000000..c8c9f5784
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassG.java
@@ -0,0 +1,18 @@
+package org.json.junit.data;
+
+import java.util.Map;
+
+public class CustomClassG {
+ public Map dataList;
+
+ public CustomClassG () {}
+ public CustomClassG (Map dataList) {
+ this.dataList = dataList;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ CustomClassG classG = (CustomClassG) object;
+ return this.dataList.equals(classG.dataList);
+ }
+}
diff --git a/src/test/java/org/json/junit/data/CustomClassH.java b/src/test/java/org/json/junit/data/CustomClassH.java
new file mode 100644
index 000000000..ce9b1af23
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassH.java
@@ -0,0 +1,22 @@
+package org.json.junit.data;
+
+import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
+
+public class CustomClassH {
+ public Map> integerMap;
+
+ public CustomClassH() {}
+ public CustomClassH(Map> integerMap) {
+ this.integerMap = integerMap;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ CustomClassH classH = (CustomClassH) object;
+ return this.integerMap.size() == classH.integerMap.size()
+ && this.integerMap.keySet().equals(classH.integerMap.keySet())
+ && new ArrayList<>(this.integerMap.values()).equals(new ArrayList<>(classH.integerMap.values()));
+ }
+}
diff --git a/src/test/java/org/json/junit/data/CustomClassI.java b/src/test/java/org/json/junit/data/CustomClassI.java
new file mode 100644
index 000000000..bd7c4ed89
--- /dev/null
+++ b/src/test/java/org/json/junit/data/CustomClassI.java
@@ -0,0 +1,12 @@
+package org.json.junit.data;
+
+import java.util.Map;
+
+public class CustomClassI {
+ public Map> integerMap;
+
+ public CustomClassI() {}
+ public CustomClassI(Map> integerMap) {
+ this.integerMap = integerMap;
+ }
+}
diff --git a/src/test/java/org/json/junit/data/ExceptionalBean.java b/src/test/java/org/json/junit/data/ExceptionalBean.java
index 72d6c0cdb..91067b86b 100644
--- a/src/test/java/org/json/junit/data/ExceptionalBean.java
+++ b/src/test/java/org/json/junit/data/ExceptionalBean.java
@@ -8,7 +8,7 @@
import java.lang.reflect.InvocationTargetException;
/**
- * Object for testing the exception handling in {@link JSONObject#populateMap}.
+ * Object for testing the exception handling in {@link org.json.JSONObject#populateMap}.
*
* @author John Aylward
*/
diff --git a/src/test/java/org/json/junit/data/GenericBean.java b/src/test/java/org/json/junit/data/GenericBean.java
index da6370d48..dd46b88e6 100644
--- a/src/test/java/org/json/junit/data/GenericBean.java
+++ b/src/test/java/org/json/junit/data/GenericBean.java
@@ -9,7 +9,7 @@
* @param
* generic number value
*/
-public class GenericBean> implements MyBean {
+public class GenericBean implements MyBean {
/**
* @param genericValue
* value to initiate with
diff --git a/src/test/java/org/json/junit/data/PersonRecord.java b/src/test/java/org/json/junit/data/PersonRecord.java
new file mode 100644
index 000000000..891f1bb9e
--- /dev/null
+++ b/src/test/java/org/json/junit/data/PersonRecord.java
@@ -0,0 +1,31 @@
+package org.json.junit.data;
+
+/**
+ * A test class that mimics Java record accessor patterns.
+ * Records use accessor methods without get/is prefixes (e.g., name() instead of getName()).
+ * This class simulates that behavior to test JSONObject's handling of such methods.
+ */
+public class PersonRecord {
+ private final String name;
+ private final int age;
+ private final boolean active;
+
+ public PersonRecord(String name, int age, boolean active) {
+ this.name = name;
+ this.age = age;
+ this.active = active;
+ }
+
+ // Record-style accessors (no "get" or "is" prefix)
+ public String name() {
+ return name;
+ }
+
+ public int age() {
+ return age;
+ }
+
+ public boolean active() {
+ return active;
+ }
+}
diff --git a/src/test/java/org/json/junit/data/RecursiveBean.java b/src/test/java/org/json/junit/data/RecursiveBean.java
new file mode 100644
index 000000000..dad6e7a65
--- /dev/null
+++ b/src/test/java/org/json/junit/data/RecursiveBean.java
@@ -0,0 +1,23 @@
+package org.json.junit.data;
+
+/**
+ * test class for verifying if recursively defined bean can be correctly identified
+ * @author Zetmas
+ *
+ */
+public class RecursiveBean {
+ private String name;
+ private Object reference;
+ private Object reference2;
+ public String getName() { return name; }
+ public Object getRef() {return reference;}
+ public Object getRef2() {return reference2;}
+ public void setRef(Object refObj) {reference = refObj;}
+ public void setRef2(Object refObj) {reference2 = refObj;}
+
+ public RecursiveBean(String name) {
+ this.name = name;
+ reference = null;
+ reference2 = null;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/json/junit/data/RecursiveBeanEquals.java b/src/test/java/org/json/junit/data/RecursiveBeanEquals.java
new file mode 100644
index 000000000..10166481e
--- /dev/null
+++ b/src/test/java/org/json/junit/data/RecursiveBeanEquals.java
@@ -0,0 +1,33 @@
+package org.json.junit.data;
+
+/** test class for verifying if recursively defined bean can be correctly identified */
+public class RecursiveBeanEquals {
+ private final String name;
+ private Object reference;
+
+ public RecursiveBeanEquals(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Object getRef() {
+ return reference;
+ }
+
+ public void setRef(Object refObj) {
+ reference = refObj;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof RecursiveBeanEquals && name.equals(((RecursiveBeanEquals) other).name);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+}
diff --git a/src/test/java/org/json/junit/data/WeirdList.java b/src/test/java/org/json/junit/data/WeirdList.java
index 834b81e86..35605863a 100644
--- a/src/test/java/org/json/junit/data/WeirdList.java
+++ b/src/test/java/org/json/junit/data/WeirdList.java
@@ -12,7 +12,7 @@
*/
public class WeirdList {
/** */
- private final List list = new ArrayList();
+ private final List list = new ArrayList<>();
/**
* @param vals
@@ -25,14 +25,14 @@ public WeirdList(Integer... vals) {
* @return a copy of the list
*/
public List get() {
- return new ArrayList(this.list);
+ return new ArrayList<>(this.list);
}
/**
* @return a copy of the list
*/
public List getALL() {
- return new ArrayList(this.list);
+ return new ArrayList<>(this.list);
}
/**
diff --git a/src/test/resources/Issue593.json b/src/test/resources/Issue593.json
new file mode 100644
index 000000000..213625af2
--- /dev/null
+++ b/src/test/resources/Issue593.json
@@ -0,0 +1,704 @@
+{
+ "success": true,
+ "error": null,
+ "response": [
+ {
+ "loc": {
+ "long": 31.25,
+ "lat": 30.063
+ },
+ "interval": "day",
+ "place": {
+ "name": "cairo",
+ "state": "qh",
+ "country": "eg"
+ },
+ "periods": [
+ {
+ "timestamp": 1665032400,
+ "validTime": "2022-10-06T07:00:00+02:00",
+ "dateTimeISO": "2022-10-06T07:00:00+02:00",
+ "maxTempC": 32,
+ "maxTempF": 90,
+ "minTempC": 19,
+ "minTempF": 66,
+ "avgTempC": 25,
+ "avgTempF": 78,
+ "tempC": null,
+ "tempF": null,
+ "maxFeelslikeC": 32,
+ "maxFeelslikeF": 89,
+ "minFeelslikeC": 21,
+ "minFeelslikeF": 70,
+ "avgFeelslikeC": 26,
+ "avgFeelslikeF": 80,
+ "feelslikeC": 21,
+ "feelslikeF": 70,
+ "maxDewpointC": 17,
+ "maxDewpointF": 63,
+ "minDewpointC": 11,
+ "minDewpointF": 52,
+ "avgDewpointC": 14,
+ "avgDewpointF": 58,
+ "dewpointC": 17,
+ "dewpointF": 63,
+ "maxHumidity": 77,
+ "minHumidity": 29,
+ "humidity": 77,
+ "pop": 0,
+ "precipMM": 0,
+ "precipIN": 0,
+ "iceaccum": null,
+ "iceaccumMM": null,
+ "iceaccumIN": null,
+ "snowCM": 0,
+ "snowIN": 0,
+ "pressureMB": 1015,
+ "pressureIN": 29.97,
+ "windDir": "N",
+ "windDirDEG": 353,
+ "windSpeedKTS": 5,
+ "windSpeedKPH": 9,
+ "windSpeedMPH": 6,
+ "windGustKTS": 21,
+ "windGustKPH": 40,
+ "windGustMPH": 25,
+ "windDirMax": "NNW",
+ "windDirMaxDEG": 342,
+ "windSpeedMaxKTS": 9,
+ "windSpeedMaxKPH": 16,
+ "windSpeedMaxMPH": 10,
+ "windDirMin": "N",
+ "windDirMinDEG": 353,
+ "windSpeedMinKTS": 1,
+ "windSpeedMinKPH": 2,
+ "windSpeedMinMPH": 1,
+ "windDir80m": "N",
+ "windDir80mDEG": 11,
+ "windSpeed80mKTS": 12,
+ "windSpeed80mKPH": 22,
+ "windSpeed80mMPH": 13,
+ "windGust80mKTS": 22,
+ "windGust80mKPH": 41,
+ "windGust80mMPH": 25,
+ "windDirMax80m": "NNW",
+ "windDirMax80mDEG": 343,
+ "windSpeedMax80mKTS": 22,
+ "windSpeedMax80mKPH": 41,
+ "windSpeedMax80mMPH": 25,
+ "windDirMin80m": "E",
+ "windDirMin80mDEG": 95,
+ "windSpeedMin80mKTS": 8,
+ "windSpeedMin80mKPH": 15,
+ "windSpeedMin80mMPH": 10,
+ "sky": 22,
+ "cloudsCoded": "FW",
+ "weather": "Mostly Sunny",
+ "weatherCoded": [],
+ "weatherPrimary": "Mostly Sunny",
+ "weatherPrimaryCoded": "::FW",
+ "icon": "fair.png",
+ "visibilityKM": 24.135,
+ "visibilityMI": 15,
+ "uvi": 6,
+ "solradWM2": 5608,
+ "solradMinWM2": 0,
+ "solradMaxWM2": 778,
+ "isDay": true,
+ "maxCoverage": "",
+ "sunrise": 1665028274,
+ "sunset": 1665070502,
+ "sunriseISO": "2022-10-06T05:51:14+02:00",
+ "sunsetISO": "2022-10-06T17:35:02+02:00"
+ },
+ {
+ "timestamp": 1665118800,
+ "validTime": "2022-10-07T07:00:00+02:00",
+ "dateTimeISO": "2022-10-07T07:00:00+02:00",
+ "maxTempC": 30,
+ "maxTempF": 86,
+ "minTempC": 19,
+ "minTempF": 66,
+ "avgTempC": 24,
+ "avgTempF": 76,
+ "tempC": null,
+ "tempF": null,
+ "maxFeelslikeC": 29,
+ "maxFeelslikeF": 85,
+ "minFeelslikeC": 19,
+ "minFeelslikeF": 67,
+ "avgFeelslikeC": 24,
+ "avgFeelslikeF": 76,
+ "feelslikeC": 19,
+ "feelslikeF": 67,
+ "maxDewpointC": 15,
+ "maxDewpointF": 60,
+ "minDewpointC": 10,
+ "minDewpointF": 50,
+ "avgDewpointC": 12,
+ "avgDewpointF": 54,
+ "dewpointC": 15,
+ "dewpointF": 60,
+ "maxHumidity": 77,
+ "minHumidity": 30,
+ "humidity": 77,
+ "pop": 0,
+ "precipMM": 0,
+ "precipIN": 0,
+ "iceaccum": null,
+ "iceaccumMM": null,
+ "iceaccumIN": null,
+ "snowCM": 0,
+ "snowIN": 0,
+ "pressureMB": 1014,
+ "pressureIN": 29.95,
+ "windDir": "NW",
+ "windDirDEG": 325,
+ "windSpeedKTS": 1,
+ "windSpeedKPH": 2,
+ "windSpeedMPH": 1,
+ "windGustKTS": 16,
+ "windGustKPH": 29,
+ "windGustMPH": 18,
+ "windDirMax": "WNW",
+ "windDirMaxDEG": 298,
+ "windSpeedMaxKTS": 7,
+ "windSpeedMaxKPH": 13,
+ "windSpeedMaxMPH": 8,
+ "windDirMin": "NW",
+ "windDirMinDEG": 325,
+ "windSpeedMinKTS": 1,
+ "windSpeedMinKPH": 2,
+ "windSpeedMinMPH": 1,
+ "windDir80m": "NNW",
+ "windDir80mDEG": 347,
+ "windSpeed80mKTS": 6,
+ "windSpeed80mKPH": 10,
+ "windSpeed80mMPH": 6,
+ "windGust80mKTS": 20,
+ "windGust80mKPH": 37,
+ "windGust80mMPH": 23,
+ "windDirMax80m": "NW",
+ "windDirMax80mDEG": 316,
+ "windSpeedMax80mKTS": 20,
+ "windSpeedMax80mKPH": 37,
+ "windSpeedMax80mMPH": 23,
+ "windDirMin80m": "NNW",
+ "windDirMin80mDEG": 347,
+ "windSpeedMin80mKTS": 6,
+ "windSpeedMin80mKPH": 10,
+ "windSpeedMin80mMPH": 6,
+ "sky": 30,
+ "cloudsCoded": "FW",
+ "weather": "Mostly Sunny",
+ "weatherCoded": [],
+ "weatherPrimary": "Mostly Sunny",
+ "weatherPrimaryCoded": "::FW",
+ "icon": "fair.png",
+ "visibilityKM": 24.135,
+ "visibilityMI": 15,
+ "uvi": 6,
+ "solradWM2": 5486,
+ "solradMinWM2": 0,
+ "solradMaxWM2": 742,
+ "isDay": true,
+ "maxCoverage": "",
+ "sunrise": 1665114710,
+ "sunset": 1665156831,
+ "sunriseISO": "2022-10-07T05:51:50+02:00",
+ "sunsetISO": "2022-10-07T17:33:51+02:00"
+ },
+ {
+ "timestamp": 1665205200,
+ "validTime": "2022-10-08T07:00:00+02:00",
+ "dateTimeISO": "2022-10-08T07:00:00+02:00",
+ "maxTempC": 30,
+ "maxTempF": 87,
+ "minTempC": 19,
+ "minTempF": 66,
+ "avgTempC": 25,
+ "avgTempF": 76,
+ "tempC": null,
+ "tempF": null,
+ "maxFeelslikeC": 30,
+ "maxFeelslikeF": 86,
+ "minFeelslikeC": 19,
+ "minFeelslikeF": 67,
+ "avgFeelslikeC": 25,
+ "avgFeelslikeF": 76,
+ "feelslikeC": 19,
+ "feelslikeF": 67,
+ "maxDewpointC": 15,
+ "maxDewpointF": 59,
+ "minDewpointC": 11,
+ "minDewpointF": 52,
+ "avgDewpointC": 13,
+ "avgDewpointF": 56,
+ "dewpointC": 15,
+ "dewpointF": 59,
+ "maxHumidity": 76,
+ "minHumidity": 32,
+ "humidity": 76,
+ "pop": 0,
+ "precipMM": 0,
+ "precipIN": 0,
+ "iceaccum": null,
+ "iceaccumMM": null,
+ "iceaccumIN": null,
+ "snowCM": 0,
+ "snowIN": 0,
+ "pressureMB": 1014,
+ "pressureIN": 29.94,
+ "windDir": "NNE",
+ "windDirDEG": 21,
+ "windSpeedKTS": 1,
+ "windSpeedKPH": 2,
+ "windSpeedMPH": 1,
+ "windGustKTS": 17,
+ "windGustKPH": 32,
+ "windGustMPH": 20,
+ "windDirMax": "WNW",
+ "windDirMaxDEG": 301,
+ "windSpeedMaxKTS": 7,
+ "windSpeedMaxKPH": 13,
+ "windSpeedMaxMPH": 8,
+ "windDirMin": "NNE",
+ "windDirMinDEG": 21,
+ "windSpeedMinKTS": 1,
+ "windSpeedMinKPH": 2,
+ "windSpeedMinMPH": 1,
+ "windDir80m": "NW",
+ "windDir80mDEG": 309,
+ "windSpeed80mKTS": 5,
+ "windSpeed80mKPH": 9,
+ "windSpeed80mMPH": 5,
+ "windGust80mKTS": 17,
+ "windGust80mKPH": 31,
+ "windGust80mMPH": 19,
+ "windDirMax80m": "NW",
+ "windDirMax80mDEG": 322,
+ "windSpeedMax80mKTS": 17,
+ "windSpeedMax80mKPH": 31,
+ "windSpeedMax80mMPH": 19,
+ "windDirMin80m": "NW",
+ "windDirMin80mDEG": 309,
+ "windSpeedMin80mKTS": 5,
+ "windSpeedMin80mKPH": 9,
+ "windSpeedMin80mMPH": 5,
+ "sky": 47,
+ "cloudsCoded": "SC",
+ "weather": "Partly Cloudy",
+ "weatherCoded": [],
+ "weatherPrimary": "Partly Cloudy",
+ "weatherPrimaryCoded": "::SC",
+ "icon": "pcloudy.png",
+ "visibilityKM": 24.135,
+ "visibilityMI": 15,
+ "uvi": 7,
+ "solradWM2": 4785,
+ "solradMinWM2": 0,
+ "solradMaxWM2": 682,
+ "isDay": true,
+ "maxCoverage": "",
+ "sunrise": 1665201146,
+ "sunset": 1665243161,
+ "sunriseISO": "2022-10-08T05:52:26+02:00",
+ "sunsetISO": "2022-10-08T17:32:41+02:00"
+ },
+ {
+ "timestamp": 1665291600,
+ "validTime": "2022-10-09T07:00:00+02:00",
+ "dateTimeISO": "2022-10-09T07:00:00+02:00",
+ "maxTempC": 31,
+ "maxTempF": 87,
+ "minTempC": 19,
+ "minTempF": 67,
+ "avgTempC": 25,
+ "avgTempF": 77,
+ "tempC": null,
+ "tempF": null,
+ "maxFeelslikeC": 30,
+ "maxFeelslikeF": 86,
+ "minFeelslikeC": 20,
+ "minFeelslikeF": 67,
+ "avgFeelslikeC": 25,
+ "avgFeelslikeF": 77,
+ "feelslikeC": 20,
+ "feelslikeF": 67,
+ "maxDewpointC": 17,
+ "maxDewpointF": 63,
+ "minDewpointC": 11,
+ "minDewpointF": 52,
+ "avgDewpointC": 14,
+ "avgDewpointF": 57,
+ "dewpointC": 17,
+ "dewpointF": 63,
+ "maxHumidity": 86,
+ "minHumidity": 31,
+ "humidity": 86,
+ "pop": 0,
+ "precipMM": 0,
+ "precipIN": 0,
+ "iceaccum": null,
+ "iceaccumMM": null,
+ "iceaccumIN": null,
+ "snowCM": 0,
+ "snowIN": 0,
+ "pressureMB": 1016,
+ "pressureIN": 29.99,
+ "windDir": "N",
+ "windDirDEG": 356,
+ "windSpeedKTS": 2,
+ "windSpeedKPH": 4,
+ "windSpeedMPH": 2,
+ "windGustKTS": 19,
+ "windGustKPH": 36,
+ "windGustMPH": 22,
+ "windDirMax": "NNW",
+ "windDirMaxDEG": 343,
+ "windSpeedMaxKTS": 8,
+ "windSpeedMaxKPH": 14,
+ "windSpeedMaxMPH": 9,
+ "windDirMin": "N",
+ "windDirMinDEG": 356,
+ "windSpeedMinKTS": 2,
+ "windSpeedMinKPH": 4,
+ "windSpeedMinMPH": 2,
+ "windDir80m": "NW",
+ "windDir80mDEG": 316,
+ "windSpeed80mKTS": 5,
+ "windSpeed80mKPH": 9,
+ "windSpeed80mMPH": 6,
+ "windGust80mKTS": 20,
+ "windGust80mKPH": 36,
+ "windGust80mMPH": 23,
+ "windDirMax80m": "N",
+ "windDirMax80mDEG": 354,
+ "windSpeedMax80mKTS": 20,
+ "windSpeedMax80mKPH": 36,
+ "windSpeedMax80mMPH": 23,
+ "windDirMin80m": "NW",
+ "windDirMin80mDEG": 316,
+ "windSpeedMin80mKTS": 5,
+ "windSpeedMin80mKPH": 9,
+ "windSpeedMin80mMPH": 6,
+ "sky": 47,
+ "cloudsCoded": "SC",
+ "weather": "Partly Cloudy",
+ "weatherCoded": [],
+ "weatherPrimary": "Partly Cloudy",
+ "weatherPrimaryCoded": "::SC",
+ "icon": "pcloudy.png",
+ "visibilityKM": 24.135,
+ "visibilityMI": 15,
+ "uvi": 7,
+ "solradWM2": 4768,
+ "solradMinWM2": 0,
+ "solradMaxWM2": 726,
+ "isDay": true,
+ "maxCoverage": "",
+ "sunrise": 1665287583,
+ "sunset": 1665329491,
+ "sunriseISO": "2022-10-09T05:53:03+02:00",
+ "sunsetISO": "2022-10-09T17:31:31+02:00"
+ },
+ {
+ "timestamp": 1665378000,
+ "validTime": "2022-10-10T07:00:00+02:00",
+ "dateTimeISO": "2022-10-10T07:00:00+02:00",
+ "maxTempC": 31,
+ "maxTempF": 87,
+ "minTempC": 21,
+ "minTempF": 70,
+ "avgTempC": 26,
+ "avgTempF": 78,
+ "tempC": null,
+ "tempF": null,
+ "maxFeelslikeC": 30,
+ "maxFeelslikeF": 86,
+ "minFeelslikeC": 21,
+ "minFeelslikeF": 69,
+ "avgFeelslikeC": 25,
+ "avgFeelslikeF": 78,
+ "feelslikeC": 21,
+ "feelslikeF": 69,
+ "maxDewpointC": 16,
+ "maxDewpointF": 61,
+ "minDewpointC": 13,
+ "minDewpointF": 55,
+ "avgDewpointC": 14,
+ "avgDewpointF": 58,
+ "dewpointC": 16,
+ "dewpointF": 61,
+ "maxHumidity": 75,
+ "minHumidity": 35,
+ "humidity": 75,
+ "pop": 0,
+ "precipMM": 0,
+ "precipIN": 0,
+ "iceaccum": null,
+ "iceaccumMM": null,
+ "iceaccumIN": null,
+ "snowCM": 0,
+ "snowIN": 0,
+ "pressureMB": 1017,
+ "pressureIN": 30.03,
+ "windDir": "N",
+ "windDirDEG": 358,
+ "windSpeedKTS": 2,
+ "windSpeedKPH": 4,
+ "windSpeedMPH": 2,
+ "windGustKTS": 16,
+ "windGustKPH": 30,
+ "windGustMPH": 19,
+ "windDirMax": "N",
+ "windDirMaxDEG": 10,
+ "windSpeedMaxKTS": 8,
+ "windSpeedMaxKPH": 15,
+ "windSpeedMaxMPH": 9,
+ "windDirMin": "N",
+ "windDirMinDEG": 358,
+ "windSpeedMinKTS": 2,
+ "windSpeedMinKPH": 4,
+ "windSpeedMinMPH": 2,
+ "windDir80m": "N",
+ "windDir80mDEG": 8,
+ "windSpeed80mKTS": 7,
+ "windSpeed80mKPH": 13,
+ "windSpeed80mMPH": 8,
+ "windGust80mKTS": 19,
+ "windGust80mKPH": 36,
+ "windGust80mMPH": 22,
+ "windDirMax80m": "N",
+ "windDirMax80mDEG": 10,
+ "windSpeedMax80mKTS": 19,
+ "windSpeedMax80mKPH": 36,
+ "windSpeedMax80mMPH": 22,
+ "windDirMin80m": "E",
+ "windDirMin80mDEG": 91,
+ "windSpeedMin80mKTS": 7,
+ "windSpeedMin80mKPH": 13,
+ "windSpeedMin80mMPH": 8,
+ "sky": 64,
+ "cloudsCoded": "SC",
+ "weather": "Partly Cloudy",
+ "weatherCoded": [],
+ "weatherPrimary": "Partly Cloudy",
+ "weatherPrimaryCoded": "::SC",
+ "icon": "pcloudy.png",
+ "visibilityKM": 24.135,
+ "visibilityMI": 15,
+ "uvi": 6,
+ "solradWM2": 4494,
+ "solradMinWM2": 0,
+ "solradMaxWM2": 597,
+ "isDay": true,
+ "maxCoverage": "",
+ "sunrise": 1665374020,
+ "sunset": 1665415821,
+ "sunriseISO": "2022-10-10T05:53:40+02:00",
+ "sunsetISO": "2022-10-10T17:30:21+02:00"
+ },
+ {
+ "timestamp": 1665464400,
+ "validTime": "2022-10-11T07:00:00+02:00",
+ "dateTimeISO": "2022-10-11T07:00:00+02:00",
+ "maxTempC": 31,
+ "maxTempF": 87,
+ "minTempC": 21,
+ "minTempF": 70,
+ "avgTempC": 26,
+ "avgTempF": 78,
+ "tempC": null,
+ "tempF": null,
+ "maxFeelslikeC": 31,
+ "maxFeelslikeF": 87,
+ "minFeelslikeC": 22,
+ "minFeelslikeF": 72,
+ "avgFeelslikeC": 26,
+ "avgFeelslikeF": 79,
+ "feelslikeC": 22,
+ "feelslikeF": 72,
+ "maxDewpointC": 17,
+ "maxDewpointF": 62,
+ "minDewpointC": 11,
+ "minDewpointF": 51,
+ "avgDewpointC": 13,
+ "avgDewpointF": 55,
+ "dewpointC": 17,
+ "dewpointF": 62,
+ "maxHumidity": 71,
+ "minHumidity": 30,
+ "humidity": 71,
+ "pop": 0,
+ "precipMM": 0,
+ "precipIN": 0,
+ "iceaccum": null,
+ "iceaccumMM": null,
+ "iceaccumIN": null,
+ "snowCM": 0,
+ "snowIN": 0,
+ "pressureMB": 1015,
+ "pressureIN": 29.98,
+ "windDir": "NNE",
+ "windDirDEG": 13,
+ "windSpeedKTS": 8,
+ "windSpeedKPH": 15,
+ "windSpeedMPH": 9,
+ "windGustKTS": 15,
+ "windGustKPH": 28,
+ "windGustMPH": 17,
+ "windDirMax": "NNE",
+ "windDirMaxDEG": 28,
+ "windSpeedMaxKTS": 15,
+ "windSpeedMaxKPH": 28,
+ "windSpeedMaxMPH": 18,
+ "windDirMin": "NNE",
+ "windDirMinDEG": 14,
+ "windSpeedMinKTS": 7,
+ "windSpeedMinKPH": 14,
+ "windSpeedMinMPH": 8,
+ "windDir80m": "NNE",
+ "windDir80mDEG": 16,
+ "windSpeed80mKTS": 10,
+ "windSpeed80mKPH": 19,
+ "windSpeed80mMPH": 12,
+ "windGust80mKTS": 17,
+ "windGust80mKPH": 31,
+ "windGust80mMPH": 19,
+ "windDirMax80m": "NNE",
+ "windDirMax80mDEG": 28,
+ "windSpeedMax80mKTS": 17,
+ "windSpeedMax80mKPH": 31,
+ "windSpeedMax80mMPH": 19,
+ "windDirMin80m": "NNE",
+ "windDirMin80mDEG": 13,
+ "windSpeedMin80mKTS": 9,
+ "windSpeedMin80mKPH": 18,
+ "windSpeedMin80mMPH": 11,
+ "sky": 0,
+ "cloudsCoded": "CL",
+ "weather": "Sunny",
+ "weatherCoded": [],
+ "weatherPrimary": "Sunny",
+ "weatherPrimaryCoded": "::CL",
+ "icon": "sunny.png",
+ "visibilityKM": 24.135,
+ "visibilityMI": 15,
+ "uvi": null,
+ "solradWM2": 5450,
+ "solradMinWM2": 0,
+ "solradMaxWM2": 758,
+ "isDay": true,
+ "maxCoverage": "",
+ "sunrise": 1665460458,
+ "sunset": 1665502153,
+ "sunriseISO": "2022-10-11T05:54:18+02:00",
+ "sunsetISO": "2022-10-11T17:29:13+02:00"
+ },
+ {
+ "timestamp": 1665550800,
+ "validTime": "2022-10-12T07:00:00+02:00",
+ "dateTimeISO": "2022-10-12T07:00:00+02:00",
+ "maxTempC": 31,
+ "maxTempF": 88,
+ "minTempC": 21,
+ "minTempF": 69,
+ "avgTempC": 26,
+ "avgTempF": 79,
+ "tempC": null,
+ "tempF": null,
+ "maxFeelslikeC": 31,
+ "maxFeelslikeF": 88,
+ "minFeelslikeC": 22,
+ "minFeelslikeF": 72,
+ "avgFeelslikeC": 26,
+ "avgFeelslikeF": 80,
+ "feelslikeC": 22,
+ "feelslikeF": 72,
+ "maxDewpointC": 16,
+ "maxDewpointF": 60,
+ "minDewpointC": 11,
+ "minDewpointF": 51,
+ "avgDewpointC": 13,
+ "avgDewpointF": 55,
+ "dewpointC": 16,
+ "dewpointF": 60,
+ "maxHumidity": 68,
+ "minHumidity": 29,
+ "humidity": 68,
+ "pop": 0,
+ "precipMM": 0,
+ "precipIN": 0,
+ "iceaccum": null,
+ "iceaccumMM": null,
+ "iceaccumIN": null,
+ "snowCM": 0,
+ "snowIN": 0,
+ "pressureMB": 1014,
+ "pressureIN": 29.95,
+ "windDir": "NNE",
+ "windDirDEG": 12,
+ "windSpeedKTS": 8,
+ "windSpeedKPH": 15,
+ "windSpeedMPH": 9,
+ "windGustKTS": 15,
+ "windGustKPH": 28,
+ "windGustMPH": 17,
+ "windDirMax": "E",
+ "windDirMaxDEG": 96,
+ "windSpeedMaxKTS": 14,
+ "windSpeedMaxKPH": 26,
+ "windSpeedMaxMPH": 16,
+ "windDirMin": "NNE",
+ "windDirMinDEG": 12,
+ "windSpeedMinKTS": 7,
+ "windSpeedMinKPH": 13,
+ "windSpeedMinMPH": 8,
+ "windDir80m": "NNE",
+ "windDir80mDEG": 15,
+ "windSpeed80mKTS": 10,
+ "windSpeed80mKPH": 19,
+ "windSpeed80mMPH": 12,
+ "windGust80mKTS": 18,
+ "windGust80mKPH": 33,
+ "windGust80mMPH": 21,
+ "windDirMax80m": "E",
+ "windDirMax80mDEG": 96,
+ "windSpeedMax80mKTS": 18,
+ "windSpeedMax80mKPH": 33,
+ "windSpeedMax80mMPH": 21,
+ "windDirMin80m": "NNE",
+ "windDirMin80mDEG": 15,
+ "windSpeedMin80mKTS": 10,
+ "windSpeedMin80mKPH": 18,
+ "windSpeedMin80mMPH": 11,
+ "sky": 27,
+ "cloudsCoded": "FW",
+ "weather": "Mostly Sunny",
+ "weatherCoded": [],
+ "weatherPrimary": "Mostly Sunny",
+ "weatherPrimaryCoded": "::FW",
+ "icon": "fair.png",
+ "visibilityKM": 24.135,
+ "visibilityMI": 15,
+ "uvi": null,
+ "solradWM2": 4740,
+ "solradMinWM2": 0,
+ "solradMaxWM2": 743,
+ "isDay": true,
+ "maxCoverage": "",
+ "sunrise": 1665546895,
+ "sunset": 1665588484,
+ "sunriseISO": "2022-10-12T05:54:55+02:00",
+ "sunsetISO": "2022-10-12T17:28:04+02:00"
+ }
+ ],
+ "profile": {
+ "tz": "Africa/Cairo",
+ "elevM": 23,
+ "elevFT": 75
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/test/resources/Issue593.xml b/src/test/resources/Issue593.xml
new file mode 100644
index 000000000..0c6c038b6
--- /dev/null
+++ b/src/test/resources/Issue593.xml
@@ -0,0 +1,691 @@
+true
+
+