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
@@ -2709,4 +3401,259 @@ private static JSONException recursivelyDefinedObjectException(String key) {
"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 the type of the object to return
+ * @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. Static fields are ignored.
+ *
+ *
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()) {
+ if (Modifier.isStatic(field.getModifiers())) {
+ continue;
+ }
+ 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.
+ * The unchecked warning is suppressed when casting valueOf() to E
+ * @param enumClass enum class
+ * @param value value of enum
+ * @param type of enum
+ */
+ @SuppressWarnings("unchecked")
+ 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 963fdec3e..34066c1aa 100644
--- a/src/main/java/org/json/JSONPointer.java
+++ b/src/main/java/org/json/JSONPointer.java
@@ -42,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();
@@ -121,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;
}
@@ -163,6 +169,12 @@ 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);
}
@@ -234,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));
}
diff --git a/src/main/java/org/json/JSONPointerException.java b/src/main/java/org/json/JSONPointerException.java
index a0e128cd5..dc5a25ad6 100644
--- a/src/main/java/org/json/JSONPointerException.java
+++ b/src/main/java/org/json/JSONPointerException.java
@@ -14,10 +14,21 @@
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/JSONPropertyName.java b/src/main/java/org/json/JSONPropertyName.java
index 4391bb76c..0e4123f37 100644
--- a/src/main/java/org/json/JSONPropertyName.java
+++ b/src/main/java/org/json/JSONPropertyName.java
@@ -21,6 +21,7 @@
@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/JSONTokener.java b/src/main/java/org/json/JSONTokener.java
index c18058013..3726856d3 100644
--- a/src/main/java/org/json/JSONTokener.java
+++ b/src/main/java/org/json/JSONTokener.java
@@ -1,6 +1,7 @@
package org.json;
import java.io.*;
+import java.nio.charset.Charset;
/*
Public Domain.
@@ -31,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);
@@ -50,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,
@@ -120,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() {
@@ -184,7 +234,7 @@ public char next() throws JSONException {
this.previous = (char) c;
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.
@@ -298,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) {
@@ -318,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 '"':
@@ -331,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:
@@ -401,27 +454,39 @@ public String nextTo(String delimiters) throws JSONException {
*/
public Object nextValue() throws JSONException {
char c = this.nextClean();
- String string;
-
switch (c) {
- case '"':
- case '\'':
- return this.nextString(c);
case '{':
this.back();
try {
- return new JSONObject(this);
+ 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);
+ 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);
+ }
/*
* Handle unquoted text. This could be the values true, false, or
@@ -444,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, jsonParserConfiguration);
+ // 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;
}
@@ -518,6 +603,11 @@ public String toString() {
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/ParserConfiguration.java b/src/main/java/org/json/ParserConfiguration.java
index 519e2099d..e80093cac 100644
--- a/src/main/java/org/json/ParserConfiguration.java
+++ b/src/main/java/org/json/ParserConfiguration.java
@@ -13,30 +13,58 @@ public class ParserConfiguration {
*/
public static final int UNDEFINED_MAXIMUM_NESTING_DEPTH = -1;
+ /**
+ * Used to indicate there's no defined limit to the maximum number length
+ */
+ public static final int UNDEFINED_MAXIMUM_NUMBER_LENGTH = -1;
+
/**
* The default maximum nesting depth when parsing a document.
*/
public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512;
+ /**
+ * The default max number length
+ */
+ public static final int DEFAULT_MAX_NUMBER_LENGTH = 1000;
+
/**
* Specifies if values should be kept as strings (true), or if
- * they should try to be guessed into JSON values (numeric, boolean, string)
+ * they should try to be guessed into JSON values (numeric, boolean, string).
*/
protected boolean keepStrings;
/**
- * The maximum nesting depth when parsing a document.
+ * The maximum nesting depth when parsing an object.
*/
protected int maxNestingDepth;
+ /**
+ * The max number of chars for any number. Exceeding this limit will cause the value to be converted to a string
+ */
+ protected int maxNumberLength;
+
+ /**
+ * Constructs a new ParserConfiguration with default settings.
+ */
public ParserConfiguration() {
this.keepStrings = false;
this.maxNestingDepth = DEFAULT_MAXIMUM_NESTING_DEPTH;
+ this.maxNumberLength = DEFAULT_MAX_NUMBER_LENGTH;
}
+ /**
+ * Constructs a new ParserConfiguration with the specified settings. Use the with* methods instead of calling this ctor.
+ *
+ * @param keepStrings A boolean indicating whether to preserve strings during parsing.
+ * @param maxNestingDepth An integer representing the maximum allowed nesting depth.
+ * @deprecated Use the with*() methods instead
+ */
+ @Deprecated
protected ParserConfiguration(final boolean keepStrings, final int maxNestingDepth) {
this.keepStrings = keepStrings;
this.maxNestingDepth = maxNestingDepth;
+ this.maxNumberLength = DEFAULT_MAX_NUMBER_LENGTH;
}
/**
@@ -49,15 +77,16 @@ protected ParserConfiguration 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 ParserConfiguration(
- this.keepStrings,
- this.maxNestingDepth
- );
+ ParserConfiguration parserConfiguration = new ParserConfiguration();
+ parserConfiguration.keepStrings = this.keepStrings;
+ parserConfiguration.maxNestingDepth = this.maxNestingDepth;
+ parserConfiguration.maxNumberLength = this.maxNumberLength;
+ return parserConfiguration;
}
/**
* 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)
+ * they should try to be guessed into JSON values (numeric, boolean, string).
*
* @return The keepStrings configuration value.
*/
@@ -69,20 +98,21 @@ public boolean isKeepStrings() {
* 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 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();
+ T newConfig = (T) this.clone();
newConfig.keepStrings = newVal;
return newConfig;
}
/**
* The maximum nesting depth that the parser will descend before throwing an exception
- * when parsing the XML into JSONML.
+ * when parsing an object (e.g. Map, Collection) into JSON-related objects.
+ *
* @return the maximum nesting depth set for this configuration
*/
public int getMaxNestingDepth() {
@@ -91,15 +121,19 @@ public int getMaxNestingDepth() {
/**
* Defines the maximum nesting depth that the parser will descend before throwing an exception
- * when parsing the XML into JSONML. The default max nesting depth is 512, which means the parser
- * will throw a JsonException if the maximum depth is reached.
+ * 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();
+ T newConfig = (T) this.clone();
if (maxNestingDepth > UNDEFINED_MAXIMUM_NESTING_DEPTH) {
newConfig.maxNestingDepth = maxNestingDepth;
@@ -109,4 +143,37 @@ public T withMaxNestingDepth(int maxNestingDepth
return newConfig;
}
+
+
+ /**
+ * The maximum number length that the parser will allow
+ *
+ * @return the maximum number lengtj set for this configuration
+ */
+ public int getMaxNumberLength() {
+ return maxNumberLength;
+ }
+
+ /**
+ * Defines the maximum number length that the parser will allow
+ * Using any negative value as a parameter is equivalent to setting no limit to the length
+ * which means any size number is allowed
+ *
+ * @param maxNumberLength the maximum number length allowed
+ * @param the type of the configuration object
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ @SuppressWarnings("unchecked")
+ public T withMaxNumberLength(int maxNumberLength) {
+ T newConfig = (T) this.clone();
+
+ if (maxNumberLength > UNDEFINED_MAXIMUM_NUMBER_LENGTH) {
+ newConfig.maxNumberLength = maxNumberLength;
+ } else {
+ newConfig.maxNumberLength = UNDEFINED_MAXIMUM_NUMBER_LENGTH;
+ }
+
+ return newConfig;
+ }
+
}
diff --git a/src/main/java/org/json/Property.java b/src/main/java/org/json/Property.java
index 83694c055..ba6c56967 100644
--- a/src/main/java/org/json/Property.java
+++ b/src/main/java/org/json/Property.java
@@ -13,6 +13,13 @@
* @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 925f056b1..f5846cc1e 100644
--- a/src/main/java/org/json/XML.java
+++ b/src/main/java/org/json/XML.java
@@ -9,7 +9,7 @@
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
@@ -21,6 +21,12 @@
@SuppressWarnings("boxing")
public class XML {
+ /**
+ * Constructs a new XML object.
+ */
+ public XML() {
+ }
+
/** The Character '&'. */
public static final Character AMP = '&';
@@ -53,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";
/**
@@ -72,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() {
@@ -81,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;
@@ -146,7 +158,7 @@ public static String escape(String string) {
* @param cp code point to test
* @return true if the code point is not valid for an XML
*/
- private static boolean mustEscape(int cp) {
+ static boolean mustEscape(int cp) {
/* Valid range from https://www.w3.org/TR/REC-xml/#charsets
*
* #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
@@ -160,8 +172,14 @@ private static boolean mustEscape(int cp) {
&& cp != 0xA
&& cp != 0xD
) || !(
- // valid the range of acceptable characters that aren't control
- (cp >= 0x20 && cp <= 0xD7FF)
+ // Valid character range per W3C XML 1.0 spec:
+ // #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
+ // Previously omitted #x9/#xA/#xD, causing unescape("
") etc.
+ // to reject valid LF/TAB/CR as illegal (see #1059)
+ cp == 0x9
+ || cp == 0xA
+ || cp == 0xD
+ || (cp >= 0x20 && cp <= 0xD7FF)
|| (cp >= 0xE000 && cp <= 0xFFFD)
|| (cp >= 0x10000 && cp <= 0x10FFFF)
)
@@ -347,10 +365,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 {
@@ -369,8 +397,13 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
context.append(tagName, JSONObject.NULL);
} else if (jsonObject.length() > 0) {
context.append(tagName, jsonObject);
- } else {
+ } else if(context.isEmpty()) { //avoids resetting the array in case of an empty tag in the middle or end
context.put(tagName, new JSONArray());
+ if (jsonObject.isEmpty()){
+ context.append(tagName, "");
+ }
+ } else {
+ context.append(tagName, "");
}
} else {
if (nilAttributeFound) {
@@ -399,8 +432,23 @@ 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));
+ }
}
}
@@ -414,7 +462,11 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
if (config.getForceList().contains(tagName)) {
// Force the value to be an array
if (jsonObject.length() == 0) {
- context.put(tagName, new JSONArray());
+ //avoids resetting the array in case of an empty element in the middle or end
+ if(context.isEmpty()) {
+ context.put(tagName, new JSONArray());
+ }
+ context.append(tagName, "");
} else if (jsonObject.length() == 1
&& jsonObject.opt(config.getcDataTagName()) != null) {
context.append(tagName, jsonObject.opt(config.getcDataTagName()));
@@ -428,6 +480,9 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
&& jsonObject.opt(config.getcDataTagName()) != null) {
context.accumulate(tagName, jsonObject.opt(config.getcDataTagName()));
} else {
+ if (!config.shouldTrimWhiteSpace()) {
+ removeEmpty(jsonObject, config);
+ }
context.accumulate(tagName, jsonObject);
}
}
@@ -442,58 +497,46 @@ 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)}.
+ * Warning! stringToValue(String) uses the default max number length. If you want to override it,
+ * use a suitable initialized XMLParserConfiguration and the method: stringToValue(String, XMLParserConfiguration).
+ * @param str 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 str) {
+ return stringToValue(str, new XMLParserConfiguration());
+ }
+
+ /**
+ * This method is the same as {@link JSONObject#stringToValue(String)}.
+ *
+ * @param string String to convert
+ * @param xmlParserConfiguration the XML parser config object
+ * @return JSON value of this string or the string. If the string represents a number that is too large,
+ * a string will be returned.
+ */
+ // 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, XMLParserConfiguration xmlParserConfiguration) {
+ 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 {
+ // user declines max number checking
+ if (xmlParserConfiguration.getMaxNumberLength() == ParserConfiguration.UNDEFINED_MAXIMUM_NUMBER_LENGTH) {
+ return stringToNumber(string);
+ }
+ if(string.length() <= xmlParserConfiguration.getMaxNumberLength()) {
+ return stringToNumber(string);
+ }
+ } catch (Exception ignore) {
+ }
+ }
+ return string;
+ }
/**
* Convert a well-formed (but not necessarily valid) XML string into a
@@ -637,6 +754,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
@@ -659,7 +814,7 @@ 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()) {
@@ -695,6 +850,38 @@ public static JSONObject toJSONObject(String string, boolean keepStrings) throws
return toJSONObject(new StringReader(string), keepStrings);
}
+ /**
+ * Convert a well-formed (but not necessarily valid) XML string into a
+ * JSONObject. Some information may be lost in this transformation because
+ * JSON is a data format and XML is a document format. XML uses elements,
+ * attributes, and content text, while JSON uses unordered collections of
+ * name/value pairs and arrays of values. JSON does not does not like to
+ * distinguish between elements and attributes. Sequences of similar
+ * elements are represented as JSONArrays. Content text may be placed in a
+ * "content" member. Comments, prologs, DTDs, and {@code
+ * <[ [ ]]>}
+ * are ignored.
+ *
+ * All 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
@@ -850,12 +1037,25 @@ private static String toString(final Object object, final String tagName, final
}
}
} else if ("".equals(value)) {
- sb.append(indent(indent));
- sb.append('<');
- sb.append(key);
- sb.append("/>");
- if(indentFactor > 0){
- sb.append("\n");
+ 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
@@ -899,14 +1099,14 @@ private static String toString(final Object object, final String tagName, final
string = (object == null) ? "null" : escape(object.toString());
-
+ String indentationSuffix = (indentFactor > 0) ? "\n" : "";
if(tagName == null){
- return indent(indent) + "\"" + string + "\"" + ((indentFactor > 0) ? "\n" : "");
+ return indent(indent) + "\"" + string + "\"" + indentationSuffix;
} else if(string.length() == 0){
- return indent(indent) + "<" + tagName + "/>" + ((indentFactor > 0) ? "\n" : "");
+ return indent(indent) + "<" + tagName + "/>" + indentationSuffix;
} else {
return indent(indent) + "<" + tagName
- + ">" + string + "" + tagName + ">" + ((indentFactor > 0) ? "\n" : "");
+ + ">" + string + "" + tagName + ">" + indentationSuffix;
}
}
diff --git a/src/main/java/org/json/XMLParserConfiguration.java b/src/main/java/org/json/XMLParserConfiguration.java
index 566146d6d..de84b90cb 100644
--- a/src/main/java/org/json/XMLParserConfiguration.java
+++ b/src/main/java/org/json/XMLParserConfiguration.java
@@ -22,6 +22,16 @@ public class XMLParserConfiguration extends ParserConfiguration {
*/
// 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();
@@ -43,6 +53,13 @@ public class XMLParserConfiguration extends ParserConfiguration {
*/
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
*/
@@ -54,9 +71,18 @@ public class XMLParserConfiguration extends ParserConfiguration {
*/
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 () {
super();
@@ -64,6 +90,7 @@ public XMLParserConfiguration () {
this.convertNilAttributeToNull = false;
this.xsiTypeMap = Collections.emptyMap();
this.forceList = Collections.emptySet();
+ this.shouldTrimWhiteSpace = true;
}
/**
@@ -125,7 +152,9 @@ public XMLParserConfiguration (final boolean keepStrings, final String cDataTagN
*/
@Deprecated
public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, final boolean convertNilAttributeToNull) {
- super(keepStrings, DEFAULT_MAXIMUM_NESTING_DEPTH);
+ super(false, DEFAULT_MAXIMUM_NESTING_DEPTH);
+ this.keepNumberAsString = keepStrings;
+ this.keepBooleanAsString = keepStrings;
this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = convertNilAttributeToNull;
}
@@ -142,15 +171,19 @@ public XMLParserConfiguration (final boolean keepStrings, final String cDataTagN
* xsi:type="integer" as integer, xsi:type="string" as string
* @param forceList new HashSet() to parse the provided tags' values as arrays
* @param maxNestingDepth int to limit the nesting depth
+ * @param closeEmptyTag boolean to turn on explicit end tag for tag with empty value
*/
private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName,
final boolean convertNilAttributeToNull, final Map> xsiTypeMap, final Set forceList,
- final int maxNestingDepth) {
- super(keepStrings, maxNestingDepth);
+ 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;
}
/**
@@ -163,14 +196,19 @@ 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.forceList,
- this.maxNestingDepth
+ this.maxNestingDepth,
+ this.closeEmptyTag,
+ this.keepNumberAsString,
+ this.keepBooleanAsString
);
+ config.shouldTrimWhiteSpace = this.shouldTrimWhiteSpace;
+ return config;
}
/**
@@ -182,9 +220,46 @@ protected XMLParserConfiguration clone() {
*
* @return The existing configuration will not be modified. A new configuration is returned.
*/
+ @SuppressWarnings("unchecked")
@Override
public XMLParserConfiguration withKeepStrings(final boolean newVal) {
- return super.withKeepStrings(newVal);
+ XMLParserConfiguration newConfig = this.clone();
+ newConfig.keepStrings = newVal;
+ newConfig.keepNumberAsString = newVal;
+ newConfig.keepBooleanAsString = newVal;
+ return newConfig;
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies if numbers should be kept as strings (1), or if
+ * they should try to be guessed into JSON values (numeric, boolean, string)
+ *
+ * @param newVal
+ * new value to use for the keepNumberAsString configuration option.
+ *
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public XMLParserConfiguration withKeepNumberAsString(final boolean newVal) {
+ XMLParserConfiguration newConfig = this.clone();
+ newConfig.keepNumberAsString = newVal;
+ newConfig.keepStrings = newConfig.keepBooleanAsString && newConfig.keepNumberAsString;
+ return newConfig;
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies if booleans should be kept as strings (true), or if
+ * they should try to be guessed into JSON values (numeric, boolean, string)
+ *
+ * @param newVal
+ * new value to use for the withKeepBooleanAsString configuration option.
+ *
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public XMLParserConfiguration withKeepBooleanAsString(final boolean newVal) {
+ XMLParserConfiguration newConfig = this.clone();
+ newConfig.keepBooleanAsString = newVal;
+ newConfig.keepStrings = newConfig.keepBooleanAsString && newConfig.keepNumberAsString;
+ return newConfig;
}
/**
@@ -198,6 +273,26 @@ 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
@@ -299,8 +394,51 @@ public XMLParserConfiguration withForceList(final Set forceList) {
* @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 957498ca2..dc90f84d4 100644
--- a/src/main/java/org/json/XMLTokener.java
+++ b/src/main/java/org/json/XMLTokener.java
@@ -20,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);
@@ -45,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 ]]>.
@@ -83,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;
}
@@ -97,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));
@@ -137,33 +151,111 @@ public Object nextEntity(@SuppressWarnings("unused") char ampersand) throws JSON
/**
* Unescape an XML entity encoding;
* @param e entity (only the actual entity value, not the preceding & or ending ;
- * @return
+ * @return the unescaped entity string
+ * @throws JSONException if the entity is malformed
*/
- static String unescapeEntity(String e) {
+ static String unescapeEntity(String e) throws JSONException {
// validate
if (e == null || e.isEmpty()) {
return "";
}
// if our entity is an encoded unicode point, parse it.
if (e.charAt(0) == '#') {
- int cp;
- if (e.charAt(1) == 'x' || e.charAt(1) == 'X') {
- // hex encoded unicode
- cp = Integer.parseInt(e.substring(2), 16);
- } else {
- // decimal encoded unicode
- cp = Integer.parseInt(e.substring(1));
+ if (e.length() < 2) {
+ throw new JSONException("Invalid numeric character reference: ");
}
- return new String(new int[] {cp},0,1);
- }
+ int cp = (e.charAt(1) == 'x' || e.charAt(1) == 'X')
+ ? parseHexEntity(e)
+ : parseDecimalEntity(e);
+ if (XML.mustEscape(cp)) {
+ throw new JSONException("Invalid numeric character reference: " + e.substring(1) + ";");
+ }
+ return new String(new int[] {cp}, 0, 1);
+ }
Character knownEntity = entity.get(e);
- if(knownEntity==null) {
+ if (knownEntity == null) {
// we don't know the entity so keep it encoded
return '&' + e + ';';
}
return knownEntity.toString();
}
+ /**
+ * Parse a hexadecimal numeric character reference (e.g., "઼").
+ * @param e entity string starting with '#' (e.g., "#x1F4A9")
+ * @return the Unicode code point
+ * @throws JSONException if the format is invalid
+ */
+ private static int parseHexEntity(String e) throws JSONException {
+ // hex encoded unicode - need at least one hex digit after #x
+ if (e.length() < 3) {
+ throw new JSONException("Invalid hex character reference: missing hex digits in " + e.substring(1) + ";");
+ }
+ String hex = e.substring(2);
+ if (!isValidHex(hex)) {
+ throw new JSONException("Invalid hex character reference: " + e.substring(1) + ";");
+ }
+ try {
+ return Integer.parseInt(hex, 16);
+ } catch (NumberFormatException nfe) {
+ throw new JSONException("Invalid hex character reference: " + e.substring(1) + ";", nfe);
+ }
+ }
+
+ /**
+ * Parse a decimal numeric character reference (e.g., "{").
+ * @param e entity string starting with '#' (e.g., "#123")
+ * @return the Unicode code point
+ * @throws JSONException if the format is invalid
+ */
+ private static int parseDecimalEntity(String e) throws JSONException {
+ String decimal = e.substring(1);
+ if (!isValidDecimal(decimal)) {
+ throw new JSONException("Invalid decimal character reference: " + decimal + ";");
+ }
+ try {
+ return Integer.parseInt(decimal);
+ } catch (NumberFormatException nfe) {
+ throw new JSONException("Invalid decimal character reference: " + decimal + ";", nfe);
+ }
+ }
+
+ /**
+ * Check if a string contains only valid hexadecimal digits.
+ * @param s the string to check
+ * @return true if s is non-empty and contains only hex digits (0-9, a-f, A-F)
+ */
+ private static boolean isValidHex(String s) {
+ if (s == null || s.isEmpty()) {
+ return false;
+ }
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Check if a string contains only valid decimal digits.
+ * @param s the string to check
+ * @return true if s is non-empty and contains only digits (0-9)
+ */
+ private static boolean isValidDecimal(String s) {
+ if (s == null || s.isEmpty()) {
+ return false;
+ }
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c < '0' || c > '9') {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* {@code
diff --git a/src/main/java/org/json/XMLXsiTypeConverter.java b/src/main/java/org/json/XMLXsiTypeConverter.java
index 0011effae..ea6739d34 100644
--- a/src/main/java/org/json/XMLXsiTypeConverter.java
+++ b/src/main/java/org/json/XMLXsiTypeConverter.java
@@ -42,5 +42,12 @@
* @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 f3364fbba..e5eb9eda8 100644
--- a/src/test/java/org/json/junit/CDLTest.java
+++ b/src/test/java/org/json/junit/CDLTest.java
@@ -24,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
@@ -39,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.
@@ -127,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.
*/
@@ -194,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);
}
/**
@@ -254,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\""));
@@ -268,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);
}
@@ -279,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);
}
@@ -293,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/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 aa8657f06..c54b61795 100644
--- a/src/test/java/org/json/junit/JSONArrayTest.java
+++ b/src/test/java/org/json/junit/JSONArrayTest.java
@@ -8,6 +8,7 @@
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;
@@ -28,10 +29,13 @@
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;
@@ -118,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));
@@ -224,6 +228,19 @@ public void verifyConstructor() {
Util.checkJSONArrayMaps(jaRaw);
Util.checkJSONArrayMaps(jaInt);
}
+
+ @Test
+ public void jsonArrayByListWithNestedNullValue() {
+ List> list = new ArrayList>();
+ Map sub = new HashMap();
+ sub.put("nullKey", null);
+ list.add(sub);
+ JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withUseNativeNulls(true);
+ JSONArray jsonArray = new JSONArray(list, parserConfiguration);
+ JSONObject subObject = jsonArray.getJSONObject(0);
+ assertTrue(subObject.has("nullKey"));
+ assertEquals(JSONObject.NULL, subObject.get("nullKey"));
+ }
/**
* Tests consecutive calls to putAll with array and collection.
@@ -256,6 +273,11 @@ 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),
@@ -368,16 +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",
- new Float(23.45e-4f).equals(jsonArray.getFloat(5)));
+ 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);
@@ -385,9 +407,9 @@ 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);
@@ -460,6 +482,30 @@ public void failedGetArrayValues() {
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());
+ }
+ }
+
/**
* Exercise JSONArray.join() by converting a JSONArray into a
* comma-separated string. Since this is very nearly a JSON document,
@@ -537,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));
@@ -582,6 +660,13 @@ 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",
@@ -599,10 +684,15 @@ 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);
Util.checkJSONArrayMaps(ja);
@@ -624,8 +714,8 @@ public void put() {
String jsonArrayStr =
"["+
- "hello,"+
- "world"+
+ "\"hello\","+
+ "\"world\""+
"]";
// 2
jsonArray.put(new JSONArray(jsonArrayStr));
@@ -702,8 +792,8 @@ public void putIndex() {
String jsonArrayStr =
"["+
- "hello,"+
- "world"+
+ "\"hello\","+
+ "\"world\""+
"]";
// 2
jsonArray.put(2, new JSONArray(jsonArrayStr));
@@ -971,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);
@@ -985,9 +1075,9 @@ 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
@@ -1326,14 +1416,15 @@ public void jsonArrayClearMethodTest() {
/**
* 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("Issue654WellFormedArray.json");
+ 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.");
+ fail("Excepected Exception due to stack overflow.");
Util.checkJSONArrayMaps(json_input);
}
@@ -1357,4 +1448,122 @@ public String toJSONString() {
.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
+ /**
+ * This test was originally for 1000 levels, which passes in test builds, but fails on my laptop.
+ * The current value of 900 seems to work.
+ */
+ public void testRecursiveDepthArrayFor900Levels() {
+ try {
+ ArrayList array = buildNestedArray(900);
+ JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withMaxNestingDepth(900);
+ new JSONArray(array, parserConfiguration);
+ } catch (StackOverflowError e) {
+ String javaVersion = System.getProperty("java.version");
+ if (javaVersion.startsWith("11.")) {
+ System.out.println(
+ "testRecursiveDepthArrayFor900Levels() allowing intermittent stackoverflow, Java Version: "
+ + javaVersion);
+ } else {
+ String errorStr = "testRecursiveDepthArrayFor900Levels() 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;
+ }
+
+ @Test
+ public void TestLenientCommas() {
+ String str = "[1,,3]";
+
+ // Test should fail if default strictMode is true, pass with an extra null entry if false
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+ if (jsonParserConfiguration.isStrictMode()) {
+ try {
+ new JSONArray(str);
+ fail("Expected to throw exception due to invalid string");
+ } catch (JSONException e) { /* no action is needed here */ }
+ } else {
+ JSONArray jsonArray = new JSONArray(str);
+ assertEquals("JSONArray in non-strictMode should contain a null entry",
+ "[1,null,3]", jsonArray.toString());
+ }
+ }
}
diff --git a/src/test/java/org/json/junit/JSONMLTest.java b/src/test/java/org/json/junit/JSONMLTest.java
index 35c0af2c4..93a6821d8 100644
--- a/src/test/java/org/json/junit/JSONMLTest.java
+++ b/src/test/java/org/json/junit/JSONMLTest.java
@@ -625,7 +625,7 @@ public void toJSONObjectToJSONArray() {
"\"subValue\","+
"{\"svAttr\":\"svValue\"},"+
"\"abc\""+
- "],"+
+ "]"+
"],"+
"[\"value\",3],"+
"[\"value\",4.1],"+
@@ -762,8 +762,8 @@ 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:
@@ -986,4 +986,70 @@ public void testToJSONObjectMaxNestingDepthWithValidFittingXML() {
}
}
+ /**
+ * Tests that malformed XML causing type mismatch throws JSONException.
+ * Previously threw ClassCastException when parse() returned String instead of JSONArray.
+ * Related to issue #1034
+ */
+ @Test(expected = JSONException.class)
+ public void testMalformedXMLThrowsJSONExceptionNotClassCast() {
+ // This malformed XML causes parse() to return wrong type
+ byte[] data = {0x3c, 0x0a, 0x2f, (byte)0xff, (byte)0xff, (byte)0xff,
+ (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
+ (byte)0xff, 0x3e, 0x42};
+ String xmlStr = new String(data);
+ JSONML.toJSONArray(xmlStr);
+ }
+
+ /**
+ * Tests that type mismatch in toJSONObject throws JSONException.
+ * Validates safe type casting in toJSONObject methods.
+ */
+ @Test
+ public void testToJSONObjectTypeMismatch() {
+ // Create XML that would cause parse() to return wrong type
+ String xmlStr = "<\n/\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff>B";
+ try {
+ JSONML.toJSONObject(xmlStr);
+ fail("Expected JSONException for type mismatch");
+ } catch (ClassCastException e) {
+ fail("Should throw JSONException, not ClassCastException");
+ } catch (JSONException e) {
+ // Expected - verify it's about type mismatch
+ assertTrue("Exception message should mention type error",
+ e.getMessage().contains("Expected") || e.getMessage().contains("got"));
+ }
+ }
+
+ /**
+ * Tests that valid XML still works correctly after the fix.
+ * Ensures the type checking doesn't break normal operation.
+ */
+ @Test
+ public void testValidXMLStillWorks() {
+ String xmlStr = "- value
";
+ try {
+ JSONArray jsonArray = JSONML.toJSONArray(xmlStr);
+ assertNotNull("JSONArray should not be null", jsonArray);
+ assertEquals("root", jsonArray.getString(0));
+ } catch (Exception e) {
+ fail("Valid XML should not throw exception: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Tests that valid XML to JSONObject still works correctly.
+ */
+ @Test
+ public void testValidXMLToJSONObjectStillWorks() {
+ String xmlStr = "- content
";
+ try {
+ JSONObject jsonObject = JSONML.toJSONObject(xmlStr);
+ assertNotNull("JSONObject should not be null", jsonObject);
+ assertEquals("root", jsonObject.getString("tagName"));
+ } catch (Exception e) {
+ fail("Valid XML should not throw exception: " + e.getMessage());
+ }
+ }
+
}
diff --git a/src/test/java/org/json/junit/JSONObjectLocaleTest.java b/src/test/java/org/json/junit/JSONObjectLocaleTest.java
index 1cdaf743d..e1a9dd64e 100755
--- a/src/test/java/org/json/junit/JSONObjectLocaleTest.java
+++ b/src/test/java/org/json/junit/JSONObjectLocaleTest.java
@@ -36,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"));
-
- /**
- * 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"));
+ // 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"));
+ } finally {
+ Locale.setDefault(defaultLocale);
+ }
}
}
diff --git a/src/test/java/org/json/junit/JSONObjectNumberTest.java b/src/test/java/org/json/junit/JSONObjectNumberTest.java
index f6e13c63d..0f2af2902 100644
--- a/src/test/java/org/json/junit/JSONObjectNumberTest.java
+++ b/src/test/java/org/json/junit/JSONObjectNumberTest.java
@@ -23,18 +23,18 @@ public class JSONObjectNumberTest {
@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}
+ {"{\"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
@@ -109,18 +109,38 @@ 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 ea0cec39c..6b692789e 100644
--- a/src/test/java/org/json/junit/JSONObjectTest.java
+++ b/src/test/java/org/json/junit/JSONObjectTest.java
@@ -9,6 +9,7 @@
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;
@@ -30,8 +31,10 @@
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;
@@ -53,3460 +56,4285 @@
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.junit.data.CustomClassJ;
+import org.junit.After;
+import org.junit.Ignore;
import org.junit.Test;
-import org.json.junit.Util;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
/**
- * JSONObject, along with JSONArray, are the central classes of the reference app.
- * All of the other classes interact with them, and JSON functionality would
- * otherwise be impossible.
+ * JSONObject, along with JSONArray, are the central classes of the reference
+ * app. All of the other classes interact with them, and JSON functionality
+ * would otherwise be impossible.
*/
public class JSONObjectTest {
- /**
- * Regular Expression Pattern that matches JSON Numbers. This is primarily used for
- * output to guarantee that we are always writing valid JSON.
- */
- static final Pattern NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?");
-
- /**
- * 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)
- .put("key3", string1);
-
- JSONObject obj2 = new JSONObject()
- .put("key1", "abc")
- .put("key2", 3)
- .put("key3", string1);
-
- JSONObject obj3 = new JSONObject()
- .put("key1", "abc")
- .put("key2", 2)
- .put("key3", new String(string1));
-
- 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
- final String[] testData = new String[] {
- null,
- "",
- "100",
- "-100",
- "abc123",
- "012345",
- "100.5e199",
- "-100.5e199",
- "DEADBEEF",
- "0xDEADBEEF",
- "1234567890.1234567890",
- "-1234567890.1234567890",
- "adloghakuidghauiehgauioehgdkjfb nsruoh aeu noerty384 nkljfgh "
- + "395h tdfn kdz8yt3 4hkls gn.ey85 4hzfhnz.o8y5a84 onvklt "
- + "yh389thub nkz8y49lihv al4itlaithknty8hnbl"
- // long (in length) number sequences with invalid data at the end of the
- // string offer very poor performance for the REGEX.
- ,"123467890123467890123467890123467890123467890123467890123467"
- + "8901234678901234678901234678901234678901234678901234678"
- + "9012346789012346789012346789012346789012346789012346789"
- + "0a"
- };
- final int testDataLength = testData.length;
- /**
- * Changed to 1000 for faster test runs
- */
- // final int iterations = 1000000;
- final int iterations = 1000;
-
- // 10 million iterations 1,000,000 * 10 (currently 100,000)
- long startTime = System.nanoTime();
- for(int i = 0; i < iterations; i++) {
- for(int j = 0; j < testDataLength; j++) {
- try {
- BigDecimal v1 = new BigDecimal(testData[j]);
- v1.signum();
- } catch(Exception ignore) {
- //do nothing
- }
- }
- }
- final long elapsedNano1 = System.nanoTime() - startTime;
- System.out.println("new BigDecimal(testData[]) : " + elapsedNano1 / 1000000 + " ms");
-
- startTime = System.nanoTime();
- for(int i = 0; i < iterations; i++) {
- for(int j = 0; j < testDataLength; j++) {
- try {
- boolean v2 = NUMBER_PATTERN.matcher(testData[j]).matches();
- assert v2 == !!v2;
- } catch(Exception ignore) {
- //do nothing
- }
- }
- }
- final long elapsedNano2 = System.nanoTime() - startTime;
- System.out.println("NUMBER_PATTERN.matcher(testData[]).matches() : " + elapsedNano2 / 1000000 + " ms");
- // don't assert normally as the testing is machine dependent.
- // assertTrue("Expected Pattern matching to be faster than BigDecimal constructor",elapsedNano2)(JsonPath.read(doc, "$"))).size() == 4);
- assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObjectByName.query("/falseKey")));
- assertTrue("expected \"nullKey\":null", JSONObject.NULL.equals(jsonObjectByName.query("/nullKey")));
- assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObjectByName.query("/stringKey")));
- assertTrue("expected \"doubleKey\":-23.45e67", new BigDecimal("-23.45e67").equals(jsonObjectByName.query("/doubleKey")));
- Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(jsonObject, jsonObjectByName)));
- }
-
- /**
- * JSONObjects can be built from a Map.
- * In this test the map is null.
- * the JSONObject(JsonTokener) ctor is not tested directly since it already
- * has full coverage from other tests.
- */
- @Test
- public void jsonObjectByNullMap() {
- Map map = null;
- JSONObject jsonObject = new JSONObject(map);
- assertTrue("jsonObject should be empty", jsonObject.isEmpty());
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * JSONObjects can be built from a Map.
- * In this test all of the map entries are valid JSON types.
- */
- @Test
- public void jsonObjectByMap() {
- Map map = new HashMap();
- map.put("trueKey", new Boolean(true));
- map.put("falseKey", new Boolean(false));
- map.put("stringKey", "hello world!");
- map.put("escapeStringKey", "h\be\tllo w\u1234orld!");
- map.put("intKey", new Long(42));
- map.put("doubleKey", new Double(-23.45e67));
- JSONObject jsonObject = new JSONObject(map);
-
- // validate JSON
- Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
- assertTrue("expected 6 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 6);
- assertTrue("expected \"trueKey\":true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
- assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
- assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObject.query("/stringKey")));
- assertTrue("expected \"escapeStringKey\":\"h\be\tllo w\u1234orld!\"", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey")));
- assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey")));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Verifies that the constructor has backwards compatability with RAW types pre-java5.
- */
- @Test
- public void verifyConstructor() {
-
- final JSONObject expected = new JSONObject("{\"myKey\":10}");
-
- @SuppressWarnings("rawtypes")
- Map myRawC = Collections.singletonMap("myKey", Integer.valueOf(10));
- JSONObject jaRaw = new JSONObject(myRawC);
-
- Map myCStrObj = Collections.singletonMap("myKey",
- (Object) Integer.valueOf(10));
- JSONObject jaStrObj = new JSONObject(myCStrObj);
-
- Map myCStrInt = Collections.singletonMap("myKey",
- Integer.valueOf(10));
- JSONObject jaStrInt = new JSONObject(myCStrInt);
-
- Map, ?> myCObjObj = Collections.singletonMap((Object) "myKey",
- (Object) Integer.valueOf(10));
- JSONObject jaObjObj = new JSONObject(myCObjObj);
-
- assertTrue(
- "The RAW Collection should give me the same as the Typed Collection",
- expected.similar(jaRaw));
- assertTrue(
- "The RAW Collection should give me the same as the Typed Collection",
- expected.similar(jaStrObj));
- assertTrue(
- "The RAW Collection should give me the same as the Typed Collection",
- expected.similar(jaStrInt));
- assertTrue(
- "The RAW Collection should give me the same as the Typed Collection",
- expected.similar(jaObjObj));
- Util.checkJSONObjectsMaps(new ArrayList(
- Arrays.asList(jaRaw, jaStrObj, jaStrInt, jaObjObj))
- );
- }
-
- /**
- * Tests Number serialization.
- */
- @Test
- public void verifyNumberOutput(){
- /**
- * MyNumberContainer is a POJO, so call JSONObject(bean),
- * which builds a map of getter names/values
- * The only getter is getMyNumber (key=myNumber),
- * whose return value is MyNumber. MyNumber extends Number,
- * but is not recognized as such by wrap() per current
- * implementation, so wrap() returns the default new JSONObject(bean).
- * The only getter is getNumber (key=number), whose return value is
- * BigDecimal(42).
- */
- JSONObject jsonObject0 = new JSONObject(new MyNumberContainer());
- String actual = jsonObject0.toString();
- String expected = "{\"myNumber\":{\"number\":42}}";
- assertEquals("Equal", expected , actual);
-
- /**
- * JSONObject.put() handles objects differently than the
- * bean constructor. Where the bean ctor wraps objects before
- * placing them in the map, put() inserts the object without wrapping.
- * In this case, a MyNumber instance is the value.
- * The MyNumber.toString() method is responsible for
- * returning a reasonable value: the string '42'.
- */
- JSONObject jsonObject1 = new JSONObject();
- jsonObject1.put("myNumber", new MyNumber());
- actual = jsonObject1.toString();
- expected = "{\"myNumber\":42}";
- assertEquals("Equal", expected , actual);
-
- /**
- * Calls the JSONObject(Map) ctor, which calls wrap() for values.
- * AtomicInteger is a Number, but is not recognized by wrap(), per
- * current implementation. However, the type is
- * 'java.util.concurrent.atomic', so due to the 'java' prefix,
- * wrap() inserts the value as a string. That is why 42 comes back
- * wrapped in quotes.
- */
- JSONObject jsonObject2 = new JSONObject(Collections.singletonMap("myNumber", new AtomicInteger(42)));
- actual = jsonObject2.toString();
- expected = "{\"myNumber\":\"42\"}";
- assertEquals("Equal", expected , actual);
-
- /**
- * JSONObject.put() inserts the AtomicInteger directly into the
- * map not calling wrap(). In toString()->write()->writeValue(),
- * AtomicInteger is recognized as a Number, and converted via
- * numberToString() into the unquoted string '42'.
- */
- JSONObject jsonObject3 = new JSONObject();
- jsonObject3.put("myNumber", new AtomicInteger(42));
- actual = jsonObject3.toString();
- expected = "{\"myNumber\":42}";
- assertEquals("Equal", expected , actual);
-
- /**
- * Calls the JSONObject(Map) ctor, which calls wrap() for values.
- * Fraction is a Number, but is not recognized by wrap(), per
- * current implementation. As a POJO, Fraction is handled as a
- * bean and inserted into a contained JSONObject. It has 2 getters,
- * for numerator and denominator.
- */
- JSONObject 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
- * map not calling wrap(). In toString()->write()->writeValue(),
- * Fraction is recognized as a Number, and converted via
- * numberToString() into the unquoted string '4/2'. But the
- * BigDecimal sanity check fails, so writeValue() defaults
- * to returning a safe JSON quoted string. Pretty slick!
- */
- JSONObject 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
- )));
- }
-
- /**
- * Verifies that the put Collection has backwards compatibility with RAW types pre-java5.
- */
- @Test
- public void verifyPutCollection() {
-
- final JSONObject expected = new JSONObject("{\"myCollection\":[10]}");
-
- @SuppressWarnings("rawtypes")
- Collection myRawC = Collections.singleton(Integer.valueOf(10));
- JSONObject jaRaw = new JSONObject();
- jaRaw.put("myCollection", myRawC);
-
- Collection myCObj = Collections.singleton((Object) Integer
- .valueOf(10));
- JSONObject jaObj = new JSONObject();
- jaObj.put("myCollection", myCObj);
-
- Collection myCInt = Collections.singleton(Integer
- .valueOf(10));
- JSONObject jaInt = new JSONObject();
- jaInt.put("myCollection", myCInt);
-
- assertTrue(
- "The RAW Collection should give me the same as the Typed Collection",
- expected.similar(jaRaw));
- assertTrue(
- "The RAW Collection should give me the same as the Typed Collection",
- expected.similar(jaObj));
- assertTrue(
- "The RAW Collection should give me the same as the Typed Collection",
- expected.similar(jaInt));
-
- Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
- jaRaw, jaObj, jaInt
- )));
- }
-
-
- /**
- * Verifies that the put Map has backwards compatibility with RAW types pre-java5.
- */
- @Test
- public void verifyPutMap() {
-
- final JSONObject expected = new JSONObject("{\"myMap\":{\"myKey\":10}}");
-
- @SuppressWarnings("rawtypes")
- Map myRawC = Collections.singletonMap("myKey", Integer.valueOf(10));
- JSONObject jaRaw = new JSONObject();
- jaRaw.put("myMap", myRawC);
-
- Map myCStrObj = Collections.singletonMap("myKey",
- (Object) Integer.valueOf(10));
- JSONObject jaStrObj = new JSONObject();
- jaStrObj.put("myMap", myCStrObj);
-
- Map myCStrInt = Collections.singletonMap("myKey",
- Integer.valueOf(10));
- JSONObject jaStrInt = new JSONObject();
- jaStrInt.put("myMap", myCStrInt);
-
- Map, ?> myCObjObj = Collections.singletonMap((Object) "myKey",
- (Object) Integer.valueOf(10));
- JSONObject jaObjObj = new JSONObject();
- jaObjObj.put("myMap", myCObjObj);
-
- assertTrue(
- "The RAW Collection should give me the same as the Typed Collection",
- expected.similar(jaRaw));
- assertTrue(
- "The RAW Collection should give me the same as the Typed Collection",
- expected.similar(jaStrObj));
- assertTrue(
- "The RAW Collection should give me the same as the Typed Collection",
- expected.similar(jaStrInt));
- assertTrue(
- "The RAW Collection should give me the same as the Typed Collection",
- expected.similar(jaObjObj));
-
- Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
- jaRaw, jaStrObj, jaStrInt, jaStrObj
- )));
- }
-
-
- /**
- * JSONObjects can be built from a Map.
- * In this test the map entries are not valid JSON types.
- * The actual conversion is kind of interesting.
- */
- @Test
- public void jsonObjectByMapWithUnsupportedValues() {
- Map jsonMap = new HashMap();
- // Just insert some random objects
- jsonMap.put("key1", new CDL());
- jsonMap.put("key2", new Exception());
-
- JSONObject jsonObject = new JSONObject(jsonMap);
-
- // validate JSON
- Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
- assertTrue("expected 2 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 2);
- assertTrue("expected 0 key1 items", ((Map,?>)(JsonPath.read(doc, "$.key1"))).size() == 0);
- assertTrue("expected \"key2\":java.lang.Exception","java.lang.Exception".equals(jsonObject.query("/key2")));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * JSONObjects can be built from a Map.
- * In this test one of the map values is null
- */
- @Test
- public void jsonObjectByMapWithNullValue() {
- Map map = new HashMap();
- map.put("trueKey", new Boolean(true));
- map.put("falseKey", new Boolean(false));
- map.put("stringKey", "hello world!");
- map.put("nullKey", null);
- map.put("escapeStringKey", "h\be\tllo w\u1234orld!");
- map.put("intKey", new Long(42));
- map.put("doubleKey", new Double(-23.45e67));
- JSONObject jsonObject = new JSONObject(map);
-
- // validate JSON
- Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
- assertTrue("expected 6 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 6);
- assertTrue("expected \"trueKey\":true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
- assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
- assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObject.query("/stringKey")));
- assertTrue("expected \"escapeStringKey\":\"h\be\tllo w\u1234orld!\"", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey")));
- assertTrue("expected \"intKey\":42", Long.valueOf("42").equals(jsonObject.query("/intKey")));
- assertTrue("expected \"doubleKey\":-23.45e67", Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey")));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * JSONObject built from a bean. In this case all but one of the
- * bean getters return valid JSON types
- */
- @SuppressWarnings("boxing")
- @Test
- public void jsonObjectByBean1() {
- /**
- * Default access classes have to be mocked since JSONObject, which is
- * not in the same package, cannot call MyBean methods by reflection.
- */
- MyBean myBean = mock(MyBean.class);
- when(myBean.getDoubleKey()).thenReturn(-23.45e7);
- when(myBean.getIntKey()).thenReturn(42);
- when(myBean.getStringKey()).thenReturn("hello world!");
- when(myBean.getEscapeStringKey()).thenReturn("h\be\tllo w\u1234orld!");
- when(myBean.isTrueKey()).thenReturn(true);
- when(myBean.isFalseKey()).thenReturn(false);
- when(myBean.getStringReaderKey()).thenReturn(
- new StringReader("") {
- });
-
- JSONObject jsonObject = new JSONObject(myBean);
-
- // validate JSON
- Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
- assertTrue("expected 8 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 8);
- assertTrue("expected 0 items in stringReaderKey", ((Map, ?>) (JsonPath.read(doc, "$.stringReaderKey"))).size() == 0);
- assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
- assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
- assertTrue("expected hello world!","hello world!".equals(jsonObject.query("/stringKey")));
- assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey")));
- assertTrue("expected 42", Integer.valueOf("42").equals(jsonObject.query("/intKey")));
- assertTrue("expected -23.45e7", Double.valueOf("-23.45e7").equals(jsonObject.query("/doubleKey")));
- // sorry, mockito artifact
- assertTrue("expected 2 callbacks items", ((List>)(JsonPath.read(doc, "$.callbacks"))).size() == 2);
- assertTrue("expected 0 handler items", ((Map,?>)(JsonPath.read(doc, "$.callbacks[0].handler"))).size() == 0);
- assertTrue("expected 0 callbacks[1] items", ((Map,?>)(JsonPath.read(doc, "$.callbacks[1]"))).size() == 0);
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * JSONObject built from a bean that has custom field names.
- */
- @Test
- public void jsonObjectByBean2() {
- JSONObject jsonObject = new JSONObject(new MyBeanCustomName());
- assertNotNull(jsonObject);
- assertEquals("Wrong number of keys found:",
- 5,
- jsonObject.keySet().size());
- assertFalse("Normal field name (someString) processing did not work",
- jsonObject.has("someString"));
- assertFalse("Normal field name (myDouble) processing did not work",
- jsonObject.has("myDouble"));
- assertFalse("Normal field name (someFloat) processing did not work",
- jsonObject.has("someFloat"));
- assertFalse("Ignored field not found!",
- jsonObject.has("ignoredInt"));
- // getSomeInt() has no user-defined annotation
- assertTrue("Normal field name (someInt) should have been found",
- jsonObject.has("someInt"));
- // the user-defined annotation does not replace any value, so someLong should be found
- assertTrue("Normal field name (someLong) should have been found",
- jsonObject.has("someLong"));
- // myStringField replaces someString property name via user-defined annotation
- assertTrue("Overridden String field name (myStringField) should have been found",
- jsonObject.has("myStringField"));
- // weird name replaces myDouble property name via user-defined annotation
- assertTrue("Overridden String field name (Some Weird NAme that Normally Wouldn't be possible!) should have been found",
- jsonObject.has("Some Weird NAme that Normally Wouldn't be possible!"));
- // InterfaceField replaces someFloat property name via user-defined annotation
- assertTrue("Overridden String field name (InterfaceField) should have been found",
- jsonObject.has("InterfaceField"));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * JSONObject built from a bean that has custom field names inherited from a parent class.
- */
- @Test
- public void jsonObjectByBean3() {
- JSONObject jsonObject = new JSONObject(new MyBeanCustomNameSubClass());
- assertNotNull(jsonObject);
- assertEquals("Wrong number of keys found:",
- 7,
- jsonObject.keySet().size());
- assertFalse("Normal int field name (someInt) found, but was overridden",
- jsonObject.has("someInt"));
- assertFalse("Normal field name (myDouble) processing did not work",
- jsonObject.has("myDouble"));
- // myDouble was replaced by weird name, and then replaced again by AMoreNormalName via user-defined annotation
- assertFalse("Overridden String field name (Some Weird NAme that Normally Wouldn't be possible!) should not be FOUND!",
- jsonObject.has("Some Weird NAme that Normally Wouldn't be possible!"));
- assertFalse("Normal field name (someFloat) found, but was overridden",
- jsonObject.has("someFloat"));
- assertFalse("Ignored field found! but was overridden",
- jsonObject.has("ignoredInt"));
- // shouldNotBeJSON property name was first ignored, then replaced by ShouldBeIgnored via user-defined annotations
- assertFalse("Ignored field at the same level as forced name should not have been found",
- jsonObject.has("ShouldBeIgnored"));
- // able property name was replaced by Getable via user-defined annotation
- assertFalse("Normally ignored field (able) with explicit property name should not have been found",
- jsonObject.has("able"));
- // property name someInt was replaced by newIntFieldName via user-defined annotation
- assertTrue("Overridden int field name (newIntFieldName) should have been found",
- jsonObject.has("newIntFieldName"));
- // property name someLong was not replaced via user-defined annotation
- assertTrue("Normal field name (someLong) should have been found",
- jsonObject.has("someLong"));
- // property name someString was replaced by myStringField via user-defined annotation
- assertTrue("Overridden String field name (myStringField) should have been found",
- jsonObject.has("myStringField"));
- // property name myDouble was replaced by a weird name, followed by AMoreNormalName via user-defined annotations
- assertTrue("Overridden double field name (AMoreNormalName) should have been found",
- jsonObject.has("AMoreNormalName"));
- // property name someFloat was replaced by InterfaceField via user-defined annotation
- assertTrue("Overridden String field name (InterfaceField) should have been found",
- jsonObject.has("InterfaceField"));
- // property name ignoredInt was replaced by none, followed by forcedInt via user-defined annotations
- assertTrue("Forced field should have been found!",
- jsonObject.has("forcedInt"));
- // property name able was replaced by Getable via user-defined annotation
- assertTrue("Overridden boolean field name (Getable) should have been found",
- jsonObject.has("Getable"));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * A bean is also an object. But in order to test the JSONObject
- * ctor that takes an object and a list of names,
- * this particular bean needs some public
- * data members, which have been added to the class.
- */
- @Test
- public void jsonObjectByObjectAndNames() {
- String[] keys = {"publicString", "publicInt"};
- // just need a class that has public data members
- MyPublicClass myPublicClass = new MyPublicClass();
- JSONObject jsonObject = new JSONObject(myPublicClass, keys);
-
- // validate JSON
- Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
- assertTrue("expected 2 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 2);
- assertTrue("expected \"publicString\":\"abc\"", "abc".equals(jsonObject.query("/publicString")));
- assertTrue("expected \"publicInt\":42", Integer.valueOf(42).equals(jsonObject.query("/publicInt")));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Exercise the JSONObject from resource bundle functionality.
- * The test resource bundle is uncomplicated, but provides adequate test coverage.
- */
- @Test
- public void jsonObjectByResourceBundle() {
- JSONObject jsonObject = new
- JSONObject("org.json.junit.data.StringsResourceBundle",
- Locale.getDefault());
-
- // validate JSON
- Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
- assertTrue("expected 2 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 2);
- assertTrue("expected 2 greetings items", ((Map,?>)(JsonPath.read(doc, "$.greetings"))).size() == 2);
- assertTrue("expected \"hello\":\"Hello, \"", "Hello, ".equals(jsonObject.query("/greetings/hello")));
- assertTrue("expected \"world\":\"World!\"", "World!".equals(jsonObject.query("/greetings/world")));
- assertTrue("expected 2 farewells items", ((Map,?>)(JsonPath.read(doc, "$.farewells"))).size() == 2);
- assertTrue("expected \"later\":\"Later, \"", "Later, ".equals(jsonObject.query("/farewells/later")));
- assertTrue("expected \"world\":\"World!\"", "Alligator!".equals(jsonObject.query("/farewells/gator")));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Exercise the JSONObject.accumulate() method
- */
- @SuppressWarnings("boxing")
- @Test
- public void jsonObjectAccumulate() {
-
- JSONObject jsonObject = new JSONObject();
- jsonObject.accumulate("myArray", true);
- jsonObject.accumulate("myArray", false);
- jsonObject.accumulate("myArray", "hello world!");
- jsonObject.accumulate("myArray", "h\be\tllo w\u1234orld!");
- jsonObject.accumulate("myArray", 42);
- jsonObject.accumulate("myArray", -23.45e7);
- // include an unsupported object for coverage
- try {
- jsonObject.accumulate("myArray", Double.NaN);
- fail("Expected exception");
- } catch (JSONException ignored) {}
-
- // validate JSON
- Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
- assertTrue("expected 1 top level item", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 1);
- assertTrue("expected 6 myArray items", ((List>)(JsonPath.read(doc, "$.myArray"))).size() == 6);
- assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/myArray/0")));
- assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/myArray/1")));
- assertTrue("expected hello world!", "hello world!".equals(jsonObject.query("/myArray/2")));
- assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/myArray/3")));
- assertTrue("expected 42", Integer.valueOf(42).equals(jsonObject.query("/myArray/4")));
- assertTrue("expected -23.45e7", Double.valueOf(-23.45e7).equals(jsonObject.query("/myArray/5")));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Exercise the JSONObject append() functionality
- */
- @SuppressWarnings("boxing")
- @Test
- public void jsonObjectAppend() {
- JSONObject jsonObject = new JSONObject();
- jsonObject.append("myArray", true);
- jsonObject.append("myArray", false);
- jsonObject.append("myArray", "hello world!");
- jsonObject.append("myArray", "h\be\tllo w\u1234orld!");
- jsonObject.append("myArray", 42);
- jsonObject.append("myArray", -23.45e7);
- // include an unsupported object for coverage
- try {
- jsonObject.append("myArray", Double.NaN);
- fail("Expected exception");
- } catch (JSONException ignored) {}
-
- // validate JSON
- Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
- assertTrue("expected 1 top level item", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 1);
- assertTrue("expected 6 myArray items", ((List>)(JsonPath.read(doc, "$.myArray"))).size() == 6);
- assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/myArray/0")));
- assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/myArray/1")));
- assertTrue("expected hello world!", "hello world!".equals(jsonObject.query("/myArray/2")));
- assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/myArray/3")));
- assertTrue("expected 42", Integer.valueOf(42).equals(jsonObject.query("/myArray/4")));
- assertTrue("expected -23.45e7", Double.valueOf(-23.45e7).equals(jsonObject.query("/myArray/5")));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Exercise the JSONObject doubleToString() method
- */
- @SuppressWarnings("boxing")
- @Test
- public void jsonObjectDoubleToString() {
- String [] expectedStrs = {"1", "1", "-23.4", "-2.345E68", "null", "null" };
- Double [] doubles = { 1.0, 00001.00000, -23.4, -23.45e67,
- Double.NaN, Double.NEGATIVE_INFINITY };
- for (int i = 0; i < expectedStrs.length; ++i) {
- String actualStr = JSONObject.doubleToString(doubles[i]);
- assertTrue("value expected ["+expectedStrs[i]+
- "] found ["+actualStr+ "]",
- expectedStrs[i].equals(actualStr));
- }
- }
-
- /**
- * Exercise some JSONObject get[type] and opt[type] methods
- */
- @Test
- public void jsonObjectValues() {
- String str =
- "{"+
- "\"trueKey\":true,"+
- "\"falseKey\":false,"+
- "\"trueStrKey\":\"true\","+
- "\"falseStrKey\":\"false\","+
- "\"stringKey\":\"hello world!\","+
- "\"intKey\":42,"+
- "\"intStrKey\":\"43\","+
- "\"longKey\":1234567890123456789,"+
- "\"longStrKey\":\"987654321098765432\","+
- "\"doubleKey\":-23.45e7,"+
- "\"doubleStrKey\":\"00001.000\","+
- "\"BigDecimalStrKey\":\"19007199254740993.35481234487103587486413587843213584\","+
- "\"negZeroKey\":-0.0,"+
- "\"negZeroStrKey\":\"-0.0\","+
- "\"arrayKey\":[0,1,2],"+
- "\"objectKey\":{\"myKey\":\"myVal\"}"+
- "}";
- JSONObject jsonObject = new JSONObject(str);
- assertTrue("trueKey should be true", jsonObject.getBoolean("trueKey"));
- assertTrue("opt trueKey should be true", jsonObject.optBoolean("trueKey"));
- assertTrue("falseKey should be false", !jsonObject.getBoolean("falseKey"));
- assertTrue("trueStrKey should be true", jsonObject.getBoolean("trueStrKey"));
- assertTrue("trueStrKey should be true", jsonObject.optBoolean("trueStrKey"));
- assertTrue("falseStrKey should be false", !jsonObject.getBoolean("falseStrKey"));
- assertTrue("stringKey should be string",
- jsonObject.getString("stringKey").equals("hello world!"));
- assertTrue("doubleKey should be double",
- jsonObject.getDouble("doubleKey") == -23.45e7);
- assertTrue("doubleStrKey should be double",
- jsonObject.getDouble("doubleStrKey") == 1);
- assertTrue("doubleKey can be float",
- jsonObject.getFloat("doubleKey") == -23.45e7f);
- assertTrue("doubleStrKey can be float",
- jsonObject.getFloat("doubleStrKey") == 1f);
- assertTrue("opt doubleKey should be double",
- jsonObject.optDouble("doubleKey") == -23.45e7);
- assertTrue("opt doubleKey with Default should be double",
- jsonObject.optDouble("doubleStrKey", Double.NaN) == 1);
- assertTrue("opt negZeroKey should be a Double",
- jsonObject.opt("negZeroKey") instanceof Double);
- assertTrue("get negZeroKey should be a Double",
- jsonObject.get("negZeroKey") instanceof Double);
- assertTrue("optNumber negZeroKey should return Double",
- jsonObject.optNumber("negZeroKey") instanceof Double);
- assertTrue("optNumber negZeroStrKey should return Double",
- jsonObject.optNumber("negZeroStrKey") instanceof Double);
- assertTrue("opt negZeroKey should be double",
- Double.compare(jsonObject.optDouble("negZeroKey"), -0.0d) == 0);
- assertTrue("opt negZeroStrKey with Default should be double",
- Double.compare(jsonObject.optDouble("negZeroStrKey"), -0.0d) == 0);
- assertTrue("optNumber negZeroKey should be -0.0",
- Double.compare(jsonObject.optNumber("negZeroKey").doubleValue(), -0.0d) == 0);
- assertTrue("optNumber negZeroStrKey should be -0.0",
- Double.compare(jsonObject.optNumber("negZeroStrKey").doubleValue(), -0.0d) == 0);
- assertTrue("optFloat doubleKey should be float",
- jsonObject.optFloat("doubleKey") == -23.45e7f);
- assertTrue("optFloat doubleKey with Default should be float",
- jsonObject.optFloat("doubleStrKey", Float.NaN) == 1f);
- assertTrue("intKey should be int",
- jsonObject.optInt("intKey") == 42);
- assertTrue("opt intKey should be int",
- jsonObject.optInt("intKey", 0) == 42);
- assertTrue("opt intKey with default should be int",
- jsonObject.getInt("intKey") == 42);
- assertTrue("intStrKey should be int",
- jsonObject.getInt("intStrKey") == 43);
- assertTrue("longKey should be long",
- jsonObject.getLong("longKey") == 1234567890123456789L);
- assertTrue("opt longKey should be long",
- jsonObject.optLong("longKey") == 1234567890123456789L);
- assertTrue("opt longKey with default should be long",
- jsonObject.optLong("longKey", 0) == 1234567890123456789L);
- assertTrue("longStrKey should be long",
- jsonObject.getLong("longStrKey") == 987654321098765432L);
- assertTrue("optNumber int should return Integer",
- jsonObject.optNumber("intKey") instanceof Integer);
- assertTrue("optNumber long should return Long",
- jsonObject.optNumber("longKey") instanceof Long);
- assertTrue("optNumber double should return BigDecimal",
- jsonObject.optNumber("doubleKey") instanceof BigDecimal);
- assertTrue("optNumber Str int should return Integer",
- jsonObject.optNumber("intStrKey") instanceof Integer);
- assertTrue("optNumber Str long should return Long",
- jsonObject.optNumber("longStrKey") instanceof Long);
- assertTrue("optNumber Str double should return BigDecimal",
- jsonObject.optNumber("doubleStrKey") instanceof BigDecimal);
- assertTrue("optNumber BigDecimalStrKey should return BigDecimal",
- jsonObject.optNumber("BigDecimalStrKey") instanceof BigDecimal);
- assertTrue("xKey should not exist",
- jsonObject.isNull("xKey"));
- assertTrue("stringKey should exist",
- jsonObject.has("stringKey"));
- assertTrue("opt stringKey should string",
- jsonObject.optString("stringKey").equals("hello world!"));
- assertTrue("opt stringKey with default should string",
- jsonObject.optString("stringKey", "not found").equals("hello world!"));
- JSONArray jsonArray = jsonObject.getJSONArray("arrayKey");
- assertTrue("arrayKey should be JSONArray",
- jsonArray.getInt(0) == 0 &&
- jsonArray.getInt(1) == 1 &&
- jsonArray.getInt(2) == 2);
- jsonArray = jsonObject.optJSONArray("arrayKey");
- assertTrue("opt arrayKey should be JSONArray",
- jsonArray.getInt(0) == 0 &&
- jsonArray.getInt(1) == 1 &&
- jsonArray.getInt(2) == 2);
- JSONObject jsonObjectInner = jsonObject.getJSONObject("objectKey");
- assertTrue("objectKey should be JSONObject",
- jsonObjectInner.get("myKey").equals("myVal"));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Check whether JSONObject handles large or high precision numbers correctly
- */
- @Test
- public void stringToValueNumbersTest() {
- assertTrue("-0 Should be a Double!",JSONObject.stringToValue("-0") instanceof Double);
- assertTrue("-0.0 Should be a Double!",JSONObject.stringToValue("-0.0") instanceof Double);
- assertTrue("'-' Should be a String!",JSONObject.stringToValue("-") instanceof String);
- assertTrue( "0.2 should be a 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 );
- /**
- * This test documents a need for BigDecimal conversion.
- */
- Object obj = JSONObject.stringToValue( "299792.457999999984" );
- assertTrue( "does not evaluate to 299792.457999999984 BigDecimal!",
- obj.equals(new BigDecimal("299792.457999999984")) );
- assertTrue( "1 should be an Integer!",
- JSONObject.stringToValue( "1" ) instanceof Integer );
- assertTrue( "Integer.MAX_VALUE should still be an Integer!",
- JSONObject.stringToValue( new Integer( Integer.MAX_VALUE ).toString() ) instanceof Integer );
- assertTrue( "Large integers should be a Long!",
- JSONObject.stringToValue( Long.valueOf(((long)Integer.MAX_VALUE) + 1 ) .toString() ) instanceof Long );
- assertTrue( "Long.MAX_VALUE should still be an Integer!",
- JSONObject.stringToValue( new Long( Long.MAX_VALUE ).toString() ) instanceof Long );
-
- String str = new BigInteger( new Long( Long.MAX_VALUE ).toString() ).add( BigInteger.ONE ).toString();
- assertTrue( "Really large integers currently evaluate to BigInteger",
- JSONObject.stringToValue(str).equals(new BigInteger("9223372036854775808")));
- }
-
- /**
- * This test documents numeric values which could be numerically
- * handled as BigDecimal or BigInteger. It helps determine what outputs
- * will change if those types are supported.
- */
- @Test
- public void jsonValidNumberValuesNeitherLongNorIEEE754Compatible() {
- // Valid JSON Numbers, probably should return BigDecimal or BigInteger objects
- String str =
- "{"+
- "\"numberWithDecimals\":299792.457999999984,"+
- "\"largeNumber\":12345678901234567890,"+
- "\"preciseNumber\":0.2000000000000000111,"+
- "\"largeExponent\":-23.45e2327"+
- "}";
- JSONObject jsonObject = new JSONObject(str);
- // Comes back as a double, but loses precision
- assertTrue( "numberWithDecimals currently evaluates to double 299792.458",
- jsonObject.get( "numberWithDecimals" ).equals( new BigDecimal( "299792.457999999984" ) ) );
- Object obj = jsonObject.get( "largeNumber" );
- assertTrue("largeNumber currently evaluates to BigInteger",
- new BigInteger("12345678901234567890").equals(obj));
- // comes back as a double but loses precision
- assertEquals( "preciseNumber currently evaluates to double 0.2",
- 0.2, jsonObject.getDouble( "preciseNumber" ), 0.0);
- obj = jsonObject.get( "largeExponent" );
- assertTrue("largeExponent should evaluate as a BigDecimal",
- new BigDecimal("-23.45e2327").equals(obj));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * This test documents how JSON-Java handles invalid numeric input.
- */
- @Test
- public void jsonInvalidNumberValues() {
- // Number-notations supported by Java and invalid as JSON
- String str =
- "{"+
- "\"hexNumber\":-0x123,"+
- "\"tooManyZeros\":00,"+
- "\"negativeInfinite\":-Infinity,"+
- "\"negativeNaN\":-NaN,"+
- "\"negativeFraction\":-.01,"+
- "\"tooManyZerosFraction\":00.001,"+
- "\"negativeHexFloat\":-0x1.fffp1,"+
- "\"hexFloat\":0x1.0P-1074,"+
- "\"floatIdentifier\":0.1f,"+
- "\"doubleIdentifier\":0.1d"+
- "}";
- JSONObject jsonObject = new JSONObject(str);
- Object obj;
- obj = jsonObject.get( "hexNumber" );
- assertFalse( "hexNumber must not be a number (should throw exception!?)",
- obj instanceof Number );
- assertTrue("hexNumber currently evaluates to string",
- obj.equals("-0x123"));
- assertTrue( "tooManyZeros currently evaluates to string",
- jsonObject.get( "tooManyZeros" ).equals("00"));
- obj = jsonObject.get("negativeInfinite");
- assertTrue( "negativeInfinite currently evaluates to string",
- obj.equals("-Infinity"));
- obj = jsonObject.get("negativeNaN");
- assertTrue( "negativeNaN currently evaluates to string",
- obj.equals("-NaN"));
- assertTrue( "negativeFraction currently evaluates to double -0.01",
- jsonObject.get( "negativeFraction" ).equals(BigDecimal.valueOf(-0.01)));
- assertTrue( "tooManyZerosFraction currently evaluates to double 0.001",
- jsonObject.get( "tooManyZerosFraction" ).equals(BigDecimal.valueOf(0.001)));
- assertTrue( "negativeHexFloat currently evaluates to double -3.99951171875",
- jsonObject.get( "negativeHexFloat" ).equals(Double.valueOf(-3.99951171875)));
- assertTrue("hexFloat currently evaluates to double 4.9E-324",
- jsonObject.get("hexFloat").equals(Double.valueOf(4.9E-324)));
- assertTrue("floatIdentifier currently evaluates to double 0.1",
- jsonObject.get("floatIdentifier").equals(Double.valueOf(0.1)));
- assertTrue("doubleIdentifier currently evaluates to double 0.1",
- jsonObject.get("doubleIdentifier").equals(Double.valueOf(0.1)));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Tests how JSONObject get[type] handles incorrect types
- */
- @Test
- public void jsonObjectNonAndWrongValues() {
- String str =
- "{"+
- "\"trueKey\":true,"+
- "\"falseKey\":false,"+
- "\"trueStrKey\":\"true\","+
- "\"falseStrKey\":\"false\","+
- "\"stringKey\":\"hello world!\","+
- "\"intKey\":42,"+
- "\"intStrKey\":\"43\","+
- "\"longKey\":1234567890123456789,"+
- "\"longStrKey\":\"987654321098765432\","+
- "\"doubleKey\":-23.45e7,"+
- "\"doubleStrKey\":\"00001.000\","+
- "\"arrayKey\":[0,1,2],"+
- "\"objectKey\":{\"myKey\":\"myVal\"}"+
- "}";
- JSONObject jsonObject = new JSONObject(str);
- try {
- jsonObject.getBoolean("nonKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("expecting an exception message",
- "JSONObject[\"nonKey\"] not found.", e.getMessage());
- }
- try {
- jsonObject.getBoolean("stringKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a Boolean (class java.lang.String : hello world!).",
- e.getMessage());
- }
- try {
- jsonObject.getString("nonKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "JSONObject[\"nonKey\"] not found.",
- e.getMessage());
- }
- try {
- jsonObject.getString("trueKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "JSONObject[\"trueKey\"] is not a string (class java.lang.Boolean : true).",
- e.getMessage());
- }
- try {
- jsonObject.getDouble("nonKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "JSONObject[\"nonKey\"] not found.",
- e.getMessage());
- }
- try {
- jsonObject.getDouble("stringKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a double (class java.lang.String : hello world!).",
- e.getMessage());
- }
- try {
- jsonObject.getFloat("nonKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "JSONObject[\"nonKey\"] not found.",
- e.getMessage());
- }
- try {
- jsonObject.getFloat("stringKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a float (class java.lang.String : hello world!).",
- e.getMessage());
- }
- try {
- jsonObject.getInt("nonKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "JSONObject[\"nonKey\"] not found.",
- e.getMessage());
- }
- try {
- jsonObject.getInt("stringKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a int (class java.lang.String : hello world!).",
- e.getMessage());
- }
- try {
- jsonObject.getLong("nonKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "JSONObject[\"nonKey\"] not found.",
- e.getMessage());
- }
- try {
- jsonObject.getLong("stringKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a long (class java.lang.String : hello world!).",
- e.getMessage());
- }
- try {
- jsonObject.getJSONArray("nonKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "JSONObject[\"nonKey\"] not found.",
- e.getMessage());
- }
- try {
- jsonObject.getJSONArray("stringKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a JSONArray (class java.lang.String : hello world!).",
- e.getMessage());
- }
- try {
- jsonObject.getJSONObject("nonKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "JSONObject[\"nonKey\"] not found.",
- e.getMessage());
- }
- try {
- jsonObject.getJSONObject("stringKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "JSONObject[\"stringKey\"] is not a JSONObject (class java.lang.String : hello world!).",
- e.getMessage());
- }
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * This test documents an unexpected numeric behavior.
- * A double that ends with .0 is parsed, serialized, then
- * parsed again. On the second parse, it has become an int.
- */
- @Test
- public void unexpectedDoubleToIntConversion() {
- String key30 = "key30";
- String key31 = "key31";
- JSONObject jsonObject = new JSONObject();
- jsonObject.put(key30, new Double(3.0));
- jsonObject.put(key31, new Double(3.1));
-
- assertTrue("3.0 should remain a double",
- jsonObject.getDouble(key30) == 3);
- assertTrue("3.1 should remain a double",
- jsonObject.getDouble(key31) == 3.1);
-
- // turns 3.0 into 3.
- String serializedString = jsonObject.toString();
- JSONObject deserialized = new JSONObject(serializedString);
- assertTrue("3.0 is now an int", deserialized.get(key30) instanceof Integer);
- assertTrue("3.0 can still be interpreted as a double",
- deserialized.getDouble(key30) == 3.0);
- assertTrue("3.1 remains a double", deserialized.getDouble(key31) == 3.1);
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Document behaviors of big numbers. Includes both JSONObject
- * and JSONArray tests
- */
- @SuppressWarnings("boxing")
- @Test
- public void bigNumberOperations() {
- /**
- * JSONObject tries to parse BigInteger as a bean, but it only has
- * one getter, getLowestBitSet(). The value is lost and an unhelpful
- * value is stored. This should be fixed.
- */
- BigInteger bigInteger = new BigInteger("123456789012345678901234567890");
- JSONObject 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",
- obj.equals(1));
-
- /**
- * JSONObject tries to parse BigDecimal as a bean, but it has
- * no getters, The value is lost and no value is stored.
- * This should be fixed.
- */
- BigDecimal bigDecimal = new BigDecimal(
- "123456789012345678901234567890.12345678901234567890123456789");
- JSONObject 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 jsonObject2 = new JSONObject();
- jsonObject2.put("bigInt", bigInteger);
- assertTrue("jsonObject.put() handles bigInt correctly",
- jsonObject2.get("bigInt").equals(bigInteger));
- assertTrue("jsonObject.getBigInteger() handles bigInt correctly",
- jsonObject2.getBigInteger("bigInt").equals(bigInteger));
- assertTrue("jsonObject.optBigInteger() handles bigInt correctly",
- jsonObject2.optBigInteger("bigInt", BigInteger.ONE).equals(bigInteger));
- assertTrue("jsonObject serializes bigInt correctly",
- jsonObject2.toString().equals("{\"bigInt\":123456789012345678901234567890}"));
- assertTrue("BigInteger as BigDecimal",
- jsonObject2.getBigDecimal("bigInt").equals(new BigDecimal(bigInteger)));
-
-
- JSONObject jsonObject3 = new JSONObject();
- jsonObject3.put("bigDec", bigDecimal);
- assertTrue("jsonObject.put() handles bigDec correctly",
- jsonObject3.get("bigDec").equals(bigDecimal));
- assertTrue("jsonObject.getBigDecimal() handles bigDec correctly",
- jsonObject3.getBigDecimal("bigDec").equals(bigDecimal));
- assertTrue("jsonObject.optBigDecimal() handles bigDec correctly",
- jsonObject3.optBigDecimal("bigDec", BigDecimal.ONE).equals(bigDecimal));
- assertTrue("jsonObject serializes bigDec correctly",
- jsonObject3.toString().equals(
- "{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}"));
-
- assertTrue("BigDecimal as BigInteger",
- jsonObject3.getBigInteger("bigDec").equals(bigDecimal.toBigInteger()));
- /**
- * exercise some exceptions
- */
- try {
- // bigInt key does not exist
- jsonObject3.getBigDecimal("bigInt");
- fail("expected an exeption");
- } catch (JSONException ignored) {}
- obj = jsonObject3.optBigDecimal("bigInt", BigDecimal.ONE);
- assertTrue("expected BigDecimal", obj.equals(BigDecimal.ONE));
- jsonObject3.put("stringKey", "abc");
- try {
- jsonObject3.getBigDecimal("stringKey");
- fail("expected an exeption");
- } catch (JSONException ignored) {}
- obj = jsonObject3.optBigInteger("bigDec", BigInteger.ONE);
- assertTrue("expected BigInteger", obj instanceof BigInteger);
- assertEquals(bigDecimal.toBigInteger(), obj);
-
- /**
- * JSONObject.numberToString() works correctly, nothing to change.
- */
- String str = JSONObject.numberToString(bigInteger);
- assertTrue("numberToString() handles bigInteger correctly",
- str.equals("123456789012345678901234567890"));
- str = JSONObject.numberToString(bigDecimal);
- assertTrue("numberToString() handles bigDecimal correctly",
- str.equals("123456789012345678901234567890.12345678901234567890123456789"));
-
- /**
- * JSONObject.stringToValue() turns bigInt into an accurate string,
- * and rounds bigDec. This incorrect, but users may have come to
- * expect this behavior. Change would be marginally better, but
- * might inconvenience users.
- */
- obj = JSONObject.stringToValue(bigInteger.toString());
- assertTrue("stringToValue() turns bigInteger string into Number",
- obj instanceof Number);
- obj = JSONObject.stringToValue(bigDecimal.toString());
- assertTrue("stringToValue() changes bigDecimal Number",
- obj instanceof Number);
-
- /**
- * wrap() vs put() big number behavior is now the same.
- */
- // bigInt map ctor
- Map map = new HashMap();
- map.put("bigInt", bigInteger);
- JSONObject 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 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 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 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 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", 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 {
- jsonArray0.getBigInteger(2);
- fail("should not be able to get big int");
- } catch (Exception ignored) {}
- try {
- jsonArray0.getBigDecimal(2);
- fail("should not be able to get big dec");
- } catch (Exception ignored) {}
- 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 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 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"));
- 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"));
- // bigInt,bigDec wrap()
- obj = JSONObject.wrap(bigInteger);
- assertTrue("wrap() returns big num",obj.equals(bigInteger));
- obj = JSONObject.wrap(bigDecimal);
- assertTrue("wrap() returns string",obj.equals(bigDecimal));
- 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());
- }
-
- /**
- * The purpose for the static method getNames() methods are not clear.
- * This method is not called from within JSON-Java. Most likely
- * uses are to prep names arrays for:
- * JSONObject(JSONObject jo, String[] names)
- * JSONObject(Object object, String names[]),
- */
- @Test
- public void jsonObjectNames() {
-
- // getNames() from null JSONObject
- assertTrue("null names from null Object",
- null == JSONObject.getNames((Object)null));
-
- // getNames() from object with no fields
- assertTrue("null names from Object with no fields",
- null == JSONObject.getNames(new MyJsonString()));
-
- // getNames from new JSONOjbect
- JSONObject jsonObject0 = new JSONObject();
- String [] names = JSONObject.getNames(jsonObject0);
- assertTrue("names should be null", names == null);
-
-
- // getNames() from empty JSONObject
- String emptyStr = "{}";
- JSONObject jsonObject1 = new JSONObject(emptyStr);
- assertTrue("empty JSONObject should have null names",
- null == JSONObject.getNames(jsonObject1));
-
- // getNames() from JSONObject
- String str =
- "{"+
- "\"trueKey\":true,"+
- "\"falseKey\":false,"+
- "\"stringKey\":\"hello world!\","+
- "}";
- JSONObject jsonObject2 = new JSONObject(str);
- names = JSONObject.getNames(jsonObject2);
- JSONArray jsonArray0 = new JSONArray(names);
-
- // validate JSON
- Object doc = Configuration.defaultConfiguration().jsonProvider()
- .parse(jsonArray0.toString());
- List> docList = JsonPath.read(doc, "$");
- assertTrue("expected 3 items", docList.size() == 3);
- assertTrue(
- "expected to find trueKey",
- ((List>) JsonPath.read(doc, "$[?(@=='trueKey')]")).size() == 1);
- assertTrue(
- "expected to find falseKey",
- ((List>) JsonPath.read(doc, "$[?(@=='falseKey')]")).size() == 1);
- assertTrue(
- "expected to find stringKey",
- ((List>) JsonPath.read(doc, "$[?(@=='stringKey')]")).size() == 1);
-
- /**
- * getNames() from an enum with properties has an interesting result.
- * It returns the enum values, not the selected enum properties
- */
- MyEnumField myEnumField = MyEnumField.VAL1;
- names = JSONObject.getNames(myEnumField);
-
- // validate JSON
- JSONArray jsonArray1 = new JSONArray(names);
- doc = Configuration.defaultConfiguration().jsonProvider()
- .parse(jsonArray1.toString());
- docList = JsonPath.read(doc, "$");
- assertTrue("expected 3 items", docList.size() == 3);
- assertTrue(
- "expected to find VAL1",
- ((List>) JsonPath.read(doc, "$[?(@=='VAL1')]")).size() == 1);
- assertTrue(
- "expected to find VAL2",
- ((List>) JsonPath.read(doc, "$[?(@=='VAL2')]")).size() == 1);
- assertTrue(
- "expected to find VAL3",
- ((List>) JsonPath.read(doc, "$[?(@=='VAL3')]")).size() == 1);
-
- /**
- * A bean is also an object. But in order to test the static
- * method getNames(), this particular bean needs some public
- * data members.
- */
- MyPublicClass myPublicClass = new MyPublicClass();
- names = JSONObject.getNames(myPublicClass);
-
- // validate JSON
- JSONArray jsonArray2 = new JSONArray(names);
- doc = Configuration.defaultConfiguration().jsonProvider()
- .parse(jsonArray2.toString());
- docList = JsonPath.read(doc, "$");
- assertTrue("expected 2 items", docList.size() == 2);
- assertTrue(
- "expected to find publicString",
- ((List>) JsonPath.read(doc, "$[?(@=='publicString')]")).size() == 1);
- assertTrue(
- "expected to find publicInt",
- ((List>) JsonPath.read(doc, "$[?(@=='publicInt')]")).size() == 1);
- Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
- jsonObject0, jsonObject1, jsonObject2
- )));
- Util.checkJSONArrayMaps(jsonArray0, jsonObject0.getMapType());
- Util.checkJSONArrayMaps(jsonArray1, jsonObject0.getMapType());
- Util.checkJSONArrayMaps(jsonArray2, jsonObject0.getMapType());
- }
-
- /**
- * Populate a JSONArray from an empty JSONObject names() method.
- * It should be empty.
- */
- @Test
- public void emptyJsonObjectNamesToJsonAray() {
- JSONObject jsonObject = new JSONObject();
- JSONArray jsonArray = jsonObject.names();
- assertTrue("jsonArray should be null", jsonArray == null);
- Util.checkJSONObjectMaps(jsonObject);
- Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
- }
-
- /**
- * Populate a JSONArray from a JSONObject names() method.
- * Confirm that it contains the expected names.
- */
- @Test
- public void jsonObjectNamesToJsonAray() {
- String str =
- "{"+
- "\"trueKey\":true,"+
- "\"falseKey\":false,"+
- "\"stringKey\":\"hello world!\","+
- "}";
-
- JSONObject jsonObject = new JSONObject(str);
- JSONArray jsonArray = jsonObject.names();
-
- // validate JSON
- Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
- assertTrue("expected 3 top level items", ((List>)(JsonPath.read(doc, "$"))).size() == 3);
- assertTrue("expected to find trueKey", ((List>) JsonPath.read(doc, "$[?(@=='trueKey')]")).size() == 1);
- assertTrue("expected to find falseKey", ((List>) JsonPath.read(doc, "$[?(@=='falseKey')]")).size() == 1);
- assertTrue("expected to find stringKey", ((List>) JsonPath.read(doc, "$[?(@=='stringKey')]")).size() == 1);
- Util.checkJSONObjectMaps(jsonObject);
- Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
- }
-
- /**
- * Exercise the JSONObject increment() method.
- */
- @SuppressWarnings("cast")
- @Test
- public void jsonObjectIncrement() {
- String str =
- "{"+
- "\"keyLong\":9999999991,"+
- "\"keyDouble\":1.1"+
- "}";
- JSONObject jsonObject = new JSONObject(str);
- jsonObject.increment("keyInt");
- jsonObject.increment("keyInt");
- jsonObject.increment("keyLong");
- jsonObject.increment("keyDouble");
- jsonObject.increment("keyInt");
- jsonObject.increment("keyLong");
- jsonObject.increment("keyDouble");
- /**
- * JSONObject constructor won't handle these types correctly, but
- * adding them via put works.
- */
- jsonObject.put("keyFloat", 1.1f);
- jsonObject.put("keyBigInt", new BigInteger("123456789123456789123456789123456780"));
- jsonObject.put("keyBigDec", new BigDecimal("123456789123456789123456789123456780.1"));
- jsonObject.increment("keyFloat");
- jsonObject.increment("keyFloat");
- jsonObject.increment("keyBigInt");
- jsonObject.increment("keyBigDec");
-
- // validate JSON
- Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
- assertTrue("expected 6 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 6);
- assertTrue("expected 3", Integer.valueOf(3).equals(jsonObject.query("/keyInt")));
- assertTrue("expected 9999999993", Long.valueOf(9999999993L).equals(jsonObject.query("/keyLong")));
- assertTrue("expected 3.1", BigDecimal.valueOf(3.1).equals(jsonObject.query("/keyDouble")));
- assertTrue("expected 123456789123456789123456789123456781", new BigInteger("123456789123456789123456789123456781").equals(jsonObject.query("/keyBigInt")));
- assertTrue("expected 123456789123456789123456789123456781.1", new BigDecimal("123456789123456789123456789123456781.1").equals(jsonObject.query("/keyBigDec")));
-
- /**
- * Should work the same way on any platform! @see https://docs.oracle
- * .com/javase/specs/jls/se7/html/jls-4.html#jls-4.2.3 This is the
- * effect of a float to double conversion and is inherent to the
- * shortcomings of the IEEE 754 format, when converting 32-bit into
- * double-precision 64-bit. Java type-casts float to double. A 32 bit
- * float is type-casted to 64 bit double by simply appending zero-bits
- * to the mantissa (and extended the signed exponent by 3 bits.) and
- * there is no way to obtain more information than it is stored in the
- * 32-bits float.
- *
- * Like 1/3 cannot be represented as base10 number because it is
- * periodically, 1/5 (for example) cannot be represented as base2 number
- * since it is periodically in base2 (take a look at
- * http://www.h-schmidt.net/FloatConverter/) The same happens to 3.1,
- * that decimal number (base10 representation) is periodic in base2
- * representation, therefore appending zero-bits is inaccurate. Only
- * repeating the periodically occurring bits (0110) would be a proper
- * conversion. However one cannot detect from a 32 bit IEE754
- * representation which bits would "repeat infinitely", since the
- * missing bits would not fit into the 32 bit float, i.e. the
- * information needed simply is not there!
- */
- assertEquals(Float.valueOf(3.1f), jsonObject.query("/keyFloat"));
-
- /**
- * float f = 3.1f; double df = (double) f; double d = 3.1d;
- * System.out.println
- * (Integer.toBinaryString(Float.floatToRawIntBits(f)));
- * System.out.println
- * (Long.toBinaryString(Double.doubleToRawLongBits(df)));
- * System.out.println
- * (Long.toBinaryString(Double.doubleToRawLongBits(d)));
- *
- * - Float:
- * seeeeeeeemmmmmmmmmmmmmmmmmmmmmmm
- * 1000000010001100110011001100110
- * - Double
- * seeeeeeeeeeemmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
- * 10000000 10001100110011001100110
- * 100000000001000110011001100110011000000000000000000000000000000
- * 100000000001000110011001100110011001100110011001100110011001101
- */
-
- /**
- * Examples of well documented but probably unexpected behavior in
- * java / with 32-bit float to 64-bit float conversion.
- */
- assertFalse("Document unexpected behaviour with explicit type-casting float as double!", (double)0.2f == 0.2d );
- assertFalse("Document unexpected behaviour with implicit type-cast!", 0.2f == 0.2d );
- Double d1 = new Double( 1.1f );
- Double d2 = new Double( "1.1f" );
- assertFalse( "Document implicit type cast from float to double before calling Double(double d) constructor", d1.equals( d2 ) );
-
- assertTrue( "Correctly converting float to double via base10 (string) representation!", new Double( 3.1d ).equals( new Double( new Float( 3.1f ).toString() ) ) );
-
- // Pinpointing the not so obvious "buggy" conversion from float to double in JSONObject
- JSONObject jo = new JSONObject();
- jo.put( "bug", 3.1f ); // will call put( String key, double value ) with implicit and "buggy" type-cast from float to double
- assertFalse( "The java-compiler did add some zero bits for you to the mantissa (unexpected, but well documented)", jo.get( "bug" ).equals( new Double( 3.1d ) ) );
-
- JSONObject inc = new JSONObject();
- inc.put( "bug", new Float( 3.1f ) ); // This will put in instance of Float into JSONObject, i.e. call put( String key, Object value )
- assertTrue( "Everything is ok here!", inc.get( "bug" ) instanceof Float );
- inc.increment( "bug" ); // after adding 1, increment will call put( String key, double value ) with implicit and "buggy" type-cast from float to double!
- // this.put(key, (Float) value + 1);
- // 1. The (Object)value will be typecasted to (Float)value since it is an instanceof Float actually nothing is done.
- // 2. Float instance will be autoboxed into float because the + operator will work on primitives not Objects!
- // 3. A float+float operation will be performed and results into a float primitive.
- // 4. There is no method that matches the signature put( String key, float value), java-compiler will choose the method
- // put( String key, double value) and does an implicit type-cast(!) by appending zero-bits to the mantissa
- assertTrue( "JSONObject increment converts Float to Double", jo.get( "bug" ) instanceof Float );
- // correct implementation (with change of behavior) would be:
- // this.put(key, new Float((Float) value + 1));
- // Probably it would be better to deprecate the method and remove some day, while convenient processing the "payload" is not
- // really in the scope of a JSON-library (IMHO.)
- Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
- jsonObject, inc
- )));
- }
-
- /**
- * Exercise JSONObject numberToString() method
- */
- @SuppressWarnings("boxing")
- @Test
- public void jsonObjectNumberToString() {
- String str;
- Double dVal;
- Integer iVal = 1;
- str = JSONObject.numberToString(iVal);
- assertTrue("expected "+iVal+" actual "+str, iVal.toString().equals(str));
- dVal = 12.34;
- str = JSONObject.numberToString(dVal);
- assertTrue("expected "+dVal+" actual "+str, dVal.toString().equals(str));
- dVal = 12.34e27;
- str = JSONObject.numberToString(dVal);
- assertTrue("expected "+dVal+" actual "+str, dVal.toString().equals(str));
- // trailing .0 is truncated, so it doesn't quite match toString()
- dVal = 5000000.0000000;
- str = JSONObject.numberToString(dVal);
- assertTrue("expected 5000000 actual "+str, str.equals("5000000"));
- }
-
- /**
- * Exercise JSONObject put() and similar() methods
- */
- @SuppressWarnings("boxing")
- @Test
- public void jsonObjectPut() {
- String expectedStr =
- "{"+
- "\"trueKey\":true,"+
- "\"falseKey\":false,"+
- "\"arrayKey\":[0,1,2],"+
- "\"objectKey\":{"+
- "\"myKey1\":\"myVal1\","+
- "\"myKey2\":\"myVal2\","+
- "\"myKey3\":\"myVal3\","+
- "\"myKey4\":\"myVal4\""+
- "}"+
- "}";
- JSONObject jsonObject = new JSONObject();
- jsonObject.put("trueKey", true);
- jsonObject.put("falseKey", false);
- Integer [] intArray = { 0, 1, 2 };
- jsonObject.put("arrayKey", Arrays.asList(intArray));
- Map myMap = new HashMap();
- myMap.put("myKey1", "myVal1");
- myMap.put("myKey2", "myVal2");
- myMap.put("myKey3", "myVal3");
- myMap.put("myKey4", "myVal4");
- jsonObject.put("objectKey", myMap);
-
- // validate JSON
- Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
- assertTrue("expected 4 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 4);
- assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
- assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
- assertTrue("expected 3 arrayKey items", ((List>)(JsonPath.read(doc, "$.arrayKey"))).size() == 3);
- assertTrue("expected 0", Integer.valueOf(0).equals(jsonObject.query("/arrayKey/0")));
- assertTrue("expected 1", Integer.valueOf(1).equals(jsonObject.query("/arrayKey/1")));
- assertTrue("expected 2", Integer.valueOf(2).equals(jsonObject.query("/arrayKey/2")));
- assertTrue("expected 4 objectKey items", ((Map,?>)(JsonPath.read(doc, "$.objectKey"))).size() == 4);
- assertTrue("expected myVal1", "myVal1".equals(jsonObject.query("/objectKey/myKey1")));
- assertTrue("expected myVal2", "myVal2".equals(jsonObject.query("/objectKey/myKey2")));
- assertTrue("expected myVal3", "myVal3".equals(jsonObject.query("/objectKey/myKey3")));
- assertTrue("expected myVal4", "myVal4".equals(jsonObject.query("/objectKey/myKey4")));
-
- jsonObject.remove("trueKey");
- JSONObject expectedJsonObject = new JSONObject(expectedStr);
- assertTrue("unequal jsonObjects should not be similar",
- !jsonObject.similar(expectedJsonObject));
- assertTrue("jsonObject should not be similar to jsonArray",
- !jsonObject.similar(new JSONArray()));
-
- String aCompareValueStr = "{\"a\":\"aval\",\"b\":true}";
- String bCompareValueStr = "{\"a\":\"notAval\",\"b\":true}";
- JSONObject aCompareValueJsonObject = new JSONObject(aCompareValueStr);
- JSONObject bCompareValueJsonObject = new JSONObject(bCompareValueStr);
- assertTrue("different values should not be similar",
- !aCompareValueJsonObject.similar(bCompareValueJsonObject));
-
- String aCompareObjectStr = "{\"a\":\"aval\",\"b\":{}}";
- String bCompareObjectStr = "{\"a\":\"aval\",\"b\":true}";
- JSONObject aCompareObjectJsonObject = new JSONObject(aCompareObjectStr);
- JSONObject bCompareObjectJsonObject = new JSONObject(bCompareObjectStr);
- assertTrue("different nested JSONObjects should not be similar",
- !aCompareObjectJsonObject.similar(bCompareObjectJsonObject));
-
- String aCompareArrayStr = "{\"a\":\"aval\",\"b\":[]}";
- String bCompareArrayStr = "{\"a\":\"aval\",\"b\":true}";
- JSONObject aCompareArrayJsonObject = new JSONObject(aCompareArrayStr);
- JSONObject bCompareArrayJsonObject = new JSONObject(bCompareArrayStr);
- assertTrue("different nested JSONArrays should not be similar",
- !aCompareArrayJsonObject.similar(bCompareArrayJsonObject));
- Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
- jsonObject, expectedJsonObject, aCompareValueJsonObject,
- aCompareArrayJsonObject, aCompareObjectJsonObject, aCompareArrayJsonObject,
- bCompareValueJsonObject, bCompareArrayJsonObject, bCompareObjectJsonObject,
- bCompareArrayJsonObject
- )));
- }
-
- /**
- * Exercise JSONObject toString() method
- */
- @Test
- public void jsonObjectToString() {
- String str =
- "{"+
- "\"trueKey\":true,"+
- "\"falseKey\":false,"+
- "\"arrayKey\":[0,1,2],"+
- "\"objectKey\":{"+
- "\"myKey1\":\"myVal1\","+
- "\"myKey2\":\"myVal2\","+
- "\"myKey3\":\"myVal3\","+
- "\"myKey4\":\"myVal4\""+
- "}"+
- "}";
- JSONObject jsonObject = new JSONObject(str);
-
- // validate JSON
- Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
- assertTrue("expected 4 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 4);
- assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
- assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
- assertTrue("expected 3 arrayKey items", ((List>)(JsonPath.read(doc, "$.arrayKey"))).size() == 3);
- assertTrue("expected 0", Integer.valueOf(0).equals(jsonObject.query("/arrayKey/0")));
- assertTrue("expected 1", Integer.valueOf(1).equals(jsonObject.query("/arrayKey/1")));
- assertTrue("expected 2", Integer.valueOf(2).equals(jsonObject.query("/arrayKey/2")));
- assertTrue("expected 4 objectKey items", ((Map,?>)(JsonPath.read(doc, "$.objectKey"))).size() == 4);
- assertTrue("expected myVal1", "myVal1".equals(jsonObject.query("/objectKey/myKey1")));
- assertTrue("expected myVal2", "myVal2".equals(jsonObject.query("/objectKey/myKey2")));
- assertTrue("expected myVal3", "myVal3".equals(jsonObject.query("/objectKey/myKey3")));
- assertTrue("expected myVal4", "myVal4".equals(jsonObject.query("/objectKey/myKey4")));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Exercise JSONObject toString() method with various indent levels.
- */
- @Test
- public void jsonObjectToStringIndent() {
- String jsonObject0Str =
- "{"+
- "\"key1\":" +
- "[1,2," +
- "{\"key3\":true}" +
- "],"+
- "\"key2\":" +
- "{\"key1\":\"val1\",\"key2\":" +
- "{\"key2\":\"val2\"}" +
- "},"+
- "\"key3\":" +
- "[" +
- "[1,2.1]" +
- "," +
- "[null]" +
- "]"+
- "}";
-
- String jsonObject1Str =
- "{\n" +
- " \"key1\": [\n" +
- " 1,\n" +
- " 2,\n" +
- " {\"key3\": true}\n" +
- " ],\n" +
- " \"key2\": {\n" +
- " \"key1\": \"val1\",\n" +
- " \"key2\": {\"key2\": \"val2\"}\n" +
- " },\n" +
- " \"key3\": [\n" +
- " [\n" +
- " 1,\n" +
- " 2.1\n" +
- " ],\n" +
- " [null]\n" +
- " ]\n" +
- "}";
- String jsonObject4Str =
- "{\n" +
- " \"key1\": [\n" +
- " 1,\n" +
- " 2,\n" +
- " {\"key3\": true}\n" +
- " ],\n" +
- " \"key2\": {\n" +
- " \"key1\": \"val1\",\n" +
- " \"key2\": {\"key2\": \"val2\"}\n" +
- " },\n" +
- " \"key3\": [\n" +
- " [\n" +
- " 1,\n" +
- " 2.1\n" +
- " ],\n" +
- " [null]\n" +
- " ]\n" +
- "}";
- JSONObject jsonObject = new JSONObject(jsonObject0Str);
- // contents are tested in other methods, in this case just validate the spacing by
- // checking length
- assertEquals("toString() length",jsonObject0Str.length(), jsonObject.toString().length());
- assertEquals("toString(0) length",jsonObject0Str.length(), jsonObject.toString(0).length());
- assertEquals("toString(1) length",jsonObject1Str.length(), jsonObject.toString(1).length());
- assertEquals("toString(4) length",jsonObject4Str.length(), jsonObject.toString(4).length());
-
- JSONObject jo = new JSONObject().put("TABLE", new JSONObject().put("yhoo", new JSONObject()));
- assertEquals("toString(2)","{\"TABLE\": {\"yhoo\": {}}}", jo.toString(2));
- Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
- jsonObject, jo
- )));
- }
-
- /**
- * Explores how JSONObject handles maps. Insert a string/string map
- * as a value in a JSONObject. It will remain a map. Convert the
- * JSONObject to string, then create a new JSONObject from the string.
- * In the new JSONObject, the value will be stored as a nested JSONObject.
- * Confirm that map and nested JSONObject have the same contents.
- */
- @Test
- public void jsonObjectToStringSuppressWarningOnCastToMap() {
- JSONObject jsonObject = new JSONObject();
- Map map = new HashMap();
- map.put("abc", "def");
- jsonObject.put("key", map);
-
- // validate JSON
- Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
- assertTrue("expected 1 top level item", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 1);
- assertTrue("expected 1 key item", ((Map,?>)(JsonPath.read(doc, "$.key"))).size() == 1);
- assertTrue("expected def", "def".equals(jsonObject.query("/key/abc")));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Explores how JSONObject handles collections. Insert a string collection
- * as a value in a JSONObject. It will remain a collection. Convert the
- * JSONObject to string, then create a new JSONObject from the string.
- * In the new JSONObject, the value will be stored as a nested JSONArray.
- * Confirm that collection and nested JSONArray have the same contents.
- */
- @Test
- public void jsonObjectToStringSuppressWarningOnCastToCollection() {
- JSONObject jsonObject = new JSONObject();
- Collection collection = new ArrayList();
- collection.add("abc");
- // ArrayList will be added as an object
- jsonObject.put("key", collection);
-
- // validate JSON
- Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
- assertTrue("expected 1 top level item", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 1);
- assertTrue("expected 1 key item", ((List>)(JsonPath.read(doc, "$.key"))).size() == 1);
- assertTrue("expected abc", "abc".equals(jsonObject.query("/key/0")));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Exercises the JSONObject.valueToString() method for various types
- */
- @Test
- public void valueToString() {
-
- assertTrue("null valueToString() incorrect",
- "null".equals(JSONObject.valueToString(null)));
- MyJsonString jsonString = new MyJsonString();
- assertTrue("jsonstring valueToString() incorrect",
- "my string".equals(JSONObject.valueToString(jsonString)));
- assertTrue("boolean valueToString() incorrect",
- "true".equals(JSONObject.valueToString(Boolean.TRUE)));
- assertTrue("non-numeric double",
- "null".equals(JSONObject.doubleToString(Double.POSITIVE_INFINITY)));
- String jsonObjectStr =
- "{"+
- "\"key1\":\"val1\","+
- "\"key2\":\"val2\","+
- "\"key3\":\"val3\""+
- "}";
- JSONObject jsonObject = new JSONObject(jsonObjectStr);
- assertTrue("jsonObject valueToString() incorrect",
- JSONObject.valueToString(jsonObject).equals(jsonObject.toString()));
- String jsonArrayStr =
- "[1,2,3]";
- JSONArray jsonArray = new JSONArray(jsonArrayStr);
- assertTrue("jsonArray valueToString() incorrect",
- JSONObject.valueToString(jsonArray).equals(jsonArray.toString()));
- Map map = new HashMap();
- map.put("key1", "val1");
- map.put("key2", "val2");
- map.put("key3", "val3");
- assertTrue("map valueToString() incorrect",
- jsonObject.toString().equals(JSONObject.valueToString(map)));
- Collection collection = new ArrayList();
- collection.add(new Integer(1));
- collection.add(new Integer(2));
- collection.add(new Integer(3));
- assertTrue("collection valueToString() expected: "+
- jsonArray.toString()+ " actual: "+
- JSONObject.valueToString(collection),
- jsonArray.toString().equals(JSONObject.valueToString(collection)));
- Integer[] array = { new Integer(1), new Integer(2), new Integer(3) };
- assertTrue("array valueToString() incorrect",
- jsonArray.toString().equals(JSONObject.valueToString(array)));
- Util.checkJSONObjectMaps(jsonObject);
- Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
- }
-
- /**
- * Confirm that https://github.com/douglascrockford/JSON-java/issues/167 is fixed.
- * The following code was throwing a ClassCastException in the
- * JSONObject(Map) constructor
- */
- @SuppressWarnings("boxing")
- @Test
- public void valueToStringConfirmException() {
- Map myMap = new HashMap();
- myMap.put(1, "myValue");
- // this is the test, it should not throw an exception
- String str = JSONObject.valueToString(myMap);
- // confirm result, just in case
- Object doc = Configuration.defaultConfiguration().jsonProvider().parse(str);
- assertTrue("expected 1 top level item", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 1);
- assertTrue("expected myValue", "myValue".equals(JsonPath.read(doc, "$.1")));
- }
-
- /**
- * Exercise the JSONObject wrap() method. Sometimes wrap() will change
- * the object being wrapped, other times not. The purpose of wrap() is
- * to ensure the value is packaged in a way that is compatible with how
- * a JSONObject value or JSONArray value is supposed to be stored.
- */
- @Test
- public void wrapObject() {
- // wrap(null) returns NULL
- assertTrue("null wrap() incorrect",
- JSONObject.NULL == JSONObject.wrap(null));
-
- // wrap(Integer) returns Integer
- Integer in = new Integer(1);
- assertTrue("Integer wrap() incorrect",
- in == JSONObject.wrap(in));
-
- /**
- * This test is to document the preferred behavior if BigDecimal is
- * supported. Previously bd returned as a string, since it
- * is recognized as being a Java package class. Now with explicit
- * support for big numbers, it remains a BigDecimal
- */
- Object bdWrap = JSONObject.wrap(BigDecimal.ONE);
- assertTrue("BigDecimal.ONE evaluates to ONE",
- bdWrap.equals(BigDecimal.ONE));
-
- // wrap JSONObject returns JSONObject
- String jsonObjectStr =
- "{"+
- "\"key1\":\"val1\","+
- "\"key2\":\"val2\","+
- "\"key3\":\"val3\""+
- "}";
- JSONObject jsonObject = new JSONObject(jsonObjectStr);
- assertTrue("JSONObject wrap() incorrect",
- jsonObject == JSONObject.wrap(jsonObject));
-
- // wrap collection returns JSONArray
- Collection collection = new ArrayList();
- collection.add(new Integer(1));
- collection.add(new Integer(2));
- collection.add(new Integer(3));
- JSONArray jsonArray = (JSONArray) (JSONObject.wrap(collection));
-
- // validate JSON
- Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
- assertTrue("expected 3 top level items", ((List>)(JsonPath.read(doc, "$"))).size() == 3);
- assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0")));
- assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1")));
- assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2")));
-
- // wrap Array returns JSONArray
- Integer[] array = { new Integer(1), new Integer(2), new Integer(3) };
- JSONArray integerArrayJsonArray = (JSONArray)(JSONObject.wrap(array));
-
- // validate JSON
- doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
- assertTrue("expected 3 top level items", ((List>)(JsonPath.read(doc, "$"))).size() == 3);
- assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0")));
- assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1")));
- assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2")));
-
- // validate JSON
- doc = Configuration.defaultConfiguration().jsonProvider().parse(integerArrayJsonArray.toString());
- assertTrue("expected 3 top level items", ((List>)(JsonPath.read(doc, "$"))).size() == 3);
- assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0")));
- assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1")));
- assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2")));
-
- // wrap map returns JSONObject
- Map map = new HashMap();
- map.put("key1", "val1");
- map.put("key2", "val2");
- map.put("key3", "val3");
- JSONObject mapJsonObject = (JSONObject) (JSONObject.wrap(map));
-
- // validate JSON
- doc = Configuration.defaultConfiguration().jsonProvider().parse(mapJsonObject.toString());
- assertTrue("expected 3 top level items", ((Map,?>)(JsonPath.read(doc, "$"))).size() == 3);
- assertTrue("expected val1", "val1".equals(mapJsonObject.query("/key1")));
- assertTrue("expected val2", "val2".equals(mapJsonObject.query("/key2")));
- assertTrue("expected val3", "val3".equals(mapJsonObject.query("/key3")));
- Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
- jsonObject, mapJsonObject
- )));
- Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
- Util.checkJSONArrayMaps(integerArrayJsonArray, jsonObject.getMapType());
- }
-
-
- /**
- * RFC 7159 defines control characters to be U+0000 through U+001F. This test verifies that the parser is checking for these in expected ways.
- */
- @Test
- public void jsonObjectParseControlCharacters(){
- for(int i = 0;i<=0x001f;i++){
- final String charString = String.valueOf((char)i);
- final String source = "{\"key\":\""+charString+"\"}";
- try {
- JSONObject jo = new JSONObject(source);
- assertTrue("Expected "+charString+"("+i+") in the JSON Object but did not find it.",charString.equals(jo.getString("key")));
- 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'
- );
- }
- }
- }
-
- /**
- * Explore how JSONObject handles parsing errors.
- */
- @SuppressWarnings({"boxing", "unused"})
- @Test
- public void jsonObjectParsingErrors() {
- try {
- // does not start with '{'
- String str = "abc";
- assertNull("Expected an exception",new JSONObject(str));
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "A JSONObject text must begin with '{' at 1 [character 2 line 1]",
- e.getMessage());
- }
- try {
- // does not end with '}'
- String str = "{";
- assertNull("Expected an exception",new JSONObject(str));
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "A JSONObject text must end with '}' at 1 [character 2 line 1]",
- e.getMessage());
- }
- try {
- // key with no ':'
- String str = "{\"myKey\" = true}";
- assertNull("Expected an exception",new JSONObject(str));
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "Expected a ':' after a key at 10 [character 11 line 1]",
- e.getMessage());
- }
- try {
- // entries with no ',' separator
- String str = "{\"myKey\":true \"myOtherKey\":false}";
- assertNull("Expected an exception",new JSONObject(str));
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "Expected a ',' or '}' at 15 [character 16 line 1]",
- e.getMessage());
- }
- try {
- // append to wrong key
- String str = "{\"myKey\":true, \"myOtherKey\":false}";
- JSONObject jsonObject = new JSONObject(str);
- jsonObject.append("myKey", "hello");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "JSONObject[\"myKey\"] is not a JSONArray (null).",
- e.getMessage());
- }
- try {
- // increment wrong key
- String str = "{\"myKey\":true, \"myOtherKey\":false}";
- JSONObject jsonObject = new JSONObject(str);
- jsonObject.increment("myKey");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "Unable to increment [\"myKey\"].",
- e.getMessage());
- }
- try {
- // invalid key
- String str = "{\"myKey\":true, \"myOtherKey\":false}";
- JSONObject jsonObject = new JSONObject(str);
- jsonObject.get(null);
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "Null key.",
- e.getMessage());
- }
- try {
- // invalid numberToString()
- JSONObject.numberToString((Number)null);
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an exception message",
- "Null pointer",
- e.getMessage());
- }
-
- try {
- // multiple putOnce key
- JSONObject jsonObject = new JSONObject("{}");
- jsonObject.putOnce("hello", "world");
- jsonObject.putOnce("hello", "world!");
- fail("Expected an exception");
- } catch (JSONException e) {
- assertTrue("", true);
- }
- try {
- // test validity of invalid double
- JSONObject.testValidity(Double.NaN);
- fail("Expected an exception");
- } catch (JSONException e) {
- assertTrue("", true);
- }
- try {
- // test validity of invalid float
- JSONObject.testValidity(Float.NEGATIVE_INFINITY);
- fail("Expected an exception");
- } catch (JSONException e) {
- assertTrue("", true);
- }
- try {
- // test exception message when including a duplicate key (level 0)
- String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr03\":\"value-04\"\n"
- + "}";
- new JSONObject(str);
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an expection message",
- "Duplicate key \"attr03\" at 90 [character 13 line 5]",
- e.getMessage());
- }
- try {
- // test exception message when including a duplicate key (level 0) holding an object
- String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr03\": {"
- +" \"attr04-01\":\"value-04-01\",n"
- +" \"attr04-02\":\"value-04-02\",n"
- +" \"attr04-03\":\"value-04-03\"n"
- + " }\n"
- + "}";
- new JSONObject(str);
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an expection message",
- "Duplicate key \"attr03\" at 90 [character 13 line 5]",
- e.getMessage());
- }
- try {
- // test exception message when including a duplicate key (level 0) holding an array
- String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr03\": [\n"
- +" {"
- +" \"attr04-01\":\"value-04-01\",n"
- +" \"attr04-02\":\"value-04-02\",n"
- +" \"attr04-03\":\"value-04-03\"n"
- +" }\n"
- + " ]\n"
- + "}";
- new JSONObject(str);
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an expection message",
- "Duplicate key \"attr03\" at 90 [character 13 line 5]",
- e.getMessage());
- }
- try {
- // test exception message when including a duplicate key (level 1)
- String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr04\": {\n"
- +" \"attr04-01\":\"value04-01\",\n"
- +" \"attr04-02\":\"value04-02\",\n"
- +" \"attr04-03\":\"value04-03\",\n"
- +" \"attr04-03\":\"value04-04\"\n"
- + " }\n"
- + "}";
- new JSONObject(str);
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an expection message",
- "Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
- e.getMessage());
- }
- try {
- // test exception message when including a duplicate key (level 1) holding an object
- String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr04\": {\n"
- +" \"attr04-01\":\"value04-01\",\n"
- +" \"attr04-02\":\"value04-02\",\n"
- +" \"attr04-03\":\"value04-03\",\n"
- +" \"attr04-03\": {\n"
- +" \"attr04-04-01\":\"value04-04-01\",\n"
- +" \"attr04-04-02\":\"value04-04-02\",\n"
- +" \"attr04-04-03\":\"value04-04-03\",\n"
- +" }\n"
- +" }\n"
- + "}";
- new JSONObject(str);
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an expection message",
- "Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
- e.getMessage());
- }
- try {
- // test exception message when including a duplicate key (level 1) holding an array
- String str = "{\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\",\n"
- +" \"attr03\":\"value-03\",\n"
- +" \"attr04\": {\n"
- +" \"attr04-01\":\"value04-01\",\n"
- +" \"attr04-02\":\"value04-02\",\n"
- +" \"attr04-03\":\"value04-03\",\n"
- +" \"attr04-03\": [\n"
- +" {\n"
- +" \"attr04-04-01\":\"value04-04-01\",\n"
- +" \"attr04-04-02\":\"value04-04-02\",\n"
- +" \"attr04-04-03\":\"value04-04-03\",\n"
- +" }\n"
- +" ]\n"
- +" }\n"
- + "}";
- new JSONObject(str);
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an expection message",
- "Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
- e.getMessage());
- }
- try {
- // test exception message when including a duplicate key in object (level 0) within an array
- String str = "[\n"
- +" {\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\":\"value-02\"\n"
- +" },\n"
- +" {\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr01\":\"value-02\"\n"
- +" }\n"
- + "]";
- new JSONArray(str);
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an expection message",
- "Duplicate key \"attr01\" at 124 [character 17 line 8]",
- e.getMessage());
- }
- try {
- // test exception message when including a duplicate key in object (level 1) within an array
- String str = "[\n"
- +" {\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\": {\n"
- +" \"attr02-01\":\"value-02-01\",\n"
- +" \"attr02-02\":\"value-02-02\"\n"
- +" }\n"
- +" },\n"
- +" {\n"
- +" \"attr01\":\"value-01\",\n"
- +" \"attr02\": {\n"
- +" \"attr02-01\":\"value-02-01\",\n"
- +" \"attr02-01\":\"value-02-02\"\n"
- +" }\n"
- +" }\n"
- + "]";
- new JSONArray(str);
- fail("Expected an exception");
- } catch (JSONException e) {
- assertEquals("Expecting an expection message",
- "Duplicate key \"attr02-01\" at 269 [character 24 line 13]",
- e.getMessage());
- }
- }
-
- /**
- * Confirm behavior when putOnce() is called with null parameters
- */
- @Test
- public void jsonObjectPutOnceNull() {
- JSONObject jsonObject = new JSONObject();
- jsonObject.putOnce(null, null);
- assertTrue("jsonObject should be empty", jsonObject.isEmpty());
- jsonObject.putOnce("", null);
- assertTrue("jsonObject should be empty", jsonObject.isEmpty());
- jsonObject.putOnce(null, "");
- assertTrue("jsonObject should be empty", jsonObject.isEmpty());
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Exercise JSONObject opt(key, default) method.
- */
- @Test
- public void jsonObjectOptDefault() {
-
- String str = "{\"myKey\": \"myval\", \"hiKey\": null}";
- JSONObject jsonObject = new JSONObject(str);
-
- assertTrue("optBigDecimal() should return default BigDecimal",
- BigDecimal.TEN.compareTo(jsonObject.optBigDecimal("myKey", BigDecimal.TEN))==0);
- assertTrue("optBigInteger() should return default BigInteger",
- BigInteger.TEN.compareTo(jsonObject.optBigInteger("myKey",BigInteger.TEN ))==0);
- assertTrue("optBoolean() should return default boolean",
- jsonObject.optBoolean("myKey", true));
- assertTrue("optInt() should return default int",
- 42 == jsonObject.optInt("myKey", 42));
- assertTrue("optEnum() should return default Enum",
- MyEnum.VAL1.equals(jsonObject.optEnum(MyEnum.class, "myKey", MyEnum.VAL1)));
- assertTrue("optJSONArray() should return null ",
- null==jsonObject.optJSONArray("myKey"));
- assertTrue("optJSONObject() should return 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("optDouble() should return default double",
- 42.3d == jsonObject.optDouble("myKey", 42.3d));
- assertTrue("optFloat() should return default float",
- 42.3f == jsonObject.optFloat("myKey", 42.3f));
- assertTrue("optNumber() should return default Number",
- 42l == jsonObject.optNumber("myKey", Long.valueOf(42)).longValue());
- assertTrue("optString() should return default string",
- "hi".equals(jsonObject.optString("hiKey", "hi")));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Exercise JSONObject opt(key, default) method when the key doesn't exist.
- */
- @Test
- public void jsonObjectOptNoKey() {
-
- JSONObject jsonObject = new JSONObject();
-
- assertNull(jsonObject.opt(null));
-
- assertTrue("optBigDecimal() should return default BigDecimal",
- BigDecimal.TEN.compareTo(jsonObject.optBigDecimal("myKey", BigDecimal.TEN))==0);
- assertTrue("optBigInteger() should return default BigInteger",
- BigInteger.TEN.compareTo(jsonObject.optBigInteger("myKey",BigInteger.TEN ))==0);
- assertTrue("optBoolean() should return default boolean",
- jsonObject.optBoolean("myKey", true));
- assertTrue("optInt() should return default int",
- 42 == jsonObject.optInt("myKey", 42));
- assertTrue("optEnum() should return default Enum",
- MyEnum.VAL1.equals(jsonObject.optEnum(MyEnum.class, "myKey", MyEnum.VAL1)));
- assertTrue("optJSONArray() should return null ",
- null==jsonObject.optJSONArray("myKey"));
- assertTrue("optJSONObject() should return 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("optDouble() should return default double",
- 42.3d == jsonObject.optDouble("myKey", 42.3d));
- assertTrue("optFloat() should return default float",
- 42.3f == jsonObject.optFloat("myKey", 42.3f));
- assertTrue("optNumber() should return default Number",
- 42l == jsonObject.optNumber("myKey", Long.valueOf(42)).longValue());
- assertTrue("optString() should return default string",
- "hi".equals(jsonObject.optString("hiKey", "hi")));
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Verifies that the opt methods properly convert string values.
- */
- @Test
- public void jsonObjectOptStringConversion() {
- JSONObject jo = new JSONObject("{\"int\":\"123\",\"true\":\"true\",\"false\":\"false\"}");
- assertTrue("unexpected optBoolean value",jo.optBoolean("true",false)==true);
- assertTrue("unexpected optBoolean value",jo.optBoolean("false",true)==false);
- assertTrue("unexpected optInt value",jo.optInt("int",0)==123);
- assertTrue("unexpected optLong value",jo.optLong("int",0)==123l);
- assertTrue("unexpected optDouble value",jo.optDouble("int",0.0d)==123.0d);
- assertTrue("unexpected optFloat value",jo.optFloat("int",0.0f)==123.0f);
- assertTrue("unexpected optBigInteger value",jo.optBigInteger("int",BigInteger.ZERO).compareTo(new BigInteger("123"))==0);
- assertTrue("unexpected optBigDecimal value",jo.optBigDecimal("int",BigDecimal.ZERO).compareTo(new BigDecimal("123"))==0);
- assertTrue("unexpected optBigDecimal value",jo.optBigDecimal("int",BigDecimal.ZERO).compareTo(new BigDecimal("123"))==0);
- assertTrue("unexpected optNumber value",jo.optNumber("int",BigInteger.ZERO).longValue()==123l);
- Util.checkJSONObjectMaps(jo);
- }
-
- /**
- * Verifies that the opt methods properly convert string values to numbers and coerce them consistently.
- */
- @Test
- public void jsonObjectOptCoercion() {
- JSONObject jo = new JSONObject("{\"largeNumberStr\":\"19007199254740993.35481234487103587486413587843213584\"}");
- // currently the parser doesn't recognize BigDecimal, to we have to put it manually
- jo.put("largeNumber", new BigDecimal("19007199254740993.35481234487103587486413587843213584"));
-
- // Test type coercion from larger to smaller
- assertEquals(new BigDecimal("19007199254740993.35481234487103587486413587843213584"), jo.optBigDecimal("largeNumber",null));
- assertEquals(new BigInteger("19007199254740993"), jo.optBigInteger("largeNumber",null));
- assertEquals(1.9007199254740992E16, jo.optDouble("largeNumber"),0.0);
- assertEquals(1.90071995E16f, jo.optFloat("largeNumber"),0.0f);
- assertEquals(19007199254740993l, jo.optLong("largeNumber"));
- assertEquals(1874919425, jo.optInt("largeNumber"));
-
- // conversion from a string
- assertEquals(new BigDecimal("19007199254740993.35481234487103587486413587843213584"), jo.optBigDecimal("largeNumberStr",null));
- assertEquals(new BigInteger("19007199254740993"), jo.optBigInteger("largeNumberStr",null));
- assertEquals(1.9007199254740992E16, jo.optDouble("largeNumberStr"),0.0);
- assertEquals(1.90071995E16f, jo.optFloat("largeNumberStr"),0.0f);
- assertEquals(19007199254740993l, jo.optLong("largeNumberStr"));
- assertEquals(1874919425, jo.optInt("largeNumberStr"));
-
- // the integer portion of the actual value is larger than a double can hold.
- assertNotEquals((long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optLong("largeNumber"));
- assertNotEquals((int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optInt("largeNumber"));
- assertNotEquals((long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optLong("largeNumberStr"));
- assertNotEquals((int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"), jo.optInt("largeNumberStr"));
- assertEquals(19007199254740992l, (long)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"));
- assertEquals(2147483647, (int)Double.parseDouble("19007199254740993.35481234487103587486413587843213584"));
- Util.checkJSONObjectMaps(jo);
- }
-
- /**
- * Verifies that the optBigDecimal method properly converts values to BigDecimal and coerce them consistently.
- */
- @Test
- public void jsonObjectOptBigDecimal() {
- JSONObject jo = new JSONObject().put("int", 123).put("long", 654L)
- .put("float", 1.234f).put("double", 2.345d)
- .put("bigInteger", new BigInteger("1234"))
- .put("bigDecimal", new BigDecimal("1234.56789"))
- .put("nullVal", JSONObject.NULL);
-
- assertEquals(new BigDecimal("123"),jo.optBigDecimal("int", null));
- assertEquals(new BigDecimal("654"),jo.optBigDecimal("long", null));
- assertEquals(new BigDecimal(1.234f),jo.optBigDecimal("float", null));
- assertEquals(new BigDecimal(2.345d),jo.optBigDecimal("double", null));
- assertEquals(new BigDecimal("1234"),jo.optBigDecimal("bigInteger", null));
- assertEquals(new BigDecimal("1234.56789"),jo.optBigDecimal("bigDecimal", null));
- assertNull(jo.optBigDecimal("nullVal", null));
- assertEquals(jo.optBigDecimal("float", null),jo.getBigDecimal("float"));
- assertEquals(jo.optBigDecimal("double", null),jo.getBigDecimal("double"));
- Util.checkJSONObjectMaps(jo);
- }
-
- /**
- * Verifies that the optBigDecimal method properly converts values to BigDecimal and coerce them consistently.
- */
- @Test
- public void jsonObjectOptBigInteger() {
- JSONObject jo = new JSONObject().put("int", 123).put("long", 654L)
- .put("float", 1.234f).put("double", 2.345d)
- .put("bigInteger", new BigInteger("1234"))
- .put("bigDecimal", new BigDecimal("1234.56789"))
- .put("nullVal", JSONObject.NULL);
-
- assertEquals(new BigInteger("123"),jo.optBigInteger("int", null));
- assertEquals(new BigInteger("654"),jo.optBigInteger("long", null));
- assertEquals(new BigInteger("1"),jo.optBigInteger("float", null));
- assertEquals(new BigInteger("2"),jo.optBigInteger("double", null));
- assertEquals(new BigInteger("1234"),jo.optBigInteger("bigInteger", null));
- assertEquals(new BigInteger("1234"),jo.optBigInteger("bigDecimal", null));
- assertNull(jo.optBigDecimal("nullVal", null));
- Util.checkJSONObjectMaps(jo);
- }
-
- /**
- * Confirm behavior when JSONObject put(key, null object) is called
- */
- @Test
- public void jsonObjectputNull() {
-
- // put null should remove the item.
- String str = "{\"myKey\": \"myval\"}";
- JSONObject jsonObjectRemove = new JSONObject(str);
- jsonObjectRemove.remove("myKey");
- assertTrue("jsonObject should be empty", jsonObjectRemove.isEmpty());
-
- JSONObject jsonObjectPutNull = new JSONObject(str);
- jsonObjectPutNull.put("myKey", (Object) null);
- assertTrue("jsonObject should be empty", jsonObjectPutNull.isEmpty());
- Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
- jsonObjectRemove, jsonObjectPutNull
- )));
- }
-
- /**
- * Exercise JSONObject quote() method
- * This purpose of quote() is to ensure that for strings with embedded
- * quotes, the quotes are properly escaped.
- */
- @Test
- public void jsonObjectQuote() {
- String str;
- str = "";
- String quotedStr;
- quotedStr = JSONObject.quote(str);
- assertTrue("quote() expected escaped quotes, found "+quotedStr,
- "\"\"".equals(quotedStr));
- str = "\"\"";
- quotedStr = JSONObject.quote(str);
- assertTrue("quote() expected escaped quotes, found "+quotedStr,
- "\"\\\"\\\"\"".equals(quotedStr));
- str = "";
- quotedStr = JSONObject.quote(str);
- assertTrue("quote() expected escaped frontslash, found "+quotedStr,
- "\"<\\/\"".equals(quotedStr));
- str = "AB\bC";
- quotedStr = JSONObject.quote(str);
- assertTrue("quote() expected escaped backspace, found "+quotedStr,
- "\"AB\\bC\"".equals(quotedStr));
- str = "ABC\n";
- quotedStr = JSONObject.quote(str);
- assertTrue("quote() expected escaped newline, found "+quotedStr,
- "\"ABC\\n\"".equals(quotedStr));
- str = "AB\fC";
- quotedStr = JSONObject.quote(str);
- assertTrue("quote() expected escaped formfeed, found "+quotedStr,
- "\"AB\\fC\"".equals(quotedStr));
- str = "\r";
- quotedStr = JSONObject.quote(str);
- assertTrue("quote() expected escaped return, found "+quotedStr,
- "\"\\r\"".equals(quotedStr));
- str = "\u1234\u0088";
- quotedStr = JSONObject.quote(str);
- assertTrue("quote() expected escaped unicode, found "+quotedStr,
- "\"\u1234\\u0088\"".equals(quotedStr));
- }
-
- /**
- * Confirm behavior when JSONObject stringToValue() is called for an
- * empty string
- */
- @Test
- public void stringToValue() {
- String str = "";
- String valueStr = (String)(JSONObject.stringToValue(str));
- assertTrue("stringToValue() expected empty String, found "+valueStr,
- "".equals(valueStr));
- }
-
- /**
- * Confirm behavior when toJSONArray is called with a null value
- */
- @Test
- public void toJSONArray() {
- assertTrue("toJSONArray() with null names should be null",
- null == new JSONObject().toJSONArray(null));
- }
-
- /**
- * Exercise the JSONObject write() method
- */
- @Test
- public void write() throws IOException {
- String str = "{\"key1\":\"value1\",\"key2\":[1,2,3]}";
- String expectedStr = str;
- JSONObject jsonObject = new JSONObject(str);
- StringWriter stringWriter = new StringWriter();
- try {
- String actualStr = jsonObject.write(stringWriter).toString();
- // key order may change. verify length and individual key content
- assertEquals("length", expectedStr.length(), actualStr.length());
- assertTrue("key1", actualStr.contains("\"key1\":\"value1\""));
- assertTrue("key2", actualStr.contains("\"key2\":[1,2,3]"));
- } finally {
- stringWriter.close();
- }
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Confirms that exceptions thrown when writing values are wrapped properly.
- */
- @Test
- public void testJSONWriterException() {
- final JSONObject jsonObject = new JSONObject();
-
- jsonObject.put("someKey",new BrokenToString());
-
- // test single element JSONObject
- StringWriter writer = new StringWriter();
- try {
- jsonObject.write(writer).toString();
- fail("Expected an exception, got a String value");
- } catch (JSONException e) {
- assertEquals("Unable to write JSONObject value for key: someKey", e.getMessage());
- } catch(Exception e) {
- fail("Expected JSONException");
- } finally {
- try {
- writer.close();
- } catch (Exception e) {}
- }
-
- //test multiElement
- jsonObject.put("somethingElse", "a value");
-
- writer = new StringWriter();
- try {
- jsonObject.write(writer).toString();
- fail("Expected an exception, got a String value");
- } catch (JSONException e) {
- assertEquals("Unable to write JSONObject value for key: someKey", e.getMessage());
- } catch(Exception e) {
- fail("Expected JSONException");
- } finally {
- try {
- writer.close();
- } catch (Exception e) {}
- }
-
- // test a more complex object
- writer = new StringWriter();
- try {
- new JSONObject()
- .put("somethingElse", "a value")
- .put("someKey", new JSONArray()
- .put(new JSONObject().put("key1", new BrokenToString())))
- .write(writer).toString();
- fail("Expected an exception, got a String value");
- } catch (JSONException e) {
- assertEquals("Unable to write JSONObject value for key: someKey", e.getMessage());
- } catch(Exception e) {
- fail("Expected JSONException");
- } finally {
- try {
- writer.close();
- } catch (Exception e) {}
- }
-
- // test a more slightly complex object
- writer = new StringWriter();
- try {
- new JSONObject()
- .put("somethingElse", "a value")
- .put("someKey", new JSONArray()
- .put(new JSONObject().put("key1", new BrokenToString()))
- .put(12345)
- )
- .write(writer).toString();
- fail("Expected an exception, got a String value");
- } catch (JSONException e) {
- assertEquals("Unable to write JSONObject value for key: someKey", e.getMessage());
- } catch(Exception e) {
- fail("Expected JSONException");
- } finally {
- try {
- writer.close();
- } catch (Exception e) {}
- }
- Util.checkJSONObjectMaps(jsonObject);
- }
-
-
- /**
- * Exercise the JSONObject write() method
- */
-/*
- @Test
- public void writeAppendable() {
- String str = "{\"key1\":\"value1\",\"key2\":[1,2,3]}";
- String expectedStr = str;
- JSONObject jsonObject = new JSONObject(str);
- StringBuilder stringBuilder = new StringBuilder();
- Appendable appendable = jsonObject.write(stringBuilder);
- String actualStr = appendable.toString();
- assertTrue("write() expected " +expectedStr+
- " but found " +actualStr,
- expectedStr.equals(actualStr));
- }
-*/
-
- /**
- * Exercise the JSONObject write(Writer, int, int) method
- */
- @Test
- public void write3Param() throws IOException {
- String str0 = "{\"key1\":\"value1\",\"key2\":[1,false,3.14]}";
- String str2 =
- "{\n" +
- " \"key1\": \"value1\",\n" +
- " \"key2\": [\n" +
- " 1,\n" +
- " false,\n" +
- " 3.14\n" +
- " ]\n" +
- " }";
- JSONObject jsonObject = new JSONObject(str0);
- StringWriter stringWriter = new StringWriter();
- try {
- String actualStr = jsonObject.write(stringWriter,0,0).toString();
-
- assertEquals("length", str0.length(), actualStr.length());
- assertTrue("key1", actualStr.contains("\"key1\":\"value1\""));
- assertTrue("key2", actualStr.contains("\"key2\":[1,false,3.14]"));
- } finally {
- try {
- stringWriter.close();
- } catch (Exception e) {}
- }
-
- stringWriter = new StringWriter();
- try {
- String actualStr = jsonObject.write(stringWriter,2,1).toString();
-
- assertEquals("length", str2.length(), actualStr.length());
- assertTrue("key1", actualStr.contains(" \"key1\": \"value1\""));
- assertTrue("key2", actualStr.contains(" \"key2\": [\n" +
- " 1,\n" +
- " false,\n" +
- " 3.14\n" +
- " ]")
- );
- } finally {
- try {
- stringWriter.close();
- } catch (Exception e) {}
- }
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Exercise the JSONObject write(Appendable, int, int) method
- */
-/*
- @Test
- public void write3ParamAppendable() {
- String str0 = "{\"key1\":\"value1\",\"key2\":[1,false,3.14]}";
- String str2 =
- "{\n" +
- " \"key1\": \"value1\",\n" +
- " \"key2\": [\n" +
- " 1,\n" +
- " false,\n" +
- " 3.14\n" +
- " ]\n" +
- " }";
- JSONObject jsonObject = new JSONObject(str0);
- String expectedStr = str0;
- StringBuilder stringBuilder = new StringBuilder();
- Appendable appendable = jsonObject.write(stringBuilder,0,0);
- String actualStr = appendable.toString();
- assertEquals(expectedStr, actualStr);
-
- expectedStr = str2;
- stringBuilder = new StringBuilder();
- appendable = jsonObject.write(stringBuilder,2,1);
- actualStr = appendable.toString();
- assertEquals(expectedStr, actualStr);
- }
-*/
-
- /**
- * Exercise the JSONObject equals() method
- */
- @Test
- public void equals() {
- String str = "{\"key\":\"value\"}";
- JSONObject aJsonObject = new JSONObject(str);
- assertTrue("Same JSONObject should be equal to itself",
- aJsonObject.equals(aJsonObject));
- Util.checkJSONObjectMaps(aJsonObject);
- }
-
- /**
- * JSON null is not the same as Java null. This test examines the differences
- * in how they are handled by JSON-java.
- */
- @Test
- public void jsonObjectNullOperations() {
- /**
- * The Javadoc for JSONObject.NULL states:
- * "JSONObject.NULL is equivalent to the value that JavaScript calls null,
- * whilst Java's null is equivalent to the value that JavaScript calls
- * undefined."
- *
- * Standard ECMA-262 6th Edition / June 2015 (included to help explain the javadoc):
- * undefined value: primitive value used when a variable has not been assigned a value
- * Undefined type: type whose sole value is the undefined value
- * null value: primitive value that represents the intentional absence of any object value
- * Null type: type whose sole value is the null value
- * Java SE8 language spec (included to help explain the javadoc):
- * The Kinds of Types and Values ...
- * There is also a special null type, the type of the expression null, which has no name.
- * Because the null type has no name, it is impossible to declare a variable of the null
- * type or to cast to the null type. The null reference is the only possible value of an
- * expression of null type. The null reference can always be assigned or cast to any reference type.
- * In practice, the programmer can ignore the null type and just pretend that null is merely
- * a special literal that can be of any reference type.
- * Extensible Markup Language (XML) 1.0 Fifth Edition / 26 November 2008
- * No mention of null
- * ECMA-404 1st Edition / October 2013:
- * JSON Text ...
- * These are three literal name tokens: ...
- * null
- *
- * There seems to be no best practice to follow, it's all about what we
- * want the code to do.
- */
-
- // add JSONObject.NULL then convert to string in the manner of XML.toString()
- JSONObject jsonObjectJONull = new JSONObject();
- Object obj = JSONObject.NULL;
- jsonObjectJONull.put("key", obj);
- Object value = jsonObjectJONull.opt("key");
- assertTrue("opt() JSONObject.NULL should find JSONObject.NULL",
- obj.equals(value));
- value = jsonObjectJONull.get("key");
- assertTrue("get() JSONObject.NULL should find JSONObject.NULL",
- obj.equals(value));
- if (value == null) {
- value = "";
- }
- String string = value instanceof String ? (String)value : null;
- assertTrue("XML toString() should convert JSONObject.NULL to null",
- string == null);
-
- // now try it with null
- JSONObject jsonObjectNull = new JSONObject();
- obj = null;
- jsonObjectNull.put("key", obj);
- value = jsonObjectNull.opt("key");
- assertNull("opt() null should find null", value);
- // what is this trying to do? It appears to test absolutely nothing...
+ /**
+ * Regular Expression Pattern that matches JSON Numbers. This is primarily used
+ * for output to guarantee that we are always writing valid JSON.
+ */
+ static final Pattern NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?");
+
+ @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).put("key3", string1);
+
+ JSONObject obj2 = new JSONObject().put("key1", "abc").put("key2", 3).put("key3", string1);
+
+ JSONObject obj3 = new JSONObject().put("key1", "abc").put("key2", 2).put("key3", new String(string1));
+
+ 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
+ final String[] testData = new String[] { null, "", "100", "-100", "abc123", "012345", "100.5e199", "-100.5e199",
+ "DEADBEEF", "0xDEADBEEF", "1234567890.1234567890", "-1234567890.1234567890",
+ "adloghakuidghauiehgauioehgdkjfb nsruoh aeu noerty384 nkljfgh "
+ + "395h tdfn kdz8yt3 4hkls gn.ey85 4hzfhnz.o8y5a84 onvklt "
+ + "yh389thub nkz8y49lihv al4itlaithknty8hnbl"
+ // long (in length) number sequences with invalid data at the end of the
+ // string offer very poor performance for the REGEX.
+ ,
+ "123467890123467890123467890123467890123467890123467890123467"
+ + "8901234678901234678901234678901234678901234678901234678"
+ + "9012346789012346789012346789012346789012346789012346789" + "0a" };
+ final int testDataLength = testData.length;
+ /**
+ * Changed to 1000 for faster test runs
+ */
+ // final int iterations = 1000000;
+ final int iterations = 1000;
+
+ // 10 million iterations 1,000,000 * 10 (currently 100,000)
+ long startTime = System.nanoTime();
+ for (int i = 0; i < iterations; i++) {
+ for (int j = 0; j < testDataLength; j++) {
+ try {
+ BigDecimal v1 = new BigDecimal(testData[j]);
+ v1.signum();
+ } catch (Exception ignore) {
+ // do nothing
+ }
+ }
+ }
+ final long elapsedNano1 = System.nanoTime() - startTime;
+ System.out.println("new BigDecimal(testData[]) : " + elapsedNano1 / 1000000 + " ms");
+
+ startTime = System.nanoTime();
+ for (int i = 0; i < iterations; i++) {
+ for (int j = 0; j < testDataLength; j++) {
+ try {
+ boolean v2 = NUMBER_PATTERN.matcher(testData[j]).matches();
+ assert v2 == !!v2;
+ } catch (Exception ignore) {
+ // do nothing
+ }
+ }
+ }
+ final long elapsedNano2 = System.nanoTime() - startTime;
+ System.out.println("NUMBER_PATTERN.matcher(testData[]).matches() : " + elapsedNano2 / 1000000 + " ms");
+ // don't assert normally as the testing is machine dependent.
+ // assertTrue("Expected Pattern matching to be faster than BigDecimal
+ // constructor",elapsedNano2) (JsonPath.read(doc, "$"))).size() == 4);
+ assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObjectByName.query("/falseKey")));
+ assertTrue("expected \"nullKey\":null", JSONObject.NULL.equals(jsonObjectByName.query("/nullKey")));
+ assertTrue("expected \"stringKey\":\"hello world!\"",
+ "hello world!".equals(jsonObjectByName.query("/stringKey")));
+ assertTrue("expected \"doubleKey\":-23.45e67",
+ new BigDecimal("-23.45e67").equals(jsonObjectByName.query("/doubleKey")));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(jsonObject, jsonObjectByName)));
+ }
+
+ /**
+ * JSONObjects can be built from a Map. In this test the map is
+ * null. the JSONObject(JsonTokener) ctor is not tested directly since it
+ * already has full coverage from other tests.
+ */
+ @Test
+ public void jsonObjectByNullMap() {
+ Map map = null;
+ JSONObject jsonObject = new JSONObject(map);
+ assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * JSONObjects can be built from a Map. In this test all of the
+ * map entries are valid JSON types.
+ */
+ @Test
+ public void jsonObjectByMap() {
+ Map map = new HashMap();
+ map.put("trueKey", 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", Long.valueOf(42));
+ map.put("doubleKey", Double.valueOf(-23.45e67));
+ JSONObject jsonObject = new JSONObject(map);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 6 top level items", ((Map, ?>) (JsonPath.read(doc, "$"))).size() == 6);
+ assertTrue("expected \"trueKey\":true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
+ assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
+ assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObject.query("/stringKey")));
+ assertTrue("expected \"escapeStringKey\":\"h\be\tllo w\u1234orld!\"",
+ "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey")));
+ assertTrue("expected \"doubleKey\":-23.45e67",
+ Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey")));
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Verifies that the constructor has backwards compatability with RAW types
+ * pre-java5.
+ */
+ @Test
+ public void verifyConstructor() {
+
+ final JSONObject expected = new JSONObject("{\"myKey\":10}");
+
+ @SuppressWarnings("rawtypes")
+ Map myRawC = Collections.singletonMap("myKey", Integer.valueOf(10));
+ JSONObject jaRaw = new JSONObject(myRawC);
+
+ Map myCStrObj = Collections.singletonMap("myKey", (Object) Integer.valueOf(10));
+ JSONObject jaStrObj = new JSONObject(myCStrObj);
+
+ Map myCStrInt = Collections.singletonMap("myKey", Integer.valueOf(10));
+ JSONObject jaStrInt = new JSONObject(myCStrInt);
+
+ Map, ?> myCObjObj = Collections.singletonMap((Object) "myKey", (Object) Integer.valueOf(10));
+ JSONObject jaObjObj = new JSONObject(myCObjObj);
+
+ assertTrue("The RAW Collection should give me the same as the Typed Collection", expected.similar(jaRaw));
+ assertTrue("The RAW Collection should give me the same as the Typed Collection", expected.similar(jaStrObj));
+ assertTrue("The RAW Collection should give me the same as the Typed Collection", expected.similar(jaStrInt));
+ assertTrue("The RAW Collection should give me the same as the Typed Collection", expected.similar(jaObjObj));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(jaRaw, jaStrObj, jaStrInt, jaObjObj)));
+ }
+
+ /**
+ * Tests Number serialization.
+ */
+ @Test
+ public void verifyNumberOutput() {
+ /**
+ * MyNumberContainer is a POJO, so call JSONObject(bean), which builds a map of
+ * getter names/values The only getter is getMyNumber (key=myNumber), whose
+ * return value is MyNumber. MyNumber extends Number, but is not recognized as
+ * such by wrap() per current implementation, so wrap() returns the default new
+ * JSONObject(bean). The only getter is getNumber (key=number), whose return
+ * value is BigDecimal(42).
+ */
+ JSONObject jsonObject0 = new JSONObject(new MyNumberContainer());
+ String actual = jsonObject0.toString();
+ String expected = "{\"myNumber\":{\"number\":42}}";
+ assertEquals("Equal", expected, actual);
+
+ /**
+ * JSONObject.put() handles objects differently than the bean constructor. Where
+ * the bean ctor wraps objects before placing them in the map, put() inserts the
+ * object without wrapping. In this case, a MyNumber instance is the value. The
+ * MyNumber.toString() method is responsible for returning a reasonable value:
+ * the string '42'.
+ */
+ JSONObject jsonObject1 = new JSONObject();
+ jsonObject1.put("myNumber", new MyNumber());
+ actual = jsonObject1.toString();
+ expected = "{\"myNumber\":42}";
+ assertEquals("Equal", expected, actual);
+
+ /**
+ * Calls the JSONObject(Map) ctor, which calls wrap() for values. AtomicInteger
+ * is a Number, but is not recognized by wrap(), per current implementation.
+ * However, the type is 'java.util.concurrent.atomic', so due to the 'java'
+ * prefix, wrap() inserts the value as a string. That is why 42 comes back
+ * wrapped in quotes.
+ */
+ JSONObject jsonObject2 = new JSONObject(Collections.singletonMap("myNumber", new AtomicInteger(42)));
+ actual = jsonObject2.toString();
+ expected = "{\"myNumber\":\"42\"}";
+ assertEquals("Equal", expected, actual);
+
+ /**
+ * JSONObject.put() inserts the AtomicInteger directly into the map not calling
+ * wrap(). In toString()->write()->writeValue(), AtomicInteger is recognized as
+ * a Number, and converted via numberToString() into the unquoted string '42'.
+ */
+ JSONObject jsonObject3 = new JSONObject();
+ jsonObject3.put("myNumber", new AtomicInteger(42));
+ actual = jsonObject3.toString();
+ expected = "{\"myNumber\":42}";
+ assertEquals("Equal", expected, actual);
+
+ /**
+ * Calls the JSONObject(Map) ctor, which calls wrap() for values. Fraction is a
+ * Number, but is not recognized by wrap(), per current implementation. As a
+ * POJO, Fraction is handled as a bean and inserted into a contained JSONObject.
+ * It has 2 getters, for numerator and denominator.
+ */
+ JSONObject 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 map not calling
+ * wrap(). In toString()->write()->writeValue(), Fraction is recognized as a
+ * Number, and converted via numberToString() into the unquoted string '4/2'.
+ * But the BigDecimal sanity check fails, so writeValue() defaults to returning
+ * a safe JSON quoted string. Pretty slick!
+ */
+ JSONObject 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)));
+ }
+
+ /**
+ * Verifies that the put Collection has backwards compatibility with RAW types
+ * pre-java5.
+ */
+ @Test
+ public void verifyPutCollection() {
+
+ final JSONObject expected = new JSONObject("{\"myCollection\":[10]}");
+
+ @SuppressWarnings("rawtypes")
+ Collection myRawC = Collections.singleton(Integer.valueOf(10));
+ JSONObject jaRaw = new JSONObject();
+ jaRaw.put("myCollection", myRawC);
+
+ Collection myCObj = Collections.singleton((Object) Integer.valueOf(10));
+ JSONObject jaObj = new JSONObject();
+ jaObj.put("myCollection", myCObj);
+
+ Collection myCInt = Collections.singleton(Integer.valueOf(10));
+ JSONObject jaInt = new JSONObject();
+ jaInt.put("myCollection", myCInt);
+
+ assertTrue("The RAW Collection should give me the same as the Typed Collection", expected.similar(jaRaw));
+ assertTrue("The RAW Collection should give me the same as the Typed Collection", expected.similar(jaObj));
+ assertTrue("The RAW Collection should give me the same as the Typed Collection", expected.similar(jaInt));
+
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(jaRaw, jaObj, jaInt)));
+ }
+
+ /**
+ * Verifies that the put Map has backwards compatibility with RAW types
+ * pre-java5.
+ */
+ @Test
+ public void verifyPutMap() {
+
+ final JSONObject expected = new JSONObject("{\"myMap\":{\"myKey\":10}}");
+
+ @SuppressWarnings("rawtypes")
+ Map myRawC = Collections.singletonMap("myKey", Integer.valueOf(10));
+ JSONObject jaRaw = new JSONObject();
+ jaRaw.put("myMap", myRawC);
+
+ Map myCStrObj = Collections.singletonMap("myKey", (Object) Integer.valueOf(10));
+ JSONObject jaStrObj = new JSONObject();
+ jaStrObj.put("myMap", myCStrObj);
+
+ Map myCStrInt = Collections.singletonMap("myKey", Integer.valueOf(10));
+ JSONObject jaStrInt = new JSONObject();
+ jaStrInt.put("myMap", myCStrInt);
+
+ Map, ?> myCObjObj = Collections.singletonMap((Object) "myKey", (Object) Integer.valueOf(10));
+ JSONObject jaObjObj = new JSONObject();
+ jaObjObj.put("myMap", myCObjObj);
+
+ assertTrue("The RAW Collection should give me the same as the Typed Collection", expected.similar(jaRaw));
+ assertTrue("The RAW Collection should give me the same as the Typed Collection", expected.similar(jaStrObj));
+ assertTrue("The RAW Collection should give me the same as the Typed Collection", expected.similar(jaStrInt));
+ assertTrue("The RAW Collection should give me the same as the Typed Collection", expected.similar(jaObjObj));
+
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(jaRaw, jaStrObj, jaStrInt, jaStrObj)));
+ }
+
+ /**
+ * JSONObjects can be built from a Map. In this test the map
+ * entries are not valid JSON types. The actual conversion is kind of
+ * interesting.
+ */
+ @Test
+ public void jsonObjectByMapWithUnsupportedValues() {
+ Map jsonMap = new HashMap();
+ // Just insert some random objects
+ jsonMap.put("key1", new CDL());
+ jsonMap.put("key2", new Exception());
+
+ JSONObject jsonObject = new JSONObject(jsonMap);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 2 top level items", ((Map, ?>) (JsonPath.read(doc, "$"))).size() == 2);
+ assertTrue("expected 0 key1 items", ((Map, ?>) (JsonPath.read(doc, "$.key1"))).size() == 0);
+ assertTrue("expected \"key2\":java.lang.Exception", "java.lang.Exception".equals(jsonObject.query("/key2")));
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * JSONObjects can be built from a Map. In this test one of the
+ * map values is null
+ */
+ @Test
+ public void jsonObjectByMapWithNullValue() {
+ Map map = new HashMap();
+ map.put("trueKey", 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", Long.valueOf(42));
+ map.put("doubleKey", Double.valueOf(-23.45e67));
+ JSONObject jsonObject = new JSONObject(map);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 6 top level items", ((Map, ?>) (JsonPath.read(doc, "$"))).size() == 6);
+ assertTrue("expected \"trueKey\":true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
+ assertTrue("expected \"falseKey\":false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
+ assertTrue("expected \"stringKey\":\"hello world!\"", "hello world!".equals(jsonObject.query("/stringKey")));
+ assertTrue("expected \"escapeStringKey\":\"h\be\tllo w\u1234orld!\"",
+ "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey")));
+ assertTrue("expected \"intKey\":42", Long.valueOf("42").equals(jsonObject.query("/intKey")));
+ assertTrue("expected \"doubleKey\":-23.45e67",
+ Double.valueOf("-23.45e67").equals(jsonObject.query("/doubleKey")));
+ 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"));
+ }
+
+ /**
+ * JSONObject built from a bean. In this case all but one of the bean getters
+ * return valid JSON types
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void jsonObjectByBean1() {
+ /**
+ * Default access classes have to be mocked since JSONObject, which is not in
+ * the same package, cannot call MyBean methods by reflection.
+ */
+ MyBean myBean = mock(MyBean.class);
+ when(myBean.getDoubleKey()).thenReturn(-23.45e7);
+ when(myBean.getIntKey()).thenReturn(42);
+ when(myBean.getStringKey()).thenReturn("hello world!");
+ when(myBean.getEscapeStringKey()).thenReturn("h\be\tllo w\u1234orld!");
+ when(myBean.isTrueKey()).thenReturn(true);
+ when(myBean.isFalseKey()).thenReturn(false);
+ when(myBean.getStringReaderKey()).thenReturn(new StringReader("") {
+ });
+
+ JSONObject jsonObject = new JSONObject(myBean);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 8 top level items", ((Map, ?>) (JsonPath.read(doc, "$"))).size() == 8);
+ assertTrue("expected 0 items in stringReaderKey",
+ ((Map, ?>) (JsonPath.read(doc, "$.stringReaderKey"))).size() == 0);
+ assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
+ assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
+ assertTrue("expected hello world!", "hello world!".equals(jsonObject.query("/stringKey")));
+ assertTrue("expected h\be\tllo w\u1234orld!",
+ "h\be\tllo w\u1234orld!".equals(jsonObject.query("/escapeStringKey")));
+ assertTrue("expected 42", Integer.valueOf("42").equals(jsonObject.query("/intKey")));
+ assertTrue("expected -23.45e7", Double.valueOf("-23.45e7").equals(jsonObject.query("/doubleKey")));
+ // sorry, mockito artifact
+ assertTrue("expected 2 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);
+ }
+
+ /**
+ * JSONObject built from a bean that has custom field names.
+ */
+ @Test
+ public void jsonObjectByBean2() {
+ JSONObject jsonObject = new JSONObject(new MyBeanCustomName());
+ assertNotNull(jsonObject);
+ assertEquals("Wrong number of keys found:", 5, jsonObject.keySet().size());
+ assertFalse("Normal field name (someString) processing did not work", jsonObject.has("someString"));
+ assertFalse("Normal field name (myDouble) processing did not work", jsonObject.has("myDouble"));
+ assertFalse("Normal field name (someFloat) processing did not work", jsonObject.has("someFloat"));
+ assertFalse("Ignored field not found!", jsonObject.has("ignoredInt"));
+ // getSomeInt() has no user-defined annotation
+ assertTrue("Normal field name (someInt) should have been found", jsonObject.has("someInt"));
+ // the user-defined annotation does not replace any value, so someLong should be
+ // found
+ assertTrue("Normal field name (someLong) should have been found", jsonObject.has("someLong"));
+ // myStringField replaces someString property name via user-defined annotation
+ assertTrue("Overridden String field name (myStringField) should have been found",
+ jsonObject.has("myStringField"));
+ // weird name replaces myDouble property name via user-defined annotation
+ assertTrue(
+ "Overridden String field name (Some Weird NAme that Normally Wouldn't be possible!) should have been found",
+ jsonObject.has("Some Weird NAme that Normally Wouldn't be possible!"));
+ // InterfaceField replaces someFloat property name via user-defined annotation
+ assertTrue("Overridden String field name (InterfaceField) should have been found",
+ jsonObject.has("InterfaceField"));
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * JSONObject built from a bean that has custom field names inherited from a
+ * parent class.
+ */
+ @Test
+ public void jsonObjectByBean3() {
+ JSONObject jsonObject = new JSONObject(new MyBeanCustomNameSubClass());
+ assertNotNull(jsonObject);
+ assertEquals("Wrong number of keys found:", 7, jsonObject.keySet().size());
+ assertFalse("Normal int field name (someInt) found, but was overridden", jsonObject.has("someInt"));
+ assertFalse("Normal field name (myDouble) processing did not work", jsonObject.has("myDouble"));
+ // myDouble was replaced by weird name, and then replaced again by
+ // AMoreNormalName via user-defined annotation
+ assertFalse(
+ "Overridden String field name (Some Weird NAme that Normally Wouldn't be possible!) should not be FOUND!",
+ jsonObject.has("Some Weird NAme that Normally Wouldn't be possible!"));
+ assertFalse("Normal field name (someFloat) found, but was overridden", jsonObject.has("someFloat"));
+ assertFalse("Ignored field found! but was overridden", jsonObject.has("ignoredInt"));
+ // shouldNotBeJSON property name was first ignored, then replaced by
+ // ShouldBeIgnored via user-defined annotations
+ assertFalse("Ignored field at the same level as forced name should not have been found",
+ jsonObject.has("ShouldBeIgnored"));
+ // able property name was replaced by Getable via user-defined annotation
+ assertFalse("Normally ignored field (able) with explicit property name should not have been found",
+ jsonObject.has("able"));
+ // property name someInt was replaced by newIntFieldName via user-defined
+ // annotation
+ assertTrue("Overridden int field name (newIntFieldName) should have been found",
+ jsonObject.has("newIntFieldName"));
+ // property name someLong was not replaced via user-defined annotation
+ assertTrue("Normal field name (someLong) should have been found", jsonObject.has("someLong"));
+ // property name someString was replaced by myStringField via user-defined
+ // annotation
+ assertTrue("Overridden String field name (myStringField) should have been found",
+ jsonObject.has("myStringField"));
+ // property name myDouble was replaced by a weird name, followed by
+ // AMoreNormalName via user-defined annotations
+ assertTrue("Overridden double field name (AMoreNormalName) should have been found",
+ jsonObject.has("AMoreNormalName"));
+ // property name someFloat was replaced by InterfaceField via user-defined
+ // annotation
+ assertTrue("Overridden String field name (InterfaceField) should have been found",
+ jsonObject.has("InterfaceField"));
+ // property name ignoredInt was replaced by none, followed by forcedInt via
+ // user-defined annotations
+ assertTrue("Forced field should have been found!", jsonObject.has("forcedInt"));
+ // property name able was replaced by Getable via user-defined annotation
+ assertTrue("Overridden boolean field name (Getable) should have been found", jsonObject.has("Getable"));
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * A bean is also an object. But in order to test the JSONObject ctor that takes
+ * an object and a list of names, this particular bean needs some public data
+ * members, which have been added to the class.
+ */
+ @Test
+ public void jsonObjectByObjectAndNames() {
+ String[] keys = { "publicString", "publicInt" };
+ // just need a class that has public data members
+ MyPublicClass myPublicClass = new MyPublicClass();
+ JSONObject jsonObject = new JSONObject(myPublicClass, keys);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 2 top level items", ((Map, ?>) (JsonPath.read(doc, "$"))).size() == 2);
+ assertTrue("expected \"publicString\":\"abc\"", "abc".equals(jsonObject.query("/publicString")));
+ assertTrue("expected \"publicInt\":42", Integer.valueOf(42).equals(jsonObject.query("/publicInt")));
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Exercise the JSONObject from resource bundle functionality. The test resource
+ * bundle is uncomplicated, but provides adequate test coverage.
+ */
+ @Test
+ public void jsonObjectByResourceBundle() {
+ JSONObject jsonObject = new JSONObject("org.json.junit.data.StringsResourceBundle", Locale.getDefault());
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 2 top level items", ((Map, ?>) (JsonPath.read(doc, "$"))).size() == 2);
+ assertTrue("expected 2 greetings items", ((Map, ?>) (JsonPath.read(doc, "$.greetings"))).size() == 2);
+ assertTrue("expected \"hello\":\"Hello, \"", "Hello, ".equals(jsonObject.query("/greetings/hello")));
+ assertTrue("expected \"world\":\"World!\"", "World!".equals(jsonObject.query("/greetings/world")));
+ assertTrue("expected 2 farewells items", ((Map, ?>) (JsonPath.read(doc, "$.farewells"))).size() == 2);
+ assertTrue("expected \"later\":\"Later, \"", "Later, ".equals(jsonObject.query("/farewells/later")));
+ assertTrue("expected \"world\":\"World!\"", "Alligator!".equals(jsonObject.query("/farewells/gator")));
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Exercise the JSONObject.accumulate() method
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void jsonObjectAccumulate() {
+
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.accumulate("myArray", true);
+ jsonObject.accumulate("myArray", false);
+ jsonObject.accumulate("myArray", "hello world!");
+ jsonObject.accumulate("myArray", "h\be\tllo w\u1234orld!");
+ jsonObject.accumulate("myArray", 42);
+ jsonObject.accumulate("myArray", -23.45e7);
+ // include an unsupported object for coverage
+ try {
+ jsonObject.accumulate("myArray", Double.NaN);
+ fail("Expected exception");
+ } catch (JSONException ignored) {
+ }
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 1 top level item", ((Map, ?>) (JsonPath.read(doc, "$"))).size() == 1);
+ assertTrue("expected 6 myArray items", ((List>) (JsonPath.read(doc, "$.myArray"))).size() == 6);
+ assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/myArray/0")));
+ assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/myArray/1")));
+ assertTrue("expected hello world!", "hello world!".equals(jsonObject.query("/myArray/2")));
+ assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/myArray/3")));
+ assertTrue("expected 42", Integer.valueOf(42).equals(jsonObject.query("/myArray/4")));
+ assertTrue("expected -23.45e7", Double.valueOf(-23.45e7).equals(jsonObject.query("/myArray/5")));
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Exercise the JSONObject append() functionality
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void jsonObjectAppend() {
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.append("myArray", true);
+ jsonObject.append("myArray", false);
+ jsonObject.append("myArray", "hello world!");
+ jsonObject.append("myArray", "h\be\tllo w\u1234orld!");
+ jsonObject.append("myArray", 42);
+ jsonObject.append("myArray", -23.45e7);
+ // include an unsupported object for coverage
+ try {
+ jsonObject.append("myArray", Double.NaN);
+ fail("Expected exception");
+ } catch (JSONException ignored) {
+ }
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 1 top level item", ((Map, ?>) (JsonPath.read(doc, "$"))).size() == 1);
+ assertTrue("expected 6 myArray items", ((List>) (JsonPath.read(doc, "$.myArray"))).size() == 6);
+ assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/myArray/0")));
+ assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/myArray/1")));
+ assertTrue("expected hello world!", "hello world!".equals(jsonObject.query("/myArray/2")));
+ assertTrue("expected h\be\tllo w\u1234orld!", "h\be\tllo w\u1234orld!".equals(jsonObject.query("/myArray/3")));
+ assertTrue("expected 42", Integer.valueOf(42).equals(jsonObject.query("/myArray/4")));
+ assertTrue("expected -23.45e7", Double.valueOf(-23.45e7).equals(jsonObject.query("/myArray/5")));
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Exercise the JSONObject doubleToString() method
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void jsonObjectDoubleToString() {
+ String[] expectedStrs = { "1", "1", "-23.4", "-2.345E68", "null", "null" };
+ Double[] doubles = { 1.0, 00001.00000, -23.4, -23.45e67, Double.NaN, Double.NEGATIVE_INFINITY };
+ for (int i = 0; i < expectedStrs.length; ++i) {
+ String actualStr = JSONObject.doubleToString(doubles[i]);
+ assertTrue("value expected [" + expectedStrs[i] + "] found [" + actualStr + "]",
+ expectedStrs[i].equals(actualStr));
+ }
+ }
+
+ /**
+ * Exercise some JSONObject get[type] and opt[type] methods
+ */
+ @Test
+ public void jsonObjectValues() {
+ String str = "{" + "\"trueKey\":true," + "\"falseKey\":false," + "\"trueStrKey\":\"true\","
+ + "\"falseStrKey\":\"false\"," + "\"stringKey\":\"hello world!\"," + "\"intKey\":42,"
+ + "\"intStrKey\":\"43\"," + "\"longKey\":1234567890123456789,"
+ + "\"longStrKey\":\"987654321098765432\"," + "\"doubleKey\":-23.45e7,"
+ + "\"doubleStrKey\":\"00001.000\","
+ + "\"BigDecimalStrKey\":\"19007199254740993.35481234487103587486413587843213584\","
+ + "\"negZeroKey\":-0.0," + "\"negZeroStrKey\":\"-0.0\"," + "\"arrayKey\":[0,1,2],"
+ + "\"objectKey\":{\"myKey\":\"myVal\"}" + "}";
+ JSONObject jsonObject = new JSONObject(str);
+ assertTrue("trueKey should be true", jsonObject.getBoolean("trueKey"));
+ assertTrue("opt trueKey should be true", jsonObject.optBoolean("trueKey"));
+ assertTrue("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!"));
+ assertTrue("doubleKey should be double", jsonObject.getDouble("doubleKey") == -23.45e7);
+ assertTrue("doubleStrKey should be double", jsonObject.getDouble("doubleStrKey") == 1);
+ assertTrue("doubleKey can be float", jsonObject.getFloat("doubleKey") == -23.45e7f);
+ assertTrue("doubleStrKey can be float", jsonObject.getFloat("doubleStrKey") == 1f);
+ assertTrue("opt doubleKey should be double", jsonObject.optDouble("doubleKey") == -23.45e7);
+ assertTrue("opt doubleKey with Default should be double",
+ jsonObject.optDouble("doubleStrKey", Double.NaN) == 1);
+ assertTrue("opt 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", jsonObject.get("negZeroKey") instanceof Double);
+ assertTrue("optNumber negZeroKey should return Double", jsonObject.optNumber("negZeroKey") instanceof Double);
+ assertTrue("optNumber negZeroStrKey should return Double",
+ jsonObject.optNumber("negZeroStrKey") instanceof Double);
+ assertTrue("opt negZeroKey should be double", Double.compare(jsonObject.optDouble("negZeroKey"), -0.0d) == 0);
+ assertTrue("opt negZeroStrKey with Default should be double",
+ Double.compare(jsonObject.optDouble("negZeroStrKey"), -0.0d) == 0);
+ assertTrue("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",
+ Double.compare(jsonObject.optNumber("negZeroStrKey").doubleValue(), -0.0d) == 0);
+ assertTrue("optFloat doubleKey should be float", jsonObject.optFloat("doubleKey") == -23.45e7f);
+ assertTrue("optFloat doubleKey with Default should be float",
+ jsonObject.optFloat("doubleStrKey", Float.NaN) == 1f);
+ assertTrue("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", jsonObject.getInt("intStrKey") == 43);
+ assertTrue("longKey should be long", jsonObject.getLong("longKey") == 1234567890123456789L);
+ assertTrue("opt longKey should be long", jsonObject.optLong("longKey") == 1234567890123456789L);
+ assertTrue("opt longKey with default should be long", jsonObject.optLong("longKey", 0) == 1234567890123456789L);
+ assertTrue("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", jsonObject.optNumber("intKey") instanceof Integer);
+ assertTrue("optNumber long should return Long", jsonObject.optNumber("longKey") instanceof Long);
+ assertTrue("optNumber double should return BigDecimal",
+ jsonObject.optNumber("doubleKey") instanceof BigDecimal);
+ assertTrue("optNumber Str int should return Integer", jsonObject.optNumber("intStrKey") instanceof Integer);
+ assertTrue("optNumber Str long should return Long", jsonObject.optNumber("longStrKey") instanceof Long);
+ assertTrue("optNumber Str double should return BigDecimal",
+ jsonObject.optNumber("doubleStrKey") instanceof BigDecimal);
+ assertTrue("optNumber BigDecimalStrKey should return BigDecimal",
+ jsonObject.optNumber("BigDecimalStrKey") instanceof BigDecimal);
+ assertTrue("xKey should not exist", jsonObject.isNull("xKey"));
+ assertTrue("stringKey should exist", jsonObject.has("stringKey"));
+ assertTrue("opt stringKey should string", jsonObject.optString("stringKey").equals("hello world!"));
+ assertTrue("opt stringKey with default should string",
+ jsonObject.optString("stringKey", "not found").equals("hello world!"));
+ JSONArray jsonArray = jsonObject.getJSONArray("arrayKey");
+ assertTrue("arrayKey should be JSONArray",
+ jsonArray.getInt(0) == 0 && jsonArray.getInt(1) == 1 && jsonArray.getInt(2) == 2);
+ jsonArray = jsonObject.optJSONArray("arrayKey");
+ assertTrue("opt arrayKey should be JSONArray",
+ jsonArray.getInt(0) == 0 && jsonArray.getInt(1) == 1 && jsonArray.getInt(2) == 2);
+ JSONObject jsonObjectInner = jsonObject.getJSONObject("objectKey");
+ assertTrue("objectKey should be JSONObject", jsonObjectInner.get("myKey").equals("myVal"));
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Check whether JSONObject handles large or high precision numbers correctly
+ */
+ @Test
+ public void stringToValueNumbersTest() {
+ assertTrue("-0 Should be a Double!", JSONObject.stringToValue("-0") instanceof Double);
+ assertTrue("-0.0 Should be a Double!", JSONObject.stringToValue("-0.0") instanceof Double);
+ assertTrue("'-' Should be a String!", JSONObject.stringToValue("-") instanceof String);
+ assertTrue("0.2 should be a BigDecimal!", JSONObject.stringToValue("0.2") instanceof BigDecimal);
+ assertTrue("Doubles should be BigDecimal, even when incorrectly converting floats!",
+ JSONObject.stringToValue(Double.valueOf("0.2f").toString()) instanceof BigDecimal);
+ /**
+ * This test documents a need for BigDecimal conversion.
+ */
+ Object obj = JSONObject.stringToValue("299792.457999999984");
+ assertTrue("does not evaluate to 299792.457999999984 BigDecimal!",
+ obj.equals(new BigDecimal("299792.457999999984")));
+ assertTrue("1 should be an Integer!", JSONObject.stringToValue("1") instanceof Integer);
+ assertTrue("Integer.MAX_VALUE should still be an Integer!",
+ JSONObject.stringToValue(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(Long.valueOf(Long.MAX_VALUE).toString()) instanceof Long);
+
+ 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")));
+ }
+
+ /**
+ * This test documents numeric values which could be numerically handled as
+ * BigDecimal or BigInteger. It helps determine what outputs will change if
+ * those types are supported.
+ */
+ @Test
+ public void jsonValidNumberValuesNeitherLongNorIEEE754Compatible() {
+ // Valid JSON Numbers, probably should return BigDecimal or BigInteger objects
+ String str = "{" + "\"numberWithDecimals\":299792.457999999984," + "\"largeNumber\":12345678901234567890,"
+ + "\"preciseNumber\":0.2000000000000000111," + "\"largeExponent\":-23.45e2327" + "}";
+ JSONObject jsonObject = new JSONObject(str);
+ // Comes back as a double, but loses precision
+ assertTrue("numberWithDecimals currently evaluates to double 299792.458",
+ jsonObject.get("numberWithDecimals").equals(new BigDecimal("299792.457999999984")));
+ Object obj = jsonObject.get("largeNumber");
+ assertTrue("largeNumber currently evaluates to BigInteger", new BigInteger("12345678901234567890").equals(obj));
+ // comes back as a double but loses precision
+ assertEquals("preciseNumber currently evaluates to double 0.2", 0.2, jsonObject.getDouble("preciseNumber"),
+ 0.0);
+ obj = jsonObject.get("largeExponent");
+ assertTrue("largeExponent should evaluate as a BigDecimal", new BigDecimal("-23.45e2327").equals(obj));
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * This test documents how JSON-Java handles invalid numeric input.
+ */
+ @Test
+ public void jsonInvalidNumberValues() {
+ // Number-notations supported by Java and invalid as JSON
+ String str = "{" + "\"hexNumber\":-0x123," + "\"tooManyZeros\":00," + "\"negativeInfinite\":-Infinity,"
+ + "\"negativeNaN\":-NaN," + "\"negativeFraction\":-.01," + "\"tooManyZerosFraction\":00.001,"
+ + "\"negativeHexFloat\":-0x1.fffp1," + "\"hexFloat\":0x1.0P-1074," + "\"floatIdentifier\":0.1f,"
+ + "\"doubleIdentifier\":0.1d" + "}";
+
+ // 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);
+ }
+ }
+
+ /**
+ * Tests how JSONObject get[type] handles incorrect types
+ */
+ @Test
+ public void jsonObjectNonAndWrongValues() {
+ String str = "{" + "\"trueKey\":true," + "\"falseKey\":false," + "\"trueStrKey\":\"true\","
+ + "\"falseStrKey\":\"false\"," + "\"stringKey\":\"hello world!\"," + "\"intKey\":42,"
+ + "\"intStrKey\":\"43\"," + "\"longKey\":1234567890123456789,"
+ + "\"longStrKey\":\"987654321098765432\"," + "\"doubleKey\":-23.45e7,"
+ + "\"doubleStrKey\":\"00001.000\"," + "\"arrayKey\":[0,1,2]," + "\"objectKey\":{\"myKey\":\"myVal\"}"
+ + "}";
+ JSONObject jsonObject = new JSONObject(str);
+ try {
+ jsonObject.getBoolean("nonKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("expecting an exception message", "JSONObject[\"nonKey\"] not found.", e.getMessage());
+ }
+ try {
+ jsonObject.getBoolean("stringKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"stringKey\"] is not a Boolean (class java.lang.String : hello world!).",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getString("nonKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message", "JSONObject[\"nonKey\"] not found.", e.getMessage());
+ }
+ try {
+ jsonObject.getString("trueKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"trueKey\"] is not a string (class java.lang.Boolean : true).", e.getMessage());
+ }
+ try {
+ jsonObject.getDouble("nonKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message", "JSONObject[\"nonKey\"] not found.", e.getMessage());
+ }
+ try {
+ jsonObject.getDouble("stringKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"stringKey\"] is not a double (class java.lang.String : hello world!).",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getFloat("nonKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message", "JSONObject[\"nonKey\"] not found.", e.getMessage());
+ }
+ try {
+ jsonObject.getFloat("stringKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"stringKey\"] is not a float (class java.lang.String : hello world!).",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getInt("nonKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message", "JSONObject[\"nonKey\"] not found.", e.getMessage());
+ }
+ try {
+ jsonObject.getInt("stringKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"stringKey\"] is not a int (class java.lang.String : hello world!).", e.getMessage());
+ }
+ try {
+ jsonObject.getLong("nonKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message", "JSONObject[\"nonKey\"] not found.", e.getMessage());
+ }
+ try {
+ jsonObject.getLong("stringKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"stringKey\"] is not a long (class java.lang.String : hello world!).", e.getMessage());
+ }
+ try {
+ jsonObject.getJSONArray("nonKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message", "JSONObject[\"nonKey\"] not found.", e.getMessage());
+ }
+ try {
+ jsonObject.getJSONArray("stringKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"stringKey\"] is not a JSONArray (class java.lang.String : hello world!).",
+ e.getMessage());
+ }
+ try {
+ jsonObject.getJSONObject("nonKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message", "JSONObject[\"nonKey\"] not found.", e.getMessage());
+ }
+ try {
+ jsonObject.getJSONObject("stringKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message",
+ "JSONObject[\"stringKey\"] is not a JSONObject (class java.lang.String : hello world!).",
+ e.getMessage());
+ }
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * This test documents an unexpected numeric behavior. A double that ends with
+ * .0 is parsed, serialized, then parsed again. On the second parse, it has
+ * become an int.
+ */
+ @Test
+ public void unexpectedDoubleToIntConversion() {
+ String key30 = "key30";
+ String key31 = "key31";
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put(key30, Double.valueOf(3.0));
+ jsonObject.put(key31, Double.valueOf(3.1));
+
+ assertTrue("3.0 should remain a double", jsonObject.getDouble(key30) == 3);
+ assertTrue("3.1 should remain a double", jsonObject.getDouble(key31) == 3.1);
+
+ // turns 3.0 into 3.
+ String serializedString = jsonObject.toString();
+ JSONObject deserialized = new JSONObject(serializedString);
+ assertTrue("3.0 is now an int", deserialized.get(key30) instanceof Integer);
+ assertTrue("3.0 can still be interpreted as a double", deserialized.getDouble(key30) == 3.0);
+ assertTrue("3.1 remains a double", deserialized.getDouble(key31) == 3.1);
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Document behaviors of big numbers. Includes both JSONObject and JSONArray
+ * tests
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void bigNumberOperations() {
+ /**
+ * JSONObject tries to parse BigInteger as a bean, but it only has one getter,
+ * getLowestBitSet(). The value is lost and an unhelpful value is stored. This
+ * should be fixed.
+ */
+ BigInteger bigInteger = new BigInteger("123456789012345678901234567890");
+ JSONObject 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", obj.equals(1));
+
+ /**
+ * JSONObject tries to parse BigDecimal as a bean, but it has no getters, The
+ * value is lost and no value is stored. This should be fixed.
+ */
+ BigDecimal bigDecimal = new BigDecimal("123456789012345678901234567890.12345678901234567890123456789");
+ JSONObject 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 jsonObject2 = new JSONObject();
+ jsonObject2.put("bigInt", bigInteger);
+ assertTrue("jsonObject.put() handles bigInt correctly", jsonObject2.get("bigInt").equals(bigInteger));
+ assertTrue("jsonObject.getBigInteger() handles bigInt correctly",
+ jsonObject2.getBigInteger("bigInt").equals(bigInteger));
+ assertTrue("jsonObject.optBigInteger() handles bigInt correctly",
+ jsonObject2.optBigInteger("bigInt", BigInteger.ONE).equals(bigInteger));
+ assertTrue("jsonObject serializes bigInt correctly",
+ jsonObject2.toString().equals("{\"bigInt\":123456789012345678901234567890}"));
+ assertTrue("BigInteger as BigDecimal", jsonObject2.getBigDecimal("bigInt").equals(new BigDecimal(bigInteger)));
+
+ JSONObject jsonObject3 = new JSONObject();
+ jsonObject3.put("bigDec", bigDecimal);
+ assertTrue("jsonObject.put() handles bigDec correctly", jsonObject3.get("bigDec").equals(bigDecimal));
+ assertTrue("jsonObject.getBigDecimal() handles bigDec correctly",
+ jsonObject3.getBigDecimal("bigDec").equals(bigDecimal));
+ assertTrue("jsonObject.optBigDecimal() handles bigDec correctly",
+ jsonObject3.optBigDecimal("bigDec", BigDecimal.ONE).equals(bigDecimal));
+ assertTrue("jsonObject serializes bigDec correctly", jsonObject3.toString()
+ .equals("{\"bigDec\":123456789012345678901234567890.12345678901234567890123456789}"));
+
+ assertTrue("BigDecimal as BigInteger", jsonObject3.getBigInteger("bigDec").equals(bigDecimal.toBigInteger()));
+ /**
+ * exercise some exceptions
+ */
+ try {
+ // bigInt key does not exist
+ jsonObject3.getBigDecimal("bigInt");
+ fail("expected an exeption");
+ } catch (JSONException ignored) {
+ }
+ obj = jsonObject3.optBigDecimal("bigInt", BigDecimal.ONE);
+ assertTrue("expected BigDecimal", obj.equals(BigDecimal.ONE));
+ jsonObject3.put("stringKey", "abc");
+ try {
+ jsonObject3.getBigDecimal("stringKey");
+ fail("expected an exeption");
+ } catch (JSONException ignored) {
+ }
+ obj = jsonObject3.optBigInteger("bigDec", BigInteger.ONE);
+ assertTrue("expected BigInteger", obj instanceof BigInteger);
+ assertEquals(bigDecimal.toBigInteger(), obj);
+
+ /**
+ * JSONObject.numberToString() works correctly, nothing to change.
+ */
+ String str = JSONObject.numberToString(bigInteger);
+ assertTrue("numberToString() handles bigInteger correctly", str.equals("123456789012345678901234567890"));
+ str = JSONObject.numberToString(bigDecimal);
+ assertTrue("numberToString() handles bigDecimal correctly",
+ str.equals("123456789012345678901234567890.12345678901234567890123456789"));
+
+ /**
+ * JSONObject.stringToValue() turns bigInt into an accurate string, and rounds
+ * bigDec. This incorrect, but users may have come to expect this behavior.
+ * Change would be marginally better, but might inconvenience users.
+ */
+ obj = JSONObject.stringToValue(bigInteger.toString());
+ assertTrue("stringToValue() turns bigInteger string into Number", obj instanceof Number);
+ obj = JSONObject.stringToValue(bigDecimal.toString());
+ assertTrue("stringToValue() changes bigDecimal Number", obj instanceof Number);
+
+ /**
+ * wrap() vs put() big number behavior is now the same.
+ */
+ // bigInt map ctor
+ Map map = new HashMap();
+ map.put("bigInt", bigInteger);
+ JSONObject 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 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 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 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 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", 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 {
+ jsonArray0.getBigInteger(2);
+ fail("should not be able to get big int");
+ } catch (Exception ignored) {
+ }
+ try {
+ jsonArray0.getBigDecimal(2);
+ fail("should not be able to get big dec");
+ } catch (Exception ignored) {
+ }
+ 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 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 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"));
+ 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"));
+ // bigInt,bigDec wrap()
+ obj = JSONObject.wrap(bigInteger);
+ assertTrue("wrap() returns big num", obj.equals(bigInteger));
+ obj = JSONObject.wrap(bigDecimal);
+ assertTrue("wrap() returns string", obj.equals(bigDecimal));
+ 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());
+ }
+
+ /**
+ * Verifies that getBigInteger / optBigInteger do not attempt to materialise a
+ * BigInteger whose decimal representation would exceed
+ * ParserConfiguration.DEFAULT_MAX_NUMBER_LENGTH digits. A short exponent-notation
+ * literal such as 1e100000000 is stored compactly as a BigDecimal at parse time
+ * but would otherwise expand to ~100 000 000 digits in BigDecimal.toBigInteger(),
+ * stalling the thread / OOM (CVE-2026-59171, issue #1063).
+ */
+ @Test(timeout = 5000)
+ public void getBigIntegerHugeExponentReturnsDefault() {
+ // BigDecimal path: value arrives via the parser as a BigDecimal
+ JSONObject jo = new JSONObject("{\"x\":1e100000000}");
+ assertTrue("huge-exponent literal parses to BigDecimal", jo.get("x") instanceof BigDecimal);
+ assertNull("optBigInteger returns default for huge exponent", jo.optBigInteger("x", null));
+ try {
+ jo.getBigInteger("x");
+ fail("getBigInteger should throw for huge exponent");
+ } catch (JSONException expected) {
+ // expected: integer part exceeds DEFAULT_MAX_NUMBER_LENGTH digits
+ }
+
+ // String path: value put() as a String, exercised via objectToBigInteger's
+ // isDecimalNotation branch
+ JSONObject jo2 = new JSONObject();
+ jo2.put("x", "1e100000000");
+ assertNull("optBigInteger returns default for huge-exponent string", jo2.optBigInteger("x", null));
+
+ // JSONArray accessors delegate to the same helper
+ JSONArray ja = new JSONArray("[1e100000000]");
+ assertTrue("optBigInteger returns default for huge exponent (array)",
+ BigInteger.ONE.equals(ja.optBigInteger(0, BigInteger.ONE)));
+
+ // Boundary: a value at the limit still converts correctly
+ JSONObject jo3 = new JSONObject("{\"x\":1e999}");
+ assertEquals("1e999 still converts", 0,
+ jo3.getBigInteger("x").compareTo(BigInteger.TEN.pow(999)));
+ }
+
+ /**
+ * Verifies that the JSONParserConfiguration.maxNumberLength setting is honoured
+ * by the getBigInteger / optBigInteger overloads on JSONObject and JSONArray.
+ */
+ @Test(timeout = 5000)
+ public void getBigIntegerHonorsMaxNumberLengthConfig() {
+ JSONObject jo = new JSONObject("{\"a\":1e1500,\"b\":1e2500}");
+
+ // Default config: DEFAULT_MAX_NUMBER_LENGTH == 1000, both rejected
+ assertNull("1e1500 rejected under default", jo.optBigInteger("a", null));
+ assertNull("1e2500 rejected under default", jo.optBigInteger("b", null));
+
+ // Custom raised limit
+ JSONParserConfiguration cfg2000 = new JSONParserConfiguration().withMaxNumberLength(2000);
+ assertEquals("1e1500 accepted under maxNumberLength=2000", 0,
+ jo.getBigInteger("a", cfg2000).compareTo(BigInteger.TEN.pow(1500)));
+ assertNull("1e2500 rejected under maxNumberLength=2000",
+ jo.optBigInteger("b", null, cfg2000));
+ try {
+ jo.getBigInteger("b", cfg2000);
+ fail("getBigInteger should throw for 1e2500 under maxNumberLength=2000");
+ } catch (JSONException expected) {
+ // expected: integer part exceeds configured maxNumberLength
+ }
+
+ // Custom lowered limit
+ JSONParserConfiguration cfg5 = new JSONParserConfiguration().withMaxNumberLength(5);
+ assertNull("1e1500 rejected under maxNumberLength=5",
+ jo.optBigInteger("a", null, cfg5));
+ JSONObject small = new JSONObject("{\"x\":1234}");
+ assertEquals("small value accepted under maxNumberLength=5",
+ BigInteger.valueOf(1234), small.getBigInteger("x", cfg5));
+
+ // Disabled: -1 turns the guard off. Use a moderate exponent so the test
+ // completes in a few ms while still exceeding the default limit.
+ JSONParserConfiguration cfgOff = new JSONParserConfiguration()
+ .withMaxNumberLength(ParserConfiguration.UNDEFINED_MAXIMUM_NUMBER_LENGTH);
+ assertEquals("1e2500 accepted when maxNumberLength is disabled", 0,
+ jo.getBigInteger("b", cfgOff).compareTo(BigInteger.TEN.pow(2500)));
+
+ // null config falls back to default
+ assertNull("null config behaves like default", jo.optBigInteger("a", null, null));
+
+ // JSONArray overloads follow the same rules
+ JSONArray ja = new JSONArray("[1e1500]");
+ assertNull("array: 1e1500 rejected under default", ja.optBigInteger(0, null));
+ assertEquals("array: 1e1500 accepted under maxNumberLength=2000", 0,
+ ja.getBigInteger(0, cfg2000).compareTo(BigInteger.TEN.pow(1500)));
+ assertEquals("array: 1e1500 accepted when disabled", 0,
+ ja.optBigInteger(0, null, cfgOff).compareTo(BigInteger.TEN.pow(1500)));
+ }
+
+ /**
+ * The purpose for the static method getNames() methods are not clear. This
+ * method is not called from within JSON-Java. Most likely uses are to prep
+ * names arrays for: JSONObject(JSONObject jo, String[] names) JSONObject(Object
+ * object, String names[]),
+ */
+ @Test
+ public void jsonObjectNames() {
+
+ // getNames() from null JSONObject
+ assertTrue("null names from null Object", null == JSONObject.getNames((Object) null));
+
+ // getNames() from object with no fields
+ assertTrue("null names from Object with no fields", null == JSONObject.getNames(new MyJsonString()));
+
+ // getNames from new JSONOjbect
+ JSONObject jsonObject0 = new JSONObject();
+ String[] names = JSONObject.getNames(jsonObject0);
+ assertTrue("names should be null", names == null);
+
+ // getNames() from empty JSONObject
+ String emptyStr = "{}";
+ JSONObject jsonObject1 = new JSONObject(emptyStr);
+ assertTrue("empty JSONObject should have null names", null == JSONObject.getNames(jsonObject1));
+
+ // getNames() from JSONObject
+ String str = "{" + "\"trueKey\":true," + "\"falseKey\":false," + "\"stringKey\":\"hello world!\"" + "}";
+ JSONObject jsonObject2 = new JSONObject(str);
+ names = JSONObject.getNames(jsonObject2);
+ JSONArray jsonArray0 = new JSONArray(names);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray0.toString());
+ List> docList = JsonPath.read(doc, "$");
+ assertTrue("expected 3 items", docList.size() == 3);
+ assertTrue("expected to find trueKey", ((List>) JsonPath.read(doc, "$[?(@=='trueKey')]")).size() == 1);
+ assertTrue("expected to find falseKey", ((List>) JsonPath.read(doc, "$[?(@=='falseKey')]")).size() == 1);
+ assertTrue("expected to find stringKey", ((List>) JsonPath.read(doc, "$[?(@=='stringKey')]")).size() == 1);
+
+ /**
+ * getNames() from an enum with properties has an interesting result. It returns
+ * the enum values, not the selected enum properties
+ */
+ MyEnumField myEnumField = MyEnumField.VAL1;
+ names = JSONObject.getNames(myEnumField);
+
+ // validate JSON
+ JSONArray jsonArray1 = new JSONArray(names);
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray1.toString());
+ docList = JsonPath.read(doc, "$");
+ assertTrue("expected 3 items", docList.size() == 3);
+ assertTrue("expected to find VAL1", ((List>) JsonPath.read(doc, "$[?(@=='VAL1')]")).size() == 1);
+ assertTrue("expected to find VAL2", ((List>) JsonPath.read(doc, "$[?(@=='VAL2')]")).size() == 1);
+ assertTrue("expected to find VAL3", ((List>) JsonPath.read(doc, "$[?(@=='VAL3')]")).size() == 1);
+
+ /**
+ * A bean is also an object. But in order to test the static method getNames(),
+ * this particular bean needs some public data members.
+ */
+ MyPublicClass myPublicClass = new MyPublicClass();
+ names = JSONObject.getNames(myPublicClass);
+
+ // validate JSON
+ JSONArray jsonArray2 = new JSONArray(names);
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray2.toString());
+ docList = JsonPath.read(doc, "$");
+ assertTrue("expected 2 items", docList.size() == 2);
+ assertTrue("expected to find publicString",
+ ((List>) JsonPath.read(doc, "$[?(@=='publicString')]")).size() == 1);
+ assertTrue("expected to find publicInt", ((List>) JsonPath.read(doc, "$[?(@=='publicInt')]")).size() == 1);
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(jsonObject0, jsonObject1, jsonObject2)));
+ Util.checkJSONArrayMaps(jsonArray0, jsonObject0.getMapType());
+ Util.checkJSONArrayMaps(jsonArray1, jsonObject0.getMapType());
+ Util.checkJSONArrayMaps(jsonArray2, jsonObject0.getMapType());
+ }
+
+ /**
+ * Populate a JSONArray from an empty JSONObject names() method. It should be
+ * empty.
+ */
+ @Test
+ public void emptyJsonObjectNamesToJsonAray() {
+ JSONObject jsonObject = new JSONObject();
+ JSONArray jsonArray = jsonObject.names();
+ assertTrue("jsonArray should be null", jsonArray == null);
+ Util.checkJSONObjectMaps(jsonObject);
+ Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
+ }
+
+ /**
+ * Populate a JSONArray from a JSONObject names() method. Confirm that it
+ * contains the expected names.
+ */
+ @Test
+ public void jsonObjectNamesToJsonAray() {
+ String str = "{" + "\"trueKey\":true," + "\"falseKey\":false," + "\"stringKey\":\"hello world!\"" + "}";
+
+ JSONObject jsonObject = new JSONObject(str);
+ JSONArray jsonArray = jsonObject.names();
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
+ assertTrue("expected 3 top level items", ((List>) (JsonPath.read(doc, "$"))).size() == 3);
+ assertTrue("expected to find trueKey", ((List>) JsonPath.read(doc, "$[?(@=='trueKey')]")).size() == 1);
+ assertTrue("expected to find falseKey", ((List>) JsonPath.read(doc, "$[?(@=='falseKey')]")).size() == 1);
+ assertTrue("expected to find stringKey", ((List>) JsonPath.read(doc, "$[?(@=='stringKey')]")).size() == 1);
+ Util.checkJSONObjectMaps(jsonObject);
+ Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
+ }
+
+ /**
+ * Exercise the JSONObject increment() method.
+ */
+ @SuppressWarnings("cast")
+ @Test
+ public void jsonObjectIncrement() {
+ String str = "{" + "\"keyLong\":9999999991," + "\"keyDouble\":1.1" + "}";
+ JSONObject jsonObject = new JSONObject(str);
+ jsonObject.increment("keyInt");
+ jsonObject.increment("keyInt");
+ jsonObject.increment("keyLong");
+ jsonObject.increment("keyDouble");
+ jsonObject.increment("keyInt");
+ jsonObject.increment("keyLong");
+ jsonObject.increment("keyDouble");
+ /**
+ * JSONObject constructor won't handle these types correctly, but adding them
+ * via put works.
+ */
+ jsonObject.put("keyFloat", 1.1f);
+ jsonObject.put("keyBigInt", new BigInteger("123456789123456789123456789123456780"));
+ jsonObject.put("keyBigDec", new BigDecimal("123456789123456789123456789123456780.1"));
+ jsonObject.increment("keyFloat");
+ jsonObject.increment("keyFloat");
+ jsonObject.increment("keyBigInt");
+ jsonObject.increment("keyBigDec");
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 6 top level items", ((Map, ?>) (JsonPath.read(doc, "$"))).size() == 6);
+ assertTrue("expected 3", Integer.valueOf(3).equals(jsonObject.query("/keyInt")));
+ assertTrue("expected 9999999993", Long.valueOf(9999999993L).equals(jsonObject.query("/keyLong")));
+ assertTrue("expected 3.1", BigDecimal.valueOf(3.1).equals(jsonObject.query("/keyDouble")));
+ assertTrue("expected 123456789123456789123456789123456781",
+ new BigInteger("123456789123456789123456789123456781").equals(jsonObject.query("/keyBigInt")));
+ assertTrue("expected 123456789123456789123456789123456781.1",
+ new BigDecimal("123456789123456789123456789123456781.1").equals(jsonObject.query("/keyBigDec")));
+
+ /**
+ * Should work the same way on any platform! @see https://docs.oracle
+ * .com/javase/specs/jls/se7/html/jls-4.html#jls-4.2.3 This is the effect of a
+ * float to double conversion and is inherent to the shortcomings of the IEEE
+ * 754 format, when converting 32-bit into double-precision 64-bit. Java
+ * type-casts float to double. A 32 bit float is type-casted to 64 bit double by
+ * simply appending zero-bits to the mantissa (and extended the signed exponent
+ * by 3 bits.) and there is no way to obtain more information than it is stored
+ * in the 32-bits float.
+ *
+ * Like 1/3 cannot be represented as base10 number because it is periodically,
+ * 1/5 (for example) cannot be represented as base2 number since it is
+ * periodically in base2 (take a look at
+ * http://www.h-schmidt.net/FloatConverter/) The same happens to 3.1, that
+ * decimal number (base10 representation) is periodic in base2 representation,
+ * therefore appending zero-bits is inaccurate. Only repeating the periodically
+ * occurring bits (0110) would be a proper conversion. However one cannot detect
+ * from a 32 bit IEE754 representation which bits would "repeat infinitely",
+ * since the missing bits would not fit into the 32 bit float, i.e. the
+ * information needed simply is not there!
+ */
+ assertEquals(Float.valueOf(3.1f), jsonObject.query("/keyFloat"));
+
+ /**
+ * float f = 3.1f; double df = (double) f; double d = 3.1d; System.out.println
+ * (Integer.toBinaryString(Float.floatToRawIntBits(f))); System.out.println
+ * (Long.toBinaryString(Double.doubleToRawLongBits(df))); System.out.println
+ * (Long.toBinaryString(Double.doubleToRawLongBits(d)));
+ *
+ * - Float: seeeeeeeemmmmmmmmmmmmmmmmmmmmmmm 1000000010001100110011001100110 -
+ * Double seeeeeeeeeeemmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+ * 10000000 10001100110011001100110
+ * 100000000001000110011001100110011000000000000000000000000000000
+ * 100000000001000110011001100110011001100110011001100110011001101
+ */
+
+ /**
+ * Examples of well documented but probably unexpected behavior in java / with
+ * 32-bit float to 64-bit float conversion.
+ */
+ assertFalse("Document unexpected behaviour with explicit type-casting float as double!", (double) 0.2f == 0.2d);
+ assertFalse("Document unexpected behaviour with implicit type-cast!", 0.2f == 0.2d);
+ Double d1 = 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!",
+ 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(Double.valueOf(3.1d)));
+
+ JSONObject inc = new JSONObject();
+ 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);
+ // 1. The (Object)value will be typecasted to (Float)value since it is an
+ // instanceof Float actually nothing is done.
+ // 2. Float instance will be autoboxed into float because the + operator will
+ // work on primitives not Objects!
+ // 3. A float+float operation will be performed and results into a float
+ // primitive.
+ // 4. There is no method that matches the signature put( String key, float
+ // value), java-compiler will choose the method
+ // put( String key, double value) and does an implicit type-cast(!) by appending
+ // zero-bits to the mantissa
+ assertTrue("JSONObject increment converts Float to Double", jo.get("bug") instanceof Float);
+ // correct implementation (with change of behavior) would be:
+ // this.put(key, new Float((Float) value + 1));
+ // Probably it would be better to deprecate the method and remove some day,
+ // while convenient processing the "payload" is not
+ // really in the scope of a JSON-library (IMHO.)
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(jsonObject, inc)));
+ }
+
+ /**
+ * Exercise JSONObject numberToString() method
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void jsonObjectNumberToString() {
+ String str;
+ Double dVal;
+ Integer iVal = 1;
+ str = JSONObject.numberToString(iVal);
+ assertTrue("expected " + iVal + " actual " + str, iVal.toString().equals(str));
+ dVal = 12.34;
+ str = JSONObject.numberToString(dVal);
+ assertTrue("expected " + dVal + " actual " + str, dVal.toString().equals(str));
+ dVal = 12.34e27;
+ str = JSONObject.numberToString(dVal);
+ assertTrue("expected " + dVal + " actual " + str, dVal.toString().equals(str));
+ // trailing .0 is truncated, so it doesn't quite match toString()
+ dVal = 5000000.0000000;
+ str = JSONObject.numberToString(dVal);
+ assertTrue("expected 5000000 actual " + str, str.equals("5000000"));
+ }
+
+ /**
+ * Exercise JSONObject put() and similar() methods
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void jsonObjectPut() {
+ String expectedStr = "{" + "\"trueKey\":true," + "\"falseKey\":false," + "\"arrayKey\":[0,1,2],"
+ + "\"objectKey\":{" + "\"myKey1\":\"myVal1\"," + "\"myKey2\":\"myVal2\"," + "\"myKey3\":\"myVal3\","
+ + "\"myKey4\":\"myVal4\"" + "}" + "}";
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("trueKey", true);
+ jsonObject.put("falseKey", false);
+ Integer[] intArray = { 0, 1, 2 };
+ jsonObject.put("arrayKey", Arrays.asList(intArray));
+ Map myMap = new HashMap();
+ myMap.put("myKey1", "myVal1");
+ myMap.put("myKey2", "myVal2");
+ myMap.put("myKey3", "myVal3");
+ myMap.put("myKey4", "myVal4");
+ jsonObject.put("objectKey", myMap);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 4 top level items", ((Map, ?>) (JsonPath.read(doc, "$"))).size() == 4);
+ assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
+ assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
+ assertTrue("expected 3 arrayKey items", ((List>) (JsonPath.read(doc, "$.arrayKey"))).size() == 3);
+ assertTrue("expected 0", Integer.valueOf(0).equals(jsonObject.query("/arrayKey/0")));
+ assertTrue("expected 1", Integer.valueOf(1).equals(jsonObject.query("/arrayKey/1")));
+ assertTrue("expected 2", Integer.valueOf(2).equals(jsonObject.query("/arrayKey/2")));
+ assertTrue("expected 4 objectKey items", ((Map, ?>) (JsonPath.read(doc, "$.objectKey"))).size() == 4);
+ assertTrue("expected myVal1", "myVal1".equals(jsonObject.query("/objectKey/myKey1")));
+ assertTrue("expected myVal2", "myVal2".equals(jsonObject.query("/objectKey/myKey2")));
+ assertTrue("expected myVal3", "myVal3".equals(jsonObject.query("/objectKey/myKey3")));
+ assertTrue("expected myVal4", "myVal4".equals(jsonObject.query("/objectKey/myKey4")));
+
+ jsonObject.remove("trueKey");
+ JSONObject expectedJsonObject = new JSONObject(expectedStr);
+ assertTrue("unequal jsonObjects should not be similar", !jsonObject.similar(expectedJsonObject));
+ assertTrue("jsonObject should not be similar to jsonArray", !jsonObject.similar(new JSONArray()));
+
+ String aCompareValueStr = "{\"a\":\"aval\",\"b\":true}";
+ String bCompareValueStr = "{\"a\":\"notAval\",\"b\":true}";
+ JSONObject aCompareValueJsonObject = new JSONObject(aCompareValueStr);
+ JSONObject bCompareValueJsonObject = new JSONObject(bCompareValueStr);
+ assertTrue("different values should not be similar", !aCompareValueJsonObject.similar(bCompareValueJsonObject));
+
+ String aCompareObjectStr = "{\"a\":\"aval\",\"b\":{}}";
+ String bCompareObjectStr = "{\"a\":\"aval\",\"b\":true}";
+ JSONObject aCompareObjectJsonObject = new JSONObject(aCompareObjectStr);
+ JSONObject bCompareObjectJsonObject = new JSONObject(bCompareObjectStr);
+ assertTrue("different nested JSONObjects should not be similar",
+ !aCompareObjectJsonObject.similar(bCompareObjectJsonObject));
+
+ String aCompareArrayStr = "{\"a\":\"aval\",\"b\":[]}";
+ String bCompareArrayStr = "{\"a\":\"aval\",\"b\":true}";
+ JSONObject aCompareArrayJsonObject = new JSONObject(aCompareArrayStr);
+ JSONObject bCompareArrayJsonObject = new JSONObject(bCompareArrayStr);
+ assertTrue("different nested JSONArrays should not be similar",
+ !aCompareArrayJsonObject.similar(bCompareArrayJsonObject));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(jsonObject, expectedJsonObject,
+ aCompareValueJsonObject, aCompareArrayJsonObject, aCompareObjectJsonObject, aCompareArrayJsonObject,
+ bCompareValueJsonObject, bCompareArrayJsonObject, bCompareObjectJsonObject, bCompareArrayJsonObject)));
+ }
+
+ /**
+ * Exercise JSONObject toString() method
+ */
+ @Test
+ public void jsonObjectToString() {
+ String str = "{" + "\"trueKey\":true," + "\"falseKey\":false," + "\"arrayKey\":[0,1,2]," + "\"objectKey\":{"
+ + "\"myKey1\":\"myVal1\"," + "\"myKey2\":\"myVal2\"," + "\"myKey3\":\"myVal3\","
+ + "\"myKey4\":\"myVal4\"" + "}" + "}";
+ JSONObject jsonObject = new JSONObject(str);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 4 top level items", ((Map, ?>) (JsonPath.read(doc, "$"))).size() == 4);
+ assertTrue("expected true", Boolean.TRUE.equals(jsonObject.query("/trueKey")));
+ assertTrue("expected false", Boolean.FALSE.equals(jsonObject.query("/falseKey")));
+ assertTrue("expected 3 arrayKey items", ((List>) (JsonPath.read(doc, "$.arrayKey"))).size() == 3);
+ assertTrue("expected 0", Integer.valueOf(0).equals(jsonObject.query("/arrayKey/0")));
+ assertTrue("expected 1", Integer.valueOf(1).equals(jsonObject.query("/arrayKey/1")));
+ assertTrue("expected 2", Integer.valueOf(2).equals(jsonObject.query("/arrayKey/2")));
+ assertTrue("expected 4 objectKey items", ((Map, ?>) (JsonPath.read(doc, "$.objectKey"))).size() == 4);
+ assertTrue("expected myVal1", "myVal1".equals(jsonObject.query("/objectKey/myKey1")));
+ assertTrue("expected myVal2", "myVal2".equals(jsonObject.query("/objectKey/myKey2")));
+ assertTrue("expected myVal3", "myVal3".equals(jsonObject.query("/objectKey/myKey3")));
+ assertTrue("expected myVal4", "myVal4".equals(jsonObject.query("/objectKey/myKey4")));
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Exercise JSONObject toString() method with various indent levels.
+ */
+ @Test
+ public void jsonObjectToStringIndent() {
+ String jsonObject0Str = "{" + "\"key1\":" + "[1,2," + "{\"key3\":true}" + "]," + "\"key2\":"
+ + "{\"key1\":\"val1\",\"key2\":" + "{\"key2\":\"val2\"}" + "}," + "\"key3\":" + "[" + "[1,2.1]" + ","
+ + "[null]" + "]" + "}";
+
+ String jsonObject1Str = "{\n" + " \"key1\": [\n" + " 1,\n" + " 2,\n" + " {\"key3\": true}\n" + " ],\n"
+ + " \"key2\": {\n" + " \"key1\": \"val1\",\n" + " \"key2\": {\"key2\": \"val2\"}\n" + " },\n"
+ + " \"key3\": [\n" + " [\n" + " 1,\n" + " 2.1\n" + " ],\n" + " [null]\n" + " ]\n" + "}";
+ String jsonObject4Str = "{\n" + " \"key1\": [\n" + " 1,\n" + " 2,\n"
+ + " {\"key3\": true}\n" + " ],\n" + " \"key2\": {\n" + " \"key1\": \"val1\",\n"
+ + " \"key2\": {\"key2\": \"val2\"}\n" + " },\n" + " \"key3\": [\n" + " [\n"
+ + " 1,\n" + " 2.1\n" + " ],\n" + " [null]\n" + " ]\n" + "}";
+ JSONObject jsonObject = new JSONObject(jsonObject0Str);
+ // contents are tested in other methods, in this case just validate the spacing
+ // by
+ // checking length
+ assertEquals("toString() length", jsonObject0Str.length(), jsonObject.toString().length());
+ assertEquals("toString(0) length", jsonObject0Str.length(), jsonObject.toString(0).length());
+ assertEquals("toString(1) length", jsonObject1Str.length(), jsonObject.toString(1).length());
+ assertEquals("toString(4) length", jsonObject4Str.length(), jsonObject.toString(4).length());
+
+ JSONObject jo = new JSONObject().put("TABLE", new JSONObject().put("yhoo", new JSONObject()));
+ assertEquals("toString(2)", "{\"TABLE\": {\"yhoo\": {}}}", jo.toString(2));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(jsonObject, jo)));
+ }
+
+ /**
+ * Explores how JSONObject handles maps. Insert a string/string map as a value
+ * in a JSONObject. It will remain a map. Convert the JSONObject to string, then
+ * create a new JSONObject from the string. In the new JSONObject, the value
+ * will be stored as a nested JSONObject. Confirm that map and nested JSONObject
+ * have the same contents.
+ */
+ @Test
+ public void jsonObjectToStringSuppressWarningOnCastToMap() {
+ JSONObject jsonObject = new JSONObject();
+ Map map = new HashMap<>();
+ map.put("abc", "def");
+ jsonObject.put("key", map);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 1 top level item", ((Map, ?>) (JsonPath.read(doc, "$"))).size() == 1);
+ assertTrue("expected 1 key item", ((Map, ?>) (JsonPath.read(doc, "$.key"))).size() == 1);
+ assertTrue("expected def", "def".equals(jsonObject.query("/key/abc")));
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Explores how JSONObject handles collections. Insert a string collection as a
+ * value in a JSONObject. It will remain a collection. Convert the JSONObject to
+ * string, then create a new JSONObject from the string. In the new JSONObject,
+ * the value will be stored as a nested JSONArray. Confirm that collection and
+ * nested JSONArray have the same contents.
+ */
+ @Test
+ public void jsonObjectToStringSuppressWarningOnCastToCollection() {
+ JSONObject jsonObject = new JSONObject();
+ Collection collection = new ArrayList();
+ collection.add("abc");
+ // ArrayList will be added as an object
+ jsonObject.put("key", collection);
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonObject.toString());
+ assertTrue("expected 1 top level item", ((Map, ?>) (JsonPath.read(doc, "$"))).size() == 1);
+ assertTrue("expected 1 key item", ((List>) (JsonPath.read(doc, "$.key"))).size() == 1);
+ assertTrue("expected abc", "abc".equals(jsonObject.query("/key/0")));
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Exercises the JSONObject.valueToString() method for various types
+ */
+ @Test
+ public void valueToString() {
+
+ assertTrue("null valueToString() incorrect", "null".equals(JSONObject.valueToString(null)));
+ MyJsonString jsonString = new MyJsonString();
+ assertTrue("jsonstring valueToString() incorrect", "my string".equals(JSONObject.valueToString(jsonString)));
+ assertTrue("boolean valueToString() incorrect", "true".equals(JSONObject.valueToString(Boolean.TRUE)));
+ assertTrue("non-numeric double", "null".equals(JSONObject.doubleToString(Double.POSITIVE_INFINITY)));
+ String jsonObjectStr = "{" + "\"key1\":\"val1\"," + "\"key2\":\"val2\"," + "\"key3\":\"val3\"" + "}";
+ JSONObject jsonObject = new JSONObject(jsonObjectStr);
+ assertTrue("jsonObject valueToString() incorrect",
+ new JSONObject(JSONObject.valueToString(jsonObject)).similar(new JSONObject(jsonObject.toString())));
+ String jsonArrayStr = "[1,2,3]";
+ JSONArray jsonArray = new JSONArray(jsonArrayStr);
+ assertTrue("jsonArray valueToString() incorrect",
+ JSONObject.valueToString(jsonArray).equals(jsonArray.toString()));
+ Map map = new HashMap();
+ map.put("key1", "val1");
+ map.put("key2", "val2");
+ map.put("key3", "val3");
+ assertTrue("map valueToString() incorrect",
+ new JSONObject(jsonObject.toString()).similar(new JSONObject(JSONObject.valueToString(map))));
+ Collection collection = new ArrayList();
+ 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 = { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3) };
+ assertTrue("array valueToString() incorrect", jsonArray.toString().equals(JSONObject.valueToString(array)));
+ Util.checkJSONObjectMaps(jsonObject);
+ Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
+ }
+
+ /**
+ * Confirm that https://github.com/douglascrockford/JSON-java/issues/167 is
+ * fixed. The following code was throwing a ClassCastException in the
+ * JSONObject(Map) constructor
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void valueToStringConfirmException() {
+ Map myMap = new HashMap();
+ myMap.put(1, "myValue");
+ // this is the test, it should not throw an exception
+ String str = JSONObject.valueToString(myMap);
+ // confirm result, just in case
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(str);
+ assertTrue("expected 1 top level item", ((Map, ?>) (JsonPath.read(doc, "$"))).size() == 1);
+ assertTrue("expected myValue", "myValue".equals(JsonPath.read(doc, "$.1")));
+ }
+
+ /**
+ * Exercise the JSONObject wrap() method. Sometimes wrap() will change the
+ * object being wrapped, other times not. The purpose of wrap() is to ensure the
+ * value is packaged in a way that is compatible with how a JSONObject value or
+ * JSONArray value is supposed to be stored.
+ */
+ @Test
+ public void wrapObject() {
+ // wrap(null) returns NULL
+ assertTrue("null wrap() incorrect", JSONObject.NULL == JSONObject.wrap(null));
+
+ // wrap(Integer) returns Integer
+ Integer in = Integer.valueOf(1);
+ assertTrue("Integer wrap() incorrect", in == JSONObject.wrap(in));
+
+ /**
+ * This test is to document the preferred behavior if BigDecimal is supported.
+ * Previously bd returned as a string, since it is recognized as being a Java
+ * package class. Now with explicit support for big numbers, it remains a
+ * BigDecimal
+ */
+ Object bdWrap = JSONObject.wrap(BigDecimal.ONE);
+ assertTrue("BigDecimal.ONE evaluates to ONE", bdWrap.equals(BigDecimal.ONE));
+
+ // wrap JSONObject returns JSONObject
+ String jsonObjectStr = "{" + "\"key1\":\"val1\"," + "\"key2\":\"val2\"," + "\"key3\":\"val3\"" + "}";
+ JSONObject jsonObject = new JSONObject(jsonObjectStr);
+ assertTrue("JSONObject wrap() incorrect", jsonObject == JSONObject.wrap(jsonObject));
+
+ // wrap collection returns JSONArray
+ Collection collection = new ArrayList();
+ collection.add(Integer.valueOf(1));
+ collection.add(Integer.valueOf(2));
+ collection.add(Integer.valueOf(3));
+ JSONArray jsonArray = (JSONArray) (JSONObject.wrap(collection));
+
+ // validate JSON
+ Object doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
+ assertTrue("expected 3 top level items", ((List>) (JsonPath.read(doc, "$"))).size() == 3);
+ assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0")));
+ assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1")));
+ assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2")));
+
+ // wrap Array returns JSONArray
+ Integer[] array = { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3) };
+ JSONArray integerArrayJsonArray = (JSONArray) (JSONObject.wrap(array));
+
+ // validate JSON
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(jsonArray.toString());
+ assertTrue("expected 3 top level items", ((List>) (JsonPath.read(doc, "$"))).size() == 3);
+ assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0")));
+ assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1")));
+ assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2")));
+
+ // validate JSON
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(integerArrayJsonArray.toString());
+ assertTrue("expected 3 top level items", ((List>) (JsonPath.read(doc, "$"))).size() == 3);
+ assertTrue("expected 1", Integer.valueOf(1).equals(jsonArray.query("/0")));
+ assertTrue("expected 2", Integer.valueOf(2).equals(jsonArray.query("/1")));
+ assertTrue("expected 3", Integer.valueOf(3).equals(jsonArray.query("/2")));
+
+ // wrap map returns JSONObject
+ Map map = new HashMap();
+ map.put("key1", "val1");
+ map.put("key2", "val2");
+ map.put("key3", "val3");
+ JSONObject mapJsonObject = (JSONObject) (JSONObject.wrap(map));
+
+ // validate JSON
+ doc = Configuration.defaultConfiguration().jsonProvider().parse(mapJsonObject.toString());
+ assertTrue("expected 3 top level items", ((Map, ?>) (JsonPath.read(doc, "$"))).size() == 3);
+ assertTrue("expected val1", "val1".equals(mapJsonObject.query("/key1")));
+ assertTrue("expected val2", "val2".equals(mapJsonObject.query("/key2")));
+ assertTrue("expected val3", "val3".equals(mapJsonObject.query("/key3")));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(jsonObject, mapJsonObject)));
+ Util.checkJSONArrayMaps(jsonArray, jsonObject.getMapType());
+ Util.checkJSONArrayMaps(integerArrayJsonArray, jsonObject.getMapType());
+ }
+
+ /**
+ * RFC 7159 defines control characters to be U+0000 through U+001F. This test
+ * verifies that the parser is checking for these in expected ways.
+ */
+ @Test
+ public void jsonObjectParseControlCharacters() {
+ for (int i = 0; i <= 0x001f; i++) {
+ final String charString = String.valueOf((char) i);
+ final String source = "{\"key\":\"" + charString + "\"}";
+ try {
+ JSONObject jo = new JSONObject(source);
+ assertTrue("Expected " + charString + "(" + i + ") in the JSON Object but did not find it.",
+ charString.equals(jo.getString("key")));
+ 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');
+ }
+ }
+ }
+
+ @Test
+ public void jsonObjectParseControlCharacterEOFAssertExceptionMessage() {
+ char c = '\0';
+ 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 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",
+ "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", "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", "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 value
+ String str = "{\"myKey\":true, \"myOtherKey\":false}";
+ JSONObject jsonObject = new JSONObject(str);
+ jsonObject.append("myKey", "hello");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message", "JSONObject[\"myKey\"] is not a JSONArray (null).",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void parsingErrorIncrementWrongValue() {
+ try {
+ // increment wrong value
+ String str = "{\"myKey\":true, \"myOtherKey\":false}";
+ JSONObject jsonObject = new JSONObject(str);
+ jsonObject.increment("myKey");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message", "Unable to increment [\"myKey\"].", e.getMessage());
+ }
+ }
+
+ @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) {
+ assertEquals("Expecting an exception message", "Null key.", e.getMessage());
+ }
+ }
+
+ @Test
+ public void parsingErrorNumberToString() {
+ try {
+ // invalid numberToString()
+ JSONObject.numberToString((Number) null);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an exception message", "Null pointer", e.getMessage());
+ }
+ }
+
+ @Test
+ public void parsingErrorPutOnceDuplicateKey() {
+ try {
+ // multiple putOnce key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.putOnce("hello", "world");
+ jsonObject.putOnce("hello", "world!");
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertTrue("", true);
+ }
+ }
+
+ @Test
+ public void parsingErrorInvalidDouble() {
+ try {
+ // test validity of invalid double
+ JSONObject.testValidity(Double.NaN);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertTrue("", true);
+ }
+ }
+
+ @Test
+ public void parsingErrorInvalidFloat() {
+ try {
+ // test validity of invalid float
+ JSONObject.testValidity(Float.NEGATIVE_INFINITY);
+ fail("Expected an exception");
+ } 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" + "}";
+ new JSONObject(str);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an expection message", "Duplicate key \"attr03\" at 90 [character 13 line 5]",
+ e.getMessage());
+ }
+ }
+
+ @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"
+ + "}";
+ new JSONObject(str);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an expection message", "Duplicate key \"attr03\" at 90 [character 13 line 5]",
+ e.getMessage());
+ }
+ }
+
+ @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" + "}";
+ new JSONObject(str);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an expection message", "Duplicate key \"attr03\" at 90 [character 13 line 5]",
+ e.getMessage());
+ }
+ }
+
+ @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"
+ + "}";
+ new JSONObject(str);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an expection message", "Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void parsingErrorDuplicateKeyDoubleNestedDictExceptionMessage() {
+ try {
+ // test exception message when including a duplicate key (level 1) holding an
+ // object
+ String str = "{\n" + " \"attr01\":\"value-01\",\n" + " \"attr02\":\"value-02\",\n"
+ + " \"attr03\":\"value-03\",\n" + " \"attr04\": {\n"
+ + " \"attr04-01\":\"value04-01\",\n" + " \"attr04-02\":\"value04-02\",\n"
+ + " \"attr04-03\":\"value04-03\",\n" + " \"attr04-03\": {\n"
+ + " \"attr04-04-01\":\"value04-04-01\",\n"
+ + " \"attr04-04-02\":\"value04-04-02\",\n"
+ + " \"attr04-04-03\":\"value04-04-03\",\n" + " }\n" + " }\n" + "}";
+ new JSONObject(str);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an expection message", "Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void parsingErrorDuplicateKeyNestedWithArrayExceptionMessage() {
+ try {
+ // test exception message when including a duplicate key (level 1) holding an
+ // array
+ String str = "{\n" + " \"attr01\":\"value-01\",\n" + " \"attr02\":\"value-02\",\n"
+ + " \"attr03\":\"value-03\",\n" + " \"attr04\": {\n"
+ + " \"attr04-01\":\"value04-01\",\n" + " \"attr04-02\":\"value04-02\",\n"
+ + " \"attr04-03\":\"value04-03\",\n" + " \"attr04-03\": [\n" + " {\n"
+ + " \"attr04-04-01\":\"value04-04-01\",\n"
+ + " \"attr04-04-02\":\"value04-04-02\",\n"
+ + " \"attr04-04-03\":\"value04-04-03\",\n" + " }\n" + " ]\n"
+ + " }\n" + "}";
+ new JSONObject(str);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an expection message", "Duplicate key \"attr04-03\" at 215 [character 20 line 9]",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void parsingErrorDuplicateKeyWithinArrayExceptionMessage() {
+ try {
+ // test exception message when including a duplicate key in object (level 0)
+ // within an array
+ String str = "[\n" + " {\n" + " \"attr01\":\"value-01\",\n" + " \"attr02\":\"value-02\"\n"
+ + " },\n" + " {\n" + " \"attr01\":\"value-01\",\n"
+ + " \"attr01\":\"value-02\"\n" + " }\n" + "]";
+ new JSONArray(str);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an expection message", "Duplicate key \"attr01\" at 124 [character 17 line 8]",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void parsingErrorDuplicateKeyDoubleNestedWithinArrayExceptionMessage() {
+ try {
+ // test exception message when including a duplicate key in object (level 1)
+ // within an array
+ String str = "[\n" + " {\n" + " \"attr01\":\"value-01\",\n" + " \"attr02\": {\n"
+ + " \"attr02-01\":\"value-02-01\",\n" + " \"attr02-02\":\"value-02-02\"\n"
+ + " }\n" + " },\n" + " {\n" + " \"attr01\":\"value-01\",\n"
+ + " \"attr02\": {\n" + " \"attr02-01\":\"value-02-01\",\n"
+ + " \"attr02-01\":\"value-02-02\"\n" + " }\n" + " }\n" + "]";
+ new JSONArray(str);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ assertEquals("Expecting an expection message", "Duplicate key \"attr02-01\" at 269 [character 24 line 13]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Confirm behavior when putOnce() is called with null parameters
+ */
+ @Test
+ public void jsonObjectPutOnceNull() {
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.putOnce(null, null);
+ assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ jsonObject.putOnce("", null);
+ assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ jsonObject.putOnce(null, "");
+ assertTrue("jsonObject should be empty", jsonObject.isEmpty());
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Exercise JSONObject opt(key, default) method.
+ */
+ @Test
+ public void jsonObjectOptDefault() {
+
+ String str = "{\"myKey\": \"myval\", \"hiKey\": null}";
+ JSONObject jsonObject = new JSONObject(str);
+
+ assertTrue("optBigDecimal() should return default BigDecimal",
+ BigDecimal.TEN.compareTo(jsonObject.optBigDecimal("myKey", BigDecimal.TEN)) == 0);
+ assertTrue("optBigInteger() should return default BigInteger",
+ BigInteger.TEN.compareTo(jsonObject.optBigInteger("myKey", BigInteger.TEN)) == 0);
+ assertTrue("optBoolean() should return default boolean", jsonObject.optBoolean("myKey", true));
+ assertTrue("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("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);
+ }
+
+ /**
+ * Exercise JSONObject opt(key, default) method when the key doesn't exist.
+ */
+ @Test
+ public void jsonObjectOptNoKey() {
+
+ JSONObject jsonObject = new JSONObject();
+
+ assertNull(jsonObject.opt(null));
+
+ assertTrue("optBigDecimal() should return default BigDecimal",
+ BigDecimal.TEN.compareTo(jsonObject.optBigDecimal("myKey", BigDecimal.TEN)) == 0);
+ assertTrue("optBigInteger() should return default BigInteger",
+ BigInteger.TEN.compareTo(jsonObject.optBigInteger("myKey", BigInteger.TEN)) == 0);
+ assertTrue("optBoolean() should return default boolean", jsonObject.optBoolean("myKey", true));
+ assertTrue("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 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);
+ }
+
+ /**
+ * Verifies that the opt methods properly convert string values.
+ */
+ @Test
+ public void jsonObjectOptStringConversion() {
+ JSONObject jo = new JSONObject("{\"int\":\"123\",\"true\":\"true\",\"false\":\"false\"}");
+ assertTrue("unexpected optBoolean value", jo.optBoolean("true", false) == true);
+ assertTrue("unexpected 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);
+ }
+
+ /**
+ * Verifies that the opt methods properly convert string values to numbers and
+ * coerce them consistently.
+ */
+ @Test
+ public void jsonObjectOptCoercion() {
+ JSONObject jo = new JSONObject(
+ "{\"largeNumberStr\":\"19007199254740993.35481234487103587486413587843213584\"}");
+ // currently the parser doesn't recognize BigDecimal, to we have to put it
+ // manually
+ jo.put("largeNumber", new BigDecimal("19007199254740993.35481234487103587486413587843213584"));
+
+ // Test type coercion from larger to smaller
+ assertEquals(new BigDecimal("19007199254740993.35481234487103587486413587843213584"),
+ jo.optBigDecimal("largeNumber", null));
+ assertEquals(new BigInteger("19007199254740993"), jo.optBigInteger("largeNumber", null));
+ assertEquals(1.9007199254740992E16, jo.optDouble("largeNumber"), 0.0);
+ assertEquals(1.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);
+ }
+
+ /**
+ * Verifies that the optBigDecimal method properly converts values to BigDecimal
+ * and coerce them consistently.
+ */
+ @Test
+ public void jsonObjectOptBigDecimal() {
+ JSONObject jo = new JSONObject().put("int", 123).put("long", 654L).put("float", 1.234f).put("double", 2.345d)
+ .put("bigInteger", new BigInteger("1234")).put("bigDecimal", new BigDecimal("1234.56789"))
+ .put("nullVal", JSONObject.NULL);
+
+ assertEquals(new BigDecimal("123"), jo.optBigDecimal("int", null));
+ assertEquals(new BigDecimal("654"), jo.optBigDecimal("long", null));
+ assertEquals(new BigDecimal(1.234f), jo.optBigDecimal("float", null));
+ assertEquals(new BigDecimal(2.345d), jo.optBigDecimal("double", null));
+ assertEquals(new BigDecimal("1234"), jo.optBigDecimal("bigInteger", null));
+ assertEquals(new BigDecimal("1234.56789"), jo.optBigDecimal("bigDecimal", null));
+ assertNull(jo.optBigDecimal("nullVal", null));
+ assertEquals(jo.optBigDecimal("float", null), jo.getBigDecimal("float"));
+ assertEquals(jo.optBigDecimal("double", null), jo.getBigDecimal("double"));
+ Util.checkJSONObjectMaps(jo);
+ }
+
+ /**
+ * Verifies that the optBigDecimal method properly converts values to BigDecimal
+ * and coerce them consistently.
+ */
+ @Test
+ public void jsonObjectOptBigInteger() {
+ JSONObject jo = new JSONObject().put("int", 123).put("long", 654L).put("float", 1.234f).put("double", 2.345d)
+ .put("bigInteger", new BigInteger("1234")).put("bigDecimal", new BigDecimal("1234.56789"))
+ .put("nullVal", JSONObject.NULL);
+
+ assertEquals(new BigInteger("123"), jo.optBigInteger("int", null));
+ assertEquals(new BigInteger("654"), jo.optBigInteger("long", null));
+ assertEquals(new BigInteger("1"), jo.optBigInteger("float", null));
+ assertEquals(new BigInteger("2"), jo.optBigInteger("double", null));
+ assertEquals(new BigInteger("1234"), jo.optBigInteger("bigInteger", null));
+ assertEquals(new BigInteger("1234"), jo.optBigInteger("bigDecimal", null));
+ assertNull(jo.optBigDecimal("nullVal", null));
+ Util.checkJSONObjectMaps(jo);
+ }
+
+ /**
+ * Confirm behavior when JSONObject put(key, null object) is called
+ */
+ @Test
+ public void jsonObjectputNull() {
+
+ // put null should remove the item.
+ String str = "{\"myKey\": \"myval\"}";
+ JSONObject jsonObjectRemove = new JSONObject(str);
+ jsonObjectRemove.remove("myKey");
+ assertTrue("jsonObject should be empty", jsonObjectRemove.isEmpty());
+
+ JSONObject jsonObjectPutNull = new JSONObject(str);
+ jsonObjectPutNull.put("myKey", (Object) null);
+ assertTrue("jsonObject should be empty", jsonObjectPutNull.isEmpty());
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(jsonObjectRemove, jsonObjectPutNull)));
+ }
+
+ /**
+ * Exercise JSONObject quote() method This purpose of quote() is to ensure that
+ * for strings with embedded quotes, the quotes are properly escaped.
+ */
+ @Test
+ public void jsonObjectQuote() {
+ String str;
+ str = "";
+ String quotedStr;
+ quotedStr = JSONObject.quote(str);
+ assertTrue("quote() expected escaped quotes, found " + quotedStr, "\"\"".equals(quotedStr));
+ str = "\"\"";
+ quotedStr = JSONObject.quote(str);
+ assertTrue("quote() expected escaped quotes, found " + quotedStr, "\"\\\"\\\"\"".equals(quotedStr));
+ str = "";
+ quotedStr = JSONObject.quote(str);
+ assertTrue("quote() expected escaped frontslash, found " + quotedStr, "\"<\\/\"".equals(quotedStr));
+ str = "AB\bC";
+ quotedStr = JSONObject.quote(str);
+ assertTrue("quote() expected escaped backspace, found " + quotedStr, "\"AB\\bC\"".equals(quotedStr));
+ str = "ABC\n";
+ quotedStr = JSONObject.quote(str);
+ assertTrue("quote() expected escaped newline, found " + quotedStr, "\"ABC\\n\"".equals(quotedStr));
+ str = "AB\fC";
+ quotedStr = JSONObject.quote(str);
+ assertTrue("quote() expected escaped formfeed, found " + quotedStr, "\"AB\\fC\"".equals(quotedStr));
+ str = "\r";
+ quotedStr = JSONObject.quote(str);
+ assertTrue("quote() expected escaped return, found " + quotedStr, "\"\\r\"".equals(quotedStr));
+ str = "\u1234\u0088";
+ quotedStr = JSONObject.quote(str);
+ assertTrue("quote() expected escaped unicode, found " + quotedStr, "\"\u1234\\u0088\"".equals(quotedStr));
+ }
+
+ /**
+ * Confirm behavior when JSONObject stringToValue() is called for an empty
+ * string
+ */
+ @Test
+ public void stringToValue() {
+ String str = "";
+ String valueStr = (String) (JSONObject.stringToValue(str));
+ assertTrue("stringToValue() expected empty String, found " + valueStr, "".equals(valueStr));
+ }
+
+ /**
+ * Confirm behavior when toJSONArray is called with a null value
+ */
+ @Test
+ public void toJSONArray() {
+ assertTrue("toJSONArray() with null names should be null", null == new JSONObject().toJSONArray(null));
+ }
+
+ /**
+ * Exercise the JSONObject write() method
+ */
+ @Test
+ public void write() throws IOException {
+ String str = "{\"key1\":\"value1\",\"key2\":[1,2,3]}";
+ String expectedStr = str;
+ JSONObject jsonObject = new JSONObject(str);
+ StringWriter stringWriter = new StringWriter();
+ try {
+ String actualStr = jsonObject.write(stringWriter).toString();
+ // key order may change. verify length and individual key content
+ assertEquals("length", expectedStr.length(), actualStr.length());
+ assertTrue("key1", actualStr.contains("\"key1\":\"value1\""));
+ assertTrue("key2", actualStr.contains("\"key2\":[1,2,3]"));
+ } finally {
+ stringWriter.close();
+ }
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Confirms that exceptions thrown when writing values are wrapped properly.
+ */
+ @Test
+ public void testJSONWriterException() {
+ final JSONObject jsonObject = new JSONObject();
+
+ jsonObject.put("someKey", new BrokenToString());
+
+ // test single element JSONObject
+ StringWriter writer = new StringWriter();
+ try {
+ jsonObject.write(writer).toString();
+ fail("Expected an exception, got a String value");
+ } catch (JSONException e) {
+ assertEquals("Unable to write JSONObject value for key: someKey", e.getMessage());
+ } catch (Exception e) {
+ fail("Expected JSONException");
+ } finally {
+ try {
+ writer.close();
+ } catch (Exception e) {
+ }
+ }
+
+ // test multiElement
+ jsonObject.put("somethingElse", "a value");
+
+ writer = new StringWriter();
+ try {
+ jsonObject.write(writer).toString();
+ fail("Expected an exception, got a String value");
+ } catch (JSONException e) {
+ assertEquals("Unable to write JSONObject value for key: someKey", e.getMessage());
+ } catch (Exception e) {
+ fail("Expected JSONException");
+ } finally {
+ try {
+ writer.close();
+ } catch (Exception e) {
+ }
+ }
+
+ // test a more complex object
+ writer = new StringWriter();
+
+ JSONObject object = new JSONObject().put("somethingElse", "a value").put("someKey",
+ new JSONArray().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());
+ } catch (Exception e) {
+ fail("Expected JSONException");
+ } finally {
+ try {
+ writer.close();
+ } catch (Exception e) {
+ }
+ }
+
+ // test a more slightly complex object
+ writer = new StringWriter();
+
+ object = new JSONObject().put("somethingElse", "a value").put("someKey",
+ new JSONArray().put(new JSONObject().put("key1", new BrokenToString())).put(12345));
+ 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());
+ } catch (Exception e) {
+ fail("Expected JSONException");
+ } finally {
+ try {
+ writer.close();
+ } catch (Exception e) {
+ }
+ }
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Exercise the JSONObject write() method
+ */
+ /*
+ * @Test public void writeAppendable() { String str =
+ * "{\"key1\":\"value1\",\"key2\":[1,2,3]}"; String expectedStr = str;
+ * JSONObject jsonObject = new JSONObject(str); StringBuilder stringBuilder =
+ * new StringBuilder(); Appendable appendable = jsonObject.write(stringBuilder);
+ * String actualStr = appendable.toString(); assertTrue("write() expected "
+ * +expectedStr+ " but found " +actualStr, expectedStr.equals(actualStr)); }
+ */
+
+ /**
+ * Exercise the JSONObject write(Writer, int, int) method
+ */
+ @Test
+ public void write3Param() throws IOException {
+ String str0 = "{\"key1\":\"value1\",\"key2\":[1,false,3.14]}";
+ String str2 = "{\n" + " \"key1\": \"value1\",\n" + " \"key2\": [\n" + " 1,\n" + " false,\n"
+ + " 3.14\n" + " ]\n" + " }";
+ JSONObject jsonObject = new JSONObject(str0);
+ StringWriter stringWriter = new StringWriter();
+ try {
+ String actualStr = jsonObject.write(stringWriter, 0, 0).toString();
+
+ assertEquals("length", str0.length(), actualStr.length());
+ assertTrue("key1", actualStr.contains("\"key1\":\"value1\""));
+ assertTrue("key2", actualStr.contains("\"key2\":[1,false,3.14]"));
+ } finally {
+ try {
+ stringWriter.close();
+ } catch (Exception e) {
+ }
+ }
+
+ stringWriter = new StringWriter();
+ try {
+ String actualStr = jsonObject.write(stringWriter, 2, 1).toString();
+
+ assertEquals("length", str2.length(), actualStr.length());
+ assertTrue("key1", actualStr.contains(" \"key1\": \"value1\""));
+ assertTrue("key2",
+ actualStr.contains(" \"key2\": [\n" + " 1,\n" + " false,\n" + " 3.14\n" + " ]"));
+ } finally {
+ try {
+ stringWriter.close();
+ } catch (Exception e) {
+ }
+ }
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Exercise the JSONObject write(Appendable, int, int) method
+ */
+ /*
+ * @Test public void write3ParamAppendable() { String str0 =
+ * "{\"key1\":\"value1\",\"key2\":[1,false,3.14]}"; String str2 = "{\n" +
+ * " \"key1\": \"value1\",\n" + " \"key2\": [\n" + " 1,\n" +
+ * " false,\n" + " 3.14\n" + " ]\n" + " }"; JSONObject jsonObject =
+ * new JSONObject(str0); String expectedStr = str0; StringBuilder stringBuilder
+ * = new StringBuilder(); Appendable appendable =
+ * jsonObject.write(stringBuilder,0,0); String actualStr =
+ * appendable.toString(); assertEquals(expectedStr, actualStr);
+ *
+ * expectedStr = str2; stringBuilder = new StringBuilder(); appendable =
+ * jsonObject.write(stringBuilder,2,1); actualStr = appendable.toString();
+ * assertEquals(expectedStr, actualStr); }
+ */
+
+ /**
+ * Exercise the JSONObject equals() method
+ */
+ @Test
+ public void equals() {
+ String str = "{\"key\":\"value\"}";
+ JSONObject aJsonObject = new JSONObject(str);
+ assertTrue("Same JSONObject should be equal to itself", aJsonObject.equals(aJsonObject));
+ Util.checkJSONObjectMaps(aJsonObject);
+ }
+
+ /**
+ * JSON null is not the same as Java null. This test examines the differences in
+ * how they are handled by JSON-java.
+ */
+ @Test
+ public void jsonObjectNullOperations() {
+ /**
+ * The Javadoc for JSONObject.NULL states: "JSONObject.NULL is equivalent to the
+ * value that JavaScript calls null, whilst Java's null is equivalent to the
+ * value that JavaScript calls undefined."
+ *
+ * Standard ECMA-262 6th Edition / June 2015 (included to help explain the
+ * javadoc): undefined value: primitive value used when a variable has not been
+ * assigned a value Undefined type: type whose sole value is the undefined value
+ * null value: primitive value that represents the intentional absence of any
+ * object value Null type: type whose sole value is the null value Java SE8
+ * language spec (included to help explain the javadoc): The Kinds of Types and
+ * Values ... There is also a special null type, the type of the expression
+ * null, which has no name. Because the null type has no name, it is impossible
+ * to declare a variable of the null type or to cast to the null type. The null
+ * reference is the only possible value of an expression of null type. The null
+ * reference can always be assigned or cast to any reference type. In practice,
+ * the programmer can ignore the null type and just pretend that null is merely
+ * a special literal that can be of any reference type. Extensible Markup
+ * Language (XML) 1.0 Fifth Edition / 26 November 2008 No mention of null
+ * ECMA-404 1st Edition / October 2013: JSON Text ... These are three literal
+ * name tokens: ... null
+ *
+ * There seems to be no best practice to follow, it's all about what we want the
+ * code to do.
+ */
+
+ // add JSONObject.NULL then convert to string in the manner of XML.toString()
+ JSONObject jsonObjectJONull = new JSONObject();
+ Object obj = JSONObject.NULL;
+ jsonObjectJONull.put("key", obj);
+ Object value = jsonObjectJONull.opt("key");
+ assertTrue("opt() JSONObject.NULL should find JSONObject.NULL", obj.equals(value));
+ value = jsonObjectJONull.get("key");
+ assertTrue("get() JSONObject.NULL should find JSONObject.NULL", obj.equals(value));
+ if (value == null) {
+ value = "";
+ }
+ String string = value instanceof String ? (String) value : null;
+ assertTrue("XML toString() should convert JSONObject.NULL to null", string == null);
+
+ // now try it with null
+ JSONObject jsonObjectNull = new JSONObject();
+ obj = null;
+ jsonObjectNull.put("key", obj);
+ value = jsonObjectNull.opt("key");
+ assertNull("opt() null should find null", value);
+ // what is this trying to do? It appears to test absolutely nothing...
// if (value == null) {
// value = "";
// }
// string = value instanceof String ? (String)value : null;
// assertTrue("should convert null to empty string", "".equals(string));
- try {
- value = jsonObjectNull.get("key");
- fail("get() null should throw exception");
- } catch (Exception ignored) {}
-
- /**
- * XML.toString() then goes on to do something with the value
- * if the key val is "content", then value.toString() will be
- * called. This will evaluate to "null" for JSONObject.NULL,
- * and the empty string for null.
- * But if the key is anything else, then JSONObject.NULL will be emitted
- * as null and null will be emitted as ""
- */
- String sJONull = XML.toString(jsonObjectJONull);
- assertTrue("JSONObject.NULL should emit a null value",
- "null ".equals(sJONull));
- String sNull = XML.toString(jsonObjectNull);
- assertTrue("null should emit an empty string", "".equals(sNull));
- Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
- jsonObjectJONull, jsonObjectNull
- )));
- }
-
- @Test(expected = JSONPointerException.class)
- public void queryWithNoResult() {
- new JSONObject().query("/a/b");
- }
-
- @Test
- public void optQueryWithNoResult() {
- assertNull(new JSONObject().optQuery("/a/b"));
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void optQueryWithSyntaxError() {
- new JSONObject().optQuery("invalid");
- }
-
- @Test(expected = JSONException.class)
- public void invalidEscapeSequence() {
- String json = "{ \"\\url\": \"value\" }";
- assertNull("Expected an exception",new JSONObject(json));
- }
-
- /**
- * Exercise JSONObject toMap() method.
- */
- @Test
- public void toMap() {
- String jsonObjectStr =
- "{" +
- "\"key1\":" +
- "[1,2," +
- "{\"key3\":true}" +
- "]," +
- "\"key2\":" +
- "{\"key1\":\"val1\",\"key2\":" +
- "{\"key2\":null}," +
- "\"key3\":42" +
- "}," +
- "\"key3\":" +
- "[" +
- "[\"value1\",2.1]" +
- "," +
- "[null]" +
- "]" +
- "}";
-
- JSONObject jsonObject = new JSONObject(jsonObjectStr);
- Map,?> map = jsonObject.toMap();
-
- assertTrue("Map should not be null", map != null);
- assertTrue("Map should have 3 elements", map.size() == 3);
-
- List> key1List = (List>)map.get("key1");
- assertTrue("key1 should not be null", key1List != null);
- assertTrue("key1 list should have 3 elements", key1List.size() == 3);
- assertTrue("key1 value 1 should be 1", key1List.get(0).equals(Integer.valueOf(1)));
- assertTrue("key1 value 2 should be 2", key1List.get(1).equals(Integer.valueOf(2)));
-
- Map,?> key1Value3Map = (Map,?>)key1List.get(2);
- assertTrue("Map should not be null", key1Value3Map != null);
- assertTrue("Map should have 1 element", key1Value3Map.size() == 1);
- assertTrue("Map key3 should be true", key1Value3Map.get("key3").equals(Boolean.TRUE));
-
- Map,?> key2Map = (Map,?>)map.get("key2");
- assertTrue("key2 should not be null", key2Map != null);
- assertTrue("key2 map should have 3 elements", key2Map.size() == 3);
- assertTrue("key2 map key 1 should be val1", key2Map.get("key1").equals("val1"));
- assertTrue("key2 map key 3 should be 42", key2Map.get("key3").equals(Integer.valueOf(42)));
-
- Map,?> key2Val2Map = (Map,?>)key2Map.get("key2");
- assertTrue("key2 map key 2 should not be null", key2Val2Map != null);
- assertTrue("key2 map key 2 should have an entry", key2Val2Map.containsKey("key2"));
- assertTrue("key2 map key 2 value should be null", key2Val2Map.get("key2") == null);
-
- List> key3List = (List>)map.get("key3");
- assertTrue("key3 should not be null", key3List != null);
- assertTrue("key3 list should have 3 elements", key3List.size() == 2);
-
- List> key3Val1List = (List>)key3List.get(0);
- assertTrue("key3 list val 1 should not be null", key3Val1List != null);
- assertTrue("key3 list val 1 should have 2 elements", key3Val1List.size() == 2);
- assertTrue("key3 list val 1 list element 1 should be value1", key3Val1List.get(0).equals("value1"));
- assertTrue("key3 list val 1 list element 2 should be 2.1", key3Val1List.get(1).equals(new BigDecimal("2.1")));
-
- List> key3Val2List = (List>)key3List.get(1);
- assertTrue("key3 list val 2 should not be null", key3Val2List != null);
- assertTrue("key3 list val 2 should have 1 element", key3Val2List.size() == 1);
- assertTrue("key3 list val 2 list element 1 should be null", key3Val2List.get(0) == null);
-
- // Assert that toMap() is a deep copy
- jsonObject.getJSONArray("key3").getJSONArray(0).put(0, "still value 1");
- assertTrue("key3 list val 1 list element 1 should be value1", key3Val1List.get(0).equals("value1"));
-
- // assert that the new map is mutable
- assertTrue("Removing a key should succeed", map.remove("key3") != null);
- assertTrue("Map should have 2 elements", map.size() == 2);
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * test that validates a singleton can be serialized as a bean.
- */
- @SuppressWarnings("boxing")
- @Test
- public void testSingletonBean() {
- final JSONObject jo = new JSONObject(Singleton.getInstance());
- assertEquals(jo.keySet().toString(), 1, jo.length());
- assertEquals(0, jo.get("someInt"));
- assertEquals(null, jo.opt("someString"));
-
- // Update the singleton values
- Singleton.getInstance().setSomeInt(42);
- Singleton.getInstance().setSomeString("Something");
- final JSONObject jo2 = new JSONObject(Singleton.getInstance());
- assertEquals(2, jo2.length());
- assertEquals(42, jo2.get("someInt"));
- assertEquals("Something", jo2.get("someString"));
-
- // ensure our original jo hasn't changed.
- assertEquals(0, jo.get("someInt"));
- assertEquals(null, jo.opt("someString"));
- Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
- jo, jo2
- )));
- }
-
- /**
- * test that validates a singleton can be serialized as a bean.
- */
- @SuppressWarnings("boxing")
- @Test
- public void testSingletonEnumBean() {
- final JSONObject jo = new JSONObject(SingletonEnum.getInstance());
- assertEquals(jo.keySet().toString(), 1, jo.length());
- assertEquals(0, jo.get("someInt"));
- assertEquals(null, jo.opt("someString"));
-
- // Update the singleton values
- SingletonEnum.getInstance().setSomeInt(42);
- SingletonEnum.getInstance().setSomeString("Something");
- final JSONObject jo2 = new JSONObject(SingletonEnum.getInstance());
- assertEquals(2, jo2.length());
- assertEquals(42, jo2.get("someInt"));
- assertEquals("Something", jo2.get("someString"));
-
- // ensure our original jo hasn't changed.
- assertEquals(0, jo.get("someInt"));
- assertEquals(null, jo.opt("someString"));
- Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
- jo, jo2
- )));
- }
-
- /**
- * Test to validate that a generic class can be serialized as a bean.
- */
- @SuppressWarnings("boxing")
- @Test
- public void testGenericBean() {
- GenericBean bean = new GenericBean(42);
- final JSONObject jo = new JSONObject(bean);
- assertEquals(jo.keySet().toString(), 8, jo.length());
- assertEquals(42, jo.get("genericValue"));
- assertEquals("Expected the getter to only be called once",
- 1, bean.genericGetCounter);
- assertEquals(0, bean.genericSetCounter);
- Util.checkJSONObjectMaps(jo);
- }
-
- /**
- * Test to validate that a generic class can be serialized as a bean.
- */
- @SuppressWarnings("boxing")
- @Test
- public void testGenericIntBean() {
- GenericBeanInt bean = new GenericBeanInt(42);
- final JSONObject jo = new JSONObject(bean);
- assertEquals(jo.keySet().toString(), 10, jo.length());
- assertEquals(42, jo.get("genericValue"));
- assertEquals("Expected the getter to only be called once",
- 1, bean.genericGetCounter);
- assertEquals(0, bean.genericSetCounter);
- Util.checkJSONObjectMaps(jo);
- }
-
- /**
- * Test to verify key limitations in the JSONObject bean serializer.
- */
- @Test
- public void testWierdListBean() {
- @SuppressWarnings("boxing")
- WeirdList bean = new WeirdList(42, 43, 44);
- final JSONObject jo = new JSONObject(bean);
- // get() should have a key of 0 length
- // get(int) should be ignored base on parameter count
- // getInt(int) should also be ignored based on parameter count
- // add(Integer) should be ignore as it doesn't start with get/is and also has a parameter
- // getALL should be mapped
- assertEquals("Expected 1 key to be mapped. Instead found: "+jo.keySet().toString(),
- 1, jo.length());
- assertNotNull(jo.get("ALL"));
- 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.
- */
- public void testObjectToBigDecimal() {
- double value = 1412078745.01074;
- Reader reader = new StringReader("[{\"value\": " + value + "}]");
- JSONTokener tokener = new JSONTokener(reader);
- JSONArray array = new JSONArray(tokener);
- JSONObject jsonObject = array.getJSONObject(0);
-
- BigDecimal current = jsonObject.getBigDecimal("value");
- BigDecimal wantedValue = BigDecimal.valueOf(value);
-
- assertEquals(current, wantedValue);
- Util.checkJSONObjectMaps(jsonObject);
- Util.checkJSONArrayMaps(array, jsonObject.getMapType());
- }
-
- /**
- * Tests the exception portions of populateMap.
- */
- @Test
- public void testExceptionalBean() {
- ExceptionalBean bean = new ExceptionalBean();
- final JSONObject jo = new JSONObject(bean);
- assertEquals("Expected 1 key to be mapped. Instead found: "+jo.keySet().toString(),
- 1, jo.length());
- assertTrue(jo.get("closeable") instanceof JSONObject);
- assertTrue(jo.getJSONObject("closeable").has("string"));
- Util.checkJSONObjectMaps(jo);
- }
-
- @Test(expected=NullPointerException.class)
- public void testPutNullBoolean() {
- // null put key
- JSONObject jsonObject = new JSONObject("{}");
- jsonObject.put(null, false);
- fail("Expected an exception");
- }
- @Test(expected=NullPointerException.class)
- public void testPutNullCollection() {
- // null put key
- JSONObject jsonObject = new JSONObject("{}");
- jsonObject.put(null, Collections.emptySet());
- fail("Expected an exception");
- }
- @Test(expected=NullPointerException.class)
- public void testPutNullDouble() {
- // null put key
- JSONObject jsonObject = new JSONObject("{}");
- jsonObject.put(null, 0.0d);
- fail("Expected an exception");
- }
- @Test(expected=NullPointerException.class)
- public void testPutNullFloat() {
- // null put key
- JSONObject jsonObject = new JSONObject("{}");
- jsonObject.put(null, 0.0f);
- fail("Expected an exception");
- }
- @Test(expected=NullPointerException.class)
- public void testPutNullInt() {
- // null put key
- JSONObject jsonObject = new JSONObject("{}");
- jsonObject.put(null, 0);
- fail("Expected an exception");
- }
- @Test(expected=NullPointerException.class)
- public void testPutNullLong() {
- // null put key
- JSONObject jsonObject = new JSONObject("{}");
- jsonObject.put(null, 0L);
- fail("Expected an exception");
- }
- @Test(expected=NullPointerException.class)
- public void testPutNullMap() {
- // null put key
- JSONObject jsonObject = new JSONObject("{}");
- jsonObject.put(null, Collections.emptyMap());
- fail("Expected an exception");
- }
- @Test(expected=NullPointerException.class)
- public void testPutNullObject() {
- // null put key
- JSONObject jsonObject = new JSONObject("{}");
- jsonObject.put(null, new Object());
- fail("Expected an exception");
- }
- @Test(expected=JSONException.class)
- public void testSelfRecursiveObject() {
- // A -> A ...
- RecursiveBean ObjA = new RecursiveBean("ObjA");
- ObjA.setRef(ObjA);
- new JSONObject(ObjA);
- fail("Expected an exception");
- }
- @Test(expected=JSONException.class)
- public void testLongSelfRecursiveObject() {
- // B -> A -> A ...
- RecursiveBean ObjA = new RecursiveBean("ObjA");
- RecursiveBean ObjB = new RecursiveBean("ObjB");
- ObjB.setRef(ObjA);
- ObjA.setRef(ObjA);
- new JSONObject(ObjB);
- fail("Expected an exception");
- }
- @Test(expected=JSONException.class)
- public void testSimpleRecursiveObject() {
- // B -> A -> B ...
- RecursiveBean ObjA = new RecursiveBean("ObjA");
- RecursiveBean ObjB = new RecursiveBean("ObjB");
- ObjB.setRef(ObjA);
- ObjA.setRef(ObjB);
- new JSONObject(ObjA);
- fail("Expected an exception");
- }
- @Test(expected=JSONException.class)
- public void testLongRecursiveObject() {
- // D -> C -> B -> A -> D ...
- RecursiveBean ObjA = new RecursiveBean("ObjA");
- RecursiveBean ObjB = new RecursiveBean("ObjB");
- RecursiveBean ObjC = new RecursiveBean("ObjC");
- RecursiveBean ObjD = new RecursiveBean("ObjD");
- ObjC.setRef(ObjB);
- ObjB.setRef(ObjA);
- ObjD.setRef(ObjC);
- ObjA.setRef(ObjD);
- new JSONObject(ObjB);
- fail("Expected an exception");
- }
- @Test(expected=JSONException.class)
- public void testRepeatObjectRecursive() {
- // C -> B -> A -> D -> C ...
- // -> D -> C ...
- RecursiveBean ObjA = new RecursiveBean("ObjA");
- RecursiveBean ObjB = new RecursiveBean("ObjB");
- RecursiveBean ObjC = new RecursiveBean("ObjC");
- RecursiveBean ObjD = new RecursiveBean("ObjD");
- ObjC.setRef(ObjB);
- ObjB.setRef(ObjA);
- ObjB.setRef2(ObjD);
- ObjA.setRef(ObjD);
- ObjD.setRef(ObjC);
- new JSONObject(ObjC);
- fail("Expected an exception");
- }
- @Test
- public void testRepeatObjectNotRecursive() {
- // C -> B -> A
- // -> A
- RecursiveBean ObjA = new RecursiveBean("ObjA");
- RecursiveBean ObjB = new RecursiveBean("ObjB");
- RecursiveBean ObjC = new RecursiveBean("ObjC");
- ObjC.setRef(ObjA);
- ObjB.setRef(ObjA);
- ObjB.setRef2(ObjA);
- JSONObject j0 = new JSONObject(ObjC);
- JSONObject j1 = new JSONObject(ObjB);
- JSONObject j2 = new JSONObject(ObjA);
- Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
- j0, j1, j2
- )));
- }
- @Test
- public void testLongRepeatObjectNotRecursive() {
- // C -> B -> A -> D -> E
- // -> D -> E
- RecursiveBean ObjA = new RecursiveBean("ObjA");
- RecursiveBean ObjB = new RecursiveBean("ObjB");
- RecursiveBean ObjC = new RecursiveBean("ObjC");
- RecursiveBean ObjD = new RecursiveBean("ObjD");
- RecursiveBean ObjE = new RecursiveBean("ObjE");
- ObjC.setRef(ObjB);
- ObjB.setRef(ObjA);
- ObjB.setRef2(ObjD);
- ObjA.setRef(ObjD);
- ObjD.setRef(ObjE);
- JSONObject j0 = new JSONObject(ObjC);
- JSONObject j1 = new JSONObject(ObjB);
- JSONObject j2 = new JSONObject(ObjA);
- JSONObject j3 = new JSONObject(ObjD);
- JSONObject j4 = new JSONObject(ObjE);
- Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(
- j0, j1, j2, j3, j4
- )));
- }
- @Test(expected=JSONException.class)
- public void testRecursiveEquals() {
- RecursiveBeanEquals a = new RecursiveBeanEquals("same");
- a.setRef(a);
- JSONObject j0 = new JSONObject(a);
- Util.checkJSONObjectMaps(j0);
- }
- @Test
- public void testNotRecursiveEquals() {
- RecursiveBeanEquals a = new RecursiveBeanEquals("same");
- RecursiveBeanEquals b = new RecursiveBeanEquals("same");
- RecursiveBeanEquals c = new RecursiveBeanEquals("same");
- a.setRef(b);
- b.setRef(c);
- JSONObject j0 = new JSONObject(a);
- Util.checkJSONObjectMaps(j0);
- }
-
-
- @Test
- public void testIssue548ObjectWithEmptyJsonArray() {
- JSONObject jsonObject = new JSONObject("{\"empty_json_array\": []}");
- assertTrue("missing expected key 'empty_json_array'", jsonObject.has("empty_json_array"));
- assertNotNull("'empty_json_array' should be an array", jsonObject.getJSONArray("empty_json_array"));
- assertEquals("'empty_json_array' should have a length of 0", 0, jsonObject.getJSONArray("empty_json_array").length());
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Tests if calling JSONObject clear() method actually makes the JSONObject empty
- */
- @Test(expected = JSONException.class)
- public void jsonObjectClearMethodTest() {
- //Adds random stuff to the JSONObject
- JSONObject jsonObject = new JSONObject();
- jsonObject.put("key1", 123);
- jsonObject.put("key2", "456");
- jsonObject.put("key3", new JSONObject());
- jsonObject.clear(); //Clears the JSONObject
- assertTrue("expected jsonObject.length() == 0", jsonObject.length() == 0); //Check if its length is 0
- jsonObject.getInt("key1"); //Should throws org.json.JSONException: JSONObject["asd"] not found
- Util.checkJSONObjectMaps(jsonObject);
- }
-
- /**
- * Tests for stack overflow. See https://github.com/stleary/JSON-java/issues/654
- */
- @Test(expected = JSONException.class)
- public void issue654StackOverflowInput() {
- //String base64Bytes ="eyJHWiI6Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMCkwLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMCkwLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sJe3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTApMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sJe3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTApMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMCkwLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMCkwLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sJe3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTApMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sJe3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTApMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7c3t7e3t7e3vPAAAAAAAAAHt7e3t7e3t7e3t7e3t7e3t7e3t7e1ste3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e88AAAAAAAAAe3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7f3syMv//e3t7e3t7e3t7e3t7e3sx//////8=";
- //String input = new String(java.util.Base64.getDecoder().decode(base64Bytes));
- String input = "{\"GZ\":[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{s{{{{{{{";
- JSONObject json_input = new JSONObject(input);
- assertNotNull(json_input);
- fail("Excepected Exception.");
- Util.checkJSONObjectMaps(json_input);
- }
-
- /**
- * Tests for incorrect object/array nesting. See https://github.com/stleary/JSON-java/issues/654
- */
- @Test(expected = JSONException.class)
- public void issue654IncorrectNestingNoKey1() {
- JSONObject json_input = new JSONObject("{{\"a\":0}}");
- assertNotNull(json_input);
- fail("Expected Exception.");
- }
-
- /**
- * Tests for incorrect object/array nesting. See https://github.com/stleary/JSON-java/issues/654
- */
- @Test(expected = JSONException.class)
- public void issue654IncorrectNestingNoKey2() {
- JSONObject json_input = new JSONObject("{[\"a\"]}");
- assertNotNull(json_input);
- fail("Excepected Exception.");
- }
-
- /**
- * Tests for stack overflow. See https://github.com/stleary/JSON-java/issues/654
- */
- @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.");
- }
-
- @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));
- }
+ try {
+ value = jsonObjectNull.get("key");
+ fail("get() null should throw exception");
+ } catch (Exception ignored) {
+ }
+
+ /**
+ * XML.toString() then goes on to do something with the value if the key val is
+ * "content", then value.toString() will be called. This will evaluate to "null"
+ * for JSONObject.NULL, and the empty string for null. But if the key is
+ * anything else, then JSONObject.NULL will be emitted as null and
+ * null will be emitted as ""
+ */
+ String sJONull = XML.toString(jsonObjectJONull);
+ assertTrue("JSONObject.NULL should emit a null value", "null ".equals(sJONull));
+ String sNull = XML.toString(jsonObjectNull);
+ assertTrue("null should emit an empty string", "".equals(sNull));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(jsonObjectJONull, jsonObjectNull)));
+ }
+
+ @Test(expected = JSONPointerException.class)
+ public void queryWithNoResult() {
+ new JSONObject().query("/a/b");
+ }
+
+ @Test
+ public void optQueryWithNoResult() {
+ assertNull(new JSONObject().optQuery("/a/b"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void optQueryWithSyntaxError() {
+ new JSONObject().optQuery("invalid");
+ }
+
+ @Test(expected = JSONException.class)
+ public void invalidEscapeSequence() {
+ String json = "{ \"\\url\": \"value\" }";
+ assertNull("Expected an exception", new JSONObject(json));
+ }
+
+ /**
+ * Exercise JSONObject toMap() method.
+ */
+ @Test
+ public void toMap() {
+ String jsonObjectStr = "{" + "\"key1\":" + "[1,2," + "{\"key3\":true}" + "]," + "\"key2\":"
+ + "{\"key1\":\"val1\",\"key2\":" + "{\"key2\":null}," + "\"key3\":42" + "}," + "\"key3\":" + "["
+ + "[\"value1\",2.1]" + "," + "[null]" + "]" + "}";
+
+ JSONObject jsonObject = new JSONObject(jsonObjectStr);
+ Map, ?> map = jsonObject.toMap();
+
+ assertTrue("Map should not be null", map != null);
+ assertTrue("Map should have 3 elements", map.size() == 3);
+
+ List> key1List = (List>) map.get("key1");
+ assertTrue("key1 should not be null", key1List != null);
+ assertTrue("key1 list should have 3 elements", key1List.size() == 3);
+ assertTrue("key1 value 1 should be 1", key1List.get(0).equals(Integer.valueOf(1)));
+ assertTrue("key1 value 2 should be 2", key1List.get(1).equals(Integer.valueOf(2)));
+
+ Map, ?> key1Value3Map = (Map, ?>) key1List.get(2);
+ assertTrue("Map should not be null", key1Value3Map != null);
+ assertTrue("Map should have 1 element", key1Value3Map.size() == 1);
+ assertTrue("Map key3 should be true", key1Value3Map.get("key3").equals(Boolean.TRUE));
+
+ Map, ?> key2Map = (Map, ?>) map.get("key2");
+ assertTrue("key2 should not be null", key2Map != null);
+ assertTrue("key2 map should have 3 elements", key2Map.size() == 3);
+ assertTrue("key2 map key 1 should be val1", key2Map.get("key1").equals("val1"));
+ assertTrue("key2 map key 3 should be 42", key2Map.get("key3").equals(Integer.valueOf(42)));
+
+ Map, ?> key2Val2Map = (Map, ?>) key2Map.get("key2");
+ assertTrue("key2 map key 2 should not be null", key2Val2Map != null);
+ assertTrue("key2 map key 2 should have an entry", key2Val2Map.containsKey("key2"));
+ assertTrue("key2 map key 2 value should be null", key2Val2Map.get("key2") == null);
+
+ List> key3List = (List>) map.get("key3");
+ assertTrue("key3 should not be null", key3List != null);
+ assertTrue("key3 list should have 3 elements", key3List.size() == 2);
+
+ List> key3Val1List = (List>) key3List.get(0);
+ assertTrue("key3 list val 1 should not be null", key3Val1List != null);
+ assertTrue("key3 list val 1 should have 2 elements", key3Val1List.size() == 2);
+ assertTrue("key3 list val 1 list element 1 should be value1", key3Val1List.get(0).equals("value1"));
+ assertTrue("key3 list val 1 list element 2 should be 2.1", key3Val1List.get(1).equals(new BigDecimal("2.1")));
+
+ List> key3Val2List = (List>) key3List.get(1);
+ assertTrue("key3 list val 2 should not be null", key3Val2List != null);
+ assertTrue("key3 list val 2 should have 1 element", key3Val2List.size() == 1);
+ assertTrue("key3 list val 2 list element 1 should be null", key3Val2List.get(0) == null);
+
+ // Assert that toMap() is a deep copy
+ jsonObject.getJSONArray("key3").getJSONArray(0).put(0, "still value 1");
+ assertTrue("key3 list val 1 list element 1 should be value1", key3Val1List.get(0).equals("value1"));
+
+ // assert that the new map is mutable
+ assertTrue("Removing a key should succeed", map.remove("key3") != null);
+ assertTrue("Map should have 2 elements", map.size() == 2);
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * test that validates a singleton can be serialized as a bean.
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void testSingletonBean() {
+ final JSONObject jo = new JSONObject(Singleton.getInstance());
+ assertEquals(jo.keySet().toString(), 1, jo.length());
+ assertEquals(0, jo.get("someInt"));
+ assertEquals(null, jo.opt("someString"));
+
+ // Update the singleton values
+ Singleton.getInstance().setSomeInt(42);
+ Singleton.getInstance().setSomeString("Something");
+ final JSONObject jo2 = new JSONObject(Singleton.getInstance());
+ assertEquals(2, jo2.length());
+ assertEquals(42, jo2.get("someInt"));
+ assertEquals("Something", jo2.get("someString"));
+
+ // ensure our original jo hasn't changed.
+ assertEquals(0, jo.get("someInt"));
+ assertEquals(null, jo.opt("someString"));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(jo, jo2)));
+ }
+
+ /**
+ * test that validates a singleton can be serialized as a bean.
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void testSingletonEnumBean() {
+ final JSONObject jo = new JSONObject(SingletonEnum.getInstance());
+ assertEquals(jo.keySet().toString(), 1, jo.length());
+ assertEquals(0, jo.get("someInt"));
+ assertEquals(null, jo.opt("someString"));
+
+ // Update the singleton values
+ SingletonEnum.getInstance().setSomeInt(42);
+ SingletonEnum.getInstance().setSomeString("Something");
+ final JSONObject jo2 = new JSONObject(SingletonEnum.getInstance());
+ assertEquals(2, jo2.length());
+ assertEquals(42, jo2.get("someInt"));
+ assertEquals("Something", jo2.get("someString"));
+
+ // ensure our original jo hasn't changed.
+ assertEquals(0, jo.get("someInt"));
+ assertEquals(null, jo.opt("someString"));
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(jo, jo2)));
+ }
+
+ /**
+ * Test to validate that a generic class can be serialized as a bean.
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void testGenericBean() {
+ GenericBean bean = new GenericBean<>(42);
+ final JSONObject jo = new JSONObject(bean);
+ assertEquals(jo.keySet().toString(), 8, jo.length());
+ assertEquals(42, jo.get("genericValue"));
+ assertEquals("Expected the getter to only be called once", 1, bean.genericGetCounter);
+ assertEquals(0, bean.genericSetCounter);
+ Util.checkJSONObjectMaps(jo);
+ }
+
+ /**
+ * Test to validate that a generic class can be serialized as a bean.
+ */
+ @SuppressWarnings("boxing")
+ @Test
+ public void testGenericIntBean() {
+ GenericBeanInt bean = new GenericBeanInt(42);
+ final JSONObject jo = new JSONObject(bean);
+ assertEquals(jo.keySet().toString(), 10, jo.length());
+ assertEquals(42, jo.get("genericValue"));
+ assertEquals("Expected the getter to only be called once", 1, bean.genericGetCounter);
+ assertEquals(0, bean.genericSetCounter);
+ Util.checkJSONObjectMaps(jo);
+ }
+
+ /**
+ * Test to verify key limitations in the JSONObject bean
+ * serializer.
+ */
+ @Test
+ public void testWierdListBean() {
+ @SuppressWarnings("boxing")
+ WeirdList bean = new WeirdList(42, 43, 44);
+ final JSONObject jo = new JSONObject(bean);
+ // get() should have a key of 0 length
+ // get(int) should be ignored base on parameter count
+ // getInt(int) should also be ignored based on parameter count
+ // add(Integer) should be ignore as it doesn't start with get/is and also has a
+ // parameter
+ // getALL should be mapped
+ assertEquals("Expected 1 key to be mapped. Instead found: " + jo.keySet().toString(), 1, jo.length());
+ assertNotNull(jo.get("ALL"));
+ 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 + "}]");
+ JSONTokener tokener = new JSONTokener(reader);
+ JSONArray array = new JSONArray(tokener);
+ JSONObject jsonObject = array.getJSONObject(0);
+
+ BigDecimal current = jsonObject.getBigDecimal("value");
+ BigDecimal wantedValue = BigDecimal.valueOf(value);
+
+ assertEquals(current, wantedValue);
+ Util.checkJSONObjectMaps(jsonObject);
+ Util.checkJSONArrayMaps(array, jsonObject.getMapType());
+ }
+
+ /**
+ * Tests the exception portions of populateMap.
+ */
+ @Test
+ public void testExceptionalBean() {
+ ExceptionalBean bean = new ExceptionalBean();
+ final JSONObject jo = new JSONObject(bean);
+ assertEquals("Expected 1 key to be mapped. Instead found: " + jo.keySet().toString(), 1, jo.length());
+ assertTrue(jo.get("closeable") instanceof JSONObject);
+ assertTrue(jo.getJSONObject("closeable").has("string"));
+ Util.checkJSONObjectMaps(jo);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testPutNullBoolean() {
+ // null put key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.put(null, false);
+ fail("Expected an exception");
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testPutNullCollection() {
+ // null put key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.put(null, Collections.emptySet());
+ fail("Expected an exception");
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testPutNullDouble() {
+ // null put key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.put(null, 0.0d);
+ fail("Expected an exception");
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testPutNullFloat() {
+ // null put key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.put(null, 0.0f);
+ fail("Expected an exception");
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testPutNullInt() {
+ // null put key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.put(null, 0);
+ fail("Expected an exception");
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testPutNullLong() {
+ // null put key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.put(null, 0L);
+ fail("Expected an exception");
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testPutNullMap() {
+ // null put key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.put(null, Collections.emptyMap());
+ fail("Expected an exception");
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testPutNullObject() {
+ // null put key
+ JSONObject jsonObject = new JSONObject("{}");
+ jsonObject.put(null, new Object());
+ fail("Expected an exception");
+ }
+
+ @Test(expected = JSONException.class)
+ public void testSelfRecursiveObject() {
+ // A -> A ...
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ ObjA.setRef(ObjA);
+ new JSONObject(ObjA);
+ fail("Expected an exception");
+ }
+
+ @Test(expected = JSONException.class)
+ public void testLongSelfRecursiveObject() {
+ // B -> A -> A ...
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ ObjB.setRef(ObjA);
+ ObjA.setRef(ObjA);
+ new JSONObject(ObjB);
+ fail("Expected an exception");
+ }
+
+ @Test(expected = JSONException.class)
+ public void testSimpleRecursiveObject() {
+ // B -> A -> B ...
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ ObjB.setRef(ObjA);
+ ObjA.setRef(ObjB);
+ new JSONObject(ObjA);
+ fail("Expected an exception");
+ }
+
+ @Test(expected = JSONException.class)
+ public void testLongRecursiveObject() {
+ // D -> C -> B -> A -> D ...
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ RecursiveBean ObjC = new RecursiveBean("ObjC");
+ RecursiveBean ObjD = new RecursiveBean("ObjD");
+ ObjC.setRef(ObjB);
+ ObjB.setRef(ObjA);
+ ObjD.setRef(ObjC);
+ ObjA.setRef(ObjD);
+ new JSONObject(ObjB);
+ fail("Expected an exception");
+ }
+
+ @Test(expected = JSONException.class)
+ public void testRepeatObjectRecursive() {
+ // C -> B -> A -> D -> C ...
+ // -> D -> C ...
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ RecursiveBean ObjC = new RecursiveBean("ObjC");
+ RecursiveBean ObjD = new RecursiveBean("ObjD");
+ ObjC.setRef(ObjB);
+ ObjB.setRef(ObjA);
+ ObjB.setRef2(ObjD);
+ ObjA.setRef(ObjD);
+ ObjD.setRef(ObjC);
+ new JSONObject(ObjC);
+ fail("Expected an exception");
+ }
+
+ @Test
+ public void testRepeatObjectNotRecursive() {
+ // C -> B -> A
+ // -> A
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ RecursiveBean ObjC = new RecursiveBean("ObjC");
+ ObjC.setRef(ObjA);
+ ObjB.setRef(ObjA);
+ ObjB.setRef2(ObjA);
+ JSONObject j0 = new JSONObject(ObjC);
+ JSONObject j1 = new JSONObject(ObjB);
+ JSONObject j2 = new JSONObject(ObjA);
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(j0, j1, j2)));
+ }
+
+ @Test
+ public void testLongRepeatObjectNotRecursive() {
+ // C -> B -> A -> D -> E
+ // -> D -> E
+ RecursiveBean ObjA = new RecursiveBean("ObjA");
+ RecursiveBean ObjB = new RecursiveBean("ObjB");
+ RecursiveBean ObjC = new RecursiveBean("ObjC");
+ RecursiveBean ObjD = new RecursiveBean("ObjD");
+ RecursiveBean ObjE = new RecursiveBean("ObjE");
+ ObjC.setRef(ObjB);
+ ObjB.setRef(ObjA);
+ ObjB.setRef2(ObjD);
+ ObjA.setRef(ObjD);
+ ObjD.setRef(ObjE);
+ JSONObject j0 = new JSONObject(ObjC);
+ JSONObject j1 = new JSONObject(ObjB);
+ JSONObject j2 = new JSONObject(ObjA);
+ JSONObject j3 = new JSONObject(ObjD);
+ JSONObject j4 = new JSONObject(ObjE);
+ Util.checkJSONObjectsMaps(new ArrayList(Arrays.asList(j0, j1, j2, j3, j4)));
+ }
+
+ @Test(expected = JSONException.class)
+ public void testRecursiveEquals() {
+ RecursiveBeanEquals a = new RecursiveBeanEquals("same");
+ a.setRef(a);
+ JSONObject j0 = new JSONObject(a);
+ Util.checkJSONObjectMaps(j0);
+ }
+
+ @Test
+ public void testNotRecursiveEquals() {
+ RecursiveBeanEquals a = new RecursiveBeanEquals("same");
+ RecursiveBeanEquals b = new RecursiveBeanEquals("same");
+ RecursiveBeanEquals c = new RecursiveBeanEquals("same");
+ a.setRef(b);
+ b.setRef(c);
+ JSONObject j0 = new JSONObject(a);
+ Util.checkJSONObjectMaps(j0);
+ }
+
+ @Test
+ public void testIssue548ObjectWithEmptyJsonArray() {
+ JSONObject jsonObject = new JSONObject("{\"empty_json_array\": []}");
+ assertTrue("missing expected key 'empty_json_array'", jsonObject.has("empty_json_array"));
+ assertNotNull("'empty_json_array' should be an array", jsonObject.getJSONArray("empty_json_array"));
+ assertEquals("'empty_json_array' should have a length of 0", 0,
+ jsonObject.getJSONArray("empty_json_array").length());
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Tests if calling JSONObject clear() method actually makes the JSONObject
+ * empty
+ */
+ @Test(expected = JSONException.class)
+ public void jsonObjectClearMethodTest() {
+ // Adds random stuff to the JSONObject
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("key1", 123);
+ jsonObject.put("key2", "456");
+ jsonObject.put("key3", new JSONObject());
+ jsonObject.clear(); // Clears the JSONObject
+ assertTrue("expected jsonObject.length() == 0", jsonObject.length() == 0); // Check if its length is 0
+ jsonObject.getInt("key1"); // Should throws org.json.JSONException: JSONObject["asd"] not found
+ Util.checkJSONObjectMaps(jsonObject);
+ }
+
+ /**
+ * Tests for stack overflow. See https://github.com/stleary/JSON-java/issues/654
+ */
+ @Test(expected = JSONException.class)
+ public void issue654StackOverflowInput() {
+ // String base64Bytes
+ // ="eyJHWiI6Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMCkwLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMCkwLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sJe3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTApMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sJe3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTApMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMCkwLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMCkwLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sJe3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTApMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3sJe3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTApMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7ewl7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7c3t7e3t7e3vPAAAAAAAAAHt7e3t7e3t7e3t7e3t7e3t7e3t7e1ste3t7e3t7e3t7e3t7e3t7e3t7e3t7CXt7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3tbLTAtMCx7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e1stMC0wLHt7e3t7e3t7e3t7e3t7e3t7e88AAAAAAAAAe3t7e3t7e3t7e3t7e3t7e3t7e3t7Wy0wLTAse3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7f3syMv//e3t7e3t7e3t7e3t7e3sx//////8=";
+ // String input = new String(java.util.Base64.getDecoder().decode(base64Bytes));
+ String input = "{\"GZ\":[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0)0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{{{{{{{{{{{{{{{{{{{{[-0-0,{{{{{{{{{{s{{{{{{{";
+ JSONObject json_input = new JSONObject(input);
+ assertNotNull(json_input);
+ fail("Excepected Exception.");
+ Util.checkJSONObjectMaps(json_input);
+ }
+
+ /**
+ * Tests for incorrect object/array nesting. See
+ * https://github.com/stleary/JSON-java/issues/654
+ */
+ @Test(expected = JSONException.class)
+ public void issue654IncorrectNestingNoKey1() {
+ JSONObject json_input = new JSONObject("{{\"a\":0}}");
+ assertNotNull(json_input);
+ fail("Expected Exception.");
+ }
+
+ /**
+ * Tests for incorrect object/array nesting. See
+ * https://github.com/stleary/JSON-java/issues/654
+ */
+ @Test(expected = JSONException.class)
+ public void issue654IncorrectNestingNoKey2() {
+ JSONObject json_input = new JSONObject("{[\"a\"]}");
+ assertNotNull(json_input);
+ fail("Excepected Exception.");
+ }
+
+ /**
+ * Tests for stack overflow. See https://github.com/stleary/JSON-java/issues/654
+ */
+ @Ignore("This test relies on system constraints and may not always pass. See: https://github.com/stleary/JSON-java/issues/821")
+ @Test(expected = JSONException.class)
+ public void issue654StackOverflowInputWellFormed() {
+ // String input = new String(java.util.Base64.getDecoder().decode(base64Bytes));
+ final InputStream resourceAsStream = JSONObjectTest.class.getClassLoader()
+ .getResourceAsStream("Issue654WellFormedObject.json");
+ JSONTokener tokener = new JSONTokener(resourceAsStream);
+ JSONObject json_input = new JSONObject(tokener);
+ assertNotNull(json_input);
+ fail("Excepected Exception due to stack overflow.");
+ }
+
+ @Test
+ public void testIssue682SimilarityOfJSONString() {
+ JSONObject jo1 = new JSONObject().put("a", new MyJsonString()).put("b", 2);
+ JSONObject jo2 = new JSONObject().put("a", new MyJsonString()).put("b", 2);
+ assertTrue(jo1.similar(jo2));
+
+ JSONObject jo3 = new JSONObject().put("a", new JSONString() {
+ @Override
+ public String toJSONString() {
+ return "\"different value\"";
+ }
+ }).put("b", 2);
+ assertFalse(jo1.similar(jo3));
+ }
+
+ private static final Number[] NON_FINITE_NUMBERS = { Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN,
+ Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NaN };
+
+ @Test
+ public void issue713MapConstructorWithNonFiniteNumbers() {
+ for (Number nonFinite : NON_FINITE_NUMBERS) {
+ Map map = new HashMap<>();
+ map.put("a", nonFinite);
+
+ assertThrows(JSONException.class, () -> new JSONObject(map));
+ }
+ }
+
+ @Test
+ public void issue713BeanConstructorWithNonFiniteNumbers() {
+ for (Number nonFinite : NON_FINITE_NUMBERS) {
+ GenericBean bean = new GenericBean<>(nonFinite);
+ assertThrows(JSONException.class, () -> new JSONObject(bean));
+ }
+ }
+
+ @Test(expected = JSONException.class)
+ public void issue743SerializationMap() {
+ HashMap map = new HashMap<>();
+ map.put("t", map);
+ JSONObject object = new JSONObject(map);
+ String jsonString = object.toString();
+ }
+
+ @Test(expected = JSONException.class)
+ public void testCircularReferenceMultipleLevel() {
+ HashMap inside = new HashMap<>();
+ HashMap jsonObject = new HashMap<>();
+ inside.put("inside", jsonObject);
+ jsonObject.put("test", inside);
+ new JSONObject(jsonObject);
+ }
+
+ @Test
+ public void issue743SerializationMapWith512Objects() {
+ HashMap map = buildNestedMap(ParserConfiguration.DEFAULT_MAXIMUM_NESTING_DEPTH);
+ JSONObject object = new JSONObject(map);
+ String jsonString = object.toString();
+ }
+
+ @Test
+ public void issue743SerializationMapWith500Objects() {
+ // TODO: find out why 1000 objects no longer works
+ HashMap map = buildNestedMap(500);
+ JSONParserConfiguration parserConfiguration = new JSONParserConfiguration().withMaxNestingDepth(500);
+ JSONObject object = new JSONObject(map, parserConfiguration);
+ String jsonString = object.toString();
+ }
+
+ @Test(expected = JSONException.class)
+ public void issue743SerializationMapWith1001Objects() {
+ HashMap map = buildNestedMap(1001);
+ JSONObject object = new JSONObject(map);
+ String jsonString = object.toString();
+ }
+
+ @Test(expected = JSONException.class)
+ public void testCircleReferenceFirstLevel() {
+ Map jsonObject = new HashMap<>();
+
+ jsonObject.put("test", jsonObject);
+
+ new JSONObject(jsonObject, new JSONParserConfiguration());
+ }
+
+ @Test(expected = StackOverflowError.class)
+ public void testCircleReferenceMultiplyLevel_notConfigured_expectedStackOverflow() {
+ Map inside = new HashMap<>();
+
+ Map jsonObject = new HashMap<>();
+ inside.put("test", jsonObject);
+ jsonObject.put("test", inside);
+
+ new JSONObject(jsonObject, new JSONParserConfiguration().withMaxNestingDepth(99999));
+ }
+
+ @Test(expected = JSONException.class)
+ public void testCircleReferenceMultiplyLevel_configured_expectedJSONException() {
+ Map inside = new HashMap<>();
+
+ Map jsonObject = new HashMap<>();
+ inside.put("test", jsonObject);
+ jsonObject.put("test", inside);
+
+ new JSONObject(jsonObject, new JSONParserConfiguration());
+ }
+
+ @Test
+ public void testDifferentKeySameInstanceNotACircleReference() {
+ Map map1 = new HashMap<>();
+ Map map2 = new HashMap<>();
+
+ map1.put("test1", map2);
+ map1.put("test2", map2);
+
+ new JSONObject(map1);
+ }
+
+ @Test
+ public void clarifyCurrentBehavior() {
+ // Behavior documented in #653 optLong vs getLong inconsistencies
+ // This problem still exists.
+ // Internally, both number_1 and number_2 are stored as strings. This is
+ // reasonable since they are parsed as strings.
+ // However, getLong and optLong should return similar results
+ JSONObject json = new JSONObject("{\"number_1\":\"01234\", \"number_2\": \"332211\"}");
+ assertEquals(json.getLong("number_1"), 1234L);
+ assertEquals(json.optLong("number_1"), 0); // THIS VALUE IS NOT RETURNED AS A NUMBER
+ assertEquals(json.getLong("number_2"), 332211L);
+ assertEquals(json.optLong("number_2"), 332211L);
+
+ // Behavior documented in #826 JSONObject parsing 0-led numeric strings as ints
+ // After reverting the code, personId is stored as a string, and the behavior is
+ // as expected
+ String personId = "\"0123\"";
+ JSONObject j1 = new JSONObject("{\"personId\": " + personId + "}");
+ assertEquals(j1.getString("personId"), "0123");
+
+ // Also #826. Here is input with missing quotes. Because of the leading zero, it
+ // should not be parsed as a number.
+ // This example was mentioned in the same ticket
+ // After reverting the code, personId is stored as a string, and the behavior is
+ // as expected
+ JSONObject j2 = new JSONObject("{\"personId\":\"0123\"}");
+ assertEquals(j2.getString("personId"), "0123");
+
+ // Behavior uncovered while working on the code
+ // All of the values are stored as strings except for hex4, which is stored as a
+ // number. This is probably incorrect
+ JSONObject j3 = new JSONObject("{ " + "\"hex1\": \"010e4\", \"hex2\": \"00f0\", \"hex3\": \"0011\", "
+ + "\"hex4\": 00e0, \"hex5\": \"00f0\", \"hex6\": \"0011\" }");
+ assertEquals(j3.getString("hex1"), "010e4");
+ assertEquals(j3.getString("hex2"), "00f0");
+ assertEquals(j3.getString("hex3"), "0011");
+ assertEquals(j3.getLong("hex4"), 0, .1);
+ assertEquals(j3.getString("hex5"), "00f0");
+ assertEquals(j3.getString("hex6"), "0011");
+ }
+
+ @Test
+ public void testStrictModeJSONTokener_expectException() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
+ JSONTokener tokener = new JSONTokener("{\"key\":\"value\"}invalidCharacters", jsonParserConfiguration);
+
+ assertThrows(JSONException.class, () -> {
+ new JSONObject(tokener);
+ });
+ }
+
+ @Test
+ public void test_strictModeWithMisCasedBooleanOrNullValue() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
+ try {
+ new JSONObject("{\"a\":True}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+ try {
+ new JSONObject("{\"a\":TRUE}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+ try {
+ new JSONObject("{\"a\":nUlL}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+ }
+
+ @Test
+ public void test_strictModeWithInappropriateKey() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration().withStrictMode();
+
+ // Parsing the following objects should fail
+ try {
+ new JSONObject("{true : 3}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+ try {
+ new JSONObject("{TRUE : 3}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+ try {
+ new JSONObject("{1 : 3}", jsonParserConfiguration);
+ fail("Expected an exception");
+ } catch (JSONException e) {
+ // No action, expected outcome
+ }
+
+ }
+
+ /**
+ * Method to build nested map of max maxDepth
+ *
+ * @param maxDepth
+ * @return
+ */
+ public static HashMap buildNestedMap(int maxDepth) {
+ if (maxDepth <= 0) {
+ return new HashMap<>();
+ }
+ HashMap nestedMap = new HashMap<>();
+ nestedMap.put("t", buildNestedMap(maxDepth - 1));
+ return nestedMap;
+ }
+
+ /**
+ * Tests the behavior of the {@link JSONObject} when parsing a bean with null
+ * fields using a custom {@link JSONParserConfiguration} that enables the use of
+ * native nulls.
+ *
+ *
+ * This test ensures that uninitialized fields in the bean are serialized
+ * correctly into the resulting JSON object, and their keys are present in the
+ * JSON string output.
+ *
+ */
+ @Test
+ public void jsonObjectParseNullFieldsWithParserConfiguration() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+ RecursiveBean bean = new RecursiveBean(null);
+ JSONObject jsonObject = new JSONObject(bean, jsonParserConfiguration.withUseNativeNulls(true));
+ assertTrue("name key should be present", jsonObject.has("name"));
+ assertTrue("ref key should be present", jsonObject.has("ref"));
+ assertTrue("ref2 key should be present", jsonObject.has("ref2"));
+ }
+
+ /**
+ * Tests the behavior of the {@link JSONObject} when parsing a bean with null
+ * fields without using a custom {@link JSONParserConfiguration}.
+ *
+ *
+ * This test ensures that uninitialized fields in the bean are not serialized
+ * into the resulting JSON object, and the object remains empty.
+ *
+ */
+ @Test
+ public void jsonObjectParseNullFieldsWithoutParserConfiguration() {
+ RecursiveBean bean = new RecursiveBean(null);
+ JSONObject jsonObject = new JSONObject(bean);
+ assertTrue("JSONObject should be empty", jsonObject.isEmpty());
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_0() {
+ JSONObject object = new JSONObject();
+ object.put("number", 12);
+ object.put("name", "Alex");
+ object.put("longNumber", 1500000000L);
+ CustomClass customClass = object.fromJson(CustomClass.class);
+ CustomClass compareClass = new CustomClass(12, "Alex", 1500000000L);
+ assertEquals(customClass, compareClass);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_1() {
+ JSONObject object = new JSONObject();
+
+ BigInteger largeInt = new BigInteger("123");
+ object.put("largeInt", largeInt.toString());
+ CustomClassA customClassA = object.fromJson(CustomClassA.class);
+ CustomClassA compareClassClassA = new CustomClassA(largeInt);
+ assertEquals(customClassA, compareClassClassA);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_2() {
+ JSONObject object = new JSONObject();
+ object.put("number", 12);
+
+ JSONObject classC = new JSONObject();
+ classC.put("stringName", "Alex");
+ classC.put("longNumber", 123456L);
+
+ object.put("classC", classC);
+
+ CustomClassB customClassB = object.fromJson(CustomClassB.class);
+ CustomClassC classCObject = new CustomClassC("Alex", 123456L);
+ CustomClassB compareClassB = new CustomClassB(12, classCObject);
+ assertEquals(customClassB, compareClassB);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_3() {
+ JSONObject object = new JSONObject();
+ JSONArray array = new JSONArray();
+ array.put("test1");
+ array.put("test2");
+ array.put("test3");
+ object.put("stringList", array);
+
+ CustomClassD customClassD = object.fromJson(CustomClassD.class);
+ CustomClassD compareClassD = new CustomClassD(Arrays.asList("test1", "test2", "test3"));
+ assertEquals(customClassD, compareClassD);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_4() {
+ JSONObject object = new JSONObject();
+ JSONArray array = new JSONArray();
+ array.put(new CustomClassC("test1", 1L).toJSON());
+ array.put(new CustomClassC("test2", 2L).toJSON());
+ object.put("listClassC", array);
+
+ CustomClassE customClassE = object.fromJson(CustomClassE.class);
+ CustomClassE compareClassE = new CustomClassE(
+ java.util.Arrays.asList(new CustomClassC("test1", 1L), new CustomClassC("test2", 2L)));
+ assertEquals(customClassE, compareClassE);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_5() {
+ JSONObject object = new JSONObject();
+ JSONArray array = new JSONArray();
+ array.put(Arrays.asList("A", "B", "C"));
+ array.put(Arrays.asList("D", "E"));
+ object.put("listOfString", array);
+
+ CustomClassF customClassF = object.fromJson(CustomClassF.class);
+ List> listOfString = new ArrayList<>();
+ listOfString.add(Arrays.asList("A", "B", "C"));
+ listOfString.add(Arrays.asList("D", "E"));
+ CustomClassF compareClassF = new CustomClassF(listOfString);
+ assertEquals(customClassF, compareClassF);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_6() {
+ JSONObject object = new JSONObject();
+ Map dataList = new HashMap<>();
+ dataList.put("A", "Aa");
+ dataList.put("B", "Bb");
+ dataList.put("C", "Cc");
+ object.put("dataList", dataList);
+
+ CustomClassG customClassG = object.fromJson(CustomClassG.class);
+ CustomClassG compareClassG = new CustomClassG(dataList);
+ assertEquals(customClassG, compareClassG);
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_7() {
+ JSONObject object = new JSONObject();
+ Map> dataList = new HashMap<>();
+ dataList.put("1", Arrays.asList(1, 2, 3, 4));
+ dataList.put("2", Arrays.asList(2, 3, 4, 5));
+ object.put("integerMap", dataList);
+
+ CustomClassH customClassH = object.fromJson(CustomClassH.class);
+ CustomClassH compareClassH = new CustomClassH(dataList);
+ assertEquals(customClassH.integerMap.toString(), compareClassH.integerMap.toString());
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_8() {
+ JSONObject object = new JSONObject();
+ Map> dataList = new HashMap<>();
+ dataList.put("1", Collections.singletonMap("1", 1));
+ dataList.put("2", Collections.singletonMap("2", 2));
+ object.put("integerMap", dataList);
+
+ CustomClassI customClassI = object.fromJson(CustomClassI.class);
+ CustomClassI compareClassI = new CustomClassI(dataList);
+ assertEquals(customClassI.integerMap.toString(), compareClassI.integerMap.toString());
+ }
+
+ @Test
+ public void jsonObjectParseFromJson_9() {
+ JSONObject object = new JSONObject();
+ object.put("number", 12);
+ object.put("classState", "mutated");
+
+ String initialClassState = CustomClassJ.classState;
+ CustomClassJ.classState = "original";
+ try {
+ CustomClassJ customClassJ = object.fromJson(CustomClassJ.class);
+ assertEquals(12, customClassJ.number);
+ assertEquals("original", CustomClassJ.classState);
+ } finally {
+ CustomClassJ.classState = initialClassState;
+ }
+ }
+
+ @Test
+ public void testMaxNumberLength() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 100; ++i) {
+ sb.append("9999999999");
+ }
+
+ // JSONObject with number just under the max length limit
+ checkJSONObjectMaxLen(sb.toString(), true, null);
+
+ // JSONArray with number just under the max length limit
+ checkJSONArrayMaxLen(sb.toString(), true, null);
+
+ // numbers without quotes are not allowed in strict mode
+ if (!jsonParserConfiguration.isStrictMode()) {
+ // JSONObject with number just over the max length limit
+ checkJSONObjectMaxLen(sb + "9", false, null);
+
+ // JSONArray with number just over the max length limit
+ checkJSONArrayMaxLen(sb + "9", false, null);
+ }
+
+ // JSONObject with number at config max length limit
+ checkJSONObjectMaxLen(sb.toString() + sb.toString(), true, new JSONParserConfiguration().withMaxNumberLength(2000));
+
+ // JSONArray with number at config max length limit
+ checkJSONArrayMaxLen((sb.toString() + sb), true, new JSONParserConfiguration().withMaxNumberLength(2000));
+
+ // numbers without quotes are not allowed in strict mode
+ if (!jsonParserConfiguration.isStrictMode()) {
+ // JSONObject with number just over config max length limit
+ checkJSONObjectMaxLen(sb.toString() + sb.toString() + "9", false, new JSONParserConfiguration().withMaxNumberLength(2000));
+
+ // JSONArray with number just over config max length limit
+ checkJSONArrayMaxLen((sb.toString() + sb + "9"), false, new JSONParserConfiguration().withMaxNumberLength(2000));
+ }
+
+ // JSONObject with large number, no checks
+ checkJSONObjectMaxLen(sb.toString() + sb.toString() + "9", true,
+ new JSONParserConfiguration().withMaxNumberLength(JSONParserConfiguration.UNDEFINED_MAXIMUM_NUMBER_LENGTH));
+
+ // JSONArray with number just over config max length limit
+ checkJSONArrayMaxLen((sb.toString() + sb + "9"), true,
+ new JSONParserConfiguration().withMaxNumberLength(JSONParserConfiguration.UNDEFINED_MAXIMUM_NUMBER_LENGTH));
+ }
+
+ /**
+ * Convenience method to check JSONObject for a long number
+ * @param s string containing the number digits to check
+ * @param isValid true if number is valid, otherwise false
+ * @param jsonParserConfiguration the config object
+ */
+ private static void checkJSONObjectMaxLen(String s, boolean isValid, JSONParserConfiguration jsonParserConfiguration) {
+ String str = "{ \"a\": " + s + "}";
+ JSONObject jsonObject;
+ if (jsonParserConfiguration == null) {
+ jsonObject = new JSONObject(str);
+ } else {
+ jsonObject = new JSONObject(str, jsonParserConfiguration);
+ }
+ Object obj = jsonObject.get("a");
+ if (isValid) {
+ assertTrue(obj instanceof Number);
+ } else {
+ assertTrue(obj instanceof String);
+ }
+ // may not work for scientific notation and BigDecimal
+ // assertEquals(s.length(), obj.toString().length());
+ }
+
+ /**
+ * Convenience method to check JSONArray for a long number
+ * @param s string containing the number digits to check
+ * @param isValid true if number is valid, otherwise false
+ */
+ private static void checkJSONArrayMaxLen(String s, boolean isValid, JSONParserConfiguration jsonParserConfiguration) {
+ String str = "[" + s + "]";
+ JSONArray jsonArray;
+ if (jsonParserConfiguration == null) {
+ jsonArray = new JSONArray(str);
+ } else {
+ jsonArray = new JSONArray(str, jsonParserConfiguration);
+ }
+ Object obj = jsonArray.get(0);
+ if (isValid) {
+ assertTrue(obj instanceof Number);
+ } else {
+ assertTrue(obj instanceof String);
+ }
+ // may not work for scientific notation and BigDecimal
+ // assertEquals(s.length(), obj.toString().length());
+ }
+
+ /**
+ * Test max number length for negative integer strings via stringToValue.
+ */
+ @Test
+ public void testMaxNumberLengthNegativeInteger() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+
+ // Build a negative number string of exactly 1000 chars: "-" + 999 digits
+ StringBuilder sb = new StringBuilder("-");
+ for (int i = 0; i < 999; ++i) {
+ sb.append("1");
+ }
+ assertEquals(1000, sb.length());
+
+ // at max length: parsed as number
+ checkJSONObjectMaxLen(sb.toString(), true, null);
+
+ // numbers without quotes are not allowed in strict mode
+ if (!jsonParserConfiguration.isStrictMode()) {
+ // over max length: returned as string
+ sb.append("1");
+ assertEquals(1001, sb.length());
+ checkJSONObjectMaxLen(sb.toString(), false, null);
+ }
+ }
+
+ /**
+ * Test max number length for decimal notation strings via stringToValue.
+ */
+ @Test
+ public void testMaxNumberLengthDecimal() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+
+ // Build a decimal number string of exactly 1000 chars: 499 digits + "." + 500
+ // digits
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 499; ++i) {
+ sb.append("1");
+ }
+ sb.append(".");
+ for (int i = 0; i < 500; ++i) {
+ sb.append("2");
+ }
+ assertEquals(1000, sb.length());
+
+ // at max length: parsed as number (BigDecimal)
+ checkJSONObjectMaxLen(sb.toString(), true, null);
+
+ // numbers without quotes are not allowed in strict mode
+ if (!jsonParserConfiguration.isStrictMode()) {
+ // over max length: returned as string
+ sb.append("3");
+ assertEquals(1001, sb.length());
+ checkJSONObjectMaxLen(sb.toString(), false, null);
+ }
+ }
+
+ /**
+ * Test max number length with scientific notation via stringToValue.
+ */
+ @Test
+ public void testMaxNumberLengthScientificNotation() {
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+
+ // Build a scientific notation string of exactly 1000 chars
+ StringBuilder sb = new StringBuilder("1.");
+ for (int i = 0; i < 994; ++i) {
+ sb.append("0");
+ }
+ sb.append("e100");
+ assertEquals(1000, sb.length());
+
+ // at max length: parsed as number
+ checkJSONObjectMaxLen(sb.toString(), true, null);
+
+ // over max length: returned as string
+ sb = new StringBuilder("1.");
+ for (int i = 0; i < 995; ++i) {
+ sb.append("0");
+ }
+
+ // numbers without quotes are not allowed in strict mode
+ if (!jsonParserConfiguration.isStrictMode()) {
+ sb.append("e100");
+ assertEquals(1001, sb.length());
+ checkJSONObjectMaxLen(sb.toString(), false, null);
+ }
+ }
+
+ /**
+ * Test that optBigDecimal enforces max number length for string values.
+ */
+ @Ignore
+ public void testMaxNumberLengthOptBigDecimal() {
+ BigDecimal defaultVal = BigDecimal.valueOf(-1);
+
+ // String value at max length: converts to BigDecimal
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 100; ++i) {
+ sb.append("1234567890");
+ }
+ assertEquals(1000, sb.toString().length());
+ JSONObject jo = new JSONObject();
+ jo.put("a", sb.toString());
+ BigDecimal result = jo.optBigDecimal("a", defaultVal);
+ assertNotEquals(defaultVal, result);
+ assertEquals(new BigDecimal(sb.toString()), result);
+
+ // String value over max length: returns default
+ sb.append("1");
+ assertEquals(1001, sb.toString().length());
+ JSONObject jo2 = new JSONObject();
+ jo2.put("a", sb.toString());
+ BigDecimal result2 = jo2.optBigDecimal("a", defaultVal);
+ assertEquals(defaultVal, result2);
+ }
+
+ /**
+ * Test that optBigDecimal handles various numeric types correctly.
+ */
+ @Test
+ public void testOptBigDecimalVariousTypes() {
+ BigDecimal defaultVal = BigDecimal.valueOf(-1);
+ JSONObject jo = new JSONObject();
+
+ // BigDecimal passthrough
+ jo.put("bd", new BigDecimal("123.456"));
+ assertEquals(new BigDecimal("123.456"), jo.optBigDecimal("bd", defaultVal));
+
+ // BigInteger conversion
+ jo.put("bi", new BigInteger("999"));
+ assertEquals(new BigDecimal(new BigInteger("999")), jo.optBigDecimal("bi", defaultVal));
+
+ // Double conversion
+ jo.put("d", 3.14);
+ BigDecimal bdResult = jo.optBigDecimal("d", defaultVal);
+ assertNotEquals(defaultVal, bdResult);
+
+ // Float conversion (via JSONObject, floats are stored as doubles)
+ jo.put("f", 2.5f);
+ BigDecimal fResult = jo.optBigDecimal("f", defaultVal);
+ assertNotEquals(defaultVal, fResult);
+
+ // Long conversion
+ jo.put("l", 123456789L);
+ assertEquals(new BigDecimal(123456789L), jo.optBigDecimal("l", defaultVal));
+
+ // Integer conversion
+ jo.put("i", 42);
+ assertEquals(new BigDecimal(42), jo.optBigDecimal("i", defaultVal));
+
+ // JSONObject.NULL returns default
+ jo.put("null", JSONObject.NULL);
+ assertEquals(defaultVal, jo.optBigDecimal("null", defaultVal));
+
+ // Missing key returns default
+ assertEquals(defaultVal, jo.optBigDecimal("missing", defaultVal));
+
+ // Non-numeric string returns default
+ jo.put("str", "not a number");
+ assertEquals(defaultVal, jo.optBigDecimal("str", defaultVal));
+
+ // Verify that non-finite doubles are rejected by put()
+ assertThrows(JSONException.class, () -> jo.put("nan", Double.NaN));
+ assertThrows(JSONException.class, () -> jo.put("inf", Double.POSITIVE_INFINITY));
+ assertThrows(JSONException.class, () -> jo.put("ninf", Double.NEGATIVE_INFINITY));
+ }
+
+ /**
+ * Test that optBigInteger enforces max number length for string values.
+ */
+ @Ignore
+ public void testMaxNumberLengthOptBigInteger() {
+ BigInteger defaultVal = BigInteger.valueOf(-1);
+
+ // String value at max length: converts to BigInteger
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 100; ++i) {
+ sb.append("1234567890");
+ }
+ assertEquals(1000, sb.toString().length());
+ JSONObject jo = new JSONObject();
+ jo.put("a", sb.toString());
+ BigInteger result = jo.optBigInteger("a", defaultVal);
+ assertNotEquals(defaultVal, result);
+ assertEquals(new BigInteger(sb.toString()), result);
+
+ // String value over max length: returns default
+ sb.append("1");
+ assertEquals(1001, sb.toString().length());
+ JSONObject jo2 = new JSONObject();
+ jo2.put("a", sb.toString());
+ BigInteger result2 = jo2.optBigInteger("a", defaultVal);
+ assertEquals(defaultVal, result2);
+ }
+
+ /**
+ * Test that optBigInteger handles various numeric types and decimal notation.
+ */
+ @Test
+ public void testOptBigIntegerVariousTypes() {
+ BigInteger defaultVal = BigInteger.valueOf(-1);
+ JSONObject jo = new JSONObject();
+
+ // BigInteger passthrough
+ jo.put("bi", new BigInteger("999"));
+ assertEquals(new BigInteger("999"), jo.optBigInteger("bi", defaultVal));
+
+ // BigDecimal conversion (truncates)
+ jo.put("bd", new BigDecimal("123.999"));
+ assertEquals(new BigInteger("123"), jo.optBigInteger("bd", defaultVal));
+
+ // Double conversion
+ jo.put("d", 3.14);
+ BigInteger dResult = jo.optBigInteger("d", defaultVal);
+ assertNotEquals(defaultVal, dResult);
+
+ // Long conversion
+ jo.put("l", 123456789L);
+ assertEquals(BigInteger.valueOf(123456789L), jo.optBigInteger("l", defaultVal));
+
+ // Integer conversion
+ jo.put("i", 42);
+ assertEquals(BigInteger.valueOf(42), jo.optBigInteger("i", defaultVal));
+
+ // JSONObject.NULL returns default
+ jo.put("null", JSONObject.NULL);
+ assertEquals(defaultVal, jo.optBigInteger("null", defaultVal));
+
+ // Missing key returns default
+ assertEquals(defaultVal, jo.optBigInteger("missing", defaultVal));
+
+ // Non-numeric string returns default
+ jo.put("str", "not a number");
+ assertEquals(defaultVal, jo.optBigInteger("str", defaultVal));
+
+ // Verify that non-finite doubles are rejected by put()
+ assertThrows(JSONException.class, () -> jo.put("nan", Double.NaN));
+ assertThrows(JSONException.class, () -> jo.put("inf", Double.POSITIVE_INFINITY));
+
+ // Decimal notation string: goes through BigDecimal→BigInteger path in
+ // objectToBigInteger
+ jo.put("decstr", "123.456");
+ assertEquals(new BigInteger("123"), jo.optBigInteger("decstr", defaultVal));
+ }
+
+ /**
+ * Test max number length via JSONArray optBigDecimal and optBigInteger.
+ */
+ @Ignore
+ public void testMaxNumberLengthJSONArray() {
+ BigDecimal bdDefault = BigDecimal.valueOf(-1);
+ BigInteger biDefault = BigInteger.valueOf(-1);
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 100; ++i) {
+ sb.append("1234567890");
+ }
+
+ // At max length: should convert
+ JSONArray ja = new JSONArray();
+ ja.put(sb.toString());
+ assertNotEquals(bdDefault, ja.optBigDecimal(0, bdDefault));
+
+ ja = new JSONArray();
+ ja.put(sb.toString());
+ assertNotEquals(biDefault, ja.optBigInteger(0, biDefault));
+
+ // Over max length: should return default
+ sb.append("1");
+ ja = new JSONArray();
+ ja.put(sb.toString());
+ assertEquals(bdDefault, ja.optBigDecimal(0, bdDefault));
+
+ ja = new JSONArray();
+ ja.put(sb.toString());
+ assertEquals(biDefault, ja.optBigInteger(0, biDefault));
+ }
+
+ /**
+ * Test max number length for XML parsing via XML.toJSONObject.
+ */
+ @Test
+ public void testMaxNumberLengthXML() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 100; ++i) {
+ sb.append("9999999999");
+ }
+
+ // at max length: parsed as number
+ String xml1 = "" + sb + " ";
+ JSONObject jo1 = XML.toJSONObject(xml1);
+ Object val1 = jo1.get("a");
+ assertTrue("Expected Number but got " + val1.getClass(), val1 instanceof Number);
+
+ // over max length: returned as string
+ sb.append("9");
+ String xml2 = "" + sb + " ";
+ JSONObject jo2 = XML.toJSONObject(xml2);
+ Object val2 = jo2.get("a");
+ assertTrue("Expected String but got " + val2.getClass(), val2 instanceof String);
+ }
+
+ /**
+ * Test stringToValue with small numbers, booleans, null, and non-numeric
+ * strings.
+ */
+ @Test
+ public void testStringToValueViaJSONObject() {
+ // Integer range number
+ JSONObject jo = new JSONObject("{\"a\": 42}");
+ Object val = jo.get("a");
+ assertTrue("Expected Integer", val instanceof Integer);
+ assertEquals(42, val);
+
+ // Long range number
+ jo = new JSONObject("{\"a\": 2147483648}");
+ val = jo.get("a");
+ assertTrue("Expected Long", val instanceof Long);
+ assertEquals(2147483648L, val);
+
+ // BigInteger range number
+ jo = new JSONObject("{\"a\": 9999999999999999999}");
+ val = jo.get("a");
+ assertTrue("Expected BigInteger", val instanceof BigInteger);
+
+ // Negative zero → treated as decimal notation
+ jo = new JSONObject("{\"a\": -0}");
+ val = jo.get("a");
+ assertTrue("Expected Double for -0", val instanceof Double);
+ assertEquals(-0.0, (Double) val, 0.0);
+
+ // Decimal number
+ jo = new JSONObject("{\"a\": 3.14}");
+ val = jo.get("a");
+ assertTrue("Expected BigDecimal", val instanceof BigDecimal);
+ }
+
+ /**
+ * Test stringToNumber rejects invalid number formats (octal-like).
+ */
+ @Test
+ public void testStringToNumberInvalidFormats() {
+ // this test should only run in non-strict mode, otherwise it will throw an exception
+ JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration();
+ if (!jsonParserConfiguration.isStrictMode()) {
+ // Leading zero followed by digit → treated as string (not octal)
+ JSONObject jo = new JSONObject("{\"a\": 01}");
+ // JSONTokener with strict mode would reject, but default mode stores as string
+ // since stringToNumber throws NumberFormatException for "01"
+ Object val = jo.get("a");
+ assertTrue("01 should be stored as string, got " + val.getClass(), val instanceof String);
+
+ // Negative with leading zero → "-01"
+ jo = new JSONObject("{\"a\": -01}");
+ val = jo.get("a");
+ assertTrue("-01 should be stored as string, got " + val.getClass(), val instanceof String);
+ }
+ }
+
}
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 45c7dbd3d..a420b297f 100644
--- a/src/test/java/org/json/junit/JSONPointerTest.java
+++ b/src/test/java/org/json/junit/JSONPointerTest.java
@@ -384,8 +384,7 @@ public void queryFromJSONObjectUsingPointer0() {
String str = "{"+
"\"string\\\\\\\\Key\":\"hello world!\","+
- "\"\\\\\":\"slash test\"," +
- "}"+
+ "\"\\\\\":\"slash test\"" +
"}";
JSONObject jsonObject = new JSONObject(str);
//Summary of issue: When a KEY in the jsonObject is "\\\\" --> it's held
diff --git a/src/test/java/org/json/junit/JSONStringTest.java b/src/test/java/org/json/junit/JSONStringTest.java
index b4fee3eb7..235df1806 100644
--- a/src/test/java/org/json/junit/JSONStringTest.java
+++ b/src/test/java/org/json/junit/JSONStringTest.java
@@ -319,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/JSONTokenerTest.java b/src/test/java/org/json/junit/JSONTokenerTest.java
index 59ca6d8f6..b0b45cb7c 100644
--- a/src/test/java/org/json/junit/JSONTokenerTest.java
+++ b/src/test/java/org/json/junit/JSONTokenerTest.java
@@ -16,10 +16,7 @@
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;
/**
@@ -98,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
@@ -325,4 +332,42 @@ public void testAutoClose(){
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/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/XMLConfigurationTest.java b/src/test/java/org/json/junit/XMLConfigurationTest.java
index 21a2b595e..e8ff3b60c 100755
--- a/src/test/java/org/json/junit/XMLConfigurationTest.java
+++ b/src/test/java/org/json/junit/XMLConfigurationTest.java
@@ -4,11 +4,6 @@
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;
@@ -27,6 +22,8 @@
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import static org.junit.Assert.*;
+
/**
* Tests for JSON-Java XML.java with XMLParserConfiguration.java
@@ -273,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/"+
@@ -557,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
*/
@@ -739,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.
*/
@@ -1000,7 +1092,7 @@ public void testEmptyForceList() {
" ";
String expectedStr =
- "{\"addresses\":[]}";
+ "{\"addresses\":[\"\"]}";
Set forceList = new HashSet();
forceList.add("addresses");
@@ -1038,7 +1130,7 @@ public void testEmptyTagForceList() {
" ";
String expectedStr =
- "{\"addresses\":[]}";
+ "{\"addresses\":[\"\"]}";
Set forceList = new HashSet();
forceList.add("addresses");
@@ -1052,6 +1144,157 @@ public void testEmptyTagForceList() {
Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
}
+ @Test
+ public void testForceListWithLastElementAsEmptyTag(){
+ final String originalXml = "