diff --git a/.gitignore b/.gitignore index 50b216e17..320843725 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ -# ignore eclipse project files -.project -.classpath -# ignore Intellij Idea project files -.idea -*.iml +# ignore eclipse project files +.project +.classpath +# ignore Intellij Idea project files +.idea +*.iml +/nbproject/private/ +/build/ +/dist/ \ No newline at end of file diff --git a/CDL.java b/CDL.java deleted file mode 100644 index 1c7df3223..000000000 --- a/CDL.java +++ /dev/null @@ -1,287 +0,0 @@ -package org.json; - -/* -Copyright (c) 2002 JSON.org - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -The Software shall be used for Good, not Evil. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - */ - -/** - * This provides static methods to convert comma delimited text into a - * JSONArray, and to convert a JSONArray into comma delimited text. Comma - * delimited text is a very popular format for data interchange. It is - * understood by most database, spreadsheet, and organizer programs. - *

- * Each row of text represents a row in a table or a data record. Each row - * ends with a NEWLINE character. Each row contains one or more values. - * Values are separated by commas. A value can contain any character except - * for comma, unless is is wrapped in single quotes or double quotes. - *

- * The first row usually contains the names of the columns. - *

- * A comma delimited list can be converted into a JSONArray of JSONObjects. - * The names for the elements in the JSONObjects can be taken from the names - * in the first row. - * @author JSON.org - * @version 2016-05-01 - */ -public class CDL { - - /** - * Get the next value. The value can be wrapped in quotes. The value can - * be empty. - * @param x A JSONTokener of the source text. - * @return The value string, or null if empty. - * @throws JSONException if the quoted string is badly formed. - */ - private static String getValue(JSONTokener x) throws JSONException { - char c; - char q; - StringBuffer sb; - do { - c = x.next(); - } while (c == ' ' || c == '\t'); - switch (c) { - case 0: - return null; - case '"': - case '\'': - q = c; - sb = new StringBuffer(); - for (;;) { - c = x.next(); - if (c == q) { - //Handle escaped double-quote - char nextC = x.next(); - if(nextC != '\"') { - // if our quote was the end of the file, don't step - if(nextC > 0) { - x.back(); - } - break; - } - } - if (c == 0 || c == '\n' || c == '\r') { - throw x.syntaxError("Missing close quote '" + q + "'."); - } - sb.append(c); - } - return sb.toString(); - case ',': - x.back(); - return ""; - default: - x.back(); - return x.nextTo(','); - } - } - - /** - * Produce a JSONArray of strings from a row of comma delimited values. - * @param x A JSONTokener of the source text. - * @return A JSONArray of strings. - * @throws JSONException - */ - public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException { - JSONArray ja = new JSONArray(); - for (;;) { - String value = getValue(x); - char c = x.next(); - if (value == null || - (ja.length() == 0 && value.length() == 0 && c != ',')) { - return null; - } - ja.put(value); - for (;;) { - if (c == ',') { - break; - } - if (c != ' ') { - if (c == '\n' || c == '\r' || c == 0) { - return ja; - } - throw x.syntaxError("Bad character '" + c + "' (" + - (int)c + ")."); - } - c = x.next(); - } - } - } - - /** - * Produce a JSONObject from a row of comma delimited text, using a - * parallel JSONArray of strings to provides the names of the elements. - * @param names A JSONArray of names. This is commonly obtained from the - * first row of a comma delimited text file using the rowToJSONArray - * method. - * @param x A JSONTokener of the source text. - * @return A JSONObject combining the names and values. - * @throws JSONException - */ - public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) - throws JSONException { - JSONArray ja = rowToJSONArray(x); - return ja != null ? ja.toJSONObject(names) : null; - } - - /** - * Produce a comma delimited text row from a JSONArray. Values containing - * the comma character will be quoted. Troublesome characters may be - * removed. - * @param ja A JSONArray of strings. - * @return A string ending in NEWLINE. - */ - public static String rowToString(JSONArray ja) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < ja.length(); i += 1) { - if (i > 0) { - sb.append(','); - } - Object object = ja.opt(i); - if (object != null) { - String string = object.toString(); - if (string.length() > 0 && (string.indexOf(',') >= 0 || - string.indexOf('\n') >= 0 || string.indexOf('\r') >= 0 || - string.indexOf(0) >= 0 || string.charAt(0) == '"')) { - sb.append('"'); - int length = string.length(); - for (int j = 0; j < length; j += 1) { - char c = string.charAt(j); - if (c >= ' ' && c != '"') { - sb.append(c); - } - } - sb.append('"'); - } else { - sb.append(string); - } - } - } - sb.append('\n'); - return sb.toString(); - } - - /** - * Produce a JSONArray of JSONObjects from a comma delimited text string, - * using the first row as a source of names. - * @param string The comma delimited text. - * @return A JSONArray of JSONObjects. - * @throws JSONException - */ - public static JSONArray toJSONArray(String string) throws JSONException { - return toJSONArray(new JSONTokener(string)); - } - - /** - * Produce a JSONArray of JSONObjects from a comma delimited text string, - * using the first row as a source of names. - * @param x The JSONTokener containing the comma delimited text. - * @return A JSONArray of JSONObjects. - * @throws JSONException - */ - public static JSONArray toJSONArray(JSONTokener x) throws JSONException { - return toJSONArray(rowToJSONArray(x), x); - } - - /** - * Produce a JSONArray of JSONObjects from a comma delimited text string - * using a supplied JSONArray as the source of element names. - * @param names A JSONArray of strings. - * @param string The comma delimited text. - * @return A JSONArray of JSONObjects. - * @throws JSONException - */ - public static JSONArray toJSONArray(JSONArray names, String string) - throws JSONException { - return toJSONArray(names, new JSONTokener(string)); - } - - /** - * Produce a JSONArray of JSONObjects from a comma delimited text string - * using a supplied JSONArray as the source of element names. - * @param names A JSONArray of strings. - * @param x A JSONTokener of the source text. - * @return A JSONArray of JSONObjects. - * @throws JSONException - */ - public static JSONArray toJSONArray(JSONArray names, JSONTokener x) - throws JSONException { - if (names == null || names.length() == 0) { - return null; - } - JSONArray ja = new JSONArray(); - for (;;) { - JSONObject jo = rowToJSONObject(names, x); - if (jo == null) { - break; - } - ja.put(jo); - } - if (ja.length() == 0) { - return null; - } - return ja; - } - - - /** - * Produce a comma delimited text from a JSONArray of JSONObjects. The - * first row will be a list of names obtained by inspecting the first - * JSONObject. - * @param ja A JSONArray of JSONObjects. - * @return A comma delimited text. - * @throws JSONException - */ - public static String toString(JSONArray ja) throws JSONException { - JSONObject jo = ja.optJSONObject(0); - if (jo != null) { - JSONArray names = jo.names(); - if (names != null) { - return rowToString(names) + toString(names, ja); - } - } - return null; - } - - /** - * Produce a comma delimited text from a JSONArray of JSONObjects using - * a provided list of names. The list of names is not included in the - * output. - * @param names A JSONArray of strings. - * @param ja A JSONArray of JSONObjects. - * @return A comma delimited text. - * @throws JSONException - */ - public static String toString(JSONArray names, JSONArray ja) - throws JSONException { - if (names == null || names.length() == 0) { - return null; - } - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < ja.length(); i += 1) { - JSONObject jo = ja.optJSONObject(i); - if (jo != null) { - sb.append(rowToString(jo.toJSONArray(names))); - } - } - return sb.toString(); - } -} diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 000000000..df28e04c3 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,59 @@ +Release history: + +~~~ +For the https://github.com/hervegirod/JSON-java fork: +1.0 this is a Netbeans project + JSONObject and JSONArray both have an equals and hashCode method + a FileUtils class with static method has been added to simply the generation to/from a File / a JSON object + The supported Java version is Java 8 +1.1 Add Unit tests from the https://github.com/stleary/JSON-Java-unit-test project +1.2 JSONObject and JSONArray now have static methods to create new JSONObject or JSONArray, allowing to simplify the creation + of a JSON content + Use multicatch in exceptions rather than using a generic exception +1.3 Add new methods in the FileUtils class to create a Javascript expression rather than a JSON content. This can be useful for + some browsers which do not allow to open JSON content in a local environment +1.4 Improve the equals method in the JSONObject class + Improve code formatting +1.5 Add new methods in the FileUtils class to create a JSONArray from a String, File, or a Reader +1.6 Fix null values being serialized incorrectly +1.7 Allows to keep the natural ordering for keys when serializing a JSON content +1.7.1 Ensures that the FileUtils class always uses UTF-8 +1.7.2 Add a new property allowing not to shave off trailing decimal points for decimal values serialization + Add a new property allowing to accept javascript comments when parsing JSON content + Improve the justification of serialized content + Add a JSONOptions general class to hold properties + The JSONObject and JSONArray now implement a new JSONElement interface + Add a new JSONOptions configuration +1.7.3 Improves the layout of the output +1.7.4 Improves the layout of the output for arrays which contain only primitives +1.7.5 The clone() method is now supported on the JSONElement interface + Adds a fromString(String) static method in the JSONElement interface to return an element from a String +1.7.6 Add methods in the JSONObject and JSONArray classes to get a JSONElement rather than having to use the specific JSONObject and JSONArray methods +1.7.7 Add methods in the FileUtils class to get JSON from an InputStream + Use Netbeans 12.5 + +For https://github.com/stleary/JSON-java project +20180813 POM change to include Automatic-Module-Name (#431) + +20180130 Recent commits + +20171018 Checkpoint for recent commits. + +20170516 Roll up recent commits. + +20160810 Revert code that was breaking opt*() methods. + +20160807 This release contains a bug in the JSONObject.opt*() and JSONArray.opt*() methods, +it is not recommended for use. +Java 1.6 compatability fixed, JSONArray.toList() and JSONObject.toMap(), +RFC4180 compatibility, JSONPointer, some exception fixes, optional XML type conversion. +Contains the latest code as of 7 Aug, 2016 + +20160212 Java 1.6 compatibility, OSGi bundle. Contains the latest code as of 12 Feb, 2016. + +20151123 JSONObject and JSONArray initialization with generics. Contains the +latest code as of 23 Nov, 2015. + +20150729 Checkpoint for Maven central repository release. Contains the latest code +as of 29 July, 2015. +~~~ diff --git a/JSONArray.java b/JSONArray.java deleted file mode 100644 index fbc1a0f73..000000000 --- a/JSONArray.java +++ /dev/null @@ -1,1541 +0,0 @@ -package org.json; - -/* - Copyright (c) 2002 JSON.org - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - The Software shall be used for Good, not Evil. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - */ - -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.lang.reflect.Array; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - - -/** - * A JSONArray is an ordered sequence of values. Its external text form is a - * string wrapped in square brackets with commas separating the values. The - * internal form is an object having get and opt - * methods for accessing the values by index, and put methods for - * adding or replacing values. The values can be any of these types: - * Boolean, JSONArray, JSONObject, - * Number, String, or the - * JSONObject.NULL object. - *

- * The constructor can convert a JSON text into a Java object. The - * toString method converts to JSON text. - *

- * A get method returns a value if one can be found, and throws an - * exception if one cannot be found. An opt method returns a - * default value instead of throwing an exception, and so is useful for - * obtaining optional values. - *

- * The generic get() and opt() methods return an - * object which you can cast or query for type. There are also typed - * get and opt methods that do type checking and type - * coercion for you. - *

- * The texts produced by the toString methods strictly conform to - * JSON syntax rules. The constructors are more forgiving in the texts they will - * accept: - *

- * - * @author JSON.org - * @version 2016-08/15 - */ -public class JSONArray implements Iterable { - - /** - * The arrayList where the JSONArray's properties are kept. - */ - private final ArrayList myArrayList; - - /** - * Construct an empty JSONArray. - */ - public JSONArray() { - this.myArrayList = new ArrayList(); - } - - /** - * Construct a JSONArray from a JSONTokener. - * - * @param x - * A JSONTokener - * @throws JSONException - * If there is a syntax error. - */ - public JSONArray(JSONTokener x) throws JSONException { - this(); - if (x.nextClean() != '[') { - throw x.syntaxError("A JSONArray text must start with '['"); - } - - char nextChar = x.nextClean(); - if (nextChar == 0) { - // array is unclosed. No ']' found, instead EOF - throw x.syntaxError("Expected a ',' or ']'"); - } - if (nextChar != ']') { - x.back(); - for (;;) { - if (x.nextClean() == ',') { - x.back(); - this.myArrayList.add(JSONObject.NULL); - } else { - x.back(); - this.myArrayList.add(x.nextValue()); - } - switch (x.nextClean()) { - case 0: - // array is unclosed. No ']' found, instead EOF - throw x.syntaxError("Expected a ',' or ']'"); - case ',': - nextChar = x.nextClean(); - if (nextChar == 0) { - // array is unclosed. No ']' found, instead EOF - throw x.syntaxError("Expected a ',' or ']'"); - } - if (nextChar == ']') { - return; - } - x.back(); - break; - case ']': - return; - default: - throw x.syntaxError("Expected a ',' or ']'"); - } - } - } - } - - /** - * Construct a JSONArray from a source JSON text. - * - * @param source - * A string that begins with [ (left - * bracket) and ends with ] - *  (right bracket). - * @throws JSONException - * If there is a syntax error. - */ - public JSONArray(String source) throws JSONException { - this(new JSONTokener(source)); - } - - /** - * Construct a JSONArray from a Collection. - * - * @param collection - * A Collection. - */ - public JSONArray(Collection collection) { - if (collection == null) { - this.myArrayList = new ArrayList(); - } else { - this.myArrayList = new ArrayList(collection.size()); - for (Object o: collection){ - this.myArrayList.add(JSONObject.wrap(o)); - } - } - } - - /** - * Construct a JSONArray from an array - * - * @throws JSONException - * If not an array or if an array value is non-finite number. - */ - public JSONArray(Object array) throws JSONException { - this(); - if (array.getClass().isArray()) { - int length = Array.getLength(array); - this.myArrayList.ensureCapacity(length); - for (int i = 0; i < length; i += 1) { - this.put(JSONObject.wrap(Array.get(array, i))); - } - } else { - throw new JSONException( - "JSONArray initial value should be a string or collection or array."); - } - } - - @Override - public Iterator iterator() { - return this.myArrayList.iterator(); - } - - /** - * Get the object value associated with an index. - * - * @param index - * The index must be between 0 and length() - 1. - * @return An object value. - * @throws JSONException - * If there is no value for the index. - */ - public Object get(int index) throws JSONException { - Object object = this.opt(index); - if (object == null) { - throw new JSONException("JSONArray[" + index + "] not found."); - } - return object; - } - - /** - * Get the boolean value associated with an index. The string values "true" - * and "false" are converted to boolean. - * - * @param index - * The index must be between 0 and length() - 1. - * @return The truth. - * @throws JSONException - * If there is no value for the index or if the value is not - * convertible to boolean. - */ - public boolean getBoolean(int index) throws JSONException { - Object object = this.get(index); - if (object.equals(Boolean.FALSE) - || (object instanceof String && ((String) object) - .equalsIgnoreCase("false"))) { - return false; - } else if (object.equals(Boolean.TRUE) - || (object instanceof String && ((String) object) - .equalsIgnoreCase("true"))) { - return true; - } - throw new JSONException("JSONArray[" + index + "] is not a boolean."); - } - - /** - * Get the double value associated with an index. - * - * @param index - * The index must be between 0 and length() - 1. - * @return The value. - * @throws JSONException - * If the key is not found or if the value cannot be converted - * to a number. - */ - public double getDouble(int index) throws JSONException { - Object object = this.get(index); - try { - return object instanceof Number ? ((Number) object).doubleValue() - : Double.parseDouble((String) object); - } catch (Exception e) { - throw new JSONException("JSONArray[" + index + "] is not a number.", e); - } - } - - /** - * Get the float value associated with a key. - * - * @param index - * The index must be between 0 and length() - 1. - * @return The numeric value. - * @throws JSONException - * if the key is not found or if the value is not a Number - * object and cannot be converted to a number. - */ - public float getFloat(int index) throws JSONException { - Object object = this.get(index); - try { - return object instanceof Number ? ((Number) object).floatValue() - : Float.parseFloat(object.toString()); - } catch (Exception e) { - throw new JSONException("JSONArray[" + index - + "] is not a number.", e); - } - } - - /** - * Get the Number value associated with a key. - * - * @param index - * The index must be between 0 and length() - 1. - * @return The numeric value. - * @throws JSONException - * if the key is not found or if the value is not a Number - * object and cannot be converted to a number. - */ - public Number getNumber(int index) throws JSONException { - Object object = this.get(index); - try { - if (object instanceof Number) { - return (Number)object; - } - return JSONObject.stringToNumber(object.toString()); - } catch (Exception e) { - throw new JSONException("JSONArray[" + index + "] is not a number.", e); - } - } - - /** - * Get the enum value associated with an index. - * - * @param clazz - * The type of enum to retrieve. - * @param index - * The index must be between 0 and length() - 1. - * @return The enum value at the index location - * @throws JSONException - * if the key is not found or if the value cannot be converted - * to an enum. - */ - public > E getEnum(Class clazz, int index) throws JSONException { - E val = optEnum(clazz, index); - if(val==null) { - // JSONException should really take a throwable argument. - // If it did, I would re-implement this with the Enum.valueOf - // method and place any thrown exception in the JSONException - throw new JSONException("JSONArray[" + index + "] is not an enum of type " - + JSONObject.quote(clazz.getSimpleName()) + "."); - } - return val; - } - - /** - * Get the BigDecimal value associated with an index. - * - * @param index - * The index must be between 0 and length() - 1. - * @return The value. - * @throws JSONException - * If the key is not found or if the value cannot be converted - * to a BigDecimal. - */ - public BigDecimal getBigDecimal (int index) throws JSONException { - Object object = this.get(index); - try { - return new BigDecimal(object.toString()); - } catch (Exception e) { - throw new JSONException("JSONArray[" + index + - "] could not convert to BigDecimal.", e); - } - } - - /** - * Get the BigInteger value associated with an index. - * - * @param index - * The index must be between 0 and length() - 1. - * @return The value. - * @throws JSONException - * If the key is not found or if the value cannot be converted - * to a BigInteger. - */ - public BigInteger getBigInteger (int index) throws JSONException { - Object object = this.get(index); - try { - return new BigInteger(object.toString()); - } catch (Exception e) { - throw new JSONException("JSONArray[" + index + - "] could not convert to BigInteger.", e); - } - } - - /** - * Get the int value associated with an index. - * - * @param index - * The index must be between 0 and length() - 1. - * @return The value. - * @throws JSONException - * If the key is not found or if the value is not a number. - */ - public int getInt(int index) throws JSONException { - Object object = this.get(index); - try { - return object instanceof Number ? ((Number) object).intValue() - : Integer.parseInt((String) object); - } catch (Exception e) { - throw new JSONException("JSONArray[" + index + "] is not a number.", e); - } - } - - /** - * Get the JSONArray associated with an index. - * - * @param index - * The index must be between 0 and length() - 1. - * @return A JSONArray value. - * @throws JSONException - * If there is no value for the index. or if the value is not a - * JSONArray - */ - public JSONArray getJSONArray(int index) throws JSONException { - Object object = this.get(index); - if (object instanceof JSONArray) { - return (JSONArray) object; - } - throw new JSONException("JSONArray[" + index + "] is not a JSONArray."); - } - - /** - * Get the JSONObject associated with an index. - * - * @param index - * subscript - * @return A JSONObject value. - * @throws JSONException - * If there is no value for the index or if the value is not a - * JSONObject - */ - public JSONObject getJSONObject(int index) throws JSONException { - Object object = this.get(index); - if (object instanceof JSONObject) { - return (JSONObject) object; - } - throw new JSONException("JSONArray[" + index + "] is not a JSONObject."); - } - - /** - * Get the long value associated with an index. - * - * @param index - * The index must be between 0 and length() - 1. - * @return The value. - * @throws JSONException - * If the key is not found or if the value cannot be converted - * to a number. - */ - public long getLong(int index) throws JSONException { - Object object = this.get(index); - try { - return object instanceof Number ? ((Number) object).longValue() - : Long.parseLong((String) object); - } catch (Exception e) { - throw new JSONException("JSONArray[" + index + "] is not a number.", e); - } - } - - /** - * Get the string associated with an index. - * - * @param index - * The index must be between 0 and length() - 1. - * @return A string value. - * @throws JSONException - * If there is no string value for the index. - */ - public String getString(int index) throws JSONException { - Object object = this.get(index); - if (object instanceof String) { - return (String) object; - } - throw new JSONException("JSONArray[" + index + "] not a string."); - } - - /** - * Determine if the value is null. - * - * @param index - * The index must be between 0 and length() - 1. - * @return true if the value at the index is null, or if there is no value. - */ - public boolean isNull(int index) { - return JSONObject.NULL.equals(this.opt(index)); - } - - /** - * Make a string from the contents of this JSONArray. The - * separator string is inserted between each element. Warning: - * This method assumes that the data structure is acyclical. - * - * @param separator - * A string that will be inserted between the elements. - * @return a string. - * @throws JSONException - * If the array contains an invalid number. - */ - public String join(String separator) throws JSONException { - int len = this.length(); - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < len; i += 1) { - if (i > 0) { - sb.append(separator); - } - sb.append(JSONObject.valueToString(this.myArrayList.get(i))); - } - return sb.toString(); - } - - /** - * Get the number of elements in the JSONArray, included nulls. - * - * @return The length (or size). - */ - public int length() { - return this.myArrayList.size(); - } - - /** - * Get the optional object value associated with an index. - * - * @param index - * The index must be between 0 and length() - 1. If not, null is returned. - * @return An object value, or null if there is no object at that index. - */ - public Object opt(int index) { - return (index < 0 || index >= this.length()) ? null : this.myArrayList - .get(index); - } - - /** - * Get the optional boolean value associated with an index. It returns false - * if there is no value at that index, or if the value is not Boolean.TRUE - * or the String "true". - * - * @param index - * The index must be between 0 and length() - 1. - * @return The truth. - */ - public boolean optBoolean(int index) { - return this.optBoolean(index, false); - } - - /** - * Get the optional boolean value associated with an index. It returns the - * defaultValue if there is no value at that index or if it is not a Boolean - * or the String "true" or "false" (case insensitive). - * - * @param index - * The index must be between 0 and length() - 1. - * @param defaultValue - * A boolean default. - * @return The truth. - */ - public boolean optBoolean(int index, boolean defaultValue) { - try { - return this.getBoolean(index); - } catch (Exception e) { - return defaultValue; - } - } - - /** - * Get the optional double value associated with an index. NaN is returned - * if there is no value for the index, or if the value is not a number and - * cannot be converted to a number. - * - * @param index - * The index must be between 0 and length() - 1. - * @return The value. - */ - public double optDouble(int index) { - return this.optDouble(index, Double.NaN); - } - - /** - * Get the optional double value associated with an index. The defaultValue - * is returned if there is no value for the index, or if the value is not a - * number and cannot be converted to a number. - * - * @param index - * subscript - * @param defaultValue - * The default value. - * @return The value. - */ - public double optDouble(int index, double defaultValue) { - Object val = this.opt(index); - if (JSONObject.NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Number){ - return ((Number) val).doubleValue(); - } - if (val instanceof String) { - try { - return Double.parseDouble((String) val); - } catch (Exception e) { - return defaultValue; - } - } - return defaultValue; - } - - /** - * Get the optional float value associated with an index. NaN is returned - * if there is no value for the index, or if the value is not a number and - * cannot be converted to a number. - * - * @param index - * The index must be between 0 and length() - 1. - * @return The value. - */ - public float optFloat(int index) { - return this.optFloat(index, Float.NaN); - } - - /** - * Get the optional float value associated with an index. The defaultValue - * is returned if there is no value for the index, or if the value is not a - * number and cannot be converted to a number. - * - * @param index - * subscript - * @param defaultValue - * The default value. - * @return The value. - */ - public float optFloat(int index, float defaultValue) { - Object val = this.opt(index); - if (JSONObject.NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Number){ - return ((Number) val).floatValue(); - } - if (val instanceof String) { - try { - return Float.parseFloat((String) val); - } catch (Exception e) { - return defaultValue; - } - } - return defaultValue; - } - - /** - * Get the optional int value associated with an index. Zero is returned if - * there is no value for the index, or if the value is not a number and - * cannot be converted to a number. - * - * @param index - * The index must be between 0 and length() - 1. - * @return The value. - */ - public int optInt(int index) { - return this.optInt(index, 0); - } - - /** - * Get the optional int value associated with an index. The defaultValue is - * returned if there is no value for the index, or if the value is not a - * number and cannot be converted to a number. - * - * @param index - * The index must be between 0 and length() - 1. - * @param defaultValue - * The default value. - * @return The value. - */ - public int optInt(int index, int defaultValue) { - Object val = this.opt(index); - if (JSONObject.NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Number){ - return ((Number) val).intValue(); - } - - if (val instanceof String) { - try { - return new BigDecimal(val.toString()).intValue(); - } catch (Exception e) { - return defaultValue; - } - } - return defaultValue; - } - - /** - * Get the enum value associated with a key. - * - * @param clazz - * The type of enum to retrieve. - * @param index - * The index must be between 0 and length() - 1. - * @return The enum value at the index location or null if not found - */ - public > E optEnum(Class clazz, int index) { - return this.optEnum(clazz, index, null); - } - - /** - * Get the enum value associated with a key. - * - * @param clazz - * The type of enum to retrieve. - * @param index - * The index must be between 0 and length() - 1. - * @param defaultValue - * The default in case the value is not found - * @return The enum value at the index location or defaultValue if - * the value is not found or cannot be assigned to clazz - */ - public > E optEnum(Class clazz, int index, E defaultValue) { - try { - Object val = this.opt(index); - if (JSONObject.NULL.equals(val)) { - return defaultValue; - } - if (clazz.isAssignableFrom(val.getClass())) { - // we just checked it! - @SuppressWarnings("unchecked") - E myE = (E) val; - return myE; - } - return Enum.valueOf(clazz, val.toString()); - } catch (IllegalArgumentException e) { - return defaultValue; - } catch (NullPointerException e) { - return defaultValue; - } - } - - - /** - * Get the optional BigInteger value associated with an index. The - * defaultValue is returned if there is no value for the index, or if the - * value is not a number and cannot be converted to a number. - * - * @param index - * The index must be between 0 and length() - 1. - * @param defaultValue - * The default value. - * @return The value. - */ - public BigInteger optBigInteger(int index, BigInteger defaultValue) { - Object val = this.opt(index); - if (JSONObject.NULL.equals(val)) { - return defaultValue; - } - if (val instanceof BigInteger){ - return (BigInteger) val; - } - if (val instanceof BigDecimal){ - return ((BigDecimal) val).toBigInteger(); - } - if (val instanceof Double || val instanceof Float){ - return new BigDecimal(((Number) val).doubleValue()).toBigInteger(); - } - if (val instanceof Long || val instanceof Integer - || val instanceof Short || val instanceof Byte){ - return BigInteger.valueOf(((Number) val).longValue()); - } - try { - final String valStr = val.toString(); - if(JSONObject.isDecimalNotation(valStr)) { - return new BigDecimal(valStr).toBigInteger(); - } - return new BigInteger(valStr); - } catch (Exception e) { - return defaultValue; - } - } - - /** - * Get the optional BigDecimal value associated with an index. The - * defaultValue is returned if there is no value for the index, or if the - * value is not a number and cannot be converted to a number. - * - * @param index - * The index must be between 0 and length() - 1. - * @param defaultValue - * The default value. - * @return The value. - */ - public BigDecimal optBigDecimal(int index, BigDecimal defaultValue) { - Object val = this.opt(index); - if (JSONObject.NULL.equals(val)) { - return defaultValue; - } - if (val instanceof BigDecimal){ - return (BigDecimal) val; - } - if (val instanceof BigInteger){ - return new BigDecimal((BigInteger) val); - } - if (val instanceof Double || val instanceof Float){ - return new BigDecimal(((Number) val).doubleValue()); - } - if (val instanceof Long || val instanceof Integer - || val instanceof Short || val instanceof Byte){ - return new BigDecimal(((Number) val).longValue()); - } - try { - return new BigDecimal(val.toString()); - } catch (Exception e) { - return defaultValue; - } - } - - /** - * Get the optional JSONArray associated with an index. - * - * @param index - * subscript - * @return A JSONArray value, or null if the index has no value, or if the - * value is not a JSONArray. - */ - public JSONArray optJSONArray(int index) { - Object o = this.opt(index); - return o instanceof JSONArray ? (JSONArray) o : null; - } - - /** - * Get the optional JSONObject associated with an index. Null is returned if - * the key is not found, or null if the index has no value, or if the value - * is not a JSONObject. - * - * @param index - * The index must be between 0 and length() - 1. - * @return A JSONObject value. - */ - public JSONObject optJSONObject(int index) { - Object o = this.opt(index); - return o instanceof JSONObject ? (JSONObject) o : null; - } - - /** - * Get the optional long value associated with an index. Zero is returned if - * there is no value for the index, or if the value is not a number and - * cannot be converted to a number. - * - * @param index - * The index must be between 0 and length() - 1. - * @return The value. - */ - public long optLong(int index) { - return this.optLong(index, 0); - } - - /** - * Get the optional long value associated with an index. The defaultValue is - * returned if there is no value for the index, or if the value is not a - * number and cannot be converted to a number. - * - * @param index - * The index must be between 0 and length() - 1. - * @param defaultValue - * The default value. - * @return The value. - */ - public long optLong(int index, long defaultValue) { - Object val = this.opt(index); - if (JSONObject.NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Number){ - return ((Number) val).longValue(); - } - - if (val instanceof String) { - try { - return new BigDecimal(val.toString()).longValue(); - } catch (Exception e) { - return defaultValue; - } - } - return defaultValue; - } - - /** - * Get an optional {@link Number} value associated with a key, or null - * if there is no such key or if the value is not a number. If the value is a string, - * an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method - * would be used in cases where type coercion of the number value is unwanted. - * - * @param index - * The index must be between 0 and length() - 1. - * @return An object which is the value. - */ - public Number optNumber(int index) { - return this.optNumber(index, null); - } - - /** - * Get an optional {@link Number} value associated with a key, or the default if there - * is no such key or if the value is not a number. If the value is a string, - * an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method - * would be used in cases where type coercion of the number value is unwanted. - * - * @param index - * The index must be between 0 and length() - 1. - * @param defaultValue - * The default. - * @return An object which is the value. - */ - public Number optNumber(int index, Number defaultValue) { - Object val = this.opt(index); - if (JSONObject.NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Number){ - return (Number) val; - } - - if (val instanceof String) { - try { - return JSONObject.stringToNumber((String) val); - } catch (Exception e) { - return defaultValue; - } - } - return defaultValue; - } - - /** - * Get the optional string value associated with an index. It returns an - * empty string if there is no value at that index. If the value is not a - * string and is not null, then it is converted to a string. - * - * @param index - * The index must be between 0 and length() - 1. - * @return A String value. - */ - public String optString(int index) { - return this.optString(index, ""); - } - - /** - * Get the optional string associated with an index. The defaultValue is - * returned if the key is not found. - * - * @param index - * The index must be between 0 and length() - 1. - * @param defaultValue - * The default value. - * @return A String value. - */ - public String optString(int index, String defaultValue) { - Object object = this.opt(index); - return JSONObject.NULL.equals(object) ? defaultValue : object - .toString(); - } - - /** - * Append a boolean value. This increases the array's length by one. - * - * @param value - * A boolean value. - * @return this. - */ - public JSONArray put(boolean value) { - return this.put(value ? Boolean.TRUE : Boolean.FALSE); - } - - /** - * Put a value in the JSONArray, where the value will be a JSONArray which - * is produced from a Collection. - * - * @param value - * A Collection value. - * @return this. - * @throws JSONException - * If the value is non-finite number. - */ - public JSONArray put(Collection value) { - return this.put(new JSONArray(value)); - } - - /** - * Append a double value. This increases the array's length by one. - * - * @param value - * A double value. - * @return this. - * @throws JSONException - * if the value is not finite. - */ - public JSONArray put(double value) throws JSONException { - return this.put(Double.valueOf(value)); - } - - /** - * Append a float value. This increases the array's length by one. - * - * @param value - * A float value. - * @return this. - * @throws JSONException - * if the value is not finite. - */ - public JSONArray put(float value) throws JSONException { - return this.put(Float.valueOf(value)); - } - - /** - * Append an int value. This increases the array's length by one. - * - * @param value - * An int value. - * @return this. - */ - public JSONArray put(int value) { - return this.put(Integer.valueOf(value)); - } - - /** - * Append an long value. This increases the array's length by one. - * - * @param value - * A long value. - * @return this. - */ - public JSONArray put(long value) { - return this.put(Long.valueOf(value)); - } - - /** - * Put a value in the JSONArray, where the value will be a JSONObject which - * is produced from a Map. - * - * @param value - * A Map value. - * @return this. - * @throws JSONException - * If a value in the map is non-finite number. - * @throws NullPointerException - * If a key in the map is null - */ - public JSONArray put(Map value) { - return this.put(new JSONObject(value)); - } - - /** - * Append an object value. This increases the array's length by one. - * - * @param value - * An object value. The value should be a Boolean, Double, - * Integer, JSONArray, JSONObject, Long, or String, or the - * JSONObject.NULL object. - * @return this. - * @throws JSONException - * If the value is non-finite number. - */ - public JSONArray put(Object value) { - JSONObject.testValidity(value); - this.myArrayList.add(value); - return this; - } - - /** - * Put or replace a boolean value in the JSONArray. If the index is greater - * than the length of the JSONArray, then null elements will be added as - * necessary to pad it out. - * - * @param index - * The subscript. - * @param value - * A boolean value. - * @return this. - * @throws JSONException - * If the index is negative. - */ - public JSONArray put(int index, boolean value) throws JSONException { - return this.put(index, value ? Boolean.TRUE : Boolean.FALSE); - } - - /** - * Put a value in the JSONArray, where the value will be a JSONArray which - * is produced from a Collection. - * - * @param index - * The subscript. - * @param value - * A Collection value. - * @return this. - * @throws JSONException - * If the index is negative or if the value is non-finite. - */ - public JSONArray put(int index, Collection value) throws JSONException { - return this.put(index, new JSONArray(value)); - } - - /** - * Put or replace a double value. If the index is greater than the length of - * the JSONArray, then null elements will be added as necessary to pad it - * out. - * - * @param index - * The subscript. - * @param value - * A double value. - * @return this. - * @throws JSONException - * If the index is negative or if the value is non-finite. - */ - public JSONArray put(int index, double value) throws JSONException { - return this.put(index, Double.valueOf(value)); - } - - /** - * Put or replace a float value. If the index is greater than the length of - * the JSONArray, then null elements will be added as necessary to pad it - * out. - * - * @param index - * The subscript. - * @param value - * A float value. - * @return this. - * @throws JSONException - * If the index is negative or if the value is non-finite. - */ - public JSONArray put(int index, float value) throws JSONException { - return this.put(index, Float.valueOf(value)); - } - - /** - * Put or replace an int value. If the index is greater than the length of - * the JSONArray, then null elements will be added as necessary to pad it - * out. - * - * @param index - * The subscript. - * @param value - * An int value. - * @return this. - * @throws JSONException - * If the index is negative. - */ - public JSONArray put(int index, int value) throws JSONException { - return this.put(index, Integer.valueOf(value)); - } - - /** - * Put or replace a long value. If the index is greater than the length of - * the JSONArray, then null elements will be added as necessary to pad it - * out. - * - * @param index - * The subscript. - * @param value - * A long value. - * @return this. - * @throws JSONException - * If the index is negative. - */ - public JSONArray put(int index, long value) throws JSONException { - return this.put(index, Long.valueOf(value)); - } - - /** - * Put a value in the JSONArray, where the value will be a JSONObject that - * is produced from a Map. - * - * @param index - * The subscript. - * @param value - * The Map value. - * @return this. - * @throws JSONException - * If the index is negative or if the the value is an invalid - * number. - * @throws NullPointerException - * If a key in the map is null - */ - public JSONArray put(int index, Map value) throws JSONException { - this.put(index, new JSONObject(value)); - return this; - } - - /** - * Put or replace an object value in the JSONArray. If the index is greater - * than the length of the JSONArray, then null elements will be added as - * necessary to pad it out. - * - * @param index - * The subscript. - * @param value - * The value to put into the array. The value should be a - * Boolean, Double, Integer, JSONArray, JSONObject, Long, or - * String, or the JSONObject.NULL object. - * @return this. - * @throws JSONException - * If the index is negative or if the the value is an invalid - * number. - */ - public JSONArray put(int index, Object value) throws JSONException { - if (index < 0) { - throw new JSONException("JSONArray[" + index + "] not found."); - } - if (index < this.length()) { - JSONObject.testValidity(value); - this.myArrayList.set(index, value); - return this; - } - if(index == this.length()){ - // simple append - return this.put(value); - } - // if we are inserting past the length, we want to grow the array all at once - // instead of incrementally. - this.myArrayList.ensureCapacity(index + 1); - while (index != this.length()) { - // we don't need to test validity of NULL objects - this.myArrayList.add(JSONObject.NULL); - } - return this.put(value); - } - - /** - * Creates a JSONPointer using an initialization string and tries to - * match it to an item within this JSONArray. For example, given a - * JSONArray initialized with this document: - *
-     * [
-     *     {"b":"c"}
-     * ]
-     * 
- * and this JSONPointer string: - *
-     * "/0/b"
-     * 
- * Then this method will return the String "c" - * A JSONPointerException may be thrown from code called by this method. - * - * @param jsonPointer string that can be used to create a JSONPointer - * @return the item matched by the JSONPointer, otherwise null - */ - public Object query(String jsonPointer) { - return query(new JSONPointer(jsonPointer)); - } - - /** - * Uses a uaer initialized JSONPointer and tries to - * match it to an item whithin this JSONArray. For example, given a - * JSONArray initialized with this document: - *
-     * [
-     *     {"b":"c"}
-     * ]
-     * 
- * and this JSONPointer: - *
-     * "/0/b"
-     * 
- * Then this method will return the String "c" - * A JSONPointerException may be thrown from code called by this method. - * - * @param jsonPointer string that can be used to create a JSONPointer - * @return the item matched by the JSONPointer, otherwise null - */ - public Object query(JSONPointer jsonPointer) { - return jsonPointer.queryFrom(this); - } - - /** - * Queries and returns a value from this object using {@code jsonPointer}, or - * returns null if the query fails due to a missing key. - * - * @param jsonPointer the string representation of the JSON pointer - * @return the queried value or {@code null} - * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax - */ - public Object optQuery(String jsonPointer) { - return optQuery(new JSONPointer(jsonPointer)); - } - - /** - * Queries and returns a value from this object using {@code jsonPointer}, or - * returns null if the query fails due to a missing key. - * - * @param jsonPointer The JSON pointer - * @return the queried value or {@code null} - * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax - */ - public Object optQuery(JSONPointer jsonPointer) { - try { - return jsonPointer.queryFrom(this); - } catch (JSONPointerException e) { - return null; - } - } - - /** - * Remove an index and close the hole. - * - * @param index - * The index of the element to be removed. - * @return The value that was associated with the index, or null if there - * was no value. - */ - public Object remove(int index) { - return index >= 0 && index < this.length() - ? this.myArrayList.remove(index) - : null; - } - - /** - * Determine if two JSONArrays are similar. - * They must contain similar sequences. - * - * @param other The other JSONArray - * @return true if they are equal - */ - public boolean similar(Object other) { - if (!(other instanceof JSONArray)) { - return false; - } - int len = this.length(); - if (len != ((JSONArray)other).length()) { - return false; - } - for (int i = 0; i < len; i += 1) { - Object valueThis = this.myArrayList.get(i); - Object valueOther = ((JSONArray)other).myArrayList.get(i); - if(valueThis == valueOther) { - continue; - } - if(valueThis == null) { - return false; - } - if (valueThis instanceof JSONObject) { - if (!((JSONObject)valueThis).similar(valueOther)) { - return false; - } - } else if (valueThis instanceof JSONArray) { - if (!((JSONArray)valueThis).similar(valueOther)) { - return false; - } - } else if (!valueThis.equals(valueOther)) { - return false; - } - } - return true; - } - - /** - * Produce a JSONObject by combining a JSONArray of names with the values of - * this JSONArray. - * - * @param names - * A JSONArray containing a list of key strings. These will be - * paired with the values. - * @return A JSONObject, or null if there are no names or if this JSONArray - * has no values. - * @throws JSONException - * If any of the names are null. - */ - public JSONObject toJSONObject(JSONArray names) throws JSONException { - if (names == null || names.isEmpty() || this.isEmpty()) { - return null; - } - JSONObject jo = new JSONObject(names.length()); - for (int i = 0; i < names.length(); i += 1) { - jo.put(names.getString(i), this.opt(i)); - } - return jo; - } - - /** - * Make a JSON text of this JSONArray. For compactness, no unnecessary - * whitespace is added. If it is not possible to produce a syntactically - * correct JSON text then null will be returned instead. This could occur if - * the array contains an invalid number. - *

- * Warning: This method assumes that the data structure is acyclical. - * - * - * @return a printable, displayable, transmittable representation of the - * array. - */ - @Override - public String toString() { - try { - return this.toString(0); - } catch (Exception e) { - return null; - } - } - - /** - * Make a pretty-printed JSON text of this JSONArray. - * - *

If indentFactor > 0 and the {@link JSONArray} has only - * one element, then the array will be output on a single line: - *

{@code [1]}
- * - *

If an array has 2 or more elements, then it will be output across - * multiple lines:

{@code
-     * [
-     * 1,
-     * "value 2",
-     * 3
-     * ]
-     * }
- *

- * Warning: This method assumes that the data structure is acyclical. - * - * - * @param indentFactor - * The number of spaces to add to each level of indentation. - * @return a printable, displayable, transmittable representation of the - * object, beginning with [ (left - * bracket) and ending with ] - *  (right bracket). - * @throws JSONException - */ - public String toString(int indentFactor) throws JSONException { - StringWriter sw = new StringWriter(); - synchronized (sw.getBuffer()) { - return this.write(sw, indentFactor, 0).toString(); - } - } - - /** - * Write the contents of the JSONArray as JSON text to a writer. For - * compactness, no whitespace is added. - *

- * Warning: This method assumes that the data structure is acyclical. - * - * - * @return The writer. - * @throws JSONException - */ - public Writer write(Writer writer) throws JSONException { - return this.write(writer, 0, 0); - } - - /** - * Write the contents of the JSONArray as JSON text to a writer. - * - *

If indentFactor > 0 and the {@link JSONArray} has only - * one element, then the array will be output on a single line: - *

{@code [1]}
- * - *

If an array has 2 or more elements, then it will be output across - * multiple lines:

{@code
-     * [
-     * 1,
-     * "value 2",
-     * 3
-     * ]
-     * }
- *

- * Warning: This method assumes that the data structure is acyclical. - * - * - * @param writer - * Writes the serialized JSON - * @param indentFactor - * The number of spaces to add to each level of indentation. - * @param indent - * The indentation of the top level. - * @return The writer. - * @throws JSONException - */ - public Writer write(Writer writer, int indentFactor, int indent) - throws JSONException { - try { - boolean commanate = false; - int length = this.length(); - writer.write('['); - - if (length == 1) { - try { - JSONObject.writeValue(writer, this.myArrayList.get(0), - indentFactor, indent); - } catch (Exception e) { - throw new JSONException("Unable to write JSONArray value at index: 0", e); - } - } else if (length != 0) { - final int newindent = indent + indentFactor; - - for (int i = 0; i < length; i += 1) { - if (commanate) { - writer.write(','); - } - if (indentFactor > 0) { - writer.write('\n'); - } - JSONObject.indent(writer, newindent); - try { - JSONObject.writeValue(writer, this.myArrayList.get(i), - indentFactor, newindent); - } catch (Exception e) { - throw new JSONException("Unable to write JSONArray value at index: " + i, e); - } - commanate = true; - } - if (indentFactor > 0) { - writer.write('\n'); - } - JSONObject.indent(writer, indent); - } - writer.write(']'); - return writer; - } catch (IOException e) { - throw new JSONException(e); - } - } - - /** - * Returns a java.util.List containing all of the elements in this array. - * If an element in the array is a JSONArray or JSONObject it will also - * be converted. - *

- * Warning: This method assumes that the data structure is acyclical. - * - * @return a java.util.List containing the elements of this array - */ - public List toList() { - List results = new ArrayList(this.myArrayList.size()); - for (Object element : this.myArrayList) { - if (element == null || JSONObject.NULL.equals(element)) { - results.add(null); - } else if (element instanceof JSONArray) { - results.add(((JSONArray) element).toList()); - } else if (element instanceof JSONObject) { - results.add(((JSONObject) element).toMap()); - } else { - results.add(element); - } - } - return results; - } - - /** - * Check if JSONArray is empty. - * - * @return true if JSONArray is empty, otherwise false. - */ - public boolean isEmpty() { - return myArrayList.isEmpty(); - } - -} diff --git a/JSONML.java b/JSONML.java deleted file mode 100644 index acec7b869..000000000 --- a/JSONML.java +++ /dev/null @@ -1,542 +0,0 @@ -package org.json; - -/* -Copyright (c) 2008 JSON.org - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -The Software shall be used for Good, not Evil. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -/** - * This provides static methods to convert an XML text into a JSONArray or - * JSONObject, and to covert a JSONArray or JSONObject into an XML text using - * the JsonML transform. - * - * @author JSON.org - * @version 2016-01-30 - */ -public class JSONML { - /** - * Parse XML values and store them in a JSONArray. - * @param x The XMLTokener containing the source string. - * @param arrayForm true if array form, false if object form. - * @param ja The JSONArray that is containing the current tag or null - * if we are at the outermost level. - * @param keepStrings Don't type-convert text nodes and attribute values - * @return A JSONArray if the value is the outermost tag, otherwise null. - * @throws JSONException - */ - private static Object parse( - XMLTokener x, - boolean arrayForm, - JSONArray ja, - boolean keepStrings - ) throws JSONException { - String attribute; - char c; - String closeTag = null; - int i; - JSONArray newja = null; - JSONObject newjo = null; - Object token; - String tagName = null; - -// Test for and skip past these forms: -// -// -// -// - - while (true) { - if (!x.more()) { - throw x.syntaxError("Bad XML"); - } - token = x.nextContent(); - if (token == XML.LT) { - token = x.nextToken(); - if (token instanceof Character) { - if (token == XML.SLASH) { - -// Close tag "); - } else { - x.back(); - } - } else if (c == '[') { - token = x.nextToken(); - if (token.equals("CDATA") && x.next() == '[') { - if (ja != null) { - ja.put(x.nextCDATA()); - } - } else { - throw x.syntaxError("Expected 'CDATA['"); - } - } else { - i = 1; - do { - token = x.nextMeta(); - if (token == null) { - throw x.syntaxError("Missing '>' after ' 0); - } - } else if (token == XML.QUEST) { - -// "); - } else { - throw x.syntaxError("Misshaped tag"); - } - -// Open tag < - - } else { - if (!(token instanceof String)) { - throw x.syntaxError("Bad tagName '" + token + "'."); - } - tagName = (String)token; - newja = new JSONArray(); - newjo = new JSONObject(); - if (arrayForm) { - newja.put(tagName); - if (ja != null) { - ja.put(newja); - } - } else { - newjo.put("tagName", tagName); - if (ja != null) { - ja.put(newjo); - } - } - token = null; - for (;;) { - if (token == null) { - token = x.nextToken(); - } - if (token == null) { - throw x.syntaxError("Misshaped tag"); - } - if (!(token instanceof String)) { - break; - } - -// attribute = value - - attribute = (String)token; - if (!arrayForm && ("tagName".equals(attribute) || "childNode".equals(attribute))) { - throw x.syntaxError("Reserved attribute."); - } - token = x.nextToken(); - if (token == XML.EQ) { - token = x.nextToken(); - if (!(token instanceof String)) { - throw x.syntaxError("Missing value"); - } - newjo.accumulate(attribute, keepStrings ? ((String)token) :XML.stringToValue((String)token)); - token = null; - } else { - newjo.accumulate(attribute, ""); - } - } - if (arrayForm && newjo.length() > 0) { - newja.put(newjo); - } - -// Empty tag <.../> - - if (token == XML.SLASH) { - if (x.nextToken() != XML.GT) { - throw x.syntaxError("Misshaped tag"); - } - if (ja == null) { - if (arrayForm) { - return newja; - } - return newjo; - } - -// Content, between <...> and - - } else { - if (token != XML.GT) { - throw x.syntaxError("Misshaped tag"); - } - closeTag = (String)parse(x, arrayForm, newja, keepStrings); - if (closeTag != null) { - if (!closeTag.equals(tagName)) { - throw x.syntaxError("Mismatched '" + tagName + - "' and '" + closeTag + "'"); - } - tagName = null; - if (!arrayForm && newja.length() > 0) { - newjo.put("childNodes", newja); - } - if (ja == null) { - if (arrayForm) { - return newja; - } - return newjo; - } - } - } - } - } else { - if (ja != null) { - ja.put(token instanceof String - ? keepStrings ? XML.unescape((String)token) :XML.stringToValue((String)token) - : token); - } - } - } - } - - - /** - * Convert a well-formed (but not necessarily valid) XML string into a - * JSONArray using the JsonML transform. Each XML tag is represented as - * a JSONArray in which the first element is the tag name. If the tag has - * attributes, then the second element will be JSONObject containing the - * name/value pairs. If the tag contains children, then strings and - * JSONArrays will represent the child tags. - * Comments, prologs, DTDs, and <[ [ ]]> are ignored. - * @param string The source string. - * @return A JSONArray containing the structured data from the XML string. - * @throws JSONException Thrown on error converting to a JSONArray - */ - public static JSONArray toJSONArray(String string) throws JSONException { - return (JSONArray)parse(new XMLTokener(string), true, null, false); - } - - - /** - * Convert a well-formed (but not necessarily valid) XML string into a - * JSONArray using the JsonML transform. Each XML tag is represented as - * a JSONArray in which the first element is the tag name. If the tag has - * attributes, then the second element will be JSONObject containing the - * name/value pairs. If the tag contains children, then strings and - * JSONArrays will represent the child tags. - * As opposed to toJSONArray this method does not attempt to convert - * any text node or attribute value to any type - * but just leaves it as a string. - * Comments, prologs, DTDs, and <[ [ ]]> are ignored. - * @param string The source string. - * @param keepStrings If true, then values will not be coerced into boolean - * or numeric values and will instead be left as strings - * @return A JSONArray containing the structured data from the XML string. - * @throws JSONException Thrown on error converting to a JSONArray - */ - public static JSONArray toJSONArray(String string, boolean keepStrings) throws JSONException { - return (JSONArray)parse(new XMLTokener(string), true, null, keepStrings); - } - - - /** - * Convert a well-formed (but not necessarily valid) XML string into a - * JSONArray using the JsonML transform. Each XML tag is represented as - * a JSONArray in which the first element is the tag name. If the tag has - * attributes, then the second element will be JSONObject containing the - * name/value pairs. If the tag contains children, then strings and - * JSONArrays will represent the child content and tags. - * As opposed to toJSONArray this method does not attempt to convert - * any text node or attribute value to any type - * but just leaves it as a string. - * Comments, prologs, DTDs, and <[ [ ]]> are ignored. - * @param x An XMLTokener. - * @param keepStrings If true, then values will not be coerced into boolean - * or numeric values and will instead be left as strings - * @return A JSONArray containing the structured data from the XML string. - * @throws JSONException Thrown on error converting to a JSONArray - */ - public static JSONArray toJSONArray(XMLTokener x, boolean keepStrings) throws JSONException { - return (JSONArray)parse(x, true, null, keepStrings); - } - - - /** - * Convert a well-formed (but not necessarily valid) XML string into a - * JSONArray using the JsonML transform. Each XML tag is represented as - * a JSONArray in which the first element is the tag name. If the tag has - * attributes, then the second element will be JSONObject containing the - * name/value pairs. If the tag contains children, then strings and - * JSONArrays will represent the child content and tags. - * Comments, prologs, DTDs, and <[ [ ]]> are ignored. - * @param x An XMLTokener. - * @return A JSONArray containing the structured data from the XML string. - * @throws JSONException Thrown on error converting to a JSONArray - */ - public static JSONArray toJSONArray(XMLTokener x) throws JSONException { - return (JSONArray)parse(x, true, null, false); - } - - - /** - * Convert a well-formed (but not necessarily valid) XML string into a - * JSONObject using the JsonML transform. Each XML tag is represented as - * a JSONObject with a "tagName" property. If the tag has attributes, then - * the attributes will be in the JSONObject as properties. If the tag - * contains children, the object will have a "childNodes" property which - * will be an array of strings and JsonML JSONObjects. - - * Comments, prologs, DTDs, and <[ [ ]]> are ignored. - * @param string The XML source text. - * @return A JSONObject containing the structured data from the XML string. - * @throws JSONException Thrown on error converting to a JSONObject - */ - public static JSONObject toJSONObject(String string) throws JSONException { - return (JSONObject)parse(new XMLTokener(string), false, null, false); - } - - - /** - * Convert a well-formed (but not necessarily valid) XML string into a - * JSONObject using the JsonML transform. Each XML tag is represented as - * a JSONObject with a "tagName" property. If the tag has attributes, then - * the attributes will be in the JSONObject as properties. If the tag - * contains children, the object will have a "childNodes" property which - * will be an array of strings and JsonML JSONObjects. - - * Comments, prologs, DTDs, and <[ [ ]]> are ignored. - * @param string The XML source text. - * @param keepStrings If true, then values will not be coerced into boolean - * or numeric values and will instead be left as strings - * @return A JSONObject containing the structured data from the XML string. - * @throws JSONException Thrown on error converting to a JSONObject - */ - public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { - return (JSONObject)parse(new XMLTokener(string), false, null, keepStrings); - } - - - /** - * Convert a well-formed (but not necessarily valid) XML string into a - * JSONObject using the JsonML transform. Each XML tag is represented as - * a JSONObject with a "tagName" property. If the tag has attributes, then - * the attributes will be in the JSONObject as properties. If the tag - * contains children, the object will have a "childNodes" property which - * will be an array of strings and JsonML JSONObjects. - - * Comments, prologs, DTDs, and <[ [ ]]> are ignored. - * @param x An XMLTokener of the XML source text. - * @return A JSONObject containing the structured data from the XML string. - * @throws JSONException Thrown on error converting to a JSONObject - */ - public static JSONObject toJSONObject(XMLTokener x) throws JSONException { - return (JSONObject)parse(x, false, null, false); - } - - - /** - * Convert a well-formed (but not necessarily valid) XML string into a - * JSONObject using the JsonML transform. Each XML tag is represented as - * a JSONObject with a "tagName" property. If the tag has attributes, then - * the attributes will be in the JSONObject as properties. If the tag - * contains children, the object will have a "childNodes" property which - * will be an array of strings and JsonML JSONObjects. - - * Comments, prologs, DTDs, and <[ [ ]]> are ignored. - * @param x An XMLTokener of the XML source text. - * @param keepStrings If true, then values will not be coerced into boolean - * or numeric values and will instead be left as strings - * @return A JSONObject containing the structured data from the XML string. - * @throws JSONException Thrown on error converting to a JSONObject - */ - public static JSONObject toJSONObject(XMLTokener x, boolean keepStrings) throws JSONException { - return (JSONObject)parse(x, false, null, keepStrings); - } - - - /** - * Reverse the JSONML transformation, making an XML text from a JSONArray. - * @param ja A JSONArray. - * @return An XML string. - * @throws JSONException Thrown on error converting to a string - */ - public static String toString(JSONArray ja) throws JSONException { - int i; - JSONObject jo; - int length; - Object object; - StringBuilder sb = new StringBuilder(); - String tagName; - -// Emit = length) { - sb.append('/'); - sb.append('>'); - } else { - sb.append('>'); - do { - object = ja.get(i); - i += 1; - if (object != null) { - if (object instanceof String) { - sb.append(XML.escape(object.toString())); - } else if (object instanceof JSONObject) { - sb.append(toString((JSONObject)object)); - } else if (object instanceof JSONArray) { - sb.append(toString((JSONArray)object)); - } else { - sb.append(object.toString()); - } - } - } while (i < length); - sb.append('<'); - sb.append('/'); - sb.append(tagName); - sb.append('>'); - } - return sb.toString(); - } - - /** - * Reverse the JSONML transformation, making an XML text from a JSONObject. - * The JSONObject must contain a "tagName" property. If it has children, - * then it must have a "childNodes" property containing an array of objects. - * The other properties are attributes with string values. - * @param jo A JSONObject. - * @return An XML string. - * @throws JSONException Thrown on error converting to a string - */ - public static String toString(JSONObject jo) throws JSONException { - StringBuilder sb = new StringBuilder(); - int i; - JSONArray ja; - int length; - Object object; - String tagName; - Object value; - -//Emit '); - } else { - sb.append('>'); - length = ja.length(); - for (i = 0; i < length; i += 1) { - object = ja.get(i); - if (object != null) { - if (object instanceof String) { - sb.append(XML.escape(object.toString())); - } else if (object instanceof JSONObject) { - sb.append(toString((JSONObject)object)); - } else if (object instanceof JSONArray) { - sb.append(toString((JSONArray)object)); - } else { - sb.append(object.toString()); - } - } - } - sb.append('<'); - sb.append('/'); - sb.append(tagName); - sb.append('>'); - } - return sb.toString(); - } -} diff --git a/JSONObject.java b/JSONObject.java deleted file mode 100644 index 8deb6bae5..000000000 --- a/JSONObject.java +++ /dev/null @@ -1,2563 +0,0 @@ -package org.json; - -import java.io.Closeable; - -/* - Copyright (c) 2002 JSON.org - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - The Software shall be used for Good, not Evil. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - */ - -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.Collection; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.ResourceBundle; -import java.util.Set; - -/** - * A JSONObject is an unordered collection of name/value pairs. Its external - * form is a string wrapped in curly braces with colons between the names and - * values, and commas between the values and names. The internal form is an - * object having get and opt methods for accessing - * the values by name, and put methods for adding or replacing - * values by name. The values can be any of these types: Boolean, - * JSONArray, JSONObject, Number, - * String, or the JSONObject.NULL object. A - * JSONObject constructor can be used to convert an external form JSON text - * into an internal form whose values can be retrieved with the - * get and opt methods, or to convert values into a - * JSON text using the put and toString methods. A - * get method returns a value if one can be found, and throws an - * exception if one cannot be found. An opt method returns a - * default value instead of throwing an exception, and so is useful for - * obtaining optional values. - *

- * The generic get() and opt() methods return an - * object, which you can cast or query for type. There are also typed - * get and opt methods that do type checking and type - * coercion for you. The opt methods differ from the get methods in that they - * do not throw. Instead, they return a specified value, such as null. - *

- * The put methods add or replace values in an object. For - * example, - * - *

- * myString = new JSONObject()
- *         .put("JSON", "Hello, World!").toString();
- * 
- * - * produces the string {"JSON": "Hello, World"}. - *

- * The texts produced by the toString methods strictly conform to - * the JSON syntax rules. The constructors are more forgiving in the texts they - * will accept: - *

    - *
  • An extra , (comma) may appear just - * before the closing brace.
  • - *
  • Strings may be quoted with ' (single - * quote).
  • - *
  • Strings do not need to be quoted at all if they do not begin with a - * quote or single quote, and if they do not contain leading or trailing - * spaces, and if they do not contain any of these characters: - * { } [ ] / \ : , # and if they do not look like numbers and - * if they are not the reserved words true, false, - * or null.
  • - *
- * - * @author JSON.org - * @version 2016-08-15 - */ -public class JSONObject { - /** - * JSONObject.NULL is equivalent to the value that JavaScript calls null, - * whilst Java's null is equivalent to the value that JavaScript calls - * undefined. - */ - private static final class Null { - - /** - * There is only intended to be a single instance of the NULL object, - * so the clone method returns itself. - * - * @return NULL. - */ - @Override - protected final Object clone() { - return this; - } - - /** - * A Null object is equal to the null value and to itself. - * - * @param object - * An object to test for nullness. - * @return true if the object parameter is the JSONObject.NULL object or - * null. - */ - @Override - public boolean equals(Object object) { - return object == null || object == this; - } - /** - * A Null object is equal to the null value and to itself. - * - * @return always returns 0. - */ - @Override - public int hashCode() { - return 0; - } - - /** - * Get the "null" string value. - * - * @return The string "null". - */ - @Override - public String toString() { - return "null"; - } - } - - /** - * The map where the JSONObject's properties are kept. - */ - private final Map map; - - /** - * It is sometimes more convenient and less ambiguous to have a - * NULL object than to use Java's null value. - * JSONObject.NULL.equals(null) returns true. - * JSONObject.NULL.toString() returns "null". - */ - public static final Object NULL = new Null(); - - /** - * Construct an empty JSONObject. - */ - public JSONObject() { - // HashMap is used on purpose to ensure that elements are unordered by - // the specification. - // JSON tends to be a portable transfer format to allows the container - // implementations to rearrange their items for a faster element - // retrieval based on associative access. - // Therefore, an implementation mustn't rely on the order of the item. - this.map = new HashMap(); - } - - /** - * Construct a JSONObject from a subset of another JSONObject. An array of - * strings is used to identify the keys that should be copied. Missing keys - * are ignored. - * - * @param jo - * A JSONObject. - * @param names - * An array of strings. - */ - public JSONObject(JSONObject jo, String[] names) { - this(names.length); - for (int i = 0; i < names.length; i += 1) { - try { - this.putOnce(names[i], jo.opt(names[i])); - } catch (Exception ignore) { - } - } - } - - /** - * Construct a JSONObject from a JSONTokener. - * - * @param x - * A JSONTokener object containing the source string. - * @throws JSONException - * If there is a syntax error in the source string or a - * duplicated key. - */ - public JSONObject(JSONTokener x) throws JSONException { - this(); - char c; - String key; - - if (x.nextClean() != '{') { - throw x.syntaxError("A JSONObject text must begin with '{'"); - } - for (;;) { - c = x.nextClean(); - switch (c) { - case 0: - throw x.syntaxError("A JSONObject text must end with '}'"); - case '}': - return; - default: - x.back(); - key = x.nextValue().toString(); - } - - // The key is followed by ':'. - - c = x.nextClean(); - if (c != ':') { - throw x.syntaxError("Expected a ':' after a key"); - } - - // Use syntaxError(..) to include error location - - if (key != null) { - // Check if key exists - if (this.opt(key) != null) { - // key already exists - throw x.syntaxError("Duplicate key \"" + key + "\""); - } - // Only add value if non-null - Object value = x.nextValue(); - if (value!=null) { - this.put(key, value); - } - } - - // Pairs are separated by ','. - - switch (x.nextClean()) { - case ';': - case ',': - if (x.nextClean() == '}') { - return; - } - x.back(); - break; - case '}': - return; - default: - throw x.syntaxError("Expected a ',' or '}'"); - } - } - } - - /** - * Construct a JSONObject from a Map. - * - * @param m - * A map object that can be used to initialize the contents of - * the JSONObject. - * @throws JSONException - * If a value in the map is non-finite number. - * @throws NullPointerException - * If a key in the map is null - */ - public JSONObject(Map m) { - if (m == null) { - this.map = new HashMap(); - } else { - this.map = new HashMap(m.size()); - for (final Entry e : m.entrySet()) { - if(e.getKey() == null) { - throw new NullPointerException("Null key."); - } - final Object value = e.getValue(); - if (value != null) { - this.map.put(String.valueOf(e.getKey()), wrap(value)); - } - } - } - } - - /** - * Construct a JSONObject from an Object using bean getters. It reflects on - * all of the public methods of the object. For each of the methods with no - * parameters and a name starting with "get" or - * "is" followed by an uppercase letter, the method is invoked, - * and a key and the value returned from the getter method are put into the - * new JSONObject. - *

- * The key is formed by removing the "get" or "is" - * prefix. If the second remaining character is not upper case, then the - * first character is converted to lower case. - *

- * Methods that are static, return void, - * have parameters, or are "bridge" methods, are ignored. - *

- * For example, if an object has a method named "getName", and - * if the result of calling object.getName() is - * "Larry Fine", then the JSONObject will contain - * "name": "Larry Fine". - *

- * The {@link JSONPropertyName} annotation can be used on a bean getter to - * override key name used in the JSONObject. For example, using the object - * above with the getName method, if we annotated it with: - *

-     * @JSONPropertyName("FullName")
-     * public String getName() { return this.name; }
-     * 
- * The resulting JSON object would contain "FullName": "Larry Fine" - *

- * Similarly, the {@link JSONPropertyName} annotation can be used on non- - * get and is methods. We can also override key - * name used in the JSONObject as seen below even though the field would normally - * be ignored: - *

-     * @JSONPropertyName("FullName")
-     * public String fullName() { return this.name; }
-     * 
- * The resulting JSON object would contain "FullName": "Larry Fine" - *

- * The {@link JSONPropertyIgnore} annotation can be used to force the bean property - * to not be serialized into JSON. If both {@link JSONPropertyIgnore} and - * {@link JSONPropertyName} are defined on the same method, a depth comparison is - * performed and the one closest to the concrete class being serialized is used. - * If both annotations are at the same level, then the {@link JSONPropertyIgnore} - * annotation takes precedent and the field is not serialized. - * For example, the following declaration would prevent the getName - * method from being serialized: - *

-     * @JSONPropertyName("FullName")
-     * @JSONPropertyIgnore 
-     * public String getName() { return this.name; }
-     * 
- *

- * - * @param bean - * An object that has getter methods that should be used to make - * a JSONObject. - */ - public JSONObject(Object bean) { - this(); - this.populateMap(bean); - } - - /** - * Construct a JSONObject from an Object, using reflection to find the - * public members. The resulting JSONObject's keys will be the strings from - * the names array, and the values will be the field values associated with - * those keys in the object. If a key is not found or not visible, then it - * will not be copied into the new JSONObject. - * - * @param object - * An object that has fields that should be used to make a - * JSONObject. - * @param names - * An array of strings, the names of the fields to be obtained - * from the object. - */ - public JSONObject(Object object, String names[]) { - this(names.length); - Class c = object.getClass(); - for (int i = 0; i < names.length; i += 1) { - String name = names[i]; - try { - this.putOpt(name, c.getField(name).get(object)); - } catch (Exception ignore) { - } - } - } - - /** - * Construct a JSONObject from a source JSON text string. This is the most - * commonly used JSONObject constructor. - * - * @param source - * A string beginning with { (left - * brace) and ending with } - *  (right brace). - * @exception JSONException - * If there is a syntax error in the source string or a - * duplicated key. - */ - public JSONObject(String source) throws JSONException { - this(new JSONTokener(source)); - } - - /** - * Construct a JSONObject from a ResourceBundle. - * - * @param baseName - * The ResourceBundle base name. - * @param locale - * The Locale to load the ResourceBundle for. - * @throws JSONException - * If any JSONExceptions are detected. - */ - public JSONObject(String baseName, Locale locale) throws JSONException { - this(); - ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale, - Thread.currentThread().getContextClassLoader()); - -// Iterate through the keys in the bundle. - - Enumeration keys = bundle.getKeys(); - while (keys.hasMoreElements()) { - Object key = keys.nextElement(); - if (key != null) { - -// Go through the path, ensuring that there is a nested JSONObject for each -// segment except the last. Add the value using the last segment's name into -// the deepest nested JSONObject. - - String[] path = ((String) key).split("\\."); - int last = path.length - 1; - JSONObject target = this; - for (int i = 0; i < last; i += 1) { - String segment = path[i]; - JSONObject nextTarget = target.optJSONObject(segment); - if (nextTarget == null) { - nextTarget = new JSONObject(); - target.put(segment, nextTarget); - } - target = nextTarget; - } - target.put(path[last], bundle.getString((String) key)); - } - } - } - - /** - * Constructor to specify an initial capacity of the internal map. Useful for library - * internal calls where we know, or at least can best guess, how big this JSONObject - * will be. - * - * @param initialCapacity initial capacity of the internal map. - */ - protected JSONObject(int initialCapacity){ - this.map = new HashMap(initialCapacity); - } - - /** - * Accumulate values under a key. It is similar to the put method except - * that if there is already an object stored under the key then a JSONArray - * is stored under the key to hold all of the accumulated values. If there - * is already a JSONArray, then the new value is appended to it. In - * contrast, the put method replaces the previous value. - * - * If only one value is accumulated that is not a JSONArray, then the result - * will be the same as using put. But if multiple values are accumulated, - * then the result will be like append. - * - * @param key - * A key string. - * @param value - * An object to be accumulated under the key. - * @return this. - * @throws JSONException - * If the value is non-finite number. - * @throws NullPointerException - * If the key is null. - */ - public JSONObject accumulate(String key, Object value) throws JSONException { - testValidity(value); - Object object = this.opt(key); - if (object == null) { - this.put(key, - value instanceof JSONArray ? new JSONArray().put(value) - : value); - } else if (object instanceof JSONArray) { - ((JSONArray) object).put(value); - } else { - this.put(key, new JSONArray().put(object).put(value)); - } - return this; - } - - /** - * Append values to the array under a key. If the key does not exist in the - * JSONObject, then the key is put in the JSONObject with its value being a - * JSONArray containing the value parameter. If the key was already - * associated with a JSONArray, then the value parameter is appended to it. - * - * @param key - * A key string. - * @param value - * An object to be accumulated under the key. - * @return this. - * @throws JSONException - * If the value is non-finite number or if the current value associated with - * the key is not a JSONArray. - * @throws NullPointerException - * If the key is null. - */ - public JSONObject append(String key, Object value) throws JSONException { - testValidity(value); - Object object = this.opt(key); - if (object == null) { - this.put(key, new JSONArray().put(value)); - } else if (object instanceof JSONArray) { - this.put(key, ((JSONArray) object).put(value)); - } else { - throw new JSONException("JSONObject[" + key - + "] is not a JSONArray."); - } - return this; - } - - /** - * Produce a string from a double. The string "null" will be returned if the - * number is not finite. - * - * @param d - * A double. - * @return A String. - */ - public static String doubleToString(double d) { - if (Double.isInfinite(d) || Double.isNaN(d)) { - return "null"; - } - -// Shave off trailing zeros and decimal point, if possible. - - String string = Double.toString(d); - if (string.indexOf('.') > 0 && string.indexOf('e') < 0 - && string.indexOf('E') < 0) { - while (string.endsWith("0")) { - string = string.substring(0, string.length() - 1); - } - if (string.endsWith(".")) { - string = string.substring(0, string.length() - 1); - } - } - return string; - } - - /** - * Get the value object associated with a key. - * - * @param key - * A key string. - * @return The object associated with the key. - * @throws JSONException - * if the key is not found. - */ - public Object get(String key) throws JSONException { - if (key == null) { - throw new JSONException("Null key."); - } - Object object = this.opt(key); - if (object == null) { - throw new JSONException("JSONObject[" + quote(key) + "] not found."); - } - return object; - } - - /** - * Get the enum value associated with a key. - * - * @param clazz - * The type of enum to retrieve. - * @param key - * A key string. - * @return The enum value associated with the key - * @throws JSONException - * if the key is not found or if the value cannot be converted - * to an enum. - */ - public > E getEnum(Class clazz, String key) throws JSONException { - E val = optEnum(clazz, key); - if(val==null) { - // JSONException should really take a throwable argument. - // If it did, I would re-implement this with the Enum.valueOf - // method and place any thrown exception in the JSONException - throw new JSONException("JSONObject[" + quote(key) - + "] is not an enum of type " + quote(clazz.getSimpleName()) - + "."); - } - return val; - } - - /** - * Get the boolean value associated with a key. - * - * @param key - * A key string. - * @return The truth. - * @throws JSONException - * if the value is not a Boolean or the String "true" or - * "false". - */ - public boolean getBoolean(String key) throws JSONException { - Object object = this.get(key); - if (object.equals(Boolean.FALSE) - || (object instanceof String && ((String) object) - .equalsIgnoreCase("false"))) { - return false; - } else if (object.equals(Boolean.TRUE) - || (object instanceof String && ((String) object) - .equalsIgnoreCase("true"))) { - return true; - } - throw new JSONException("JSONObject[" + quote(key) - + "] is not a Boolean."); - } - - /** - * Get the BigInteger value associated with a key. - * - * @param key - * A key string. - * @return The numeric value. - * @throws JSONException - * if the key is not found or if the value cannot - * be converted to BigInteger. - */ - public BigInteger getBigInteger(String key) throws JSONException { - Object object = this.get(key); - try { - return new BigInteger(object.toString()); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) - + "] could not be converted to BigInteger.", e); - } - } - - /** - * Get the BigDecimal value associated with a key. - * - * @param key - * A key string. - * @return The numeric value. - * @throws JSONException - * if the key is not found or if the value - * cannot be converted to BigDecimal. - */ - public BigDecimal getBigDecimal(String key) throws JSONException { - Object object = this.get(key); - if (object instanceof BigDecimal) { - return (BigDecimal)object; - } - try { - return new BigDecimal(object.toString()); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) - + "] could not be converted to BigDecimal.", e); - } - } - - /** - * Get the double value associated with a key. - * - * @param key - * A key string. - * @return The numeric value. - * @throws JSONException - * if the key is not found or if the value is not a Number - * object and cannot be converted to a number. - */ - public double getDouble(String key) throws JSONException { - Object object = this.get(key); - try { - return object instanceof Number ? ((Number) object).doubleValue() - : Double.parseDouble(object.toString()); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) - + "] is not a number.", e); - } - } - - /** - * Get the float value associated with a key. - * - * @param key - * A key string. - * @return The numeric value. - * @throws JSONException - * if the key is not found or if the value is not a Number - * object and cannot be converted to a number. - */ - public float getFloat(String key) throws JSONException { - Object object = this.get(key); - try { - return object instanceof Number ? ((Number) object).floatValue() - : Float.parseFloat(object.toString()); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) - + "] is not a number.", e); - } - } - - /** - * Get the Number value associated with a key. - * - * @param key - * A key string. - * @return The numeric value. - * @throws JSONException - * if the key is not found or if the value is not a Number - * object and cannot be converted to a number. - */ - public Number getNumber(String key) throws JSONException { - Object object = this.get(key); - try { - if (object instanceof Number) { - return (Number)object; - } - return stringToNumber(object.toString()); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) - + "] is not a number.", e); - } - } - - /** - * Get the int value associated with a key. - * - * @param key - * A key string. - * @return The integer value. - * @throws JSONException - * if the key is not found or if the value cannot be converted - * to an integer. - */ - public int getInt(String key) throws JSONException { - Object object = this.get(key); - try { - return object instanceof Number ? ((Number) object).intValue() - : Integer.parseInt((String) object); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) - + "] is not an int.", e); - } - } - - /** - * Get the JSONArray value associated with a key. - * - * @param key - * A key string. - * @return A JSONArray which is the value. - * @throws JSONException - * if the key is not found or if the value is not a JSONArray. - */ - public JSONArray getJSONArray(String key) throws JSONException { - Object object = this.get(key); - if (object instanceof JSONArray) { - return (JSONArray) object; - } - throw new JSONException("JSONObject[" + quote(key) - + "] is not a JSONArray."); - } - - /** - * Get the JSONObject value associated with a key. - * - * @param key - * A key string. - * @return A JSONObject which is the value. - * @throws JSONException - * if the key is not found or if the value is not a JSONObject. - */ - public JSONObject getJSONObject(String key) throws JSONException { - Object object = this.get(key); - if (object instanceof JSONObject) { - return (JSONObject) object; - } - throw new JSONException("JSONObject[" + quote(key) - + "] is not a JSONObject."); - } - - /** - * Get the long value associated with a key. - * - * @param key - * A key string. - * @return The long value. - * @throws JSONException - * if the key is not found or if the value cannot be converted - * to a long. - */ - public long getLong(String key) throws JSONException { - Object object = this.get(key); - try { - return object instanceof Number ? ((Number) object).longValue() - : Long.parseLong((String) object); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) - + "] is not a long.", e); - } - } - - /** - * Get an array of field names from a JSONObject. - * - * @return An array of field names, or null if there are no names. - */ - public static String[] getNames(JSONObject jo) { - if (jo.isEmpty()) { - return null; - } - return jo.keySet().toArray(new String[jo.length()]); - } - - /** - * Get an array of field names from an Object. - * - * @return An array of field names, or null if there are no names. - */ - public static String[] getNames(Object object) { - if (object == null) { - return null; - } - Class klass = object.getClass(); - Field[] fields = klass.getFields(); - int length = fields.length; - if (length == 0) { - return null; - } - String[] names = new String[length]; - for (int i = 0; i < length; i += 1) { - names[i] = fields[i].getName(); - } - return names; - } - - /** - * Get the string associated with a key. - * - * @param key - * A key string. - * @return A string which is the value. - * @throws JSONException - * if there is no string value for the key. - */ - public String getString(String key) throws JSONException { - Object object = this.get(key); - if (object instanceof String) { - return (String) object; - } - throw new JSONException("JSONObject[" + quote(key) + "] not a string."); - } - - /** - * Determine if the JSONObject contains a specific key. - * - * @param key - * A key string. - * @return true if the key exists in the JSONObject. - */ - public boolean has(String key) { - return this.map.containsKey(key); - } - - /** - * Increment a property of a JSONObject. If there is no such property, - * create one with a value of 1. If there is such a property, and if it is - * an Integer, Long, Double, or Float, then add one to it. - * - * @param key - * A key string. - * @return this. - * @throws JSONException - * If there is already a property with this name that is not an - * Integer, Long, Double, or Float. - */ - public JSONObject increment(String key) throws JSONException { - Object value = this.opt(key); - if (value == null) { - this.put(key, 1); - } else if (value instanceof BigInteger) { - this.put(key, ((BigInteger)value).add(BigInteger.ONE)); - } else if (value instanceof BigDecimal) { - this.put(key, ((BigDecimal)value).add(BigDecimal.ONE)); - } else if (value instanceof Integer) { - this.put(key, ((Integer) value).intValue() + 1); - } else if (value instanceof Long) { - this.put(key, ((Long) value).longValue() + 1L); - } else if (value instanceof Double) { - this.put(key, ((Double) value).doubleValue() + 1.0d); - } else if (value instanceof Float) { - this.put(key, ((Float) value).floatValue() + 1.0f); - } else { - throw new JSONException("Unable to increment [" + quote(key) + "]."); - } - return this; - } - - /** - * Determine if the value associated with the key is null or if there is no - * value. - * - * @param key - * A key string. - * @return true if there is no value associated with the key or if the value - * is the JSONObject.NULL object. - */ - public boolean isNull(String key) { - return JSONObject.NULL.equals(this.opt(key)); - } - - /** - * Get an enumeration of the keys of the JSONObject. Modifying this key Set will also - * modify the JSONObject. Use with caution. - * - * @see Set#iterator() - * - * @return An iterator of the keys. - */ - public Iterator keys() { - return this.keySet().iterator(); - } - - /** - * Get a set of keys of the JSONObject. Modifying this key Set will also modify the - * JSONObject. Use with caution. - * - * @see Map#keySet() - * - * @return A keySet. - */ - public Set keySet() { - return this.map.keySet(); - } - - /** - * Get a set of entries of the JSONObject. These are raw values and may not - * match what is returned by the JSONObject get* and opt* functions. Modifying - * the returned EntrySet or the Entry objects contained therein will modify the - * backing JSONObject. This does not return a clone or a read-only view. - * - * Use with caution. - * - * @see Map#entrySet() - * - * @return An Entry Set - */ - protected Set> entrySet() { - return this.map.entrySet(); - } - - /** - * Get the number of keys stored in the JSONObject. - * - * @return The number of keys in the JSONObject. - */ - public int length() { - return this.map.size(); - } - - /** - * Check if JSONObject is empty. - * - * @return true if JSONObject is empty, otherwise false. - */ - public boolean isEmpty() { - return map.isEmpty(); - } - - /** - * Produce a JSONArray containing the names of the elements of this - * JSONObject. - * - * @return A JSONArray containing the key strings, or null if the JSONObject - * is empty. - */ - public JSONArray names() { - if(this.map.isEmpty()) { - return null; - } - return new JSONArray(this.map.keySet()); - } - - /** - * Produce a string from a Number. - * - * @param number - * A Number - * @return A String. - * @throws JSONException - * If n is a non-finite number. - */ - public static String numberToString(Number number) throws JSONException { - if (number == null) { - throw new JSONException("Null pointer"); - } - testValidity(number); - - // Shave off trailing zeros and decimal point, if possible. - - String string = number.toString(); - if (string.indexOf('.') > 0 && string.indexOf('e') < 0 - && string.indexOf('E') < 0) { - while (string.endsWith("0")) { - string = string.substring(0, string.length() - 1); - } - if (string.endsWith(".")) { - string = string.substring(0, string.length() - 1); - } - } - return string; - } - - /** - * Get an optional value associated with a key. - * - * @param key - * A key string. - * @return An object which is the value, or null if there is no value. - */ - public Object opt(String key) { - return key == null ? null : this.map.get(key); - } - - /** - * Get the enum value associated with a key. - * - * @param clazz - * The type of enum to retrieve. - * @param key - * A key string. - * @return The enum value associated with the key or null if not found - */ - public > E optEnum(Class clazz, String key) { - return this.optEnum(clazz, key, null); - } - - /** - * Get the enum value associated with a key. - * - * @param clazz - * The type of enum to retrieve. - * @param key - * A key string. - * @param defaultValue - * The default in case the value is not found - * @return The enum value associated with the key or defaultValue - * if the value is not found or cannot be assigned to clazz - */ - public > E optEnum(Class clazz, String key, E defaultValue) { - try { - Object val = this.opt(key); - if (NULL.equals(val)) { - return defaultValue; - } - if (clazz.isAssignableFrom(val.getClass())) { - // we just checked it! - @SuppressWarnings("unchecked") - E myE = (E) val; - return myE; - } - return Enum.valueOf(clazz, val.toString()); - } catch (IllegalArgumentException e) { - return defaultValue; - } catch (NullPointerException e) { - return defaultValue; - } - } - - /** - * Get an optional boolean associated with a key. It returns false if there - * is no such key, or if the value is not Boolean.TRUE or the String "true". - * - * @param key - * A key string. - * @return The truth. - */ - public boolean optBoolean(String key) { - return this.optBoolean(key, false); - } - - /** - * Get an optional boolean associated with a key. It returns the - * defaultValue if there is no such key, or if it is not a Boolean or the - * String "true" or "false" (case insensitive). - * - * @param key - * A key string. - * @param defaultValue - * The default. - * @return The truth. - */ - public boolean optBoolean(String key, boolean defaultValue) { - Object val = this.opt(key); - if (NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Boolean){ - return ((Boolean) val).booleanValue(); - } - try { - // we'll use the get anyway because it does string conversion. - return this.getBoolean(key); - } catch (Exception e) { - return defaultValue; - } - } - - /** - * Get an optional BigDecimal associated with a key, or the defaultValue if - * there is no such key or if its value is not a number. If the value is a - * string, an attempt will be made to evaluate it as a number. - * - * @param key - * A key string. - * @param defaultValue - * The default. - * @return An object which is the value. - */ - public BigDecimal optBigDecimal(String key, BigDecimal defaultValue) { - Object val = this.opt(key); - if (NULL.equals(val)) { - return defaultValue; - } - if (val instanceof BigDecimal){ - return (BigDecimal) val; - } - if (val instanceof BigInteger){ - return new BigDecimal((BigInteger) val); - } - if (val instanceof Double || val instanceof Float){ - return new BigDecimal(((Number) val).doubleValue()); - } - if (val instanceof Long || val instanceof Integer - || val instanceof Short || val instanceof Byte){ - return new BigDecimal(((Number) val).longValue()); - } - // don't check if it's a string in case of unchecked Number subclasses - try { - return new BigDecimal(val.toString()); - } catch (Exception e) { - return defaultValue; - } - } - - /** - * Get an optional BigInteger associated with a key, or the defaultValue if - * there is no such key or if its value is not a number. If the value is a - * string, an attempt will be made to evaluate it as a number. - * - * @param key - * A key string. - * @param defaultValue - * The default. - * @return An object which is the value. - */ - public BigInteger optBigInteger(String key, BigInteger defaultValue) { - Object val = this.opt(key); - if (NULL.equals(val)) { - return defaultValue; - } - if (val instanceof BigInteger){ - return (BigInteger) val; - } - if (val instanceof BigDecimal){ - return ((BigDecimal) val).toBigInteger(); - } - if (val instanceof Double || val instanceof Float){ - return new BigDecimal(((Number) val).doubleValue()).toBigInteger(); - } - if (val instanceof Long || val instanceof Integer - || val instanceof Short || val instanceof Byte){ - return BigInteger.valueOf(((Number) val).longValue()); - } - // don't check if it's a string in case of unchecked Number subclasses - try { - // the other opt functions handle implicit conversions, i.e. - // jo.put("double",1.1d); - // jo.optInt("double"); -- will return 1, not an error - // this conversion to BigDecimal then to BigInteger is to maintain - // that type cast support that may truncate the decimal. - final String valStr = val.toString(); - if(isDecimalNotation(valStr)) { - return new BigDecimal(valStr).toBigInteger(); - } - return new BigInteger(valStr); - } catch (Exception e) { - return defaultValue; - } - } - - /** - * Get an optional double associated with a key, or NaN if there is no such - * key or if its value is not a number. If the value is a string, an attempt - * will be made to evaluate it as a number. - * - * @param key - * A string which is the key. - * @return An object which is the value. - */ - public double optDouble(String key) { - return this.optDouble(key, Double.NaN); - } - - /** - * Get an optional double associated with a key, or the defaultValue if - * there is no such key or if its value is not a number. If the value is a - * string, an attempt will be made to evaluate it as a number. - * - * @param key - * A key string. - * @param defaultValue - * The default. - * @return An object which is the value. - */ - public double optDouble(String key, double defaultValue) { - Object val = this.opt(key); - if (NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Number){ - return ((Number) val).doubleValue(); - } - if (val instanceof String) { - try { - return Double.parseDouble((String) val); - } catch (Exception e) { - return defaultValue; - } - } - return defaultValue; - } - - /** - * Get the optional double value associated with an index. NaN is returned - * if there is no value for the index, or if the value is not a number and - * cannot be converted to a number. - * - * @param key - * A key string. - * @return The value. - */ - public float optFloat(String key) { - return this.optFloat(key, Float.NaN); - } - - /** - * Get the optional double value associated with an index. The defaultValue - * is returned if there is no value for the index, or if the value is not a - * number and cannot be converted to a number. - * - * @param key - * A key string. - * @param defaultValue - * The default value. - * @return The value. - */ - public float optFloat(String key, float defaultValue) { - Object val = this.opt(key); - if (JSONObject.NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Number){ - return ((Number) val).floatValue(); - } - if (val instanceof String) { - try { - return Float.parseFloat((String) val); - } catch (Exception e) { - return defaultValue; - } - } - return defaultValue; - } - - /** - * Get an optional int value associated with a key, or zero if there is no - * such key or if the value is not a number. If the value is a string, an - * attempt will be made to evaluate it as a number. - * - * @param key - * A key string. - * @return An object which is the value. - */ - public int optInt(String key) { - return this.optInt(key, 0); - } - - /** - * Get an optional int value associated with a key, or the default if there - * is no such key or if the value is not a number. If the value is a string, - * an attempt will be made to evaluate it as a number. - * - * @param key - * A key string. - * @param defaultValue - * The default. - * @return An object which is the value. - */ - public int optInt(String key, int defaultValue) { - Object val = this.opt(key); - if (NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Number){ - return ((Number) val).intValue(); - } - - if (val instanceof String) { - try { - return new BigDecimal((String) val).intValue(); - } catch (Exception e) { - return defaultValue; - } - } - return defaultValue; - } - - /** - * Get an optional JSONArray associated with a key. It returns null if there - * is no such key, or if its value is not a JSONArray. - * - * @param key - * A key string. - * @return A JSONArray which is the value. - */ - public JSONArray optJSONArray(String key) { - Object o = this.opt(key); - return o instanceof JSONArray ? (JSONArray) o : null; - } - - /** - * Get an optional JSONObject associated with a key. It returns null if - * there is no such key, or if its value is not a JSONObject. - * - * @param key - * A key string. - * @return A JSONObject which is the value. - */ - public JSONObject optJSONObject(String key) { - Object object = this.opt(key); - return object instanceof JSONObject ? (JSONObject) object : null; - } - - /** - * Get an optional long value associated with a key, or zero if there is no - * such key or if the value is not a number. If the value is a string, an - * attempt will be made to evaluate it as a number. - * - * @param key - * A key string. - * @return An object which is the value. - */ - public long optLong(String key) { - return this.optLong(key, 0); - } - - /** - * Get an optional long value associated with a key, or the default if there - * is no such key or if the value is not a number. If the value is a string, - * an attempt will be made to evaluate it as a number. - * - * @param key - * A key string. - * @param defaultValue - * The default. - * @return An object which is the value. - */ - public long optLong(String key, long defaultValue) { - Object val = this.opt(key); - if (NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Number){ - return ((Number) val).longValue(); - } - - if (val instanceof String) { - try { - return new BigDecimal((String) val).longValue(); - } catch (Exception e) { - return defaultValue; - } - } - return defaultValue; - } - - /** - * Get an optional {@link Number} value associated with a key, or null - * if there is no such key or if the value is not a number. If the value is a string, - * an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method - * would be used in cases where type coercion of the number value is unwanted. - * - * @param key - * A key string. - * @return An object which is the value. - */ - public Number optNumber(String key) { - return this.optNumber(key, null); - } - - /** - * Get an optional {@link Number} value associated with a key, or the default if there - * is no such key or if the value is not a number. If the value is a string, - * an attempt will be made to evaluate it as a number. This method - * would be used in cases where type coercion of the number value is unwanted. - * - * @param key - * A key string. - * @param defaultValue - * The default. - * @return An object which is the value. - */ - public Number optNumber(String key, Number defaultValue) { - Object val = this.opt(key); - if (NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Number){ - return (Number) val; - } - - if (val instanceof String) { - try { - return stringToNumber((String) val); - } catch (Exception e) { - return defaultValue; - } - } - return defaultValue; - } - - /** - * Get an optional string associated with a key. It returns an empty string - * if there is no such key. If the value is not a string and is not null, - * then it is converted to a string. - * - * @param key - * A key string. - * @return A string which is the value. - */ - public String optString(String key) { - return this.optString(key, ""); - } - - /** - * Get an optional string associated with a key. It returns the defaultValue - * if there is no such key. - * - * @param key - * A key string. - * @param defaultValue - * The default. - * @return A string which is the value. - */ - public String optString(String key, String defaultValue) { - Object object = this.opt(key); - return NULL.equals(object) ? defaultValue : object.toString(); - } - - /** - * Populates the internal map of the JSONObject with the bean properties. The - * bean can not be recursive. - * - * @see JSONObject#JSONObject(Object) - * - * @param bean - * the bean - */ - private void populateMap(Object bean) { - Class klass = bean.getClass(); - - // If klass is a System class then set includeSuperClass to false. - - boolean includeSuperClass = klass.getClassLoader() != null; - - Method[] methods = includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods(); - for (final Method method : methods) { - final int modifiers = method.getModifiers(); - if (Modifier.isPublic(modifiers) - && !Modifier.isStatic(modifiers) - && method.getParameterTypes().length == 0 - && !method.isBridge() - && method.getReturnType() != Void.TYPE - && isValidMethodName(method.getName())) { - final String key = getKeyNameFromMethod(method); - if (key != null && !key.isEmpty()) { - try { - final Object result = method.invoke(bean); - if (result != null) { - this.map.put(key, wrap(result)); - // we don't use the result anywhere outside of wrap - // if it's a resource we should be sure to close it - // after calling toString - if (result instanceof Closeable) { - try { - ((Closeable) result).close(); - } catch (IOException ignore) { - } - } - } - } catch (IllegalAccessException ignore) { - } catch (IllegalArgumentException ignore) { - } catch (InvocationTargetException ignore) { - } - } - } - } - } - - private boolean isValidMethodName(String name) { - return !"getClass".equals(name) && !"getDeclaringClass".equals(name); - } - - private String getKeyNameFromMethod(Method method) { - final int ignoreDepth = getAnnotationDepth(method, JSONPropertyIgnore.class); - if (ignoreDepth > 0) { - final int forcedNameDepth = getAnnotationDepth(method, JSONPropertyName.class); - if (forcedNameDepth < 0 || ignoreDepth <= forcedNameDepth) { - // the hierarchy asked to ignore, and the nearest name override - // was higher or non-existent - return null; - } - } - JSONPropertyName annotation = getAnnotation(method, JSONPropertyName.class); - if (annotation != null && annotation.value() != null && !annotation.value().isEmpty()) { - return annotation.value(); - } - String key; - final String name = method.getName(); - if (name.startsWith("get") && name.length() > 3) { - key = name.substring(3); - } else if (name.startsWith("is") && name.length() > 2) { - key = name.substring(2); - } else { - return null; - } - // if the first letter in the key is not uppercase, then skip. - // This is to maintain backwards compatibility before PR406 - // (https://github.com/stleary/JSON-java/pull/406/) - if (Character.isLowerCase(key.charAt(0))) { - return null; - } - if (key.length() == 1) { - key = key.toLowerCase(Locale.ROOT); - } else if (!Character.isUpperCase(key.charAt(1))) { - key = key.substring(0, 1).toLowerCase(Locale.ROOT) + key.substring(1); - } - return key; - } - - /** - * Searches the class hierarchy to see if the method or it's super - * implementations and interfaces has the annotation. - * - * @param - * type of the annotation - * - * @param m - * method to check - * @param annotationClass - * annotation to look for - * @return the {@link Annotation} if the annotation exists on the current method - * or one of it's super class definitions - */ - private static A getAnnotation(final Method m, final Class annotationClass) { - // if we have invalid data the result is null - if (m == null || annotationClass == null) { - return null; - } - - if (m.isAnnotationPresent(annotationClass)) { - return m.getAnnotation(annotationClass); - } - - // if we've already reached the Object class, return null; - Class c = m.getDeclaringClass(); - if (c.getSuperclass() == null) { - return null; - } - - // check directly implemented interfaces for the method being checked - for (Class i : c.getInterfaces()) { - try { - Method im = i.getMethod(m.getName(), m.getParameterTypes()); - return getAnnotation(im, annotationClass); - } catch (final SecurityException ex) { - continue; - } catch (final NoSuchMethodException ex) { - continue; - } - } - - try { - return getAnnotation( - c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()), - annotationClass); - } catch (final SecurityException ex) { - return null; - } catch (final NoSuchMethodException ex) { - return null; - } - } - - /** - * Searches the class hierarchy to see if the method or it's super - * implementations and interfaces has the annotation. Returns the depth of the - * annotation in the hierarchy. - * - * @param - * type of the annotation - * - * @param m - * method to check - * @param annotationClass - * annotation to look for - * @return Depth of the annotation or -1 if the annotation is not on the method. - */ - private static int getAnnotationDepth(final Method m, final Class annotationClass) { - // if we have invalid data the result is -1 - if (m == null || annotationClass == null) { - return -1; - } - - if (m.isAnnotationPresent(annotationClass)) { - return 1; - } - - // if we've already reached the Object class, return -1; - Class c = m.getDeclaringClass(); - if (c.getSuperclass() == null) { - return -1; - } - - // check directly implemented interfaces for the method being checked - for (Class i : c.getInterfaces()) { - try { - Method im = i.getMethod(m.getName(), m.getParameterTypes()); - int d = getAnnotationDepth(im, annotationClass); - if (d > 0) { - // since the annotation was on the interface, add 1 - return d + 1; - } - } catch (final SecurityException ex) { - continue; - } catch (final NoSuchMethodException ex) { - continue; - } - } - - try { - int d = getAnnotationDepth( - c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()), - annotationClass); - if (d > 0) { - // since the annotation was on the superclass, add 1 - return d + 1; - } - return -1; - } catch (final SecurityException ex) { - return -1; - } catch (final NoSuchMethodException ex) { - return -1; - } - } - - /** - * Put a key/boolean pair in the JSONObject. - * - * @param key - * A key string. - * @param value - * A boolean which is the value. - * @return this. - * @throws JSONException - * If the value is non-finite number. - * @throws NullPointerException - * If the key is null. - */ - public JSONObject put(String key, boolean value) throws JSONException { - return this.put(key, value ? Boolean.TRUE : Boolean.FALSE); - } - - /** - * Put a key/value pair in the JSONObject, where the value will be a - * JSONArray which is produced from a Collection. - * - * @param key - * A key string. - * @param value - * A Collection value. - * @return this. - * @throws JSONException - * If the value is non-finite number. - * @throws NullPointerException - * If the key is null. - */ - public JSONObject put(String key, Collection value) throws JSONException { - return this.put(key, new JSONArray(value)); - } - - /** - * Put a key/double pair in the JSONObject. - * - * @param key - * A key string. - * @param value - * A double which is the value. - * @return this. - * @throws JSONException - * If the value is non-finite number. - * @throws NullPointerException - * If the key is null. - */ - public JSONObject put(String key, double value) throws JSONException { - return this.put(key, Double.valueOf(value)); - } - - /** - * Put a key/float pair in the JSONObject. - * - * @param key - * A key string. - * @param value - * A float which is the value. - * @return this. - * @throws JSONException - * If the value is non-finite number. - * @throws NullPointerException - * If the key is null. - */ - public JSONObject put(String key, float value) throws JSONException { - return this.put(key, Float.valueOf(value)); - } - - /** - * Put a key/int pair in the JSONObject. - * - * @param key - * A key string. - * @param value - * An int which is the value. - * @return this. - * @throws JSONException - * If the value is non-finite number. - * @throws NullPointerException - * If the key is null. - */ - public JSONObject put(String key, int value) throws JSONException { - return this.put(key, Integer.valueOf(value)); - } - - /** - * Put a key/long pair in the JSONObject. - * - * @param key - * A key string. - * @param value - * A long which is the value. - * @return this. - * @throws JSONException - * If the value is non-finite number. - * @throws NullPointerException - * If the key is null. - */ - public JSONObject put(String key, long value) throws JSONException { - return this.put(key, Long.valueOf(value)); - } - - /** - * Put a key/value pair in the JSONObject, where the value will be a - * JSONObject which is produced from a Map. - * - * @param key - * A key string. - * @param value - * A Map value. - * @return this. - * @throws JSONException - * If the value is non-finite number. - * @throws NullPointerException - * If the key is null. - */ - public JSONObject put(String key, Map value) throws JSONException { - return this.put(key, new JSONObject(value)); - } - - /** - * Put a key/value pair in the JSONObject. If the value is null, then the - * key will be removed from the JSONObject if it is present. - * - * @param key - * A key string. - * @param value - * An object which is the value. It should be of one of these - * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, - * String, or the JSONObject.NULL object. - * @return this. - * @throws JSONException - * If the value is non-finite number. - * @throws NullPointerException - * If the key is null. - */ - public JSONObject put(String key, Object value) throws JSONException { - if (key == null) { - throw new NullPointerException("Null key."); - } - if (value != null) { - testValidity(value); - this.map.put(key, value); - } else { - this.remove(key); - } - return this; - } - - /** - * Put a key/value pair in the JSONObject, but only if the key and the value - * are both non-null, and only if there is not already a member with that - * name. - * - * @param key string - * @param value object - * @return this. - * @throws JSONException - * if the key is a duplicate - */ - public JSONObject putOnce(String key, Object value) throws JSONException { - if (key != null && value != null) { - if (this.opt(key) != null) { - throw new JSONException("Duplicate key \"" + key + "\""); - } - return this.put(key, value); - } - return this; - } - - /** - * Put a key/value pair in the JSONObject, but only if the key and the value - * are both non-null. - * - * @param key - * A key string. - * @param value - * An object which is the value. It should be of one of these - * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, - * String, or the JSONObject.NULL object. - * @return this. - * @throws JSONException - * If the value is a non-finite number. - */ - public JSONObject putOpt(String key, Object value) throws JSONException { - if (key != null && value != null) { - return this.put(key, value); - } - return this; - } - - /** - * Creates a JSONPointer using an initialization string and tries to - * match it to an item within this JSONObject. For example, given a - * JSONObject initialized with this document: - *

-     * {
-     *     "a":{"b":"c"}
-     * }
-     * 
- * and this JSONPointer string: - *
-     * "/a/b"
-     * 
- * Then this method will return the String "c". - * A JSONPointerException may be thrown from code called by this method. - * - * @param jsonPointer string that can be used to create a JSONPointer - * @return the item matched by the JSONPointer, otherwise null - */ - public Object query(String jsonPointer) { - return query(new JSONPointer(jsonPointer)); - } - /** - * Uses a user initialized JSONPointer and tries to - * match it to an item within this JSONObject. For example, given a - * JSONObject initialized with this document: - *
-     * {
-     *     "a":{"b":"c"}
-     * }
-     * 
- * and this JSONPointer: - *
-     * "/a/b"
-     * 
- * Then this method will return the String "c". - * A JSONPointerException may be thrown from code called by this method. - * - * @param jsonPointer string that can be used to create a JSONPointer - * @return the item matched by the JSONPointer, otherwise null - */ - public Object query(JSONPointer jsonPointer) { - return jsonPointer.queryFrom(this); - } - - /** - * Queries and returns a value from this object using {@code jsonPointer}, or - * returns null if the query fails due to a missing key. - * - * @param jsonPointer the string representation of the JSON pointer - * @return the queried value or {@code null} - * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax - */ - public Object optQuery(String jsonPointer) { - return optQuery(new JSONPointer(jsonPointer)); - } - - /** - * Queries and returns a value from this object using {@code jsonPointer}, or - * returns null if the query fails due to a missing key. - * - * @param jsonPointer The JSON pointer - * @return the queried value or {@code null} - * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax - */ - public Object optQuery(JSONPointer jsonPointer) { - try { - return jsonPointer.queryFrom(this); - } catch (JSONPointerException e) { - return null; - } - } - - /** - * Produce a string in double quotes with backslash sequences in all the - * right places. A backslash will be inserted within = '\u0080' && c < '\u00a0') - || (c >= '\u2000' && c < '\u2100')) { - w.write("\\u"); - hhhh = Integer.toHexString(c); - w.write("0000", 0, 4 - hhhh.length()); - w.write(hhhh); - } else { - w.write(c); - } - } - } - w.write('"'); - return w; - } - - /** - * Remove a name and its value, if present. - * - * @param key - * The name to be removed. - * @return The value that was associated with the name, or null if there was - * no value. - */ - public Object remove(String key) { - return this.map.remove(key); - } - - /** - * Determine if two JSONObjects are similar. - * They must contain the same set of names which must be associated with - * similar values. - * - * @param other The other JSONObject - * @return true if they are equal - */ - public boolean similar(Object other) { - try { - if (!(other instanceof JSONObject)) { - return false; - } - if (!this.keySet().equals(((JSONObject)other).keySet())) { - return false; - } - for (final Entry entry : this.entrySet()) { - String name = entry.getKey(); - Object valueThis = entry.getValue(); - Object valueOther = ((JSONObject)other).get(name); - if(valueThis == valueOther) { - continue; - } - if(valueThis == null) { - return false; - } - if (valueThis instanceof JSONObject) { - if (!((JSONObject)valueThis).similar(valueOther)) { - return false; - } - } else if (valueThis instanceof JSONArray) { - if (!((JSONArray)valueThis).similar(valueOther)) { - return false; - } - } else if (!valueThis.equals(valueOther)) { - return false; - } - } - return true; - } catch (Throwable exception) { - return false; - } - } - - /** - * Tests if the value should be tried as a decimal. It makes no test if there are actual digits. - * - * @param val value to test - * @return true if the string is "-0" or if it contains '.', 'e', or 'E', false otherwise. - */ - protected static boolean isDecimalNotation(final String val) { - return val.indexOf('.') > -1 || val.indexOf('e') > -1 - || val.indexOf('E') > -1 || "-0".equals(val); - } - - /** - * Converts a string to a number using the narrowest possible type. Possible - * returns for this function are BigDecimal, Double, BigInteger, Long, and Integer. - * When a Double is returned, it should always be a valid Double and not NaN or +-infinity. - * - * @param val value to convert - * @return Number representation of the value. - * @throws NumberFormatException thrown if the value is not a valid number. A public - * caller should catch this and wrap it in a {@link JSONException} if applicable. - */ - protected static Number stringToNumber(final String val) throws NumberFormatException { - char initial = val.charAt(0); - if ((initial >= '0' && initial <= '9') || initial == '-') { - // decimal representation - if (isDecimalNotation(val)) { - // quick dirty way to see if we need a BigDecimal instead of a Double - // this only handles some cases of overflow or underflow - if (val.length()>14) { - return new BigDecimal(val); - } - final Double d = Double.valueOf(val); - if (d.isInfinite() || d.isNaN()) { - // if we can't parse it as a double, go up to BigDecimal - // this is probably due to underflow like 4.32e-678 - // or overflow like 4.65e5324. The size of the string is small - // but can't be held in a Double. - return new BigDecimal(val); - } - return d; - } - // integer representation. - // This will narrow any values to the smallest reasonable Object representation - // (Integer, Long, or BigInteger) - - // string version - // The compare string length method reduces GC, - // but leads to smaller integers being placed in larger wrappers even though not - // needed. i.e. 1,000,000,000 -> Long even though it's an Integer - // 1,000,000,000,000,000,000 -> BigInteger even though it's a Long - //if(val.length()<=9){ - // return Integer.valueOf(val); - //} - //if(val.length()<=18){ - // return Long.valueOf(val); - //} - //return new BigInteger(val); - - // BigInteger version: We use a similar bitLenth compare as - // BigInteger#intValueExact uses. Increases GC, but objects hold - // only what they need. i.e. Less runtime overhead if the value is - // long lived. Which is the better tradeoff? This is closer to what's - // in stringToValue. - BigInteger bi = new BigInteger(val); - if(bi.bitLength()<=31){ - return Integer.valueOf(bi.intValue()); - } - if(bi.bitLength()<=63){ - return Long.valueOf(bi.longValue()); - } - return bi; - } - throw new NumberFormatException("val ["+val+"] is not a valid number."); - } - - /** - * Try to convert a string into a number, boolean, or null. If the string - * can't be converted, return the string. - * - * @param string - * A String. - * @return A simple JSON value. - */ - // Changes to this method must be copied to the corresponding method in - // the XML class to keep full support for Android - public static Object stringToValue(String string) { - if (string.equals("")) { - return string; - } - if (string.equalsIgnoreCase("true")) { - return Boolean.TRUE; - } - if (string.equalsIgnoreCase("false")) { - return Boolean.FALSE; - } - if (string.equalsIgnoreCase("null")) { - 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 { - // if we want full Big Number support this block can be replaced with: - // return stringToNumber(string); - if (isDecimalNotation(string)) { - Double d = Double.valueOf(string); - if (!d.isInfinite() && !d.isNaN()) { - return d; - } - } else { - Long myLong = Long.valueOf(string); - if (string.equals(myLong.toString())) { - if (myLong.longValue() == myLong.intValue()) { - return Integer.valueOf(myLong.intValue()); - } - return myLong; - } - } - } catch (Exception ignore) { - } - } - return string; - } - - /** - * Throw an exception if the object is a NaN or infinite number. - * - * @param o - * The object to test. - * @throws JSONException - * If o is a non-finite number. - */ - public static void testValidity(Object o) throws JSONException { - if (o != null) { - if (o instanceof Double) { - if (((Double) o).isInfinite() || ((Double) o).isNaN()) { - throw new JSONException( - "JSON does not allow non-finite numbers."); - } - } else if (o instanceof Float) { - if (((Float) o).isInfinite() || ((Float) o).isNaN()) { - throw new JSONException( - "JSON does not allow non-finite numbers."); - } - } - } - } - - /** - * Produce a JSONArray containing the values of the members of this - * JSONObject. - * - * @param names - * A JSONArray containing a list of key strings. This determines - * the sequence of the values in the result. - * @return A JSONArray of values. - * @throws JSONException - * If any of the values are non-finite numbers. - */ - public JSONArray toJSONArray(JSONArray names) throws JSONException { - if (names == null || names.isEmpty()) { - return null; - } - JSONArray ja = new JSONArray(); - for (int i = 0; i < names.length(); i += 1) { - ja.put(this.opt(names.getString(i))); - } - return ja; - } - - /** - * Make a JSON text of this JSONObject. For compactness, no whitespace is - * added. If this would not result in a syntactically correct JSON text, - * then null will be returned instead. - *

- * Warning: This method assumes that the data structure is acyclical. - * - * - * @return a printable, displayable, portable, transmittable representation - * of the object, beginning with { (left - * brace) and ending with } (right - * brace). - */ - @Override - public String toString() { - try { - return this.toString(0); - } catch (Exception e) { - return null; - } - } - - /** - * Make a pretty-printed JSON text of this JSONObject. - * - *

If indentFactor > 0 and the {@link JSONObject} - * has only one key, then the object will be output on a single line: - *

{@code {"key": 1}}
- * - *

If an object has 2 or more keys, then it will be output across - * multiple lines:

{
-     *  "key1": 1,
-     *  "key2": "value 2",
-     *  "key3": 3
-     * }
- *

- * Warning: This method assumes that the data structure is acyclical. - * - * - * @param indentFactor - * The number of spaces to add to each level of indentation. - * @return a printable, displayable, portable, transmittable representation - * of the object, beginning with { (left - * brace) and ending with } (right - * brace). - * @throws JSONException - * If the object contains an invalid number. - */ - public String toString(int indentFactor) throws JSONException { - StringWriter w = new StringWriter(); - synchronized (w.getBuffer()) { - return this.write(w, indentFactor, 0).toString(); - } - } - - /** - * Make a JSON text of an Object value. If the object has an - * value.toJSONString() method, then that method will be used to produce the - * JSON text. The method is required to produce a strictly conforming text. - * If the object does not contain a toJSONString method (which is the most - * common case), then a text will be produced by other means. If the value - * is an array or Collection, then a JSONArray will be made from it and its - * toJSONString method will be called. If the value is a MAP, then a - * JSONObject will be made from it and its toJSONString method will be - * called. Otherwise, the value's toString method will be called, and the - * result will be quoted. - * - *

- * Warning: This method assumes that the data structure is acyclical. - * - * @param value - * The value to be serialized. - * @return a printable, displayable, transmittable representation of the - * object, beginning with { (left - * brace) and ending with } (right - * brace). - * @throws JSONException - * If the value is or contains an invalid number. - */ - public static String valueToString(Object value) throws JSONException { - // moves the implementation to JSONWriter as: - // 1. It makes more sense to be part of the writer class - // 2. For Android support this method is not available. By implementing it in the Writer - // Android users can use the writer with the built in Android JSONObject implementation. - return JSONWriter.valueToString(value); - } - - /** - * Wrap an object, if necessary. If the object is null, return the NULL - * object. If it is an array or collection, wrap it in a JSONArray. If it is - * a map, wrap it in a JSONObject. If it is a standard property (Double, - * String, et al) then it is already wrapped. Otherwise, if it comes from - * one of the java packages, turn it into a string. And if it doesn't, try - * to wrap it in a JSONObject. If the wrapping fails, then null is returned. - * - * @param object - * The object to wrap - * @return The wrapped value - */ - public static Object wrap(Object object) { - try { - if (object == null) { - return NULL; - } - if (object instanceof JSONObject || object instanceof JSONArray - || NULL.equals(object) || object instanceof JSONString - || object instanceof Byte || object instanceof Character - || object instanceof Short || object instanceof Integer - || object instanceof Long || object instanceof Boolean - || object instanceof Float || object instanceof Double - || object instanceof String || object instanceof BigInteger - || object instanceof BigDecimal || object instanceof Enum) { - return object; - } - - if (object instanceof Collection) { - Collection coll = (Collection) object; - return new JSONArray(coll); - } - if (object.getClass().isArray()) { - return new JSONArray(object); - } - if (object instanceof Map) { - Map map = (Map) object; - return new JSONObject(map); - } - Package objectPackage = object.getClass().getPackage(); - String objectPackageName = objectPackage != null ? objectPackage - .getName() : ""; - if (objectPackageName.startsWith("java.") - || objectPackageName.startsWith("javax.") - || object.getClass().getClassLoader() == null) { - return object.toString(); - } - return new JSONObject(object); - } catch (Exception exception) { - return null; - } - } - - /** - * Write the contents of the JSONObject as JSON text to a writer. For - * compactness, no whitespace is added. - *

- * Warning: This method assumes that the data structure is acyclical. - * - * - * @return The writer. - * @throws JSONException - */ - public Writer write(Writer writer) throws JSONException { - return this.write(writer, 0, 0); - } - - static final Writer writeValue(Writer writer, Object value, - int indentFactor, int indent) throws JSONException, IOException { - if (value == null || value.equals(null)) { - writer.write("null"); - } else if (value instanceof JSONString) { - Object o; - try { - o = ((JSONString) value).toJSONString(); - } catch (Exception e) { - throw new JSONException(e); - } - writer.write(o != null ? o.toString() : quote(value.toString())); - } else if (value instanceof Number) { - // not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary - final String numberAsString = numberToString((Number) value); - try { - // Use the BigDecimal constructor for its parser to validate the format. - @SuppressWarnings("unused") - BigDecimal testNum = new BigDecimal(numberAsString); - // Close enough to a JSON number that we will use it unquoted - writer.write(numberAsString); - } catch (NumberFormatException ex){ - // The Number value is not a valid JSON number. - // Instead we will quote it as a string - quote(numberAsString, writer); - } - } else if (value instanceof Boolean) { - writer.write(value.toString()); - } else if (value instanceof Enum) { - writer.write(quote(((Enum)value).name())); - } else if (value instanceof JSONObject) { - ((JSONObject) value).write(writer, indentFactor, indent); - } else if (value instanceof JSONArray) { - ((JSONArray) value).write(writer, indentFactor, indent); - } else if (value instanceof Map) { - Map map = (Map) value; - new JSONObject(map).write(writer, indentFactor, indent); - } else if (value instanceof Collection) { - Collection coll = (Collection) value; - new JSONArray(coll).write(writer, indentFactor, indent); - } else if (value.getClass().isArray()) { - new JSONArray(value).write(writer, indentFactor, indent); - } else { - quote(value.toString(), writer); - } - return writer; - } - - static final void indent(Writer writer, int indent) throws IOException { - for (int i = 0; i < indent; i += 1) { - writer.write(' '); - } - } - - /** - * Write the contents of the JSONObject as JSON text to a writer. - * - *

If indentFactor > 0 and the {@link JSONObject} - * has only one key, then the object will be output on a single line: - *

{@code {"key": 1}}
- * - *

If an object has 2 or more keys, then it will be output across - * multiple lines:

{
-     *  "key1": 1,
-     *  "key2": "value 2",
-     *  "key3": 3
-     * }
- *

- * Warning: This method assumes that the data structure is acyclical. - * - * - * @param writer - * Writes the serialized JSON - * @param indentFactor - * The number of spaces to add to each level of indentation. - * @param indent - * The indentation of the top level. - * @return The writer. - * @throws JSONException - */ - public Writer write(Writer writer, int indentFactor, int indent) - throws JSONException { - try { - boolean commanate = false; - final int length = this.length(); - writer.write('{'); - - if (length == 1) { - final Entry entry = this.entrySet().iterator().next(); - final String key = entry.getKey(); - writer.write(quote(key)); - writer.write(':'); - if (indentFactor > 0) { - writer.write(' '); - } - try{ - writeValue(writer, entry.getValue(), indentFactor, indent); - } catch (Exception e) { - throw new JSONException("Unable to write JSONObject value for key: " + key, e); - } - } else if (length != 0) { - final int newindent = indent + indentFactor; - for (final Entry entry : this.entrySet()) { - if (commanate) { - writer.write(','); - } - if (indentFactor > 0) { - writer.write('\n'); - } - indent(writer, newindent); - final String key = entry.getKey(); - writer.write(quote(key)); - writer.write(':'); - if (indentFactor > 0) { - writer.write(' '); - } - try { - writeValue(writer, entry.getValue(), indentFactor, newindent); - } catch (Exception e) { - throw new JSONException("Unable to write JSONObject value for key: " + key, e); - } - commanate = true; - } - if (indentFactor > 0) { - writer.write('\n'); - } - indent(writer, indent); - } - writer.write('}'); - return writer; - } catch (IOException exception) { - throw new JSONException(exception); - } - } - - /** - * 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 - * be converted. - *

- * Warning: This method assumes that the data structure is acyclical. - * - * @return a java.util.Map containing the entries of this object - */ - public Map toMap() { - Map results = new HashMap(); - for (Entry entry : this.entrySet()) { - Object value; - if (entry.getValue() == null || NULL.equals(entry.getValue())) { - value = null; - } else if (entry.getValue() instanceof JSONObject) { - value = ((JSONObject) entry.getValue()).toMap(); - } else if (entry.getValue() instanceof JSONArray) { - value = ((JSONArray) entry.getValue()).toList(); - } else { - value = entry.getValue(); - } - results.put(entry.getKey(), value); - } - return results; - } -} diff --git a/JSONPointer.java b/JSONPointer.java deleted file mode 100644 index fc0b04b7c..000000000 --- a/JSONPointer.java +++ /dev/null @@ -1,293 +0,0 @@ -package org.json; - -import static java.lang.String.format; - -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/* -Copyright (c) 2002 JSON.org - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -The Software shall be used for Good, not Evil. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -/** - * A JSON Pointer is a simple query language defined for JSON documents by - * RFC 6901. - * - * In a nutshell, JSONPointer allows the user to navigate into a JSON document - * using strings, and retrieve targeted objects, like a simple form of XPATH. - * Path segments are separated by the '/' char, which signifies the root of - * the document when it appears as the first char of the string. Array - * elements are navigated using ordinals, counting from 0. JSONPointer strings - * may be extended to any arbitrary number of segments. If the navigation - * is successful, the matched item is returned. A matched item may be a - * JSONObject, a JSONArray, or a JSON value. If the JSONPointer string building - * fails, an appropriate exception is thrown. If the navigation fails to find - * a match, a JSONPointerException is thrown. - * - * @author JSON.org - * @version 2016-05-14 - */ -public class JSONPointer { - - // used for URL encoding and decoding - private static final String ENCODING = "utf-8"; - - /** - * This class allows the user to build a JSONPointer in steps, using - * exactly one segment in each step. - */ - public static class Builder { - - // Segments for the eventual JSONPointer string - private final List refTokens = new ArrayList(); - - /** - * Creates a {@code JSONPointer} instance using the tokens previously set using the - * {@link #append(String)} method calls. - */ - public JSONPointer build() { - return new JSONPointer(this.refTokens); - } - - /** - * Adds an arbitrary token to the list of reference tokens. It can be any non-null value. - * - * Unlike in the case of JSON string or URI fragment representation of JSON pointers, the - * argument of this method MUST NOT be escaped. If you want to query the property called - * {@code "a~b"} then you should simply pass the {@code "a~b"} string as-is, there is no - * need to escape it as {@code "a~0b"}. - * - * @param token the new token to be appended to the list - * @return {@code this} - * @throws NullPointerException if {@code token} is null - */ - public Builder append(String token) { - if (token == null) { - throw new NullPointerException("token cannot be null"); - } - this.refTokens.add(token); - return this; - } - - /** - * Adds an integer to the reference token list. Although not necessarily, mostly this token will - * denote an array index. - * - * @param arrayIndex the array index to be added to the token list - * @return {@code this} - */ - public Builder append(int arrayIndex) { - this.refTokens.add(String.valueOf(arrayIndex)); - return this; - } - } - - /** - * Static factory method for {@link Builder}. Example usage: - * - *


-     * JSONPointer pointer = JSONPointer.builder()
-     *       .append("obj")
-     *       .append("other~key").append("another/key")
-     *       .append("\"")
-     *       .append(0)
-     *       .build();
-     * 
- * - * @return a builder instance which can be used to construct a {@code JSONPointer} instance by chained - * {@link Builder#append(String)} calls. - */ - public static Builder builder() { - return new Builder(); - } - - // Segments for the JSONPointer string - private final List refTokens; - - /** - * Pre-parses and initializes a new {@code JSONPointer} instance. If you want to - * evaluate the same JSON Pointer on different JSON documents then it is recommended - * to keep the {@code JSONPointer} instances due to performance considerations. - * - * @param pointer the JSON String or URI Fragment representation of the JSON pointer. - * @throws IllegalArgumentException if {@code pointer} is not a valid JSON pointer - */ - public JSONPointer(final String pointer) { - if (pointer == null) { - throw new NullPointerException("pointer cannot be null"); - } - if (pointer.isEmpty() || pointer.equals("#")) { - this.refTokens = Collections.emptyList(); - return; - } - String refs; - if (pointer.startsWith("#/")) { - refs = pointer.substring(2); - try { - refs = URLDecoder.decode(refs, ENCODING); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } else if (pointer.startsWith("/")) { - refs = pointer.substring(1); - } else { - throw new IllegalArgumentException("a JSON pointer should start with '/' or '#/'"); - } - this.refTokens = new ArrayList(); - int slashIdx = -1; - int prevSlashIdx = 0; - do { - prevSlashIdx = slashIdx + 1; - slashIdx = refs.indexOf('/', prevSlashIdx); - if(prevSlashIdx == slashIdx || prevSlashIdx == refs.length()) { - // found 2 slashes in a row ( obj//next ) - // or single slash at the end of a string ( obj/test/ ) - this.refTokens.add(""); - } else if (slashIdx >= 0) { - final String token = refs.substring(prevSlashIdx, slashIdx); - this.refTokens.add(unescape(token)); - } else { - // last item after separator, or no separator at all. - final String token = refs.substring(prevSlashIdx); - this.refTokens.add(unescape(token)); - } - } while (slashIdx >= 0); - // using split does not take into account consecutive separators or "ending nulls" - //for (String token : refs.split("/")) { - // this.refTokens.add(unescape(token)); - //} - } - - public JSONPointer(List refTokens) { - this.refTokens = new ArrayList(refTokens); - } - - private String unescape(String token) { - return token.replace("~1", "/").replace("~0", "~") - .replace("\\\"", "\"") - .replace("\\\\", "\\"); - } - - /** - * Evaluates this JSON Pointer on the given {@code document}. The {@code document} - * is usually a {@link JSONObject} or a {@link JSONArray} instance, but the empty - * JSON Pointer ({@code ""}) can be evaluated on any JSON values and in such case the - * returned value will be {@code document} itself. - * - * @param document the JSON document which should be the subject of querying. - * @return the result of the evaluation - * @throws JSONPointerException if an error occurs during evaluation - */ - public Object queryFrom(Object document) throws JSONPointerException { - if (this.refTokens.isEmpty()) { - return document; - } - Object current = document; - for (String token : this.refTokens) { - if (current instanceof JSONObject) { - current = ((JSONObject) current).opt(unescape(token)); - } else if (current instanceof JSONArray) { - current = readByIndexToken(current, token); - } else { - throw new JSONPointerException(format( - "value [%s] is not an array or object therefore its key %s cannot be resolved", current, - token)); - } - } - return current; - } - - /** - * Matches a JSONArray element by ordinal position - * @param current the JSONArray to be evaluated - * @param indexToken the array index in string form - * @return the matched object. If no matching item is found a - * @throws JSONPointerException is thrown if the index is out of bounds - */ - private Object readByIndexToken(Object current, String indexToken) throws JSONPointerException { - try { - int index = Integer.parseInt(indexToken); - JSONArray currentArr = (JSONArray) current; - if (index >= currentArr.length()) { - throw new JSONPointerException(format("index %d is out of bounds - the array has %d elements", index, - currentArr.length())); - } - try { - return currentArr.get(index); - } catch (JSONException e) { - throw new JSONPointerException("Error reading value at index position " + index, e); - } - } catch (NumberFormatException e) { - throw new JSONPointerException(format("%s is not an array index", indexToken), e); - } - } - - /** - * Returns a string representing the JSONPointer path value using string - * representation - */ - @Override - public String toString() { - StringBuilder rval = new StringBuilder(""); - for (String token: this.refTokens) { - rval.append('/').append(escape(token)); - } - return rval.toString(); - } - - /** - * Escapes path segment values to an unambiguous form. - * The escape char to be inserted is '~'. The chars to be escaped - * are ~, which maps to ~0, and /, which maps to ~1. Backslashes - * and double quote chars are also escaped. - * @param token the JSONPointer segment value to be escaped - * @return the escaped value for the token - */ - private String escape(String token) { - return token.replace("~", "~0") - .replace("/", "~1") - .replace("\\", "\\\\") - .replace("\"", "\\\""); - } - - /** - * Returns a string representing the JSONPointer path value using URI - * fragment identifier representation - */ - public String toURIFragment() { - try { - StringBuilder rval = new StringBuilder("#"); - for (String token : this.refTokens) { - rval.append('/').append(URLEncoder.encode(token, ENCODING)); - } - return rval.toString(); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/JSONTokener.java b/JSONTokener.java deleted file mode 100644 index 36bce45c2..000000000 --- a/JSONTokener.java +++ /dev/null @@ -1,529 +0,0 @@ -package org.json; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.StringReader; - -/* -Copyright (c) 2002 JSON.org - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -The Software shall be used for Good, not Evil. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - */ - -/** - * A JSONTokener takes a source string and extracts characters and tokens from - * it. It is used by the JSONObject and JSONArray constructors to parse - * JSON source strings. - * @author JSON.org - * @version 2014-05-03 - */ -public class JSONTokener { - /** current read character position on the current line. */ - private long character; - /** flag to indicate if the end of the input has been found. */ - private boolean eof; - /** current read index of the input. */ - private long index; - /** current line of the input. */ - private long line; - /** previous character read from the input. */ - private char previous; - /** Reader for the input. */ - private final Reader reader; - /** flag to indicate that a previous character was requested. */ - private boolean usePrevious; - /** the number of characters read in the previous line. */ - private long characterPreviousLine; - - - /** - * Construct a JSONTokener from a Reader. The caller must close the Reader. - * - * @param reader A reader. - */ - public JSONTokener(Reader reader) { - this.reader = reader.markSupported() - ? reader - : new BufferedReader(reader); - this.eof = false; - this.usePrevious = false; - this.previous = 0; - this.index = 0; - this.character = 1; - this.characterPreviousLine = 0; - 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)); - } - - - /** - * Construct a JSONTokener from a string. - * - * @param s A source string. - */ - public JSONTokener(String s) { - this(new StringReader(s)); - } - - - /** - * Back up one character. This provides a sort of lookahead capability, - * so that you can test for a digit or letter before attempting to parse - * the next number or identifier. - * @throws JSONException Thrown if trying to step back more than 1 step - * or if already at the start of the string - */ - public void back() throws JSONException { - if (this.usePrevious || this.index <= 0) { - throw new JSONException("Stepping back two steps is not supported"); - } - this.decrementIndexes(); - this.usePrevious = true; - this.eof = false; - } - - /** - * Decrements the indexes for the {@link #back()} method based on the previous character read. - */ - private void decrementIndexes() { - this.index--; - if(this.previous=='\r' || this.previous == '\n') { - this.line--; - this.character=this.characterPreviousLine ; - } else if(this.character > 0){ - this.character--; - } - } - - /** - * Get the hex value of a character (base16). - * @param c A character between '0' and '9' or between 'A' and 'F' or - * between 'a' and 'f'. - * @return An int between 0 and 15, or -1 if c was not a hex digit. - */ - public static int dehexchar(char c) { - if (c >= '0' && c <= '9') { - return c - '0'; - } - if (c >= 'A' && c <= 'F') { - return c - ('A' - 10); - } - if (c >= 'a' && c <= 'f') { - return c - ('a' - 10); - } - return -1; - } - - /** - * 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() { - return this.eof && !this.usePrevious; - } - - - /** - * Determine if the source string still contains characters that next() - * can consume. - * @return true if not yet at the end of the source. - * @throws JSONException thrown if there is an error stepping forward - * or backward while checking for more data. - */ - public boolean more() throws JSONException { - if(this.usePrevious) { - return true; - } - try { - this.reader.mark(1); - } catch (IOException e) { - throw new JSONException("Unable to preserve stream position", e); - } - try { - // -1 is EOF, but next() can not consume the null character '\0' - if(this.reader.read() <= 0) { - this.eof = true; - return false; - } - this.reader.reset(); - } catch (IOException e) { - throw new JSONException("Unable to read the next character from the stream", e); - } - return true; - } - - - /** - * Get the next character in the source string. - * - * @return The next character, or 0 if past the end of the source string. - * @throws JSONException Thrown if there is an error reading the source string. - */ - public char next() throws JSONException { - int c; - if (this.usePrevious) { - this.usePrevious = false; - c = this.previous; - } else { - try { - c = this.reader.read(); - } catch (IOException exception) { - throw new JSONException(exception); - } - } - if (c <= 0) { // End of stream - this.eof = true; - return 0; - } - this.incrementIndexes(c); - this.previous = (char) c; - return this.previous; - } - - /** - * Increments the internal indexes according to the previous character - * read and the character passed as the current character. - * @param c the current character read. - */ - private void incrementIndexes(int c) { - if(c > 0) { - this.index++; - if(c=='\r') { - this.line++; - this.characterPreviousLine = this.character; - this.character=0; - }else if (c=='\n') { - if(this.previous != '\r') { - this.line++; - this.characterPreviousLine = this.character; - } - this.character=0; - } else { - this.character++; - } - } - } - - /** - * Consume the next character, and check that it matches a specified - * character. - * @param c The character to match. - * @return The character. - * @throws JSONException if the character does not match. - */ - public char next(char c) throws JSONException { - char n = this.next(); - if (n != c) { - if(n > 0) { - throw this.syntaxError("Expected '" + c + "' and instead saw '" + - n + "'"); - } - throw this.syntaxError("Expected '" + c + "' and instead saw ''"); - } - return n; - } - - - /** - * Get the next n characters. - * - * @param n The number of characters to take. - * @return A string of n characters. - * @throws JSONException - * Substring bounds error if there are not - * n characters remaining in the source string. - */ - public String next(int n) throws JSONException { - if (n == 0) { - return ""; - } - - char[] chars = new char[n]; - int pos = 0; - - while (pos < n) { - chars[pos] = this.next(); - if (this.end()) { - throw this.syntaxError("Substring bounds error"); - } - pos += 1; - } - return new String(chars); - } - - - /** - * Get the next char in the string, skipping whitespace. - * @throws JSONException Thrown if there is an error reading the source string. - * @return A character, or 0 if there are no more characters. - */ - public char nextClean() throws JSONException { - for (;;) { - char c = this.next(); - if (c == 0 || c > ' ') { - return c; - } - } - } - - - /** - * Return the characters up to the next close quote character. - * Backslash processing is done. The formal JSON format does not - * allow strings in single quotes, but an implementation is allowed to - * accept them. - * @param quote The quoting character, either - * " (double quote) or - * ' (single quote). - * @return A String. - * @throws JSONException Unterminated string. - */ - public String nextString(char quote) throws JSONException { - char c; - StringBuilder sb = new StringBuilder(); - for (;;) { - c = this.next(); - switch (c) { - case 0: - case '\n': - case '\r': - throw this.syntaxError("Unterminated string"); - case '\\': - c = this.next(); - switch (c) { - case 'b': - sb.append('\b'); - break; - case 't': - sb.append('\t'); - break; - case 'n': - sb.append('\n'); - break; - case 'f': - sb.append('\f'); - break; - case 'r': - sb.append('\r'); - break; - case 'u': - try { - sb.append((char)Integer.parseInt(this.next(4), 16)); - } catch (NumberFormatException e) { - throw this.syntaxError("Illegal escape.", e); - } - break; - case '"': - case '\'': - case '\\': - case '/': - sb.append(c); - break; - default: - throw this.syntaxError("Illegal escape."); - } - break; - default: - if (c == quote) { - return sb.toString(); - } - sb.append(c); - } - } - } - - - /** - * Get the text up but not including the specified character or the - * end of line, whichever comes first. - * @param delimiter A delimiter character. - * @return A string. - * @throws JSONException Thrown if there is an error while searching - * for the delimiter - */ - public String nextTo(char delimiter) throws JSONException { - StringBuilder sb = new StringBuilder(); - for (;;) { - char c = this.next(); - if (c == delimiter || c == 0 || c == '\n' || c == '\r') { - if (c != 0) { - this.back(); - } - return sb.toString().trim(); - } - sb.append(c); - } - } - - - /** - * Get the text up but not including one of the specified delimiter - * characters or the end of line, whichever comes first. - * @param delimiters A set of delimiter characters. - * @return A string, trimmed. - * @throws JSONException Thrown if there is an error while searching - * for the delimiter - */ - public String nextTo(String delimiters) throws JSONException { - char c; - StringBuilder sb = new StringBuilder(); - for (;;) { - c = this.next(); - if (delimiters.indexOf(c) >= 0 || c == 0 || - c == '\n' || c == '\r') { - if (c != 0) { - this.back(); - } - return sb.toString().trim(); - } - sb.append(c); - } - } - - - /** - * Get the next value. The value can be a Boolean, Double, Integer, - * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object. - * @throws JSONException If syntax error. - * - * @return An object. - */ - public Object nextValue() throws JSONException { - char c = this.nextClean(); - String string; - - switch (c) { - case '"': - case '\'': - return this.nextString(c); - case '{': - this.back(); - return new JSONObject(this); - case '[': - this.back(); - return new JSONArray(this); - } - - /* - * Handle unquoted text. This could be the values true, false, or - * null, or it can be a number. An implementation (such as this one) - * is allowed to also accept non-standard forms. - * - * Accumulate characters until we reach the end of the text or a - * formatting character. - */ - - StringBuilder sb = new StringBuilder(); - while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { - sb.append(c); - c = this.next(); - } - this.back(); - - string = sb.toString().trim(); - if ("".equals(string)) { - throw this.syntaxError("Missing value"); - } - return JSONObject.stringToValue(string); - } - - - /** - * Skip characters until the next character is the requested character. - * If the requested character is not found, no characters are skipped. - * @param to A character to skip to. - * @return The requested character, or zero if the requested character - * is not found. - * @throws JSONException Thrown if there is an error while searching - * for the to character - */ - public char skipTo(char to) throws JSONException { - char c; - try { - long startIndex = this.index; - long startCharacter = this.character; - long startLine = this.line; - this.reader.mark(1000000); - do { - c = this.next(); - if (c == 0) { - // in some readers, reset() may throw an exception if - // the remaining portion of the input is greater than - // the mark size (1,000,000 above). - this.reader.reset(); - this.index = startIndex; - this.character = startCharacter; - this.line = startLine; - return 0; - } - } while (c != to); - this.reader.mark(1); - } catch (IOException exception) { - throw new JSONException(exception); - } - this.back(); - return c; - } - - /** - * Make a JSONException to signal a syntax error. - * - * @param message The error message. - * @return A JSONException object, suitable for throwing - */ - public JSONException syntaxError(String message) { - return new JSONException(message + this.toString()); - } - - /** - * Make a JSONException to signal a syntax error. - * - * @param message The error message. - * @param causedBy The throwable that caused the error. - * @return A JSONException object, suitable for throwing - */ - public JSONException syntaxError(String message, Throwable causedBy) { - return new JSONException(message + this.toString(), causedBy); - } - - /** - * Make a printable string of this JSONTokener. - * - * @return " at {index} [character {character} line {line}]" - */ - @Override - public String toString() { - return " at " + this.index + " [character " + this.character + " line " + - this.line + "]"; - } -} diff --git a/README.md b/README.md index c7ecb243e..438225e1d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,15 @@ JSON in Java [package org.json] =============================== -[![Maven Central](https://img.shields.io/maven-central/v/org.json/json.svg)](https://mvnrepository.com/artifact/org.json/json) +This is a fork of the JSON project in github (https://github.com/stleary/JSON-java) for the 20180813 version. + +Compared to this implementation: +- this is a Netbeans project +- JSONObject and JSONArray both have an equals and hashCode method +- a FileUtils class with static method has been added to simply the generation to/from a File / a JSON object +- The supported Java version is Java 8 +- JSONObject and JSONArray implements a new JSONElement interface +- there is a new JSONOptions class which allows to configure the reading and the writing of JSON content JSON is a light-weight, language independent, data interchange format. See http://www.JSON.org/ @@ -18,7 +26,7 @@ The license includes this restriction: "The software shall be used for good, not evil." If your conscience cannot live with that, then choose a different package. -The package compiles on Java 1.6-1.8. +The package compiles on Java 1.8. **JSONObject.java**: The `JSONObject` can parse text from a `String` or a `JSONTokener` @@ -91,9 +99,6 @@ This package fully supports `Integer`, `Long`, and `Double` Java types. Partial for `BigInteger` and `BigDecimal` values in `JSONObject` and `JSONArray` objects is provided in the form of `get()`, `opt()`, and `put()` API methods. -Although 1.6 compatibility is currently supported, it is not a project goal and may be -removed in some future release. - In compliance with RFC7159 page 10 section 9, the parser is more lax with what is valid JSON than the Generator. For Example, the tab character (U+0009) is allowed when reading JSON Text strings, but when output by the Generator, tab is properly converted to \t in @@ -101,36 +106,3 @@ the string. Other instances may occur where reading invalid JSON text does not c error to be generated. Malformed JSON Texts such as missing end " (quote) on strings or invalid number formats (1.2e6.3) will cause errors as such documents can not be read reliably. - -Release history: - -~~~ -20180813 POM change to include Automatic-Module-Name (#431) - -20180130 Recent commits - -20171018 Checkpoint for recent commits. - -20170516 Roll up recent commits. - -20160810 Revert code that was breaking opt*() methods. - -20160807 This release contains a bug in the JSONObject.opt*() and JSONArray.opt*() methods, -it is not recommended for use. -Java 1.6 compatability fixed, JSONArray.toList() and JSONObject.toMap(), -RFC4180 compatibility, JSONPointer, some exception fixes, optional XML type conversion. -Contains the latest code as of 7 Aug, 2016 - -20160212 Java 1.6 compatibility, OSGi bundle. Contains the latest code as of 12 Feb, 2016. - -20151123 JSONObject and JSONArray initialization with generics. Contains the -latest code as of 23 Nov, 2015. - -20150729 Checkpoint for Maven central repository release. Contains the latest code -as of 29 July, 2015. -~~~ - - -JSON-java releases can be found by searching the Maven repository for groupId "org.json" -and artifactId "json". For example: -https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.json%22%20AND%20a%3A%22json%22 diff --git a/XML.java b/XML.java deleted file mode 100644 index 55362b274..000000000 --- a/XML.java +++ /dev/null @@ -1,683 +0,0 @@ -package org.json; - -/* -Copyright (c) 2015 JSON.org - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -The Software shall be used for Good, not Evil. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -import java.io.Reader; -import java.io.StringReader; -import java.util.Iterator; - -/** - * This provides static methods to convert an XML text into a JSONObject, and to - * covert a JSONObject into an XML text. - * - * @author JSON.org - * @version 2016-08-10 - */ -@SuppressWarnings("boxing") -public class XML { - /** The Character '&'. */ - public static final Character AMP = '&'; - - /** The Character '''. */ - public static final Character APOS = '\''; - - /** The Character '!'. */ - public static final Character BANG = '!'; - - /** The Character '='. */ - public static final Character EQ = '='; - - /** The Character '>'. */ - public static final Character GT = '>'; - - /** The Character '<'. */ - public static final Character LT = '<'; - - /** The Character '?'. */ - public static final Character QUEST = '?'; - - /** The Character '"'. */ - public static final Character QUOT = '"'; - - /** The Character '/'. */ - public static final Character SLASH = '/'; - - /** - * Creates an iterator for navigating Code Points in a string instead of - * characters. Once Java7 support is dropped, this can be replaced with - * - * string.codePoints() - * - * which is available in Java8 and above. - * - * @see http://stackoverflow.com/a/21791059/6030888 - */ - private static Iterable codePointIterator(final String string) { - return new Iterable() { - @Override - public Iterator iterator() { - return new Iterator() { - private int nextIndex = 0; - private int length = string.length(); - - @Override - public boolean hasNext() { - return this.nextIndex < this.length; - } - - @Override - public Integer next() { - int result = string.codePointAt(this.nextIndex); - this.nextIndex += Character.charCount(result); - return result; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - }; - } - - /** - * Replace special characters with XML escapes: - * - *
-     * & (ampersand) is replaced by &amp;
-     * < (less than) is replaced by &lt;
-     * > (greater than) is replaced by &gt;
-     * " (double quote) is replaced by &quot;
-     * ' (single quote / apostrophe) is replaced by &apos;
-     * 
- * - * @param string - * The string to be escaped. - * @return The escaped string. - */ - public static String escape(String string) { - StringBuilder sb = new StringBuilder(string.length()); - for (final int cp : codePointIterator(string)) { - switch (cp) { - case '&': - sb.append("&"); - break; - case '<': - sb.append("<"); - break; - case '>': - sb.append(">"); - break; - case '"': - sb.append("""); - break; - case '\'': - sb.append("'"); - break; - default: - if (mustEscape(cp)) { - sb.append("&#x"); - sb.append(Integer.toHexString(cp)); - sb.append(';'); - } else { - sb.appendCodePoint(cp); - } - } - } - return sb.toString(); - } - - /** - * @param cp code point to test - * @return true if the code point is not valid for an XML - */ - private static boolean mustEscape(int cp) { - /* Valid range from https://www.w3.org/TR/REC-xml/#charsets - * - * #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] - * - * any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. - */ - // isISOControl is true when (cp >= 0 && cp <= 0x1F) || (cp >= 0x7F && cp <= 0x9F) - // all ISO control characters are out of range except tabs and new lines - return (Character.isISOControl(cp) - && cp != 0x9 - && cp != 0xA - && cp != 0xD - ) || !( - // valid the range of acceptable characters that aren't control - (cp >= 0x20 && cp <= 0xD7FF) - || (cp >= 0xE000 && cp <= 0xFFFD) - || (cp >= 0x10000 && cp <= 0x10FFFF) - ) - ; - } - - /** - * Removes XML escapes from the string. - * - * @param string - * string to remove escapes from - * @return string with converted entities - */ - public static String unescape(String string) { - StringBuilder sb = new StringBuilder(string.length()); - for (int i = 0, length = string.length(); i < length; i++) { - char c = string.charAt(i); - if (c == '&') { - final int semic = string.indexOf(';', i); - if (semic > i) { - final String entity = string.substring(i + 1, semic); - sb.append(XMLTokener.unescapeEntity(entity)); - // skip past the entity we just parsed. - i += entity.length() + 1; - } else { - // this shouldn't happen in most cases since the parser - // errors on unclosed entries. - sb.append(c); - } - } else { - // not part of an entity - sb.append(c); - } - } - return sb.toString(); - } - - /** - * Throw an exception if the string contains whitespace. Whitespace is not - * allowed in tagNames and attributes. - * - * @param string - * A string. - * @throws JSONException Thrown if the string contains whitespace or is empty. - */ - public static void noSpace(String string) throws JSONException { - int i, length = string.length(); - if (length == 0) { - throw new JSONException("Empty string."); - } - for (i = 0; i < length; i += 1) { - if (Character.isWhitespace(string.charAt(i))) { - throw new JSONException("'" + string - + "' contains a space character."); - } - } - } - - /** - * Scan the content following the named tag, attaching it to the context. - * - * @param x - * The XMLTokener containing the source string. - * @param context - * The JSONObject that will include the new material. - * @param name - * The tag name. - * @return true if the close tag is processed. - * @throws JSONException - */ - private static boolean parse(XMLTokener x, JSONObject context, String name, boolean keepStrings) - throws JSONException { - char c; - int i; - JSONObject jsonobject = null; - String string; - String tagName; - Object token; - - // Test for and skip past these forms: - // - // - // - // - // Report errors for these forms: - // <> - // <= - // << - - token = x.nextToken(); - - // "); - return false; - } - x.back(); - } else if (c == '[') { - token = x.nextToken(); - if ("CDATA".equals(token)) { - if (x.next() == '[') { - string = x.nextCDATA(); - if (string.length() > 0) { - context.accumulate("content", string); - } - return false; - } - } - throw x.syntaxError("Expected 'CDATA['"); - } - i = 1; - do { - token = x.nextMeta(); - if (token == null) { - throw x.syntaxError("Missing '>' after ' 0); - return false; - } else if (token == QUEST) { - - // "); - return false; - } else if (token == SLASH) { - - // Close tag - if (x.nextToken() != GT) { - throw x.syntaxError("Misshaped tag"); - } - if (jsonobject.length() > 0) { - context.accumulate(tagName, jsonobject); - } else { - context.accumulate(tagName, ""); - } - return false; - - } else if (token == GT) { - // Content, between <...> and - for (;;) { - token = x.nextContent(); - if (token == null) { - if (tagName != null) { - throw x.syntaxError("Unclosed tag " + tagName); - } - return false; - } else if (token instanceof String) { - string = (String) token; - if (string.length() > 0) { - jsonobject.accumulate("content", - keepStrings ? string : stringToValue(string)); - } - - } else if (token == LT) { - // Nested element - if (parse(x, jsonobject, tagName,keepStrings)) { - if (jsonobject.length() == 0) { - context.accumulate(tagName, ""); - } else if (jsonobject.length() == 1 - && jsonobject.opt("content") != null) { - context.accumulate(tagName, - jsonobject.opt("content")); - } else { - context.accumulate(tagName, jsonobject); - } - return false; - } - } - } - } else { - throw x.syntaxError("Misshaped tag"); - } - } - } - } - - /** - * 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. - public static Object stringToValue(String string) { - if (string.equals("")) { - return string; - } - if (string.equalsIgnoreCase("true")) { - return Boolean.TRUE; - } - if (string.equalsIgnoreCase("false")) { - return Boolean.FALSE; - } - if (string.equalsIgnoreCase("null")) { - 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 { - // if we want full Big Number support this block can be replaced with: - // return stringToNumber(string); - if (string.indexOf('.') > -1 || string.indexOf('e') > -1 - || string.indexOf('E') > -1 || "-0".equals(string)) { - Double d = Double.valueOf(string); - if (!d.isInfinite() && !d.isNaN()) { - return d; - } - } else { - Long myLong = Long.valueOf(string); - if (string.equals(myLong.toString())) { - if (myLong.longValue() == myLong.intValue()) { - return Integer.valueOf(myLong.intValue()); - } - return myLong; - } - } - } catch (Exception ignore) { - } - } - return string; - } - - /** - * 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 <[ [ ]]> - * are ignored. - * - * @param string - * The source string. - * @return A JSONObject containing the structured data from the XML string. - * @throws JSONException Thrown if there is an errors while parsing the string - */ - public static JSONObject toJSONObject(String string) throws JSONException { - return toJSONObject(string, false); - } - - /** - * 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 <[ [ ]]> - * are ignored. - * - * @param reader The XML source reader. - * @return A JSONObject containing the structured data from the XML string. - * @throws JSONException Thrown if there is an errors while parsing the string - */ - public static JSONObject toJSONObject(Reader reader) throws JSONException { - return toJSONObject(reader, false); - } - - /** - * 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 <[ [ ]]> - * are ignored. - * - * All values are converted as strings, for 1, 01, 29.0 will not be coerced to - * numbers but will instead be the exact value as seen in the XML document. - * - * @param reader The XML source reader. - * @param keepStrings If true, then values will not be coerced into boolean - * or numeric values and will instead be left as strings - * @return A JSONObject containing the structured data from the XML string. - * @throws JSONException Thrown if there is an errors while parsing the string - */ - public static JSONObject toJSONObject(Reader reader, boolean keepStrings) throws JSONException { - JSONObject jo = new JSONObject(); - XMLTokener x = new XMLTokener(reader); - while (x.more()) { - x.skipPast("<"); - if(x.more()) { - parse(x, jo, null, keepStrings); - } - } - return jo; - } - - /** - * 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 <[ [ ]]> - * are ignored. - * - * All values are converted as strings, for 1, 01, 29.0 will not be coerced to - * numbers but will instead be the exact value as seen in the XML document. - * - * @param string - * The source string. - * @param keepStrings If true, then values will not be coerced into boolean - * or numeric values and will instead be left as strings - * @return A JSONObject containing the structured data from the XML string. - * @throws JSONException Thrown if there is an errors while parsing the string - */ - public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { - return toJSONObject(new StringReader(string), keepStrings); - } - - /** - * Convert a JSONObject into a well-formed, element-normal XML string. - * - * @param object - * A JSONObject. - * @return A string. - * @throws JSONException Thrown if there is an error parsing the string - */ - public static String toString(Object object) throws JSONException { - return toString(object, null); - } - - /** - * Convert a JSONObject into a well-formed, element-normal XML string. - * - * @param object - * A JSONObject. - * @param tagName - * The optional name of the enclosing tag. - * @return A string. - * @throws JSONException Thrown if there is an error parsing the string - */ - public static String toString(final Object object, final String tagName) - throws JSONException { - StringBuilder sb = new StringBuilder(); - JSONArray ja; - JSONObject jo; - String string; - - if (object instanceof JSONObject) { - - // Emit - if (tagName != null) { - sb.append('<'); - sb.append(tagName); - sb.append('>'); - } - - // Loop thru the keys. - // don't use the new entrySet accessor to maintain Android Support - jo = (JSONObject) object; - for (final String key : jo.keySet()) { - Object value = jo.opt(key); - if (value == null) { - value = ""; - } else if (value.getClass().isArray()) { - value = new JSONArray(value); - } - - // Emit content in body - if ("content".equals(key)) { - if (value instanceof JSONArray) { - ja = (JSONArray) value; - int jaLength = ja.length(); - // don't use the new iterator API to maintain support for Android - for (int i = 0; i < jaLength; i++) { - if (i > 0) { - sb.append('\n'); - } - Object val = ja.opt(i); - sb.append(escape(val.toString())); - } - } else { - sb.append(escape(value.toString())); - } - - // Emit an array of similar keys - - } else if (value instanceof JSONArray) { - ja = (JSONArray) value; - int jaLength = ja.length(); - // don't use the new iterator API to maintain support for Android - for (int i = 0; i < jaLength; i++) { - Object val = ja.opt(i); - if (val instanceof JSONArray) { - sb.append('<'); - sb.append(key); - sb.append('>'); - sb.append(toString(val)); - sb.append("'); - } else { - sb.append(toString(val, key)); - } - } - } else if ("".equals(value)) { - sb.append('<'); - sb.append(key); - sb.append("/>"); - - // Emit a new tag - - } else { - sb.append(toString(value, key)); - } - } - if (tagName != null) { - - // Emit the close tag - sb.append("'); - } - return sb.toString(); - - } - - if (object != null && (object instanceof JSONArray || object.getClass().isArray())) { - if(object.getClass().isArray()) { - ja = new JSONArray(object); - } else { - ja = (JSONArray) object; - } - int jaLength = ja.length(); - // don't use the new iterator API to maintain support for Android - for (int i = 0; i < jaLength; i++) { - Object val = ja.opt(i); - // XML does not have good support for arrays. If an array - // appears in a place where XML is lacking, synthesize an - // element. - sb.append(toString(val, tagName == null ? "array" : tagName)); - } - return sb.toString(); - } - - string = (object == null) ? "null" : escape(object.toString()); - return (tagName == null) ? "\"" + string + "\"" - : (string.length() == 0) ? "<" + tagName + "/>" : "<" + tagName - + ">" + string + ""; - - } -} diff --git a/XMLTokener.java b/XMLTokener.java deleted file mode 100644 index 50e3acce3..000000000 --- a/XMLTokener.java +++ /dev/null @@ -1,407 +0,0 @@ -package org.json; - -/* -Copyright (c) 2002 JSON.org - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -The Software shall be used for Good, not Evil. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -import java.io.Reader; - -/** - * The XMLTokener extends the JSONTokener to provide additional methods - * for the parsing of XML texts. - * @author JSON.org - * @version 2015-12-09 - */ -public class XMLTokener extends JSONTokener { - - - /** The table of entity values. It initially contains Character values for - * amp, apos, gt, lt, quot. - */ - public static final java.util.HashMap entity; - - static { - entity = new java.util.HashMap(8); - entity.put("amp", XML.AMP); - entity.put("apos", XML.APOS); - entity.put("gt", XML.GT); - entity.put("lt", XML.LT); - entity.put("quot", XML.QUOT); - } - - /** - * Construct an XMLTokener from a Reader. - * @param r A source reader. - */ - public XMLTokener(Reader r) { - super(r); - } - - /** - * Construct an XMLTokener from a string. - * @param s A source string. - */ - public XMLTokener(String s) { - super(s); - } - - /** - * Get the text in the CDATA block. - * @return The string up to the ]]>. - * @throws JSONException If the ]]> is not found. - */ - public String nextCDATA() throws JSONException { - char c; - int i; - StringBuilder sb = new StringBuilder(); - while (more()) { - c = next(); - sb.append(c); - i = sb.length() - 3; - if (i >= 0 && sb.charAt(i) == ']' && - sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') { - sb.setLength(i); - return sb.toString(); - } - } - throw syntaxError("Unclosed CDATA"); - } - - - /** - * Get the next XML outer token, trimming whitespace. There are two kinds - * of tokens: the '<' character which begins a markup tag, and the content - * text between markup tags. - * - * @return A string, or a '<' Character, or null if there is no more - * source text. - * @throws JSONException - */ - public Object nextContent() throws JSONException { - char c; - StringBuilder sb; - do { - c = next(); - } while (Character.isWhitespace(c)); - if (c == 0) { - return null; - } - if (c == '<') { - return XML.LT; - } - sb = new StringBuilder(); - for (;;) { - if (c == 0) { - return sb.toString().trim(); - } - if (c == '<') { - back(); - return sb.toString().trim(); - } - if (c == '&') { - sb.append(nextEntity(c)); - } else { - sb.append(c); - } - c = next(); - } - } - - - /** - * Return the next entity. These entities are translated to Characters: - * & ' > < ". - * @param ampersand An ampersand character. - * @return A Character or an entity String if the entity is not recognized. - * @throws JSONException If missing ';' in XML entity. - */ - public Object nextEntity(char ampersand) throws JSONException { - StringBuilder sb = new StringBuilder(); - for (;;) { - char c = next(); - if (Character.isLetterOrDigit(c) || c == '#') { - sb.append(Character.toLowerCase(c)); - } else if (c == ';') { - break; - } else { - throw syntaxError("Missing ';' in XML entity: &" + sb); - } - } - String string = sb.toString(); - return unescapeEntity(string); - } - - /** - * Unescapes an XML entity encoding; - * @param e entity (only the actual entity value, not the preceding & or ending ; - * @return - */ - static String unescapeEntity(String e) { - // 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') { - // hex encoded unicode - cp = Integer.parseInt(e.substring(2), 16); - } else { - // decimal encoded unicode - cp = Integer.parseInt(e.substring(1)); - } - return new String(new int[] {cp},0,1); - } - Character knownEntity = entity.get(e); - if(knownEntity==null) { - // we don't know the entity so keep it encoded - return '&' + e + ';'; - } - return knownEntity.toString(); - } - - - /** - * Returns the next XML meta token. This is used for skipping over - * and structures. - * @return Syntax characters (< > / = ! ?) are returned as - * Character, and strings and names are returned as Boolean. We don't care - * what the values actually are. - * @throws JSONException If a string is not properly closed or if the XML - * is badly structured. - */ - public Object nextMeta() throws JSONException { - char c; - char q; - do { - c = next(); - } while (Character.isWhitespace(c)); - switch (c) { - case 0: - throw syntaxError("Misshaped meta tag"); - case '<': - return XML.LT; - case '>': - return XML.GT; - case '/': - return XML.SLASH; - case '=': - return XML.EQ; - case '!': - return XML.BANG; - case '?': - return XML.QUEST; - case '"': - case '\'': - q = c; - for (;;) { - c = next(); - if (c == 0) { - throw syntaxError("Unterminated string"); - } - if (c == q) { - return Boolean.TRUE; - } - } - default: - for (;;) { - c = next(); - if (Character.isWhitespace(c)) { - return Boolean.TRUE; - } - switch (c) { - case 0: - case '<': - case '>': - case '/': - case '=': - case '!': - case '?': - case '"': - case '\'': - back(); - return Boolean.TRUE; - } - } - } - } - - - /** - * Get the next XML Token. These tokens are found inside of angle - * brackets. It may be one of these characters: / > = ! ? or it - * may be a string wrapped in single quotes or double quotes, or it may be a - * name. - * @return a String or a Character. - * @throws JSONException If the XML is not well formed. - */ - public Object nextToken() throws JSONException { - char c; - char q; - StringBuilder sb; - do { - c = next(); - } while (Character.isWhitespace(c)); - switch (c) { - case 0: - throw syntaxError("Misshaped element"); - case '<': - throw syntaxError("Misplaced '<'"); - case '>': - return XML.GT; - case '/': - return XML.SLASH; - case '=': - return XML.EQ; - case '!': - return XML.BANG; - case '?': - return XML.QUEST; - -// Quoted string - - case '"': - case '\'': - q = c; - sb = new StringBuilder(); - for (;;) { - c = next(); - if (c == 0) { - throw syntaxError("Unterminated string"); - } - if (c == q) { - return sb.toString(); - } - if (c == '&') { - sb.append(nextEntity(c)); - } else { - sb.append(c); - } - } - default: - -// Name - - sb = new StringBuilder(); - for (;;) { - sb.append(c); - c = next(); - if (Character.isWhitespace(c)) { - return sb.toString(); - } - switch (c) { - case 0: - return sb.toString(); - case '>': - case '/': - case '=': - case '!': - case '?': - case '[': - case ']': - back(); - return sb.toString(); - case '<': - case '"': - case '\'': - throw syntaxError("Bad character in a name"); - } - } - } - } - - - /** - * Skip characters until past the requested string. - * If it is not found, we are left at the end of the source with a result of false. - * @param to A string to skip past. - */ - // The Android implementation of JSONTokener has a public method of public void skipPast(String to) - // even though ours does not have that method, to have API compatibility, our method in the subclass - // should match. - public void skipPast(String to) { - boolean b; - char c; - int i; - int j; - int offset = 0; - int length = to.length(); - char[] circle = new char[length]; - - /* - * First fill the circle buffer with as many characters as are in the - * to string. If we reach an early end, bail. - */ - - for (i = 0; i < length; i += 1) { - c = next(); - if (c == 0) { - return; - } - circle[i] = c; - } - - /* We will loop, possibly for all of the remaining characters. */ - - for (;;) { - j = offset; - b = true; - - /* Compare the circle buffer with the to string. */ - - for (i = 0; i < length; i += 1) { - if (circle[j] != to.charAt(i)) { - b = false; - break; - } - j += 1; - if (j >= length) { - j -= length; - } - } - - /* If we exit the loop with b intact, then victory is ours. */ - - if (b) { - return; - } - - /* Get the next character. If there isn't one, then defeat is ours. */ - - c = next(); - if (c == 0) { - return; - } - /* - * Shove the character in the circle buffer and advance the - * circle offset. The offset is mod n. - */ - circle[offset] = c; - offset += 1; - if (offset >= length) { - offset -= length; - } - } - } -} diff --git a/build.xml b/build.xml new file mode 100644 index 000000000..283ef313b --- /dev/null +++ b/build.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + Builds, tests, and runs the project JSON. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml new file mode 100644 index 000000000..a6b8e6a2b --- /dev/null +++ b/nbproject/build-impl.xml @@ -0,0 +1,1799 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set platform.home + Must set platform.bootcp + Must set platform.java + Must set platform.javac + + The J2SE Platform is not correctly set up. + Your active platform is: ${platform.active}, but the corresponding property "platforms.${platform.active}.home" is not found in the project's properties files. + Either open the project in the IDE and setup the Platform with the same name or add it manually. + For example like this: + ant -Duser.properties.file=<path_to_property_file> jar (where you put the property "platforms.${platform.active}.home" in a .properties file) + or ant -Dplatforms.${platform.active}.home=<path_to_JDK_home> jar (where no properties file is used) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set test.test.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + ${platform.java} -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + Must select one file in the IDE or set profile.class + This target only works when run from inside the NetBeans IDE. + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + Must select some files in the IDE or set test.includes + + + + + Must select one file in the IDE or set run.class + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties new file mode 100644 index 000000000..ed8beb838 --- /dev/null +++ b/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=0c8496bc +build.xml.script.CRC32=4b528be8 +build.xml.stylesheet.CRC32=8064a381@1.80.1.48 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=b5f5947d +nbproject/build-impl.xml.script.CRC32=42b7a353 +nbproject/build-impl.xml.stylesheet.CRC32=12e0a6c2@1.100.0.48 diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 000000000..3232d72a0 --- /dev/null +++ b/nbproject/project.properties @@ -0,0 +1,94 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=JSON +application.vendor=Herve +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.modulepath=\ + ${run.modulepath} +debug.test.classpath=\ + ${run.test.classpath} +debug.test.modulepath=\ + ${run.test.modulepath} +# Files in build.classes.dir which should be excluded from distribution jar +dist.archive.excludes= +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/JSON.jar +dist.javadoc.dir=${dist.dir}/javadoc +endorsed.classpath= +excludes= +file.reference.JSON-java-src=src +includes=** +jar.compress=false +javac.classpath= +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.external.vm=true +javac.modulepath= +javac.processormodulepath= +javac.processorpath=\ + ${javac.classpath} +javac.source=1.8 +javac.target=1.8 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir}:\ + ${libs.junit_4.classpath}:\ + ${libs.hamcrest.classpath} +javac.test.modulepath=\ + ${javac.modulepath} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.html5=false +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +jlink.launcher=false +jlink.launcher.name=JSON +main.class=org.json.Main +manifest.file=manifest.mf +meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=false +platform.active=JDK_1.8 +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project. +# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. +# To set system properties for unit tests define test-sys-prop.name=value: +run.jvmargs= +run.modulepath=\ + ${javac.modulepath} +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +run.test.modulepath=\ + ${javac.test.modulepath} +source.encoding=UTF-8 +src.dir=${file.reference.JSON-java-src} +test.test.dir=test diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 000000000..43c98470d --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,16 @@ + + + org.netbeans.modules.java.j2seproject + + + JSON + + + + + + + + + + diff --git a/src/manifest.mf b/src/manifest.mf new file mode 100644 index 000000000..8b9236646 --- /dev/null +++ b/src/manifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: org.json.Main +Automatic-Module-Name: org.json diff --git a/src/org/json/CDL.java b/src/org/json/CDL.java new file mode 100644 index 000000000..af6b519cc --- /dev/null +++ b/src/org/json/CDL.java @@ -0,0 +1,295 @@ +package org.json; + +/* +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +/** + * This provides static methods to convert comma delimited text into a + * JSONArray, and to convert a JSONArray into comma delimited text. Comma + * delimited text is a very popular format for data interchange. It is + * understood by most database, spreadsheet, and organizer programs. + *

+ * Each row of text represents a row in a table or a data record. Each row + * ends with a NEWLINE character. Each row contains one or more values. + * Values are separated by commas. A value can contain any character except + * for comma, unless is is wrapped in single quotes or double quotes. + *

+ * The first row usually contains the names of the columns. + *

+ * A comma delimited list can be converted into a JSONArray of JSONObjects. + * The names for the elements in the JSONObjects can be taken from the names + * in the first row. + * + * @author JSON.org + * @version 2016-05-01 + */ +public class CDL { + + /** + * Get the next value. The value can be wrapped in quotes. The value can + * be empty. + * + * @param x A JSONTokener of the source text. + * @return The value string, or null if empty. + * @throws JSONException if the quoted string is badly formed. + */ + private static String getValue(JSONTokener x) throws JSONException { + char c; + char q; + StringBuilder sb; + do { + c = x.next(); + } while (c == ' ' || c == '\t'); + switch (c) { + case 0: + return null; + case '"': + case '\'': + q = c; + sb = new StringBuilder(); + for (;;) { + c = x.next(); + if (c == q) { + //Handle escaped double-quote + char nextC = x.next(); + if (nextC != '\"') { + // if our quote was the end of the file, don't step + if (nextC > 0) { + x.back(); + } + break; + } + } + if (c == 0 || c == '\n' || c == '\r') { + throw x.syntaxError("Missing close quote '" + q + "'."); + } + sb.append(c); + } + return sb.toString(); + case ',': + x.back(); + return ""; + default: + x.back(); + return x.nextTo(','); + } + } + + /** + * Produce a JSONArray of strings from a row of comma delimited values. + * + * @param x A JSONTokener of the source text. + * @return A JSONArray of strings. + * @throws JSONException + */ + public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException { + JSONArray ja = new JSONArray(); + for (;;) { + String value = getValue(x); + char c = x.next(); + if (value == null + || (ja.length() == 0 && value.length() == 0 && c != ',')) { + return null; + } + ja.put(value); + for (;;) { + if (c == ',') { + break; + } + if (c != ' ') { + if (c == '\n' || c == '\r' || c == 0) { + return ja; + } + throw x.syntaxError("Bad character '" + c + "' (" + (int) c + ")."); + } + c = x.next(); + } + } + } + + /** + * Produce a JSONObject from a row of comma delimited text, using a + * parallel JSONArray of strings to provides the names of the elements. + * + * @param names A JSONArray of names. This is commonly obtained from the + * first row of a comma delimited text file using the rowToJSONArray + * method. + * @param x A JSONTokener of the source text. + * @return A JSONObject combining the names and values. + * @throws JSONException + */ + public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) + throws JSONException { + JSONArray ja = rowToJSONArray(x); + return ja != null ? ja.toJSONObject(names) : null; + } + + /** + * Produce a comma delimited text row from a JSONArray. Values containing + * the comma character will be quoted. Troublesome characters may be + * removed. + * + * @param ja A JSONArray of strings. + * @return A string ending in NEWLINE. + */ + public static String rowToString(JSONArray ja) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < ja.length(); i += 1) { + if (i > 0) { + sb.append(','); + } + Object object = ja.opt(i); + if (object != null) { + String string = object.toString(); + if (string.length() > 0 && (string.indexOf(',') >= 0 + || string.indexOf('\n') >= 0 || string.indexOf('\r') >= 0 + || string.indexOf(0) >= 0 || string.charAt(0) == '"')) { + sb.append('"'); + int length = string.length(); + for (int j = 0; j < length; j += 1) { + char c = string.charAt(j); + if (c >= ' ' && c != '"') { + sb.append(c); + } + } + sb.append('"'); + } else { + sb.append(string); + } + } + } + sb.append('\n'); + return sb.toString(); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string, + * using the first row as a source of names. + * + * @param string The comma delimited text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(String string) throws JSONException { + return toJSONArray(new JSONTokener(string)); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string, + * using the first row as a source of names. + * + * @param x The JSONTokener containing the comma delimited text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(JSONTokener x) throws JSONException { + return toJSONArray(rowToJSONArray(x), x); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string + * using a supplied JSONArray as the source of element names. + * + * @param names A JSONArray of strings. + * @param string The comma delimited text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(JSONArray names, String string) + throws JSONException { + return toJSONArray(names, new JSONTokener(string)); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string + * using a supplied JSONArray as the source of element names. + * + * @param names A JSONArray of strings. + * @param x A JSONTokener of the source text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(JSONArray names, JSONTokener x) + throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + JSONArray ja = new JSONArray(); + for (;;) { + JSONObject jo = rowToJSONObject(names, x); + if (jo == null) { + break; + } + ja.put(jo); + } + if (ja.length() == 0) { + return null; + } + return ja; + } + + /** + * Produce a comma delimited text from a JSONArray of JSONObjects. The + * first row will be a list of names obtained by inspecting the first + * JSONObject. + * + * @param ja A JSONArray of JSONObjects. + * @return A comma delimited text. + * @throws JSONException + */ + public static String toString(JSONArray ja) throws JSONException { + JSONObject jo = ja.optJSONObject(0); + if (jo != null) { + JSONArray names = jo.names(); + if (names != null) { + return rowToString(names) + toString(names, ja); + } + } + return null; + } + + /** + * Produce a comma delimited text from a JSONArray of JSONObjects using + * a provided list of names. The list of names is not included in the + * output. + * + * @param names A JSONArray of strings. + * @param ja A JSONArray of JSONObjects. + * @return A comma delimited text. + * @throws JSONException + */ + public static String toString(JSONArray names, JSONArray ja) + throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < ja.length(); i += 1) { + JSONObject jo = ja.optJSONObject(i); + if (jo != null) { + sb.append(rowToString(jo.toJSONArray(names))); + } + } + return sb.toString(); + } +} diff --git a/Cookie.java b/src/org/json/Cookie.java similarity index 100% rename from Cookie.java rename to src/org/json/Cookie.java diff --git a/CookieList.java b/src/org/json/CookieList.java similarity index 100% rename from CookieList.java rename to src/org/json/CookieList.java diff --git a/src/org/json/FileUtils.java b/src/org/json/FileUtils.java new file mode 100644 index 000000000..cb266014c --- /dev/null +++ b/src/org/json/FileUtils.java @@ -0,0 +1,723 @@ +/* +Copyright (C) 2018, 2019, 2020, 2021, 2024 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringReader; +import java.net.URL; + +/** + * Contains several utility methods to convert from / to a File / an URL / a JSON object. + * + * @version 1.7.7 + */ +public class FileUtils { + private FileUtils() { + } + + /** + * Save a JSONArray as a File, using a specified indentFactor and indent. The result will be a javascript file, where the result of the JSON + * content will be affected to the specified variable. + * + * @param element the JSONElement + * @param file the file + * @param var the variable to affect + * @throws IOException if an IOException occured + * @throws JSONException if the JSONArray was unable to write + */ + public static void toFile(JSONElement element, File file, String var) throws IOException, JSONException { + toFile(element, file, var, null); + } + + /** + * Save a JSONElement as a File. The result will be a javascript file, where the result of the JSON + * content will be affected to the specified variable. + * + * @param element the JSONElement + * @param file the file + * @param var the variable to affect + * @param options the JSON configuration options + * @throws IOException if an IOException occured + * @throws JSONException if the JSONArray was unable to write + */ + public static void toFile(JSONElement element, File file, String var, JSONOptions options) throws IOException, JSONException { + options = JSONOptions.getOptions(options); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), options.getCharset())); + if (var != null) { + writer.write("var " + var + " = "); + } + element.write(writer, 0, true, options); + writer.flush(); + } + + /** + * Save a JSONElement as a File. The result will be a JSON file. + * + * @param element the JSONElement + * @param file the file + * @param options the JSON configuration options + * @throws IOException if an IOException occured + * @throws JSONException if the JSONArray was unable to write + */ + public static void toFile(JSONElement element, File file, JSONOptions options) throws IOException, JSONException { + toFile(element, file, null, options); + } + + /** + * Save a JSONElement as a File. The result will be a JSON file. + * + * @param element the JSONElement + * @param file the file + * @throws IOException if an IOException occured + * @throws JSONException if the JSONArray was unable to write + */ + public static void toFile(JSONElement element, File file) throws IOException, JSONException { + toFile(element, file, null, null); + } + + /** + * Save a JSONElement as an URL, using a specified indentFactor and indent. + * + * @param element the JSONElement + * @param url the URL + * @throws IOException if an IOException occured + * @throws JSONException if the JSONArray was unable to write + */ + public static void toURL(JSONElement element, URL url) throws IOException, JSONException { + toURL(element, url, null, null); + } + + /** + * Save a JSONElement as an URL, using a specified indentFactor and indent. + * + * @param element the JSONElement + * @param url the URL + * @param options the JSON configuration options + * @throws IOException if an IOException occured + * @throws JSONException if the JSONArray was unable to write + */ + public static void toURL(JSONElement element, URL url, JSONOptions options) throws IOException, JSONException { + toURL(element, url, null, options); + } + + /** + * Save a JSONElement as an URL, using a specified indentFactor and indent. + * + * @param element the JSONElement + * @param url the URL + * @param var the variable to affect + * @param options the JSON configuration options + * @throws IOException if an IOException occured + * @throws JSONException if the JSONArray was unable to write + */ + public static void toURL(JSONElement element, URL url, String var, JSONOptions options) throws IOException, JSONException { + File file = new File(url.getFile()); + toFile(element, file, var, options); + } + + private static String toStringRemoveComments(URL url, JSONOptions options) throws IOException { + try ( InputStream stream = url.openStream()) { + return toStringRemoveComments(stream, options); + } + } + + private static String toStringRemoveComments(InputStream stream, JSONOptions options) throws IOException { + StringBuilder buf = new StringBuilder(); + boolean first = true; + boolean inComment = false; + BufferedReader reader = new BufferedReader(new InputStreamReader(stream, options.getCharset())); + while (true) { + String line = reader.readLine(); + if (line == null) { + break; + } + boolean toAdd = true; + String trimmed = line.trim(); + if (!inComment && trimmed.startsWith("/*")) { + inComment = true; + toAdd = false; + if (trimmed.endsWith(" */")) { + inComment = false; + } + } else if (inComment && trimmed.endsWith("*/")) { + inComment = false; + toAdd = false; + } else if (inComment) { + toAdd = false; + } + if (toAdd) { + if (first) { + first = false; + } else { + if (!inComment) { + buf.append("\n"); + } + } + buf.append(line); + } else if (first) { + first = false; + } + } + return buf.toString(); + } + + private static String toStringRemoveComments(File file, JSONOptions options) throws IOException { + StringBuilder buf = new StringBuilder(); + boolean first = true; + boolean inComment = false; + try ( BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), options.getCharset()))) { + while (true) { + String line = reader.readLine(); + if (line == null) { + break; + } + boolean toAdd = true; + String trimmed = line.trim(); + if (!inComment && trimmed.startsWith("/*")) { + inComment = true; + toAdd = false; + if (trimmed.endsWith(" */")) { + inComment = false; + } + } else if (inComment && trimmed.endsWith("*/")) { + inComment = false; + toAdd = false; + } else if (inComment) { + toAdd = false; + } + if (toAdd) { + if (first) { + first = false; + } else { + if (!inComment) { + buf.append("\n"); + } + } + buf.append(line); + } else if (first) { + first = false; + } + } + } + return buf.toString(); + } + + /** + * Load a File as a JSONElement. + * + * @param file the File + * @return the JSONElement + * @throws IOException if an IOException occured + * @throws JSONException if the File is not a valid JSON file + */ + public static JSONElement toJSONElement(File file) throws IOException, JSONException { + try { + JSONElement element = toJSONArray(file, null); + return element; + } catch (JSONException ex) { + JSONElement element = toJSONObject(file, null); + return element; + } + } + + /** + * Load a File as a JSONElement. + * + * @param file the File + * @param options the JSON configuration options + * @return the JSONElement + * @throws IOException if an IOException occured + * @throws JSONException if the File is not a valid JSON file + */ + public static JSONElement toJSONElement(File file, JSONOptions options) throws IOException, JSONException { + try { + JSONElement element = toJSONArray(file, options); + return element; + } catch (JSONException ex) { + JSONElement element = toJSONObject(file, options); + return element; + } + } + + /** + * Load a File as a JSONArray. + * + * @param file the File + * @return the JSONArray + * @throws IOException if an IOException occured + * @throws JSONException if the File is not a valid JSON file + */ + public static JSONArray toJSONArray(File file) throws IOException, JSONException { + return toJSONArray(file, null); + } + + /** + * Load a File as a JSONArray. + * + * @param file the File + * @param options the JSON configuration options + * @return the JSONArray + * @throws IOException if an IOException occured + * @throws JSONException if the File is not a valid JSON file + */ + public static JSONArray toJSONArray(File file, JSONOptions options) throws IOException, JSONException { + JSONArray array; + options = JSONOptions.getOptions(options); + if (options.isAcceptingJavascriptComments()) { + String content = toStringRemoveComments(file, options); + array = new JSONArray(content); + } else { + BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), options.getCharset())); + array = toJSONArray(reader); + } + return array; + } + + /** + * Load an URL as a JSONElement. + * + * @param url the URL + * @return the JSONArray + * @throws IOException if an IOException occured + * @throws JSONException if the URL is not a valid JSON file + */ + public static JSONElement toJSONElement(URL url) throws IOException, JSONException { + try { + JSONElement element = toJSONArray(url, null); + return element; + } catch (JSONException ex) { + JSONElement element = toJSONObject(url, null); + return element; + } + } + + /** + * Load an URL as a JSONElement. + * + * @param url the URL + * @param options the JSON configuration options + * @return the JSONArray + * @throws IOException if an IOException occured + * @throws JSONException if the URL is not a valid JSON file + */ + public static JSONElement toJSONElement(URL url, JSONOptions options) throws IOException, JSONException { + try { + JSONElement element = toJSONArray(url, options); + return element; + } catch (JSONException ex) { + JSONElement element = toJSONObject(url, options); + return element; + } + } + + /** + * Load an InputStream as a JSONElement. + * + * @param stream the InputStream + * @return the JSONArray + * @throws IOException if an IOException occured + * @throws JSONException if the URL is not a valid JSON file + */ + public static JSONElement toJSONElement(InputStream stream) throws IOException, JSONException { + return toJSONElement(stream, null); + } + + /** + * Load an InputStream as a JSONElement. + * + * @param stream the InputStream + * @param options the JSON configuration options + * @return the JSONArray + * @throws IOException if an IOException occured + * @throws JSONException if the URL is not a valid JSON file + */ + public static JSONElement toJSONElement(InputStream stream, JSONOptions options) throws IOException, JSONException { + options = JSONOptions.getOptions(options); + // see https://stackoverflow.com/questions/309424/how-do-i-read-convert-an-inputstream-into-a-string-in-java + String content = streamToString(stream, options.getCharset()); + try { + Reader inputString = new StringReader(content); + BufferedReader reader = new BufferedReader(inputString); + JSONElement element = toJSONArray(reader); + return element; + } catch (JSONException ex) { + Reader inputString = new StringReader(content); + BufferedReader reader = new BufferedReader(inputString); + JSONElement element = toJSONObject(reader); + return element; + } + } + + private static String streamToString(InputStream stream, String charset) throws IOException { + int bufferSize = 1024; + char[] buffer = new char[bufferSize]; + StringBuilder buf = new StringBuilder(); + Reader in = new InputStreamReader(stream, charset); + for (int numRead; (numRead = in.read(buffer, 0, buffer.length)) > 0;) { + buf.append(buffer, 0, numRead); + } + return buf.toString(); + } + + /** + * Load an URL as a JSONArray. + * + * @param url the URL + * @return the JSONArray + * @throws IOException if an IOException occured + * @throws JSONException if the URL is not a valid JSON file + */ + public static JSONArray toJSONArray(URL url) throws IOException, JSONException { + return toJSONArray(url, null); + } + + /** + * Load an InputStream as a JSONArray. + * + * @param stream the InputStream + * @return the JSONArray + * @throws IOException if an IOException occured + * @throws JSONException if the URL is not a valid JSON file + */ + public static JSONArray toJSONArray(InputStream stream) throws IOException, JSONException { + return toJSONArray(stream, null); + } + + /** + * Load an URL as a JSONArray. + * + * @param url the URL + * @param options the JSON configuration optionss + * @return the JSONArray + * @throws IOException if an IOException occured + * @throws JSONException if the URL is not a valid JSON file + */ + public static JSONArray toJSONArray(URL url, JSONOptions options) throws IOException, JSONException { + JSONArray array; + options = JSONOptions.getOptions(options); + if (options.isAcceptingJavascriptComments()) { + String content = toStringRemoveComments(url, options); + array = new JSONArray(content); + } else { + InputStream stream = url.openStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(stream, options.getCharset())); + array = toJSONArray(reader); + } + return array; + } + + /** + * Load an InputStream as a JSONArray. + * + * @param stream the InputStream + * @param options the JSON configuration optionss + * @return the JSONArray + * @throws IOException if an IOException occured + * @throws JSONException if the URL is not a valid JSON file + */ + public static JSONArray toJSONArray(InputStream stream, JSONOptions options) throws IOException, JSONException { + JSONArray array; + options = JSONOptions.getOptions(options); + if (options.isAcceptingJavascriptComments()) { + String content = toStringRemoveComments(stream, options); + array = new JSONArray(content); + } else { + BufferedReader reader = new BufferedReader(new InputStreamReader(stream, options.getCharset())); + array = toJSONArray(reader); + } + + return array; + } + + /** + * Use a reader to load a JSONArray. + * + * @param reader the reader + * @return the JSONArray + * @throws IOException if an IOException occured + * @throws JSONException if a JSONException occured + */ + public static JSONArray toJSONArray(BufferedReader reader) throws IOException, JSONException { + StringBuilder buf = new StringBuilder(); + boolean started = false; + while (true) { + String line = reader.readLine(); + if (line != null) { + buf.append(line); + if (started) { + buf.append('\n'); + } else { + started = true; + } + } else { + break; + } + } + reader.close(); + JSONArray array = new JSONArray(buf.toString()); + return array; + } + + /** + * Load an URL as a JSONObject. + * + * @param url the URL + * @return the JSONObject + * @throws IOException if an IOException occured + * @throws JSONException if the URL is not a valid JSON file + */ + public static JSONObject toJSONObject(URL url) throws IOException, JSONException { + return toJSONObject(url, null); + } + + /** + * Load an URL as a JSONObject. + * + * @param url the URL + * @param options the JSON configuration options + * @return the JSONObject + * @throws IOException if an IOException occured + * @throws JSONException if the URL is not a valid JSON file + */ + public static JSONObject toJSONObject(URL url, JSONOptions options) throws IOException, JSONException { + JSONObject obj; + options = JSONOptions.getOptions(options); + if (options.isAcceptingJavascriptComments()) { + String content = toStringRemoveComments(url, options); + obj = new JSONObject(content); + } else { + InputStream stream = url.openStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(stream, options.getCharset())); + obj = toJSONObject(reader); + } + return obj; + } + + /** + * Load an InputStream as a JSONObject. + * + * @param stream the InputStream + * @return the JSONObject + * @throws IOException if an IOException occured + * @throws JSONException if the URL is not a valid JSON file + */ + public static JSONObject toJSONObject(InputStream stream) throws IOException, JSONException { + return toJSONObject(stream, null); + } + + /** + * Load an InputStream as a JSONObject. + * + * @param stream the InputStream + * @param options the JSON configuration options + * @return the JSONObject + * @throws IOException if an IOException occured + * @throws JSONException if the URL is not a valid JSON file + */ + public static JSONObject toJSONObject(InputStream stream, JSONOptions options) throws IOException, JSONException { + JSONObject obj; + options = JSONOptions.getOptions(options); + if (options.isAcceptingJavascriptComments()) { + String content = toStringRemoveComments(stream, options); + obj = new JSONObject(content); + } else { + BufferedReader reader = new BufferedReader(new InputStreamReader(stream, options.getCharset())); + obj = toJSONObject(reader); + } + return obj; + } + + /** + * Load a File as a JSONObject. + * + * @param file the File + * @return the JSONObject + * @throws IOException if an IOException occured + * @throws JSONException if the File is not a valid JSON file + */ + public static JSONObject toJSONObject(File file) throws IOException, JSONException { + return toJSONObject(file, null); + } + + ; + + /** + * Load a File as a JSONObject. + * + * @param file the File + * @param options the JSON configuration options + * @return the JSONObject + * @throws IOException if an IOException occured + * @throws JSONException if the File is not a valid JSON file + */ + public static JSONObject toJSONObject(File file, JSONOptions options) throws IOException, JSONException { + JSONObject obj; + options = JSONOptions.getOptions(options); + if (options.isAcceptingJavascriptComments()) { + String content = toStringRemoveComments(file, options); + obj = new JSONObject(content); + } else { + BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), options.getCharset())); + obj = toJSONObject(reader); + } + return obj; + } + + /** + * Use a reader to load a JSONObject. + * + * @param reader the reader + * @return the JSONObject + * @throws IOException if an IOException occured + * @throws JSONException if a JSONException occured + */ + public static JSONObject toJSONObject(BufferedReader reader) throws IOException, JSONException { + StringBuilder buf = new StringBuilder(); + boolean started = false; + while (true) { + String line = reader.readLine(); + if (line != null) { + buf.append(line); + if (started) { + buf.append('\n'); + } else { + started = true; + } + } else { + break; + } + } + reader.close(); + JSONObject obj = new JSONObject(buf.toString()); + return obj; + } + + /** + * Convert an XML URL to a JSONObject. + * + * @param url the XML URL + * @return the JSONObject + * @throws IOException if an IOException occured + * @throws JSONException if the XML URL is not a valid JSON file + */ + public static JSONObject toJSONObjectFromXML(URL url) throws IOException, JSONException { + return toJSONObjectFromXML(url, null); + } + + /** + * Convert an XML URL to a JSONObject. + * + * @param url the XML URL + * @param options the JSON configuration options + * @return the JSONObject + * @throws IOException if an IOException occured + * @throws JSONException if the XML URL is not a valid JSON file + */ + public static JSONObject toJSONObjectFromXML(URL url, JSONOptions options) throws IOException, JSONException { + String content; + options = JSONOptions.getOptions(options); + if (options.isAcceptingJavascriptComments()) { + content = toStringRemoveComments(url, options); + } else { + InputStream stream = url.openStream(); + StringBuilder buf = new StringBuilder(); + try ( BufferedReader reader = new BufferedReader(new InputStreamReader(stream, options.getCharset()))) { + boolean started = false; + while (true) { + String line = reader.readLine(); + if (line != null) { + buf.append(line); + if (started) { + buf.append('\n'); + } else { + started = true; + } + } else { + break; + } + } + } + content = buf.toString(); + } + JSONObject obj = XML.toJSONObject(content, options.isKeepingXMLStrings()); + return obj; + } + + /** + * Convert an XML File to a JSONObject. + * + * @param file the XML File + * @return the JSONObject + * @throws IOException if an IOException occured + * @throws JSONException if the XML File is not a valid JSON file + */ + public static JSONObject toJSONObjectFromXML(File file) throws IOException, JSONException { + return toJSONObjectFromXML(file, null); + } + + /** + * Convert an XML File to a JSONObject. + * + * @param file the XML File + * @param options the JSON configuration options + * @return the JSONObject + * @throws IOException if an IOException occured + * @throws JSONException if the XML File is not a valid JSON file + */ + public static JSONObject toJSONObjectFromXML(File file, JSONOptions options) throws IOException, JSONException { + String content; + options = JSONOptions.getOptions(options); + if (options.isKeepingXMLStrings()) { + content = toStringRemoveComments(file, options); + } else { + StringBuilder buf = new StringBuilder(); + try ( BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), options.getCharset()))) { + boolean started = false; + while (true) { + String line = reader.readLine(); + if (line != null) { + buf.append(line); + if (started) { + buf.append('\n'); + } else { + started = true; + } + } else { + break; + } + } + } + content = buf.toString(); + } + JSONObject obj = XML.toJSONObject(content, options.isKeepingXMLStrings()); + return obj; + } +} diff --git a/HTTP.java b/src/org/json/HTTP.java similarity index 100% rename from HTTP.java rename to src/org/json/HTTP.java diff --git a/HTTPTokener.java b/src/org/json/HTTPTokener.java similarity index 100% rename from HTTPTokener.java rename to src/org/json/HTTPTokener.java diff --git a/src/org/json/JSONArray.java b/src/org/json/JSONArray.java new file mode 100644 index 000000000..57a0779e0 --- /dev/null +++ b/src/org/json/JSONArray.java @@ -0,0 +1,1475 @@ +package org.json; + +/* + Copyright (c) 2002 JSON.org + Copyright (C) 2021 by Herve Girod + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * A JSONArray is an ordered sequence of values. Its external text form is a string wrapped in square brackets with commas separating the values. The + * internal form is an object having get and opt methods for accessing the values by index, and put methods for + * adding or replacing values. The values can be any of these types: Boolean, JSONArray, JSONObject, + * Number, String, or the JSONObject.NULL object. + *

+ * The constructor can convert a JSON text into a Java object. The toString method converts to JSON text. + *

+ * A get method returns a value if one can be found, and throws an exception if one cannot be found. An opt method returns a + * default value instead of throwing an exception, and so is useful for obtaining optional values. + *

+ * The generic get() and opt() methods return an object which you can cast or query for type. There are also typed + * get and opt methods that do type checking and type coercion for you. + *

+ * The texts produced by the toString methods strictly conform to JSON syntax rules. The constructors are more forgiving in the texts + * they will accept: + *

    + *
  • An extra , (comma) may appear just before the closing bracket.
  • + *
  • The null value will be inserted when there is ,  (comma) elision.
  • + *
  • Strings may be quoted with ' (single quote).
  • + *
  • Strings do not need to be quoted at all if they do not begin with a quote or single quote, and if they do not contain leading or trailing + * spaces, and if they do not contain any of these characters: { } [ ] / \ : , # and if they do not look like numbers and if they are not + * the reserved words true, false, or null.
  • + *
+ * + * @author JSON.org + * @version 1.7.5 + */ +public class JSONArray implements JSONElement, Iterable { + + /** + * The arrayList where the JSONArray's properties are kept. + */ + private final ArrayList array; + + /** + * Construct an empty JSONArray. + */ + public JSONArray() { + this.array = new ArrayList<>(); + } + + /** + * Construct a JSONArray from a JSONTokener. + * + * @param x A JSONTokener + * @throws JSONException If there is a syntax error. + */ + public JSONArray(JSONTokener x) throws JSONException { + this(); + if (x.nextClean() != '[') { + throw x.syntaxError("A JSONArray text must start with '['"); + } + + char nextChar = x.nextClean(); + if (nextChar == 0) { + // array is unclosed. No ']' found, instead EOF + throw x.syntaxError("Expected a ',' or ']'"); + } + if (nextChar != ']') { + x.back(); + for (;;) { + if (x.nextClean() == ',') { + x.back(); + this.array.add(JSONObject.NULL); + } else { + x.back(); + this.array.add(x.nextValue()); + } + switch (x.nextClean()) { + case 0: + // array is unclosed. No ']' found, instead EOF + throw x.syntaxError("Expected a ',' or ']'"); + case ',': + nextChar = x.nextClean(); + if (nextChar == 0) { + // array is unclosed. No ']' found, instead EOF + throw x.syntaxError("Expected a ',' or ']'"); + } + if (nextChar == ']') { + return; + } + x.back(); + break; + case ']': + return; + default: + throw x.syntaxError("Expected a ',' or ']'"); + } + } + } + } + + /** + * Construct a JSONArray from a source JSON text. + * + * @param source A string that begins with [ (left bracket) and ends with ]  (right + * bracket). + * @throws JSONException If there is a syntax error. + */ + public JSONArray(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONArray from a Collection. + * + * @param collection A Collection. + */ + public JSONArray(Collection collection) { + if (collection == null) { + this.array = new ArrayList<>(); + } else { + this.array = new ArrayList<>(collection.size()); + for (Object o : collection) { + this.array.add(JSONObject.wrap(o)); + } + } + } + + /** + * Construct a JSONArray from an array + * + * @param array the array + * @throws JSONException If not an array or if an array value is non-finite number. + */ + public JSONArray(Object array) throws JSONException { + this(); + if (array.getClass().isArray()) { + int length = Array.getLength(array); + this.array.ensureCapacity(length); + for (int i = 0; i < length; i += 1) { + this.put(JSONObject.wrap(Array.get(array, i))); + } + } else { + throw new JSONException("JSONArray initial value should be a string or collection or array."); + } + } + + /** + * Create a new JSONArray. + * + * @return the JSONArray + */ + public static JSONArray create() { + JSONArray obj = new JSONArray(); + return obj; + } + + @Override + public JSONArray clone() { + JSONArray jsonarray = new JSONArray(array); + return jsonarray; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof JSONArray)) { + return false; + } + JSONArray other = (JSONArray) o; + if (other.array.size() != array.size()) { + return false; + } + for (int i = 0; i < array.size(); i++) { + Object cell = array.get(i); + Object otherCell = other.array.get(i); + if (!(isEqual(cell, otherCell))) { + return false; + } + } + return true; + } + + private boolean isEqual(Object value1, Object value2) { + if (value1 instanceof Number && value2 instanceof Number) { + Number number1 = (Number) value1; + Number number2 = (Number) value2; + return number1.floatValue() == number2.floatValue(); + } else { + return value1.equals(value2); + } + } + + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + Objects.hashCode(this.array); + return hash; + } + + @Override + public Iterator iterator() { + return this.array.iterator(); + } + + /** + * Get the object value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return An object value. + * @throws JSONException If there is no value for the index. + */ + public Object get(int index) throws JSONException { + Object object = this.opt(index); + if (object == null) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + return object; + } + + /** + * Get the boolean value associated with an index. The string values "true" and "false" are converted to boolean. + * + * @param index The index must be between 0 and length() - 1. + * @return The truth. + * @throws JSONException If there is no value for the index or if the value is not convertible to boolean. + */ + public boolean getBoolean(int index) throws JSONException { + Object object = this.get(index); + if (object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("false"))) { + return false; + } else if (object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("JSONArray[" + index + "] is not a boolean."); + } + + /** + * Get the double value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException If the key is not found or if the value cannot be converted to a number. + */ + public double getDouble(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number ? ((Number) object).doubleValue() : Double.parseDouble((String) object); + } catch (NumberFormatException e) { + throw new JSONException("JSONArray[" + index + "] is not a number.", e); + } + } + + /** + * Get the float value associated with a key. + * + * @param index The index must be between 0 and length() - 1. + * @return The numeric value. + * @throws JSONException if the key is not found or if the value is not a Number object and cannot be converted to a number. + */ + public float getFloat(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number ? ((Number) object).floatValue() : Float.parseFloat(object.toString()); + } catch (NumberFormatException e) { + throw new JSONException("JSONArray[" + index + "] is not a number.", e); + } + } + + /** + * Get the Number value associated with a key. + * + * @param index The index must be between 0 and length() - 1. + * @return The numeric value. + * @throws JSONException if the key is not found or if the value is not a Number object and cannot be converted to a number. + */ + public Number getNumber(int index) throws JSONException { + Object object = this.get(index); + try { + if (object instanceof Number) { + return (Number) object; + } + return JSONObject.stringToNumber(object.toString()); + } catch (NumberFormatException e) { + throw new JSONException("JSONArray[" + index + "] is not a number.", e); + } + } + + /** + * Get the enum value associated with an index. + * + * @param the enum class + * @param clazz The type of enum to retrieve + * @param index The index must be between 0 and length() - 1 + * @return The enum value at the index location + * @throws JSONException if the key is not found or if the value cannot be converted to an enum. + */ + public > E getEnum(Class clazz, int index) throws JSONException { + E val = optEnum(clazz, index); + if (val == null) { + // JSONException should really take a throwable argument. + // If it did, I would re-implement this with the Enum.valueOf + // method and place any thrown exception in the JSONException + throw new JSONException("JSONArray[" + index + "] is not an enum of type " + + JSONObject.quote(clazz.getSimpleName()) + "."); + } + return val; + } + + /** + * Get the BigDecimal value associated with an index. + * + * @param index The index must be between 0 and length() - 1 + * @return The value. + * @throws JSONException If the key is not found or if the value cannot be converted to a BigDecimal + */ + public BigDecimal getBigDecimal(int index) throws JSONException { + Object object = this.get(index); + try { + return new BigDecimal(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] could not convert to BigDecimal.", e); + } + } + + /** + * Get the BigInteger value associated with an index. + * + * @param index The index must be between 0 and length() - 1 + * @return The value. + * @throws JSONException If the key is not found or if the value cannot be converted to a BigInteger. + */ + public BigInteger getBigInteger(int index) throws JSONException { + Object object = this.get(index); + try { + return new BigInteger(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] could not convert to BigInteger.", e); + } + } + + /** + * Get the int value associated with an index. + * + * @param index The index must be between 0 and length() - 1 + * @return The value. + * @throws JSONException If the key is not found or if the value is not a number + */ + public int getInt(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number ? ((Number) object).intValue() : Integer.parseInt((String) object); + } catch (NumberFormatException e) { + throw new JSONException("JSONArray[" + index + "] is not a number.", e); + } + } + + /** + * Get the JSONArray associated with an index. + * + * @param index The index must be between 0 and length() - 1 + * @return A JSONArray value. + * @throws JSONException If there is no value for the index. or if the value is not a JSONArray + */ + public JSONArray getJSONArray(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONArray."); + } + + /** + * Get the JSONObject associated with an index. + * + * @param index subscript + * @return A JSONObject value. + * @throws JSONException If there is no value for the index or if the value is not a JSONObject + */ + public JSONObject getJSONObject(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONObject."); + } + + /** + * Get the long value associated with an index. + * + * @param index The index must be between 0 and length() - 1 + * @return The value + * @throws JSONException If the key is not found or if the value cannot be converted to a number. + */ + public long getLong(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number ? ((Number) object).longValue() : Long.parseLong((String) object); + } catch (NumberFormatException e) { + throw new JSONException("JSONArray[" + index + "] is not a number.", e); + } + } + + /** + * Get the string associated with an index. + * + * @param index The index must be between 0 and length() - 1 + * @return A string value. + * @throws JSONException If there is no string value for the index. + */ + public String getString(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONArray[" + index + "] not a string."); + } + + /** + * Determine if the value is null. + * + * @param index The index must be between 0 and length() - 1 + * @return true if the value at the index is null, or if there is no value. + */ + public boolean isNull(int index) { + return JSONObject.NULL.equals(this.opt(index)); + } + + /** + * Make a string from the contents of this JSONArray. The separator string is inserted between each element. Warning: This method + * assumes that the data structure is acyclical. + * + * @param separator A string that will be inserted between the elements + * @return a string + * @throws JSONException If the array contains an invalid number. + */ + public String join(String separator) throws JSONException { + int len = this.length(); + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < len; i += 1) { + if (i > 0) { + sb.append(separator); + } + sb.append(JSONObject.valueToString(this.array.get(i))); + } + return sb.toString(); + } + + /** + * Get the number of elements in the JSONArray, included nulls. + * + * @return The length (or size). + */ + @Override + public int length() { + return this.array.size(); + } + + /** + * Get the optional object value associated with an index. + * + * @param index The index must be between 0 and length() - 1. If not, null is returned + * @return An object value, or null if there is no object at that index. + */ + public Object opt(int index) { + return (index < 0 || index >= this.length()) ? null : this.array.get(index); + } + + /** + * Get the optional boolean value associated with an index. It returns false if there is no value at that index, or if the value is not + * Boolean.TRUE or the String "true". + * + * @param index The index must be between 0 and length() - 1 + * @return The truth + */ + public boolean optBoolean(int index) { + return this.optBoolean(index, false); + } + + /** + * Get the optional boolean value associated with an index. It returns the defaultValue if there is no value at that index or if it is not a + * Boolean or the String "true" or "false" (case insensitive). + * + * @param index The index must be between 0 and length() - 1 + * @param defaultValue A boolean default. + * @return The truth + */ + public boolean optBoolean(int index, boolean defaultValue) { + try { + return this.getBoolean(index); + } catch (JSONException e) { + return defaultValue; + } + } + + /** + * Get the optional double value associated with an index. NaN is returned if there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1 + * @return The value. + */ + public double optDouble(int index) { + return this.optDouble(index, Double.NaN); + } + + /** + * Get the optional double value associated with an index. The defaultValue is returned if there is no value for the index, or if the value is not + * a number and cannot be converted to a number. + * + * @param index subscript + * @param defaultValue The default value + * @return The value + */ + public double optDouble(int index, double defaultValue) { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number) { + return ((Number) val).doubleValue(); + } + if (val instanceof String) { + try { + return Double.parseDouble((String) val); + } catch (NumberFormatException e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get the optional float value associated with an index. NaN is returned if there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1 + * @return The value + */ + public float optFloat(int index) { + return this.optFloat(index, Float.NaN); + } + + /** + * Get the optional float value associated with an index. The defaultValue is returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index subscript + * @param defaultValue The default value + * @return The value + */ + public float optFloat(int index, float defaultValue) { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number) { + return ((Number) val).floatValue(); + } + if (val instanceof String) { + try { + return Float.parseFloat((String) val); + } catch (NumberFormatException e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get the optional int value associated with an index. Zero is returned if there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1 + * @return The value + */ + public int optInt(int index) { + return this.optInt(index, 0); + } + + /** + * Get the optional int value associated with an index. The defaultValue is returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1 + * @param defaultValue The default value + * @return The value + */ + public int optInt(int index, int defaultValue) { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number) { + return ((Number) val).intValue(); + } + + if (val instanceof String) { + try { + return new BigDecimal(val.toString()).intValue(); + } catch (Exception e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get the enum value associated with a key. + * + * @param the enum class + * @param clazz The type of enum to retrieve + * @param index The index must be between 0 and length() - 1 + * @return The enum value at the index location or null if not found + */ + public > E optEnum(Class clazz, int index) { + return this.optEnum(clazz, index, null); + } + + /** + * Get the enum value associated with a key. + * + * @param the enum class + * @param clazz The type of enum to retrieve + * @param index The index must be between 0 and length() - 1 + * @param defaultValue The default in case the value is not found + * @return The enum value at the index location or defaultValue if the value is not found or cannot be assigned to clazz + */ + public > E optEnum(Class clazz, int index, E defaultValue) { + try { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (clazz.isAssignableFrom(val.getClass())) { + // we just checked it! + @SuppressWarnings("unchecked") + E myE = (E) val; + return myE; + } + return Enum.valueOf(clazz, val.toString()); + } catch (IllegalArgumentException | NullPointerException e) { + return defaultValue; + } + } + + /** + * Get the optional BigInteger value associated with an index. The defaultValue is returned if there is no value for the index, or if the value is + * not a number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1 + * @param defaultValue The default value. + * @return The value + */ + public BigInteger optBigInteger(int index, BigInteger defaultValue) { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof BigInteger) { + return (BigInteger) val; + } + if (val instanceof BigDecimal) { + return ((BigDecimal) val).toBigInteger(); + } + if (val instanceof Double || val instanceof Float) { + return new BigDecimal(((Number) val).doubleValue()).toBigInteger(); + } + if (val instanceof Long || val instanceof Integer + || val instanceof Short || val instanceof Byte) { + return BigInteger.valueOf(((Number) val).longValue()); + } + try { + final String valStr = val.toString(); + if (JSONObject.isDecimalNotation(valStr)) { + return new BigDecimal(valStr).toBigInteger(); + } + return new BigInteger(valStr); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional BigDecimal value associated with an index. The defaultValue is returned if there is no value for the index, or if the value is + * not a number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1 + * @param defaultValue The default value + * @return The value + */ + public BigDecimal optBigDecimal(int index, BigDecimal defaultValue) { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof BigDecimal) { + return (BigDecimal) val; + } + if (val instanceof BigInteger) { + return new BigDecimal((BigInteger) val); + } + if (val instanceof Double || val instanceof Float) { + return new BigDecimal(((Number) val).doubleValue()); + } + if (val instanceof Long || val instanceof Integer + || val instanceof Short || val instanceof Byte) { + return new BigDecimal(((Number) val).longValue()); + } + try { + return new BigDecimal(val.toString()); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional JSONArray associated with an index. + * + * @param index subscript + * @return A JSONArray value, or null if the index has no value, or if the value is not a JSONArray. + */ + public JSONArray optJSONArray(int index) { + Object o = this.opt(index); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get the optional JSONObject associated with an index. Null is returned if the key is not found, or null if the index has no value, or if the + * value is not a JSONObject. + * + * @param index The index must be between 0 and length() - 1 + * @return A JSONObject value + */ + public JSONObject optJSONObject(int index) { + Object o = this.opt(index); + return o instanceof JSONObject ? (JSONObject) o : null; + } + + /** + * Get the optional long value associated with an index. Zero is returned if there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1 + * @return The value + */ + public long optLong(int index) { + return this.optLong(index, 0); + } + + /** + * Get the optional long value associated with an index. The defaultValue is returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1 + * @param defaultValue The default value + * @return The value + */ + public long optLong(int index, long defaultValue) { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number) { + return ((Number) val).longValue(); + } + + if (val instanceof String) { + try { + return new BigDecimal(val.toString()).longValue(); + } catch (Exception e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get an optional {@link Number} value associated with a key, or null if there is no such key or if the value is not a number. If the + * value is a string, an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method would be used in cases where type + * coercion of the number value is unwanted. + * + * @param index The index must be between 0 and length() - 1 + * @return An object which is the value + */ + public Number optNumber(int index) { + return this.optNumber(index, null); + } + + /** + * Get an optional {@link Number} value associated with a key, or the default if there is no such key or if the value is not a number. If the value + * is a string, an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method would be used in cases where type coercion of + * the number value is unwanted. + * + * @param index The index must be between 0 and length() - 1 + * @param defaultValue The default + * @return An object which is the value + */ + public Number optNumber(int index, Number defaultValue) { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number) { + return (Number) val; + } + + if (val instanceof String) { + try { + return JSONObject.stringToNumber((String) val); + } catch (NumberFormatException e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get the optional string value associated with an index. It returns an empty string if there is no value at that index. If the value is not a + * string and is not null, then it is converted to a string. + * + * @param index The index must be between 0 and length() - 1 + * @return A String value + */ + public String optString(int index) { + return this.optString(index, ""); + } + + /** + * Get the optional string associated with an index. The defaultValue is returned if the key is not found. + * + * @param index The index must be between 0 and length() - 1 + * @param defaultValue The default value + * @return A String value + */ + public String optString(int index, String defaultValue) { + Object object = this.opt(index); + return JSONObject.NULL.equals(object) ? defaultValue : object.toString(); + } + + /** + * Append a boolean value. This increases the array's length by one. + * + * @param value A boolean value + * @return this + */ + public JSONArray put(boolean value) { + return this.put(value ? Boolean.TRUE : Boolean.FALSE); + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which is produced from a Collection. + * + * @param value A Collection value + * @return this + * @throws JSONException If the value is non-finite number + */ + public JSONArray put(Collection value) { + return this.put(new JSONArray(value)); + } + + /** + * Append a double value. This increases the array's length by one. + * + * @param value A double value + * @return this + * @throws JSONException if the value is not finite + */ + public JSONArray put(double value) throws JSONException { + return this.put(Double.valueOf(value)); + } + + /** + * Append a float value. This increases the array's length by one. + * + * @param value A float value + * @return this + * @throws JSONException if the value is not finite + */ + public JSONArray put(float value) throws JSONException { + return this.put(Float.valueOf(value)); + } + + /** + * Append an int value. This increases the array's length by one. + * + * @param value An int value + * @return this + */ + public JSONArray put(int value) { + return this.put(Integer.valueOf(value)); + } + + /** + * Append an long value. This increases the array's length by one. + * + * @param value A long value + * @return this + */ + public JSONArray put(long value) { + return this.put(Long.valueOf(value)); + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject which is produced from a Map. + * + * @param value A Map value + * @return this + * @throws JSONException If a value in the map is non-finite number + * @throws NullPointerException If a key in the map is null + */ + public JSONArray put(Map value) { + return this.put(new JSONObject(value)); + } + + /** + * Append an object value. This increases the array's length by one. + * + * @param value An object value. The value should be a Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the JSONObject.NULL + * object. + * @return this + * @throws JSONException If the value is non-finite number + */ + public JSONArray put(Object value) { + JSONObject.testValidity(value); + this.array.add(value); + return this; + } + + /** + * Put or replace a boolean value in the JSONArray. If the index is greater than the length of the JSONArray, then null elements will be added as + * necessary to pad it out. + * + * @param index The subscript + * @param value A boolean value + * @return this + * @throws JSONException If the index is negative + */ + public JSONArray put(int index, boolean value) throws JSONException { + return this.put(index, value ? Boolean.TRUE : Boolean.FALSE); + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which is produced from a Collection. + * + * @param index The subscript + * @param value A Collection value + * @return this + * @throws JSONException If the index is negative or if the value is non-finite + */ + public JSONArray put(int index, Collection value) throws JSONException { + return this.put(index, new JSONArray(value)); + } + + /** + * Put or replace a double value. If the index is greater than the length of the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index The subscript + * @param value A double value + * @return this + * @throws JSONException If the index is negative or if the value is non-finite + */ + public JSONArray put(int index, double value) throws JSONException { + return this.put(index, Double.valueOf(value)); + } + + /** + * Put or replace a float value. If the index is greater than the length of the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index The subscript + * @param value A float value + * @return this + * @throws JSONException If the index is negative or if the value is non-finite + */ + public JSONArray put(int index, float value) throws JSONException { + return this.put(index, Float.valueOf(value)); + } + + /** + * Put or replace an int value. If the index is greater than the length of the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index The subscript + * @param value An int value + * @return this + * @throws JSONException If the index is negative + */ + public JSONArray put(int index, int value) throws JSONException { + return this.put(index, Integer.valueOf(value)); + } + + /** + * Put or replace a long value. If the index is greater than the length of the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index The subscript + * @param value A long value + * @return this + * @throws JSONException If the index is negative + */ + public JSONArray put(int index, long value) throws JSONException { + return this.put(index, Long.valueOf(value)); + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject that is produced from a Map. + * + * @param index The subscript + * @param value The Map value + * @return this + * @throws JSONException If the index is negative or if the the value is an invalid number + * @throws NullPointerException If a key in the map is null + */ + public JSONArray put(int index, Map value) throws JSONException { + this.put(index, new JSONObject(value)); + return this; + } + + /** + * Put or replace an object value in the JSONArray. If the index is greater than the length of the JSONArray, then null elements will be added as + * necessary to pad it out. + * + * @param index The subscript + * @param value The value to put into the array. The value should be a Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the + * JSONObject.NULL object. + * @return this + * @throws JSONException If the index is negative or if the the value is an invalid number + */ + public JSONArray put(int index, Object value) throws JSONException { + if (index < 0) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + if (index < this.length()) { + this.array.set(index, value); + } else if (index == this.length()) { + // simple append + return this.put(value); + } + // if we are inserting past the length, we want to grow the array all at once + // instead of incrementally. + this.array.ensureCapacity(index + 1); + while (index != this.length()) { + // we don't need to test validity of NULL objects + this.put(JSONObject.NULL); + } + return this.put(value); + } + + /** + * Creates a JSONPointer using an initialization string and tries to match it to an item within this JSONArray. For example, given a JSONArray + * initialized with this document: + *
+    * [
+    *     {"b":"c"}
+    * ]
+    * 
and this JSONPointer string: + *
+    * "/0/b"
+    * 
Then this method will return the String "c" A JSONPointerException may be thrown from code called by this method. + * + * @param jsonPointer string that can be used to create a JSONPointer + * @return the item matched by the JSONPointer, otherwise null + */ + @Override + public Object query(String jsonPointer) { + return query(new JSONPointer(jsonPointer)); + } + + /** + * Uses a uaer initialized JSONPointer and tries to match it to an item whithin this JSONArray. For example, given a JSONArray initialized with + * this document: + *
+    * [
+    *     {"b":"c"}
+    * ]
+    * 
and this JSONPointer: + *
+    * "/0/b"
+    * 
Then this method will return the String "c" A JSONPointerException may be thrown from code called by this method. + * + * @param jsonPointer string that can be used to create a JSONPointer + * @return the item matched by the JSONPointer, otherwise null + */ + @Override + public Object query(JSONPointer jsonPointer) { + return jsonPointer.queryFrom(this); + } + + /** + * Queries and returns a value from this object using {@code jsonPointer}, or returns null if the query fails due to a missing key. + * + * @param jsonPointer the string representation of the JSON pointer + * @return the queried value or {@code null} + * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax + */ + public Object optQuery(String jsonPointer) { + return optQuery(new JSONPointer(jsonPointer)); + } + + /** + * Queries and returns a value from this object using {@code jsonPointer}, or returns null if the query fails due to a missing key. + * + * @param jsonPointer The JSON pointer + * @return the queried value or {@code null} + * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax + */ + public Object optQuery(JSONPointer jsonPointer) { + try { + return jsonPointer.queryFrom(this); + } catch (JSONPointerException e) { + return null; + } + } + + /** + * Remove an index and close the hole. + * + * @param index The index of the element to be removed + * @return The value that was associated with the index, or null if there was no value + */ + public Object remove(int index) { + return index >= 0 && index < this.length() ? this.array.remove(index) : null; + } + + /** + * Determine if two JSONArrays are similar. They must contain similar sequences. + * + * @param other The other JSONArray + * @return true if they are equal + */ + public boolean similar(Object other) { + if (!(other instanceof JSONArray)) { + return false; + } + int len = this.length(); + if (len != ((JSONArray) other).length()) { + return false; + } + for (int i = 0; i < len; i += 1) { + Object valueThis = this.array.get(i); + Object valueOther = ((JSONArray) other).array.get(i); + if (valueThis == valueOther) { + continue; + } + if (valueThis == null) { + return false; + } + if (valueThis instanceof JSONObject) { + if (!((JSONObject) valueThis).similar(valueOther)) { + return false; + } + } else if (valueThis instanceof JSONArray) { + if (!((JSONArray) valueThis).similar(valueOther)) { + return false; + } + } else if (!valueThis.equals(valueOther)) { + return false; + } + } + return true; + } + + /** + * Produce a JSONObject by combining a JSONArray of names with the values of this JSONArray. + * + * @param names A JSONArray containing a list of key strings. These will be paired with the values + * @return A JSONObject, or null if there are no names or if this JSONArray has no values + * @throws JSONException If any of the names are null + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + if (names == null || names.isEmpty() || this.isEmpty()) { + return null; + } + JSONObject jo = new JSONObject(names.length()); + for (int i = 0; i < names.length(); i += 1) { + jo.put(names.getString(i), this.opt(i)); + } + return jo; + } + + /** + * Make a JSON text of this JSONArray. For compactness, no unnecessary whitespace is added. If it is not possible to produce a syntactically + * correct JSON text then null will be returned instead. This could occur if the array contains an invalid number. + *

+ * + * Warning: This method assumes that the data structure is acyclical. + * + * + * @return a printable, displayable, transmittable representation of the array. + */ + @Override + public String toString() { + try { + return this.toString(-1); + } catch (JSONException e) { + return null; + } + } + + /** + * Make a pretty-printed JSON text of this JSONArray. + * + *

+ * If indentFactor > 0 and the {@link JSONArray} has only one element, then the array will be output on a single line: + *

{@code [1]}
+ * + *

+ * If an array has 2 or more elements, then it will be output across multiple lines: + *

{@code
+    * [
+    * 1,
+    * "value 2",
+    * 3
+    * ]
+    * }
+ *

+ * + * Warning: This method assumes that the data structure is acyclical. + * + * + * @param indentFactor The number of spaces to add to each level of indentation + * @return a printable, displayable, transmittable representation of the object, beginning with [ (left bracket) + * and ending with ]  (right bracket). + * @throws JSONException + */ + @Override + public String toString(int indentFactor) throws JSONException { + StringWriter sw = new StringWriter(); + JSONOptions options; + if (indentFactor == -1) { + options = JSONOptions.getDefault(); + } else { + options = JSONOptions.createOptions().setIndentFactor(indentFactor); + } + synchronized (sw.getBuffer()) { + return this.write(sw, 0, options).toString(); + } + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. For compactness, no whitespace is added. + *

+ * + * Warning: This method assumes that the data structure is acyclical. + * + * + * @param writer the writer + * @return The writer. + * @throws JSONException + */ + @Override + public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0); + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. + * + * @param writer Writes the serialized JSON + * @param indent The indentation of the top level + * @return The writer + * @throws JSONException + */ + @Override + public Writer write(Writer writer, int indent) throws JSONException { + return write(writer, indent, null); + } + + private boolean shouldIndent(int length, JSONOptions options) { + if (length == 1) { + switch (options.getIndentationType()) { + case JSONOptions.INDENT_ALL: + return true; + case JSONOptions.INDENT_DEFAULT: + return false; + case JSONOptions.INDENT_OBJECTS: + Object value = array.get(0); + return value instanceof JSONElement; + default: + return false; + } + } else { + return true; + } + } + + private boolean isPrimitivesOnly() { + int size = array.size(); + if (size == 0) { + return true; + } else { + boolean isPrim = true; + for (int i = 0; i < size; i++) { + Object o = array.get(i); + if (o instanceof JSONElement) { + isPrim = false; + break; + } + } + return isPrim; + } + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. + * + *

+ * If indentFactor > 0 and the {@link JSONArray} has only one element, then the array will be output on a single line: + *

{@code [1]}
+ * + *

+ * If an array has 2 or more elements, then it will be output across multiple lines: + *

{@code
+    * [
+    * 1,
+    * "value 2",
+    * 3
+    * ]
+    * }
+ *

+ * + * Warning: This method assumes that the data structure is acyclical. + * + * + * @param writer Writes the serialized JSON + * @param indent The indentation of the top level + * @param isValue true to write a value + * @param options true if the natural ordering of the keys must be kept + * @return The writer + * @throws JSONException + */ + @Override + public Writer write(Writer writer, int indent, boolean isValue, JSONOptions options) throws JSONException { + try { + options = JSONOptions.getOptions(options); + int indentFactor = options.getIndentFactor(); + boolean commanate = false; + int length = this.length(); + writer.write('['); + + boolean isPrimitivesOnly = isPrimitivesOnly(); + if (length != 0) { + boolean shouldIndent = shouldIndent(length, options); + if (!shouldIndent) { + try { + if (isPrimitivesOnly) { + for (int i = 0; i < length; i += 1) { + JSONObject.writeValue(writer, this.array.get(i), 0, options); + } + } else { + for (int i = 0; i < length; i += 1) { + JSONObject.writeValue(writer, this.array.get(i), indent, options); + } + } + } catch (IOException | JSONException e) { + throw new JSONException("Unable to write JSONArray value at index: 0", e); + } + } else { + int newindent = indent; + if (isPrimitivesOnly) { + for (int i = 0; i < length; i += 1) { + if (commanate) { + writer.write(", "); + } + try { + JSONObject.writeValue(writer, this.array.get(i), 0, options); + } catch (IOException | JSONException e) { + throw new JSONException("Unable to write JSONArray value at index: " + i, e); + } + commanate = true; + } + } else { + for (int i = 0; i < length; i += 1) { + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + newindent = indent + indentFactor; + try { + JSONObject.writeValue(writer, this.array.get(i), newindent, options); + } catch (IOException | JSONException e) { + throw new JSONException("Unable to write JSONArray value at index: " + i, e); + } + commanate = true; + } + } + if (!isPrimitivesOnly) { + if (indentFactor > 0) { + writer.write('\n'); + } + JSONObject.indent(writer, indent); + } + } + } + writer.write(']'); + return writer; + } catch (IOException e) { + throw new JSONException(e); + } + } + + /** + * Returns a java.util.List containing all of the elements in this array. If an element in the array is a JSONArray or JSONObject it will also be + * converted. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a java.util.List containing the elements of this array + */ + public List toList() { + List results = new ArrayList<>(this.array.size()); + for (Object element : this.array) { + if (element == null || JSONObject.NULL.equals(element)) { + results.add(null); + } else if (element instanceof JSONArray) { + results.add(((JSONArray) element).toList()); + } else if (element instanceof JSONObject) { + results.add(((JSONObject) element).toMap()); + } else { + results.add(element); + } + } + return results; + } + + /** + * Check if JSONArray is empty. + * + * @return true if JSONArray is empty, otherwise false. + */ + @Override + public boolean isEmpty() { + return array.isEmpty(); + } + +} diff --git a/src/org/json/JSONElement.java b/src/org/json/JSONElement.java new file mode 100644 index 000000000..93c0652ed --- /dev/null +++ b/src/org/json/JSONElement.java @@ -0,0 +1,143 @@ +/* +Copyright (C) 2018, 2019, 2020, 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import java.io.Writer; + +/** + * Represents a JSON element. + * + * @version 1.7.5 + */ +public interface JSONElement extends Cloneable { + /** + * Get the length of the the JSONElement. + * + * @return the length + */ + public int length(); + /** + * Check if JSONObject is empty. + * + * @return true if JSONElement is empty, otherwise false + */ + public boolean isEmpty(); + + /** + * Creates a JSONElement from a String. + * + * @param value the String value + * @return JSONElement + * @throws JSONException if the element if not a JSONArray nor a JSONObject + */ + public static JSONElement fromString(String value) throws JSONException { + value = value.trim(); + if (value.startsWith("[")) { + JSONArray jsonArray = new JSONArray(value); + return jsonArray; + } else { + JSONObject jsonObject = new JSONObject(value); + return jsonObject; + } + } + + /** + * Clone the element. + * + * @return the cloned element + */ + public JSONElement clone(); + + /** + * Creates a JSONPointer using an initialization string and tries to match it to an item within this JSONElement. + * + * @param jsonPointer string that can be used to create a JSONPointer + * @return the item matched by the JSONPointer, otherwise null + */ + public Object query(String jsonPointer); + + /** + * Uses a user initialized JSONPointer and tries to match it to an item within this JSONElement. + * + * @param jsonPointer string that can be used to create a JSONPointer + * @return the item matched by the JSONPointer, otherwise null + */ + public Object query(JSONPointer jsonPointer); + + /** + * Make a pretty-printed JSON text of this JSONElement. + * + * @param indentFactor the indentation Factor + * @return the pretty-printed JSON text + * @throws JSONException + */ + public String toString(int indentFactor) throws JSONException; + + /** + * Write the contents of the JSONObject as JSON text to a writer. For compactness, no whitespace is added. + *

+ * + * Warning: This method assumes that the data structure is acyclical. + * + * + * @param writer the writer + * @return The writer. + * @throws JSONException if the JSONElement was unable to be serialized + */ + public Writer write(Writer writer) throws JSONException; + + /** + * Write the contents of the JSONElement as JSON text to a writer, without keeping the keys natural order. + * + * + * @param writer Writes the serialized JSON + * @param indent The indentation of the top level + * @return The writer + * @throws JSONException if the JSONElement was unable to be serialized + */ + public Writer write(Writer writer, int indent) throws JSONException; + + /** + * Write the contents of the JSONElement as JSON text to a writer. + * + * @param writer Writes the serialized JSON + * @param indent The indentation of the top level + * @param options the JSON configuration options + * @return The writer + * @throws JSONException if the JSONElement was unable to be serialized + */ + public default Writer write(Writer writer, int indent, JSONOptions options) throws JSONException { + return write(writer, indent, false, options); + } + + /** + * Write the contents of the JSONElement as JSON text to a writer. + * + * @param writer Writes the serialized JSON + * @param indent The indentation of the top level + * @param isValue true to write a value + * @param options the JSON configuration options + * @return The writer + * @throws JSONException if the JSONElement was unable to write + */ + public Writer write(Writer writer, int indent, boolean isValue, JSONOptions options) throws JSONException; +} diff --git a/JSONException.java b/src/org/json/JSONException.java similarity index 100% rename from JSONException.java rename to src/org/json/JSONException.java diff --git a/src/org/json/JSONML.java b/src/org/json/JSONML.java new file mode 100644 index 000000000..5a04b4112 --- /dev/null +++ b/src/org/json/JSONML.java @@ -0,0 +1,520 @@ +package org.json; + +/* +Copyright (c) 2008 JSON.org +Copyright (c) 2019 Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +/** + * This provides static methods to convert an XML text into a JSONArray or + * JSONObject, and to convert a JSONArray or JSONObject into an XML text using + * the JsonML transform. + * + * @version 1.4 + */ +public class JSONML { + /** + * Parse XML values and store them in a JSONArray. + * + * @param x The XMLTokener containing the source string. + * @param arrayForm true if array form, false if object form. + * @param ja The JSONArray that is containing the current tag or null if we are at the outermost level. + * @param keepStringsDon't type-convert text nodes and attribute values + * @return A JSONArray if the value is the outermost tag, otherwise null. + * @throws JSONException + */ + private static Object parse(XMLTokener x, boolean arrayForm, JSONArray ja, boolean keepStrings) throws JSONException { + String attribute; + char c; + String closeTag; + int i; + JSONArray newja; + JSONObject newjo; + Object token; + String tagName; + +// Test for and skip past these forms: +// +// +// +// + while (true) { + if (!x.more()) { + throw x.syntaxError("Bad XML"); + } + token = x.nextContent(); + if (token == XML.LT) { + token = x.nextToken(); + if (token instanceof Character) { + if (token == XML.SLASH) { + +// Close tag "); + } else { + x.back(); + } + } else if (c == '[') { + token = x.nextToken(); + if (token.equals("CDATA") && x.next() == '[') { + if (ja != null) { + ja.put(x.nextCDATA()); + } + } else { + throw x.syntaxError("Expected 'CDATA['"); + } + } else { + i = 1; + do { + token = x.nextMeta(); + if (token == null) { + throw x.syntaxError("Missing '>' after ' 0); + } + } else if (token == XML.QUEST) { + +// "); + } else { + throw x.syntaxError("Misshaped tag"); + } + +// Open tag < + } else { + if (!(token instanceof String)) { + throw x.syntaxError("Bad tagName '" + token + "'."); + } + tagName = (String) token; + newja = new JSONArray(); + newjo = new JSONObject(); + if (arrayForm) { + newja.put(tagName); + if (ja != null) { + ja.put(newja); + } + } else { + newjo.put("tagName", tagName); + if (ja != null) { + ja.put(newjo); + } + } + token = null; + for (;;) { + if (token == null) { + token = x.nextToken(); + } + if (token == null) { + throw x.syntaxError("Misshaped tag"); + } + if (!(token instanceof String)) { + break; + } + +// attribute = value + attribute = (String) token; + if (!arrayForm && ("tagName".equals(attribute) || "childNode".equals(attribute))) { + throw x.syntaxError("Reserved attribute."); + } + token = x.nextToken(); + if (token == XML.EQ) { + token = x.nextToken(); + if (!(token instanceof String)) { + throw x.syntaxError("Missing value"); + } + newjo.accumulate(attribute, keepStrings ? ((String) token) : XML.stringToValue((String) token)); + token = null; + } else { + newjo.accumulate(attribute, ""); + } + } + if (arrayForm && newjo.length() > 0) { + newja.put(newjo); + } + +// Empty tag <.../> + if (token == XML.SLASH) { + if (x.nextToken() != XML.GT) { + throw x.syntaxError("Misshaped tag"); + } + if (ja == null) { + if (arrayForm) { + return newja; + } + return newjo; + } + +// Content, between <...> and + } else { + if (token != XML.GT) { + throw x.syntaxError("Misshaped tag"); + } + closeTag = (String) parse(x, arrayForm, newja, keepStrings); + if (closeTag != null) { + if (!closeTag.equals(tagName)) { + throw x.syntaxError("Mismatched '" + tagName + "' and '" + closeTag + "'"); + } + tagName = null; + if (!arrayForm && newja.length() > 0) { + newjo.put("childNodes", newja); + } + if (ja == null) { + if (arrayForm) { + return newja; + } + return newjo; + } + } + } + } + } else { + if (ja != null) { + ja.put(token instanceof String + ? keepStrings ? XML.unescape((String) token) : XML.stringToValue((String) token) + : token); + } + } + } + } + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONArray using the JsonML transform. Each XML tag is represented as + * a JSONArray in which the first element is the tag name. If the tag has + * attributes, then the second element will be JSONObject containing the + * name/value pairs. If the tag contains children, then strings and + * JSONArrays will represent the child tags. + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * + * @param string The source string. + * @return A JSONArray containing the structured data from the XML string. + * @throws JSONException Thrown on error converting to a JSONArray + */ + public static JSONArray toJSONArray(String string) throws JSONException { + return (JSONArray) parse(new XMLTokener(string), true, null, false); + } + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONArray using the JsonML transform. Each XML tag is represented as + * a JSONArray in which the first element is the tag name. If the tag has + * attributes, then the second element will be JSONObject containing the + * name/value pairs. If the tag contains children, then strings and + * JSONArrays will represent the child tags. + * As opposed to toJSONArray this method does not attempt to convert + * any text node or attribute value to any type + * but just leaves it as a string. + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * + * @param string The source string. + * @param keepStrings If true, then values will not be coerced into boolean + * or numeric values and will instead be left as strings + * @return A JSONArray containing the structured data from the XML string. + * @throws JSONException Thrown on error converting to a JSONArray + */ + public static JSONArray toJSONArray(String string, boolean keepStrings) throws JSONException { + return (JSONArray) parse(new XMLTokener(string), true, null, keepStrings); + } + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONArray using the JsonML transform. Each XML tag is represented as + * a JSONArray in which the first element is the tag name. If the tag has + * attributes, then the second element will be JSONObject containing the + * name/value pairs. If the tag contains children, then strings and + * JSONArrays will represent the child content and tags. + * As opposed to toJSONArray this method does not attempt to convert + * any text node or attribute value to any type + * but just leaves it as a string. + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * + * @param x An XMLTokener. + * @param keepStrings If true, then values will not be coerced into boolean + * or numeric values and will instead be left as strings + * @return A JSONArray containing the structured data from the XML string. + * @throws JSONException Thrown on error converting to a JSONArray + */ + public static JSONArray toJSONArray(XMLTokener x, boolean keepStrings) throws JSONException { + return (JSONArray) parse(x, true, null, keepStrings); + } + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONArray using the JsonML transform. Each XML tag is represented as + * a JSONArray in which the first element is the tag name. If the tag has + * attributes, then the second element will be JSONObject containing the + * name/value pairs. If the tag contains children, then strings and + * JSONArrays will represent the child content and tags. + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * + * @param x An XMLTokener. + * @return A JSONArray containing the structured data from the XML string. + * @throws JSONException Thrown on error converting to a JSONArray + */ + public static JSONArray toJSONArray(XMLTokener x) throws JSONException { + return (JSONArray) parse(x, true, null, false); + } + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONObject using the JsonML transform. Each XML tag is represented as + * a JSONObject with a "tagName" property. If the tag has attributes, then + * the attributes will be in the JSONObject as properties. If the tag + * contains children, the object will have a "childNodes" property which + * will be an array of strings and JsonML JSONObjects. + * + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * + * @param string The XML source text. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown on error converting to a JSONObject + */ + public static JSONObject toJSONObject(String string) throws JSONException { + return (JSONObject) parse(new XMLTokener(string), false, null, false); + } + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONObject using the JsonML transform. Each XML tag is represented as + * a JSONObject with a "tagName" property. If the tag has attributes, then + * the attributes will be in the JSONObject as properties. If the tag + * contains children, the object will have a "childNodes" property which + * will be an array of strings and JsonML JSONObjects. + * + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * + * @param string The XML source text. + * @param keepStrings If true, then values will not be coerced into boolean + * or numeric values and will instead be left as strings + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown on error converting to a JSONObject + */ + public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { + return (JSONObject) parse(new XMLTokener(string), false, null, keepStrings); + } + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONObject using the JsonML transform. Each XML tag is represented as + * a JSONObject with a "tagName" property. If the tag has attributes, then + * the attributes will be in the JSONObject as properties. If the tag + * contains children, the object will have a "childNodes" property which + * will be an array of strings and JsonML JSONObjects. + * + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * + * @param x An XMLTokener of the XML source text. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown on error converting to a JSONObject + */ + public static JSONObject toJSONObject(XMLTokener x) throws JSONException { + return (JSONObject) parse(x, false, null, false); + } + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONObject using the JsonML transform. Each XML tag is represented as + * a JSONObject with a "tagName" property. If the tag has attributes, then + * the attributes will be in the JSONObject as properties. If the tag + * contains children, the object will have a "childNodes" property which + * will be an array of strings and JsonML JSONObjects. + * + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * + * @param x An XMLTokener of the XML source text. + * @param keepStrings If true, then values will not be coerced into boolean + * or numeric values and will instead be left as strings + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown on error converting to a JSONObject + */ + public static JSONObject toJSONObject(XMLTokener x, boolean keepStrings) throws JSONException { + return (JSONObject) parse(x, false, null, keepStrings); + } + + /** + * Reverse the JSONML transformation, making an XML text from a JSONArray. + * + * @param ja A JSONArray. + * @return An XML string. + * @throws JSONException Thrown on error converting to a string + */ + public static String toString(JSONArray ja) throws JSONException { + int i; + JSONObject jo; + int length; + Object object; + StringBuilder sb = new StringBuilder(); + String tagName; + +// Emit = length) { + sb.append('/'); + sb.append('>'); + } else { + sb.append('>'); + do { + object = ja.get(i); + i += 1; + if (object != null) { + if (object instanceof String) { + sb.append(XML.escape(object.toString())); + } else if (object instanceof JSONObject) { + sb.append(toString((JSONObject) object)); + } else if (object instanceof JSONArray) { + sb.append(toString((JSONArray) object)); + } else { + sb.append(object.toString()); + } + } + } while (i < length); + sb.append('<'); + sb.append('/'); + sb.append(tagName); + sb.append('>'); + } + return sb.toString(); + } + + /** + * Reverse the JSONML transformation, making an XML text from a JSONObject. + * The JSONObject must contain a "tagName" property. If it has children, + * then it must have a "childNodes" property containing an array of objects. + * The other properties are attributes with string values. + * + * @param jo A JSONObject. + * @return An XML string. + * @throws JSONException Thrown on error converting to a string + */ + public static String toString(JSONObject jo) throws JSONException { + StringBuilder sb = new StringBuilder(); + int i; + JSONArray ja; + int length; + Object object; + String tagName; + Object value; + +//Emit '); + } else { + sb.append('>'); + length = ja.length(); + for (i = 0; i < length; i += 1) { + object = ja.get(i); + if (object != null) { + if (object instanceof String) { + sb.append(XML.escape(object.toString())); + } else if (object instanceof JSONObject) { + sb.append(toString((JSONObject) object)); + } else if (object instanceof JSONArray) { + sb.append(toString((JSONArray) object)); + } else { + sb.append(object.toString()); + } + } + } + sb.append('<'); + sb.append('/'); + sb.append(tagName); + sb.append('>'); + } + return sb.toString(); + } +} diff --git a/src/org/json/JSONObject.java b/src/org/json/JSONObject.java new file mode 100644 index 000000000..638403f9b --- /dev/null +++ b/src/org/json/JSONObject.java @@ -0,0 +1,2561 @@ +package org.json; + +/* + Copyright (c) 2002 JSON.org + Copyright (C) 2021 by Herve Girod + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ +import java.io.Closeable; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.TreeMap; + +/** + * A JSONObject is an unordered collection of name/value pairs. Its external form is a string wrapped in curly braces with colons between the names + * and values, and commas between the values and names. The internal form is an object having get and opt methods for + * accessing the values by name, and put methods for adding or replacing values by name. The values can be any of these types: + * Boolean, JSONArray, JSONObject, Number, String, or the + * JSONObject.NULL object. A JSONObject constructor can be used to convert an external form JSON text into an internal form whose values + * can be retrieved with the get and opt methods, or to convert values into a JSON text using the put and + * toString methods. A get method returns a value if one can be found, and throws an exception if one cannot be found. An + * opt method returns a default value instead of throwing an exception, and so is useful for obtaining optional values. + *

+ * The generic get() and opt() methods return an object, which you can cast or query for type. There are also typed + * get and opt methods that do type checking and type coercion for you. The opt methods differ from the get methods in that + * they do not throw. Instead, they return a specified value, such as null. + *

+ * The put methods add or replace values in an object. For example, + * + *

+ * myString = new JSONObject().put("JSON", "Hello, World!").toString();
+ * 
+ * + * produces the string {"JSON": "Hello, World"}. + *

+ * The texts produced by the toString methods strictly conform to the JSON syntax rules. The constructors are more forgiving in the texts + * they will accept: + *

    + *
  • An extra , (comma) may appear just before the closing brace.
  • + *
  • Strings may be quoted with ' (single quote).
  • + *
  • Strings do not need to be quoted at all if they do not begin with a quote or single quote, and if they do not contain leading or trailing + * spaces, and if they do not contain any of these characters: { } [ ] / \ : , # and if they do not look like numbers and if they are not + * the reserved words true, false, or null.
  • + *
+ * + * @author JSON.org + * @version 1.7.5 + */ +public class JSONObject implements JSONElement { + /** + * JSONObject.NULL is equivalent to the value that JavaScript calls null, whilst Java's null is equivalent to the value that JavaScript calls + * undefined. + */ + private static final class Null { + + /** + * There is only intended to be a single instance of the NULL object, so the clone method returns itself. + * + * @return NULL. + */ + @Override + protected final Object clone() { + return this; + } + + /** + * A Null object is equal to the null value and to itself. + * + * @param object An object to test for nullness. + * @return true if the object parameter is the JSONObject.NULL object or null. + */ + @Override + public boolean equals(Object object) { + return object == null || object == this; + } + + /** + * A Null object is equal to the null value and to itself. + * + * @return always returns 0. + */ + @Override + public int hashCode() { + return 0; + } + + /** + * Get the "null" string value. + * + * @return The string "null". + */ + @Override + public String toString() { + return "null"; + } + } + + /** + * The map where the JSONObject's properties are kept. + */ + private final Map map; + + /** + * It is sometimes more convenient and less ambiguous to have a NULL object than to use Java's null value. + * JSONObject.NULL.equals(null) returns true. JSONObject.NULL.toString() returns "null". + */ + public static final Object NULL = new Null(); + + /** + * Construct an empty JSONObject. + */ + public JSONObject() { + // HashMap is used on purpose to ensure that elements are unordered by + // the specification. + // JSON tends to be a portable transfer format to allows the container + // implementations to rearrange their items for a faster element + // retrieval based on associative access. + // Therefore, an implementation mustn't rely on the order of the item. + this.map = new HashMap<>(); + } + + /** + * Construct a JSONObject from a subset of another JSONObject. An array of strings is used to identify the keys that should be copied. Missing keys + * are ignored. + * + * @param jo A JSONObject + * @param names An array of strings + */ + public JSONObject(JSONObject jo, String[] names) { + this(names.length); + for (int i = 0; i < names.length; i += 1) { + try { + this.putOnce(names[i], jo.opt(names[i])); + } catch (JSONException ignore) { + } + } + } + + /** + * Construct a JSONObject from a JSONTokener. + * + * @param x A JSONTokener object containing the source string + * @throws JSONException If there is a syntax error in the source string or a duplicated key + */ + public JSONObject(JSONTokener x) throws JSONException { + this(); + char c; + String key; + + if (x.nextClean() != '{') { + throw x.syntaxError("A JSONObject text must begin with '{'"); + } + for (;;) { + c = x.nextClean(); + switch (c) { + case 0: + throw x.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + x.back(); + key = x.nextValue().toString(); + } + + // The key is followed by ':'. + c = x.nextClean(); + if (c != ':') { + throw x.syntaxError("Expected a ':' after a key"); + } + + // Use syntaxError(..) to include error location + if (key != null) { + // Check if key exists + if (this.opt(key) != null) { + // key already exists + throw x.syntaxError("Duplicate key \"" + key + "\""); + } + // Only add value if non-null + Object value = x.nextValue(); + if (value != null) { + this.put(key, value); + } + } + + // Pairs are separated by ','. + switch (x.nextClean()) { + case ';': + case ',': + if (x.nextClean() == '}') { + return; + } + x.back(); + break; + case '}': + return; + default: + throw x.syntaxError("Expected a ',' or '}'"); + } + } + } + + /** + * Construct a JSONObject from a Map. + * + * @param m A map object that can be used to initialize the contents of the JSONObject. + * @throws JSONException If a value in the map is non-finite number. + * @throws NullPointerException If a key in the map is null + */ + public JSONObject(Map m) { + if (m == null) { + this.map = new HashMap<>(); + } else { + this.map = new HashMap<>(m.size()); + for (final Entry e : m.entrySet()) { + if (e.getKey() == null) { + throw new NullPointerException("Null key."); + } + final Object value = e.getValue(); + if (value != null) { + this.map.put(String.valueOf(e.getKey()), wrap(value)); + } + } + } + } + + /** + * Construct a JSONObject from an Object using bean getters. It reflects on all of the public methods of the object. For each of the methods with + * no parameters and a name starting with "get" or "is" followed by an uppercase letter, the method is invoked, and a key + * and the value returned from the getter method are put into the new JSONObject. + *

+ * The key is formed by removing the "get" or "is" prefix. If the second remaining character is not upper case, then the + * first character is converted to lower case. + *

+ * Methods that are static, return void, have parameters, or are "bridge" methods, are ignored. + *

+ * For example, if an object has a method named "getName", and if the result of calling object.getName() is + * "Larry Fine", then the JSONObject will contain "name": "Larry Fine". + *

+ * The {@link JSONPropertyName} annotation can be used on a bean getter to override key name used in the JSONObject. For example, using the object + * above with the getName method, if we annotated it with: + *

+    * @JSONPropertyName("FullName")
+    * public String getName() { return this.name; }
+    * 
The resulting JSON object would contain "FullName": "Larry Fine" + *

+ * Similarly, the {@link JSONPropertyName} annotation can be used on non- get and is methods. We can also override key + * name used in the JSONObject as seen below even though the field would normally be ignored: + *

+    * @JSONPropertyName("FullName")
+    * public String fullName() { return this.name; }
+    * 
The resulting JSON object would contain "FullName": "Larry Fine" + *

+ * The {@link JSONPropertyIgnore} annotation can be used to force the bean property to not be serialized into JSON. If both + * {@link JSONPropertyIgnore} and {@link JSONPropertyName} are defined on the same method, a depth comparison is performed and the one closest to + * the concrete class being serialized is used. If both annotations are at the same level, then the {@link JSONPropertyIgnore} annotation takes + * precedent and the field is not serialized. For example, the following declaration would prevent the getName method from being + * serialized: + *

+    * @JSONPropertyName("FullName")
+    * @JSONPropertyIgnore
+    * public String getName() { return this.name; }
+    * 
+ *

+ * + * @param bean An object that has getter methods that should be used to make a JSONObject. + */ + public JSONObject(Object bean) { + this(); + this.populateMap(bean); + } + + /** + * Construct a JSONObject from an Object, using reflection to find the public members. The resulting JSONObject's keys will be the strings from the + * names array, and the values will be the field values associated with those keys in the object. If a key is not found or not visible, then it + * will not be copied into the new JSONObject. + * + * @param object An object that has fields that should be used to make a JSONObject. + * @param names An array of strings, the names of the fields to be obtained from the object. + */ + public JSONObject(Object object, String names[]) { + this(names.length); + Class c = object.getClass(); + for (int i = 0; i < names.length; i += 1) { + String name = names[i]; + try { + this.putOpt(name, c.getField(name).get(object)); + } catch (Exception ignore) { + } + } + } + + /** + * Construct a JSONObject from a source JSON text string. This is the most commonly used JSONObject constructor. + * + * @param source A string beginning with { (left brace) and ending with }  (right + * brace). + * @exception JSONException If there is a syntax error in the source string or a duplicated key. + */ + public JSONObject(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONObject from a ResourceBundle. + * + * @param baseName The ResourceBundle base name + * @param locale The Locale to load the ResourceBundle for + * @throws JSONException If any JSONExceptions are detected + */ + public JSONObject(String baseName, Locale locale) throws JSONException { + this(); + ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale, Thread.currentThread().getContextClassLoader()); + + // Iterate through the keys in the bundle. + Enumeration keys = bundle.getKeys(); + while (keys.hasMoreElements()) { + Object key = keys.nextElement(); + if (key != null) { + // Go through the path, ensuring that there is a nested JSONObject for each + // segment except the last. Add the value using the last segment's name into + // the deepest nested JSONObject. + String[] path = ((String) key).split("\\."); + int last = path.length - 1; + JSONObject target = this; + for (int i = 0; i < last; i += 1) { + String segment = path[i]; + JSONObject nextTarget = target.optJSONObject(segment); + if (nextTarget == null) { + nextTarget = new JSONObject(); + target.put(segment, nextTarget); + } + target = nextTarget; + } + target.put(path[last], bundle.getString((String) key)); + } + } + } + + /** + * Create a new JSONObject. + * + * @return the JSONObject + */ + public static JSONObject create() { + JSONObject obj = new JSONObject(); + return obj; + } + + /** + * Constructor to specify an initial capacity of the internal map. Useful for library internal calls where we know, or at least can best guess, how + * big this JSONObject will be. + * + * @param initialCapacity initial capacity of the internal map. + */ + protected JSONObject(int initialCapacity) { + this.map = new HashMap<>(initialCapacity); + } + + @Override + public JSONObject clone() { + JSONObject jsonobj = new JSONObject(map); + return jsonobj; + } + + /** + * Returns true if this JSONObject is equal to another. + * + * @param o the other object + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof JSONObject)) { + return false; + } + JSONObject other = (JSONObject) o; + if (other.map.size() != map.size()) { + return false; + } + Iterator it = map.keySet().iterator(); + while (it.hasNext()) { + String key = it.next(); + if (!other.map.containsKey(key)) { + return false; + } + Object value = map.get(key); + Object otherValue = other.map.get(key); + if (!isEqual(value, otherValue)) { + return false; + } + } + return true; + } + + private boolean isEqual(Object value1, Object value2) { + if (value1 instanceof Number && value2 instanceof Number) { + Number number1 = (Number) value1; + Number number2 = (Number) value2; + return number1.floatValue() == number2.floatValue(); + } else if (value1 instanceof Number && value2 instanceof String) { + Number number1 = (Number) value1; + String string2 = (String) value2; + try { + float number2 = Float.parseFloat(string2); + return number1.floatValue() == number2; + } catch (NumberFormatException e) { + return false; + } + } else if (value1 instanceof String && value2 instanceof Number) { + String string1 = (String) value1; + Number number2 = (Number) value2; + try { + float number1 = Float.parseFloat(string1); + return number1 == number2.floatValue(); + } catch (NumberFormatException e) { + return false; + } + } else { + return value1.equals(value2); + } + } + + @Override + public int hashCode() { + int hash = 5; + hash = 13 * hash + Objects.hashCode(this.map); + return hash; + } + + /** + * Accumulate values under a key. It is similar to the put method except that if there is already an object stored under the key then a JSONArray + * is stored under the key to hold all of the accumulated values. If there is already a JSONArray, then the new value is appended to it. In + * contrast, the put method replaces the previous value. + * + * If only one value is accumulated that is not a JSONArray, then the result will be the same as using put. But if multiple values are accumulated, + * then the result will be like append. + * + * @param key A key string. + * @param value An object to be accumulated under the key. + * @return this. + * @throws JSONException If the value is non-finite number. + * @throws NullPointerException If the key is null. + */ + public JSONObject accumulate(String key, Object value) throws JSONException { + testValidity(value); + Object object = this.opt(key); + if (object == null) { + this.put(key, value instanceof JSONArray ? new JSONArray().put(value) : value); + } else if (object instanceof JSONArray) { + ((JSONArray) object).put(value); + } else { + this.put(key, new JSONArray().put(object).put(value)); + } + return this; + } + + /** + * Append values to the array under a key. If the key does not exist in the JSONObject, then the key is put in the JSONObject with its value being + * a JSONArray containing the value parameter. If the key was already associated with a JSONArray, then the value parameter is appended to it. + * + * @param key A key string + * @param value An object to be accumulated under the key + * @return this + * @throws JSONException If the value is non-finite number or if the current value associated with the key is not a JSONArray. + * @throws NullPointerException If the key is null + */ + public JSONObject append(String key, Object value) throws JSONException { + testValidity(value); + Object object = this.opt(key); + if (object == null) { + this.put(key, new JSONArray().put(value)); + } else if (object instanceof JSONArray) { + this.put(key, ((JSONArray) object).put(value)); + } else { + throw new JSONException("JSONObject[" + key + "] is not a JSONArray."); + } + return this; + } + + /** + * Produce a string from a double. The string "null" will be returned if the number is not finite. + * + * @param d A double + * @return A String + */ + public static String doubleToString(double d) { + return doubleToString(d, false); + } + + /** + * Produce a string from a double. The string "null" will be returned if the number is not finite. + * + * @param d A double + * @param keepDecimalPoints if decimal points must be kept in numbers + * @return A String + */ + public static String doubleToString(double d, boolean keepDecimalPoints) { + if (Double.isInfinite(d) || Double.isNaN(d)) { + return "null"; + } + + // Shave off trailing zeros and decimal point, if possible. + String string = Double.toString(d); + if (!keepDecimalPoints) { + // Shave off trailing zeros and decimal point, if possible. + return shaveOffTrailingDecimalPoint(string); + } else { + return formatNumber(string); + } + } + + /** + * Get the value object associated with a key. + * + * @param key A key string + * @return The object associated with the key. + * @throws JSONException if the key is not found + */ + public Object get(String key) throws JSONException { + if (key == null) { + throw new JSONException("Null key."); + } + Object object = this.opt(key); + if (object == null) { + throw new JSONException("JSONObject[" + quote(key) + "] not found."); + } + return object; + } + + /** + * Get the enum value associated with a key. + * + * @param the enum class + * @param clazz The type of enum to retrieve + * @param key A key string + * @return The enum value associated with the key + * @throws JSONException if the key is not found or if the value cannot be converted to an enum + */ + public > E getEnum(Class clazz, String key) throws JSONException { + E val = optEnum(clazz, key); + if (val == null) { + // JSONException should really take a throwable argument. + // If it did, I would re-implement this with the Enum.valueOf + // method and place any thrown exception in the JSONException + throw new JSONException("JSONObject[" + quote(key) + "] is not an enum of type " + quote(clazz.getSimpleName()) + "."); + } + return val; + } + + /** + * Get the boolean value associated with a key. + * + * @param key A key string + * @return The boolean value + * @throws JSONException if the value is not a Boolean or the String "true" or "false" + */ + public boolean getBoolean(String key) throws JSONException { + Object object = this.get(key); + if (object.equals(Boolean.FALSE) || (object instanceof String && ((String) object).equalsIgnoreCase("false"))) { + return false; + } else if (object.equals(Boolean.TRUE) || (object instanceof String && ((String) object).equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("JSONObject[" + quote(key) + "] is not a Boolean."); + } + + /** + * Get the BigInteger value associated with a key. + * + * @param key A key string + * @return The numeric value + * @throws JSONException if the key is not found or if the value cannot be converted to BigInteger + */ + public BigInteger getBigInteger(String key) throws JSONException { + Object object = this.get(key); + try { + return new BigInteger(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + "] could not be converted to BigInteger.", e); + } + } + + /** + * Get the BigDecimal value associated with a key. + * + * @param key A key string + * @return The numeric value + * @throws JSONException if the key is not found or if the value cannot be converted to BigDecimal + */ + public BigDecimal getBigDecimal(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof BigDecimal) { + return (BigDecimal) object; + } + try { + return new BigDecimal(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + "] could not be converted to BigDecimal.", e); + } + } + + /** + * Get the double value associated with a key. + * + * @param key A key string + * @return The numeric value + * @throws JSONException if the key is not found or if the value is not a Number object and cannot be converted to a number + */ + public double getDouble(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number ? ((Number) object).doubleValue() : Double.parseDouble(object.toString()); + } catch (NumberFormatException e) { + throw new JSONException("JSONObject[" + quote(key) + "] is not a number.", e); + } + } + + /** + * Get the float value associated with a key. + * + * @param key A key string + * @return The numeric value + * @throws JSONException if the key is not found or if the value is not a Number object and cannot be converted to a number + */ + public float getFloat(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number ? ((Number) object).floatValue() : Float.parseFloat(object.toString()); + } catch (NumberFormatException e) { + throw new JSONException("JSONObject[" + quote(key) + "] is not a number.", e); + } + } + + /** + * Get the Number value associated with a key. + * + * @param key A key string + * @return The numeric value + * @throws JSONException if the key is not found or if the value is not a Number object and cannot be converted to a number + */ + public Number getNumber(String key) throws JSONException { + Object object = this.get(key); + try { + if (object instanceof Number) { + return (Number) object; + } + return stringToNumber(object.toString()); + } catch (NumberFormatException e) { + throw new JSONException("JSONObject[" + quote(key) + "] is not a number.", e); + } + } + + /** + * Get the int value associated with a key. + * + * @param key A key string + * @return The integer value + * @throws JSONException if the key is not found or if the value cannot be converted to an integer + */ + public int getInt(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number ? ((Number) object).intValue() : Integer.parseInt((String) object); + } catch (NumberFormatException e) { + throw new JSONException("JSONObject[" + quote(key) + "] is not an int.", e); + } + } + + /** + * Get the JSONArray value associated with a key. + * + * @param key A key string + * @return A JSONArray which is the value + * @throws JSONException if the key is not found or if the value is not a JSONArray + */ + public JSONArray getJSONArray(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof Null) { + return null; + } else if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONArray."); + } + + /** + * Get the JSONObject value associated with a key. + * + * @param key A key string + * @return A JSONObject which is the value + * @throws JSONException if the key is not found or if the value is not a JSONObject + */ + public JSONObject getJSONObject(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONObject."); + } + + /** + * Get the long value associated with a key. + * + * @param key A key string + * @return The long value + * @throws JSONException if the key is not found or if the value cannot be converted to a long + */ + public long getLong(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number ? ((Number) object).longValue() : Long.parseLong((String) object); + } catch (NumberFormatException e) { + throw new JSONException("JSONObject[" + quote(key) + "] is not a long.", e); + } + } + + /** + * Get an array of field names from a JSONObject. + * + * @param jo the JSONObject + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(JSONObject jo) { + if (jo.isEmpty()) { + return null; + } + return jo.keySet().toArray(new String[jo.length()]); + } + + /** + * Get an array of field names from an Object. + * + * @param object the Object + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(Object object) { + if (object == null) { + return null; + } + Class klass = object.getClass(); + Field[] fields = klass.getFields(); + int length = fields.length; + if (length == 0) { + return null; + } + String[] names = new String[length]; + for (int i = 0; i < length; i += 1) { + names[i] = fields[i].getName(); + } + return names; + } + + /** + * Get the string associated with a key. + * + * @param key A key string + * @return A string which is the value + * @throws JSONException if there is no string value for the key + */ + public String getString(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof Null) { + return null; + } else if (object instanceof String) { + return (String) object; + } else if (object instanceof Number) { + return ((Number) object).toString(); + } + throw new JSONException("JSONObject[" + quote(key) + "] not a string."); + } + + /** + * Determine if the JSONObject contains a specific key. + * + * @param key A key string + * @return true if the key exists in the JSONObject + */ + public boolean has(String key) { + return this.map.containsKey(key); + } + + /** + * Increment a property of a JSONObject. If there is no such property, create one with a value of 1. If there is such a property, and if it is an + * Integer, Long, Double, or Float, then add one to it. + * + * @param key A key string + * @return this + * @throws JSONException If there is already a property with this name that is not an Integer, Long, Double, or Float + */ + public JSONObject increment(String key) throws JSONException { + Object value = this.opt(key); + if (value == null) { + this.put(key, 1); + } else if (value instanceof BigInteger) { + this.put(key, ((BigInteger) value).add(BigInteger.ONE)); + } else if (value instanceof BigDecimal) { + this.put(key, ((BigDecimal) value).add(BigDecimal.ONE)); + } else if (value instanceof Integer) { + this.put(key, ((Integer) value) + 1); + } else if (value instanceof Long) { + this.put(key, ((Long) value) + 1L); + } else if (value instanceof Double) { + this.put(key, ((Double) value) + 1.0d); + } else if (value instanceof Float) { + this.put(key, ((Float) value) + 1.0f); + } else { + throw new JSONException("Unable to increment [" + quote(key) + "]."); + } + return this; + } + + /** + * Decrement a property of a JSONObject. If there is no such property, create one with a value of -1. If there is such a property, and if it is an + * Integer, Long, Double, or Float, then substract one to it. + * + * @param key A key string + * @return this + * @throws JSONException If there is already a property with this name that is not an Integer, Long, Double, or Float + */ + public JSONObject decrement(String key) throws JSONException { + Object value = this.opt(key); + if (value == null) { + this.put(key, -1); + } else if (value instanceof BigInteger) { + this.put(key, ((BigInteger) value).subtract(BigInteger.ONE)); + } else if (value instanceof BigDecimal) { + this.put(key, ((BigDecimal) value).subtract(BigDecimal.ONE)); + } else if (value instanceof Integer) { + this.put(key, ((Integer) value) - 1); + } else if (value instanceof Long) { + this.put(key, ((Long) value) - 1L); + } else if (value instanceof Double) { + this.put(key, ((Double) value) - 1.0d); + } else if (value instanceof Float) { + this.put(key, ((Float) value) - 1.0f); + } else { + throw new JSONException("Unable to decrement [" + quote(key) + "]."); + } + return this; + } + + /** + * Add a number to a property. If there is no such property, create one with the specified value. If the property is not a number, emit an + * exception. + * + * @param key the property key + * @param value the number to add + * @return this + * @throws JSONException if the property is not a number + */ + public JSONObject add(String key, Number value) throws JSONException { + Object _value = this.opt(key); + if (_value == null) { + return put(key, value); + } else if (_value instanceof Integer) { + int i = (Integer) _value + value.intValue(); + return put(key, i); + } else if (_value instanceof Long) { + long l = (Long) _value + value.longValue(); + return put(key, l); + } else if (_value instanceof Double) { + double d = (Double) _value + value.doubleValue(); + return put(key, d); + } else if (_value instanceof Float) { + float f = (Float) _value + value.floatValue(); + return put(key, f); + } else if (_value instanceof BigDecimal) { + _value = ((BigDecimal) _value).add(BigDecimal.valueOf(value.doubleValue())); + return put(key, _value); + } else if (_value instanceof BigInteger) { + _value = ((BigInteger) _value).add(BigInteger.valueOf(value.longValue())); + return put(key, _value); + } else { + throw new JSONException("Unable add a value for [" + quote(key) + "] because the existing value is not a number."); + } + } + + /** + * Substract a number to a property. If there is no such property, create one with the specified value. If the property is not a number, emit an + * exception. + * + * @param key the property key + * @param value the number to substract + * @return this + * @throws JSONException if the property is not a number + */ + public JSONObject substract(String key, Number value) throws JSONException { + Object _value = this.opt(key); + if (_value == null) { + return put(key, value); + } else if (_value instanceof Integer) { + int i = (Integer) _value - value.intValue(); + return put(key, i); + } else if (_value instanceof Long) { + long l = (Long) _value - value.longValue(); + return put(key, l); + } else if (_value instanceof Double) { + double d = (Double) _value - value.doubleValue(); + return put(key, d); + } else if (_value instanceof Float) { + float f = (Float) _value - value.floatValue(); + return put(key, f); + } else if (_value instanceof BigDecimal) { + _value = ((BigDecimal) _value).subtract(BigDecimal.valueOf(value.doubleValue())); + return put(key, _value); + } else if (_value instanceof BigInteger) { + _value = ((BigInteger) _value).subtract(BigInteger.valueOf(value.longValue())); + return put(key, _value); + } else { + throw new JSONException("Unable substract a value for [" + quote(key) + "] because the existing value is not a number."); + } + } + + /** + * Determine if the value associated with the key is null or if there is no value. + * + * @param key A key string + * @return true if there is no value associated with the key or if the value is the JSONObject.NULL object + */ + public boolean isNull(String key) { + return JSONObject.NULL.equals(this.opt(key)); + } + + /** + * Get an enumeration of the keys of the JSONObject. Modifying this key Set will also modify the JSONObject. Use with caution. + * + * @see Set#iterator() + * + * @return An iterator of the keys. + */ + public Iterator keys() { + return this.keySet().iterator(); + } + + /** + * Get a set of keys of the JSONObject. Modifying this key Set will also modify the JSONObject. Use with caution. + * + * @return A keySet + */ + public Set keySet() { + return this.map.keySet(); + } + + /** + * Get a collection of values of the JSONObject + * + * @return collection of values + */ + public Collection values() { + return this.map.values(); + } + + /** + * Get a set of entries of the JSONObject. These are raw values and may not match what is returned by the JSONObject get* and opt* functions. + * Modifying the returned EntrySet or the Entry objects contained therein will modify the backing JSONObject. This does not return a clone or a + * read-only view. + * + * Use with caution. + * + * @return An Entry Set + */ + protected Set> entrySet() { + return this.map.entrySet(); + } + + /** + * Get the number of keys stored in the JSONObject. + * + * @return The number of keys in the JSONObject. + */ + @Override + public int length() { + return this.map.size(); + } + + /** + * Check if JSONObject is empty. + * + * @return true if JSONObject is empty, otherwise false. + */ + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + /** + * Produce a JSONArray containing the names of the elements of this JSONObject. + * + * @return A JSONArray containing the key strings, or null if the JSONObject is empty. + */ + public JSONArray names() { + if (this.map.isEmpty()) { + return null; + } + return new JSONArray(this.map.keySet()); + } + + /** + * Produce a string from a Number. + * + * @param number A Number + * @return A String + * @throws JSONException If n is a non-finite number + */ + public static String numberToString(Number number) throws JSONException { + return numberToString(number, false); + } + + /** + * Produce a string from a Number. + * + * @param number A Number + * @param keepDecimalPoints if decimal points must be kept in numbers + * @return A String + * @throws JSONException If n is a non-finite number + */ + public static String numberToString(Number number, boolean keepDecimalPoints) throws JSONException { + if (number == null) { + throw new JSONException("Null pointer"); + } + testValidity(number); + + String string = number.toString(); + if (!keepDecimalPoints) { + // Shave off trailing zeros and decimal point, if possible. + return shaveOffTrailingDecimalPoint(string); + } else if (number instanceof Double || number instanceof Float || number instanceof BigDecimal) { + return formatNumber(string); + } else { + return shaveOffTrailingDecimalPoint(string); + } + } + + /** + * Shave off trailing zeros and decimal point, if possible. + * + * @param number the number + * @return the string + */ + private static String formatNumber(String string) { + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string + "0"; + } + } + return string; + } + + /** + * Shave off trailing zeros and decimal point, if possible. + * + * @param number the number + * @return the string + */ + private static String shaveOffTrailingDecimalPoint(String string) { + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get an optional value associated with a key. + * + * @param key A key string. + * @return An object which is the value, or null if there is no value. + */ + public Object opt(String key) { + return key == null ? null : this.map.get(key); + } + + /** + * Get the enum value associated with a key. + * + * @param The enum type subclass + * @param clazz The type of enum to retrieve. + * @param key A key string. + * @return The enum value associated with the key or null if not found + */ + public > E optEnum(Class clazz, String key) { + return this.optEnum(clazz, key, null); + } + + /** + * Get the enum value associated with a key. + * + * @param The enum type subclass + * @param clazz The type of enum to retrieve. + * @param key A key string. + * @param defaultValue The default in case the value is not found + * @return The enum value associated with the key or defaultValue if the value is not found or cannot be assigned to clazz + */ + public > E optEnum(Class clazz, String key, E defaultValue) { + try { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (clazz.isAssignableFrom(val.getClass())) { + // we just checked it! + @SuppressWarnings("unchecked") + E myE = (E) val; + return myE; + } + return Enum.valueOf(clazz, val.toString()); + } catch (IllegalArgumentException | NullPointerException e) { + return defaultValue; + } + } + + /** + * Get an optional boolean associated with a key. It returns false if there is no such key, or if the value is not Boolean.TRUE or the String + * "true". + * + * @param key A key string. + * @return The truth. + */ + public boolean optBoolean(String key) { + return this.optBoolean(key, false); + } + + /** + * Get an optional boolean associated with a key. It returns the defaultValue if there is no such key, or if it is not a Boolean or the String + * "true" or "false" (case insensitive). + * + * @param key A key string. + * @param defaultValue The default. + * @return The truth. + */ + public boolean optBoolean(String key, boolean defaultValue) { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Boolean) { + return ((Boolean) val); + } + try { + // we'll use the get anyway because it does string conversion. + return this.getBoolean(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional BigDecimal associated with a key, or the defaultValue if there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public BigDecimal optBigDecimal(String key, BigDecimal defaultValue) { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof BigDecimal) { + return (BigDecimal) val; + } + if (val instanceof BigInteger) { + return new BigDecimal((BigInteger) val); + } + if (val instanceof Double || val instanceof Float) { + return new BigDecimal(((Number) val).doubleValue()); + } + if (val instanceof Long || val instanceof Integer + || val instanceof Short || val instanceof Byte) { + return new BigDecimal(((Number) val).longValue()); + } + // don't check if it's a string in case of unchecked Number subclasses + try { + return new BigDecimal(val.toString()); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional BigInteger associated with a key, or the defaultValue if there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public BigInteger optBigInteger(String key, BigInteger defaultValue) { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof BigInteger) { + return (BigInteger) val; + } + if (val instanceof BigDecimal) { + return ((BigDecimal) val).toBigInteger(); + } + if (val instanceof Double || val instanceof Float) { + return new BigDecimal(((Number) val).doubleValue()).toBigInteger(); + } + if (val instanceof Long || val instanceof Integer + || val instanceof Short || val instanceof Byte) { + return BigInteger.valueOf(((Number) val).longValue()); + } + // don't check if it's a string in case of unchecked Number subclasses + try { + // the other opt functions handle implicit conversions, i.e. + // jo.put("double",1.1d); + // jo.optInt("double"); -- will return 1, not an error + // this conversion to BigDecimal then to BigInteger is to maintain + // that type cast support that may truncate the decimal. + final String valStr = val.toString(); + if (isDecimalNotation(valStr)) { + return new BigDecimal(valStr).toBigInteger(); + } + return new BigInteger(valStr); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional double associated with a key, or NaN if there is no such key or if its value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key A string which is the key. + * @return An object which is the value. + */ + public double optDouble(String key) { + return this.optDouble(key, Double.NaN); + } + + /** + * Get an optional double associated with a key, or the defaultValue if there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public double optDouble(String key, double defaultValue) { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number) { + return ((Number) val).doubleValue(); + } + if (val instanceof String) { + try { + return Double.parseDouble((String) val); + } catch (NumberFormatException e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get the optional double value associated with an index. NaN is returned if there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param key A key string. + * @return The value. + */ + public float optFloat(String key) { + return this.optFloat(key, Float.NaN); + } + + /** + * Get the optional double value associated with an index. The defaultValue is returned if there is no value for the index, or if the value is not + * a number and cannot be converted to a number. + * + * @param key A key string. + * @param defaultValue The default value. + * @return The value. + */ + public float optFloat(String key, float defaultValue) { + Object val = this.opt(key); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number) { + return ((Number) val).floatValue(); + } + if (val instanceof String) { + try { + return Float.parseFloat((String) val); + } catch (Exception e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get an optional int value associated with a key, or zero if there is no such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @return An object which is the value. + */ + public int optInt(String key) { + return this.optInt(key, 0); + } + + /** + * Get an optional int value associated with a key, or the default if there is no such key or if the value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public int optInt(String key, int defaultValue) { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number) { + return ((Number) val).intValue(); + } + + if (val instanceof String) { + try { + return new BigDecimal((String) val).intValue(); + } catch (Exception e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get an optional JSONArray associated with a key. It returns null if there is no such key, or if its value is not a JSONArray. + * + * @param key A key string. + * @return A JSONArray which is the value. + */ + public JSONArray optJSONArray(String key) { + Object o = this.opt(key); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get an optional JSONObject associated with a key. It returns null if there is no such key, or if its value is not a JSONObject. + * + * @param key A key string. + * @return A JSONObject which is the value. + */ + public JSONObject optJSONObject(String key) { + Object object = this.opt(key); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Get an optional long value associated with a key, or zero if there is no such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @return An object which is the value. + */ + public long optLong(String key) { + return this.optLong(key, 0); + } + + /** + * Get an optional long value associated with a key, or the default if there is no such key or if the value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public long optLong(String key, long defaultValue) { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number) { + return ((Number) val).longValue(); + } + + if (val instanceof String) { + try { + return new BigDecimal((String) val).longValue(); + } catch (Exception e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get an optional {@link Number} value associated with a key, or null if there is no such key or if the value is not a number. If the + * value is a string, an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method would be used in cases where type + * coercion of the number value is unwanted. + * + * @param key A key string. + * @return An object which is the value. + */ + public Number optNumber(String key) { + return this.optNumber(key, null); + } + + /** + * Get an optional {@link Number} value associated with a key, or the default if there is no such key or if the value is not a number. If the value + * is a string, an attempt will be made to evaluate it as a number. This method would be used in cases where type coercion of the number value is + * unwanted. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public Number optNumber(String key, Number defaultValue) { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number) { + return (Number) val; + } + + if (val instanceof String) { + try { + return stringToNumber((String) val); + } catch (NumberFormatException e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get an optional string associated with a key. It returns an empty string if there is no such key. If the value is not a string and is not null, + * then it is converted to a string. + * + * @param key A key string. + * @return A string which is the value. + */ + public String optString(String key) { + return this.optString(key, ""); + } + + /** + * Get an optional string associated with a key. It returns the defaultValue if there is no such key. + * + * @param key A key string. + * @param defaultValue The default. + * @return A string which is the value. + */ + public String optString(String key, String defaultValue) { + Object object = this.opt(key); + return NULL.equals(object) ? defaultValue : object.toString(); + } + + /** + * Populates the internal map of the JSONObject with the bean properties. The bean can not be recursive. + * + * @see JSONObject#JSONObject(Object) + * + * @param bean the bean + */ + private void populateMap(Object bean) { + Class klass = bean.getClass(); + + // If klass is a System class then set includeSuperClass to false. + boolean includeSuperClass = klass.getClassLoader() != null; + + Method[] methods = includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods(); + for (final Method method : methods) { + final int modifiers = method.getModifiers(); + if (Modifier.isPublic(modifiers) + && !Modifier.isStatic(modifiers) + && method.getParameterTypes().length == 0 + && !method.isBridge() + && method.getReturnType() != Void.TYPE + && isValidMethodName(method.getName())) { + final String key = getKeyNameFromMethod(method); + if (key != null && !key.isEmpty()) { + try { + final Object result = method.invoke(bean); + if (result != null) { + this.map.put(key, wrap(result)); + // we don't use the result anywhere outside of wrap + // if it's a resource we should be sure to close it + // after calling toString + if (result instanceof Closeable) { + try { + ((Closeable) result).close(); + } catch (IOException ignore) { + } + } + } + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ignore) { + } + } + } + } + } + + private boolean isValidMethodName(String name) { + return !"getClass".equals(name) && !"getDeclaringClass".equals(name); + } + + private String getKeyNameFromMethod(Method method) { + final int ignoreDepth = getAnnotationDepth(method, JSONPropertyIgnore.class); + if (ignoreDepth > 0) { + final int forcedNameDepth = getAnnotationDepth(method, JSONPropertyName.class); + if (forcedNameDepth < 0 || ignoreDepth <= forcedNameDepth) { + // the hierarchy asked to ignore, and the nearest name override + // was higher or non-existent + return null; + } + } + JSONPropertyName annotation = getAnnotation(method, JSONPropertyName.class); + if (annotation != null && annotation.value() != null && !annotation.value().isEmpty()) { + return annotation.value(); + } + String key; + final String name = method.getName(); + if (name.startsWith("get") && name.length() > 3) { + key = name.substring(3); + } else if (name.startsWith("is") && name.length() > 2) { + key = name.substring(2); + } else { + return null; + } + // if the first letter in the key is not uppercase, then skip. + // This is to maintain backwards compatibility before PR406 + // (https://github.com/stleary/JSON-java/pull/406/) + if (Character.isLowerCase(key.charAt(0))) { + return null; + } + if (key.length() == 1) { + key = key.toLowerCase(Locale.ROOT); + } else if (!Character.isUpperCase(key.charAt(1))) { + key = key.substring(0, 1).toLowerCase(Locale.ROOT) + key.substring(1); + } + return key; + } + + /** + * Searches the class hierarchy to see if the method or it's super implementations and interfaces has the annotation. + * + * @param type of the annotation + * + * @param m method to check + * @param annotationClass annotation to look for + * @return the {@link Annotation} if the annotation exists on the current method or one of it's super class definitions + */ + private static A getAnnotation(final Method m, final Class annotationClass) { + // if we have invalid data the result is null + if (m == null || annotationClass == null) { + return null; + } + + if (m.isAnnotationPresent(annotationClass)) { + return m.getAnnotation(annotationClass); + } + + // if we've already reached the Object class, return null; + Class c = m.getDeclaringClass(); + if (c.getSuperclass() == null) { + return null; + } + + // check directly implemented interfaces for the method being checked + for (Class i : c.getInterfaces()) { + try { + Method im = i.getMethod(m.getName(), m.getParameterTypes()); + return getAnnotation(im, annotationClass); + } catch (final SecurityException | NoSuchMethodException ex) { + } + } + + try { + return getAnnotation( + c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()), + annotationClass); + } catch (final SecurityException | NoSuchMethodException ex) { + return null; + } + } + + /** + * Searches the class hierarchy to see if the method or it's super implementations and interfaces has the annotation. Returns the depth of the + * annotation in the hierarchy. + * + * @param type of the annotation + * + * @param m method to check + * @param annotationClass annotation to look for + * @return Depth of the annotation or -1 if the annotation is not on the method. + */ + private static int getAnnotationDepth(final Method m, final Class annotationClass) { + // if we have invalid data the result is -1 + if (m == null || annotationClass == null) { + return -1; + } + + if (m.isAnnotationPresent(annotationClass)) { + return 1; + } + + // if we've already reached the Object class, return -1; + Class c = m.getDeclaringClass(); + if (c.getSuperclass() == null) { + return -1; + } + + // check directly implemented interfaces for the method being checked + for (Class i : c.getInterfaces()) { + try { + Method im = i.getMethod(m.getName(), m.getParameterTypes()); + int d = getAnnotationDepth(im, annotationClass); + if (d > 0) { + // since the annotation was on the interface, add 1 + return d + 1; + } + } catch (final SecurityException | NoSuchMethodException ex) { + } + } + + try { + int d = getAnnotationDepth( + c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()), + annotationClass); + if (d > 0) { + // since the annotation was on the superclass, add 1 + return d + 1; + } + return -1; + } catch (final SecurityException | NoSuchMethodException ex) { + return -1; + } + } + + /** + * Put a key/boolean pair in the JSONObject. + * + * @param key A key string + * @param value A boolean which is the value + * @return this + * @throws JSONException If the value is non-finite number + * @throws NullPointerException If the key is null + */ + public JSONObject put(String key, boolean value) throws JSONException { + return this.put(key, value ? Boolean.TRUE : Boolean.FALSE); + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a JSONArray which is produced from a Collection. + * + * @param key A key string + * @param value A Collection value + * @return this + * @throws JSONException If the value is non-finite number + * @throws NullPointerException If the key is null + */ + public JSONObject put(String key, Collection value) throws JSONException { + return this.put(key, new JSONArray(value)); + } + + /** + * Put a key/double pair in the JSONObject. + * + * @param key A key string + * @param value A double which is the value + * @return this + * @throws JSONException If the value is non-finite number + * @throws NullPointerException If the key is null + */ + public JSONObject put(String key, double value) throws JSONException { + return this.put(key, Double.valueOf(value)); + } + + /** + * Put a key/float pair in the JSONObject. + * + * @param key A key string + * @param value A float which is the value + * @return this + * @throws JSONException If the value is non-finite number. + * @throws NullPointerException If the key is null. + */ + public JSONObject put(String key, float value) throws JSONException { + return this.put(key, Float.valueOf(value)); + } + + /** + * Put a key/int pair in the JSONObject. + * + * @param key A key string. + * @param value An int which is the value. + * @return this + * @throws JSONException If the value is non-finite number. + * @throws NullPointerException If the key is null. + */ + public JSONObject put(String key, int value) throws JSONException { + return this.put(key, Integer.valueOf(value)); + } + + /** + * Put a key/NULL object pair in the JSONObject. + * + * @param key A key string + * @return this + * @throws JSONException If the value is non-finite number + * @throws NullPointerException If the key is null + */ + public JSONObject putNULL(String key) throws JSONException { + return this.put(key, NULL); + } + + /** + * Put a key/long pair in the JSONObject. + * + * @param key A key string. + * @param value A long which is the value. + * @return this. + * @throws JSONException If the value is non-finite number. + * @throws NullPointerException If the key is null. + */ + public JSONObject put(String key, long value) throws JSONException { + return this.put(key, Long.valueOf(value)); + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a JSONObject which is produced from a Map. + * + * @param key A key string. + * @param value A Map value. + * @return this. + * @throws JSONException If the value is non-finite number. + * @throws NullPointerException If the key is null. + */ + public JSONObject put(String key, Map value) throws JSONException { + return this.put(key, new JSONObject(value)); + } + + /** + * Put a key/value pair in the JSONObject. If the value is null, then the key will be removed from the JSONObject if it is present. + * + * @param key A key string. + * @param value An object which is the value. It should be of one of these types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or + * the JSONObject.NULL object. + * @return this. + * @throws JSONException If the value is non-finite number. + * @throws NullPointerException If the key is null. + */ + public JSONObject put(String key, Object value) throws JSONException { + if (key == null) { + throw new NullPointerException("Null key."); + } + if (value != null) { + testValidity(value); + this.map.put(key, value); + } else { + this.remove(key); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value are both non-null, and only if there is not already a member with that + * name. + * + * @param key string + * @param value object + * @return this. + * @throws JSONException if the key is a duplicate + */ + public JSONObject putOnce(String key, Object value) throws JSONException { + if (key != null && value != null) { + if (this.opt(key) != null) { + throw new JSONException("Duplicate key \"" + key + "\""); + } + return this.put(key, value); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value are both non-null. + * + * @param key A key string. + * @param value An object which is the value. It should be of one of these types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or + * the JSONObject.NULL object. + * @return this. + * @throws JSONException If the value is a non-finite number. + */ + public JSONObject putOpt(String key, Object value) throws JSONException { + if (key != null && value != null) { + return this.put(key, value); + } + return this; + } + + /** + * Creates a JSONPointer using an initialization string and tries to match it to an item within this JSONObject. For example, given a JSONObject + * initialized with this document: + *
+    * {
+    *     "a":{"b":"c"}
+    * }
+    * 
and this JSONPointer string: + *
+    * "/a/b"
+    * 
Then this method will return the String "c". A JSONPointerException may be thrown from code called by this method. + * + * @param jsonPointer string that can be used to create a JSONPointer + * @return the item matched by the JSONPointer, otherwise null + */ + @Override + public Object query(String jsonPointer) { + return query(new JSONPointer(jsonPointer)); + } + + /** + * Uses a user initialized JSONPointer and tries to match it to an item within this JSONObject. For example, given a JSONObject initialized with + * this document: + *
+    * {
+    *     "a":{"b":"c"}
+    * }
+    * 
and this JSONPointer: + *
+    * "/a/b"
+    * 
Then this method will return the String "c". A JSONPointerException may be thrown from code called by this method. + * + * @param jsonPointer string that can be used to create a JSONPointer + * @return the item matched by the JSONPointer, otherwise null + */ + @Override + public Object query(JSONPointer jsonPointer) { + return jsonPointer.queryFrom(this); + } + + /** + * Queries and returns a value from this object using {@code jsonPointer}, or returns null if the query fails due to a missing key. + * + * @param jsonPointer the string representation of the JSON pointer + * @return the queried value or {@code null} + * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax + */ + public Object optQuery(String jsonPointer) { + return optQuery(new JSONPointer(jsonPointer)); + } + + /** + * Queries and returns a value from this object using {@code jsonPointer}, or returns null if the query fails due to a missing key. + * + * @param jsonPointer The JSON pointer + * @return the queried value or {@code null} + * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax + */ + public Object optQuery(JSONPointer jsonPointer) { + try { + return jsonPointer.queryFrom(this); + } catch (JSONPointerException e) { + return null; + } + } + + /** + * Produce a string in double quotes with backslash sequences in all the right places. A backslash will be inserted within = '\u0080' && c < '\u00a0') + || (c >= '\u2000' && c < '\u2100')) { + w.write("\\u"); + hhhh = Integer.toHexString(c); + w.write("0000", 0, 4 - hhhh.length()); + w.write(hhhh); + } else { + w.write(c); + } + } + } + w.write('"'); + return w; + } + + /** + * Remove a name and its value, if present. + * + * @param key The name to be removed. + * @return The value that was associated with the name, or null if there was no value. + */ + public Object remove(String key) { + return this.map.remove(key); + } + + /** + * Determine if two JSONObjects are similar. They must contain the same set of names which must be associated with similar values. + * + * @param other The other JSONObject + * @return true if they are equal + */ + public boolean similar(Object other) { + try { + if (!(other instanceof JSONObject)) { + return false; + } + if (!this.keySet().equals(((JSONObject) other).keySet())) { + return false; + } + for (final Entry entry : this.entrySet()) { + String name = entry.getKey(); + Object valueThis = entry.getValue(); + Object valueOther = ((JSONObject) other).get(name); + if (valueThis == valueOther) { + continue; + } + if (valueThis == null) { + return false; + } + if (valueThis instanceof JSONObject) { + if (!((JSONObject) valueThis).similar(valueOther)) { + return false; + } + } else if (valueThis instanceof JSONArray) { + if (!((JSONArray) valueThis).similar(valueOther)) { + return false; + } + } else if (!valueThis.equals(valueOther)) { + return false; + } + } + return true; + } catch (Throwable exception) { + return false; + } + } + + /** + * Tests if the value should be tried as a decimal. It makes no test if there are actual digits. + * + * @param val value to test + * @return true if the string is "-0" or if it contains '.', 'e', or 'E', false otherwise. + */ + protected static boolean isDecimalNotation(final String val) { + return val.indexOf('.') > -1 || val.indexOf('e') > -1 + || val.indexOf('E') > -1 || "-0".equals(val); + } + + /** + * Converts a string to a number using the narrowest possible type. Possible returns for this function are BigDecimal, Double, BigInteger, Long, + * and Integer. When a Double is returned, it should always be a valid Double and not NaN or +-infinity. + * + * @param val value to convert + * @return Number representation of the value. + * @throws NumberFormatException thrown if the value is not a valid number. A public caller should catch this and wrap it in a + * {@link JSONException} if applicable. + */ + protected static Number stringToNumber(final String val) throws NumberFormatException { + char initial = val.charAt(0); + if ((initial >= '0' && initial <= '9') || initial == '-') { + // decimal representation + if (isDecimalNotation(val)) { + // quick dirty way to see if we need a BigDecimal instead of a Double + // this only handles some cases of overflow or underflow + if (val.length() > 14) { + return new BigDecimal(val); + } + final Double d = Double.valueOf(val); + if (d.isInfinite() || d.isNaN()) { + // if we can't parse it as a double, go up to BigDecimal + // this is probably due to underflow like 4.32e-678 + // or overflow like 4.65e5324. The size of the string is small + // but can't be held in a Double. + return new BigDecimal(val); + } + return d; + } + // integer representation. + // This will narrow any values to the smallest reasonable Object representation + // (Integer, Long, or BigInteger) + + // string version + // The compare string length method reduces GC, + // but leads to smaller integers being placed in larger wrappers even though not + // needed. i.e. 1,000,000,000 -> Long even though it's an Integer + // 1,000,000,000,000,000,000 -> BigInteger even though it's a Long + //if(val.length()<=9){ + // return Integer.valueOf(val); + //} + //if(val.length()<=18){ + // return Long.valueOf(val); + //} + //return new BigInteger(val); + // BigInteger version: We use a similar bitLenth compare as + // BigInteger#intValueExact uses. Increases GC, but objects hold + // only what they need. i.e. Less runtime overhead if the value is + // long lived. Which is the better tradeoff? This is closer to what's + // in stringToValue. + BigInteger bi = new BigInteger(val); + if (bi.bitLength() <= 31) { + return bi.intValue(); + } + if (bi.bitLength() <= 63) { + return bi.longValue(); + } + return bi; + } + throw new NumberFormatException("val [" + val + "] is not a valid number."); + } + + /** + * Try to convert a string into a number, boolean, or null. If the string can't be converted, return the string. + * + * @param string A String. + * @return A simple JSON value. + */ + // Changes to this method must be copied to the corresponding method in + // the XML class to keep full support for Android + public static Object stringToValue(String string) { + if (string.equals("")) { + return string; + } + if (string.equalsIgnoreCase("true")) { + return Boolean.TRUE; + } + if (string.equalsIgnoreCase("false")) { + return Boolean.FALSE; + } + if (string.equalsIgnoreCase("null")) { + 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 { + // if we want full Big Number support this block can be replaced with: + // return stringToNumber(string); + if (isDecimalNotation(string)) { + Double d = Double.valueOf(string); + if (!d.isInfinite() && !d.isNaN()) { + return d; + } + } else { + Long myLong = Long.valueOf(string); + if (string.equals(myLong.toString())) { + if (myLong.longValue() == myLong.intValue()) { + return myLong.intValue(); + } + return myLong; + } + } + } catch (NumberFormatException ignore) { + } + } + return string; + } + + /** + * Throw an exception if the object is a NaN or infinite number. + * + * @param o The object to test. + * @throws JSONException If o is a non-finite number. + */ + public static void testValidity(Object o) throws JSONException { + if (o != null) { + if (o instanceof Double) { + if (((Double) o).isInfinite() || ((Double) o).isNaN()) { + throw new JSONException("JSON does not allow non-finite numbers."); + } + } else if (o instanceof Float) { + if (((Float) o).isInfinite() || ((Float) o).isNaN()) { + throw new JSONException("JSON does not allow non-finite numbers."); + } + } + } + } + + /** + * Produce a JSONArray containing the values of the members of this JSONObject. + * + * @param names A JSONArray containing a list of key strings. This determines the sequence of the values in the result. + * @return A JSONArray of values. + * @throws JSONException If any of the values are non-finite numbers. + */ + public JSONArray toJSONArray(JSONArray names) throws JSONException { + if (names == null || names.isEmpty()) { + return null; + } + JSONArray ja = new JSONArray(); + for (int i = 0; i < names.length(); i += 1) { + ja.put(this.opt(names.getString(i))); + } + return ja; + } + + /** + * Make a JSON text of this JSONObject. For compactness, no whitespace is added. If this would not result in a syntactically correct JSON text, + * then null will be returned instead. + *

+ * + * Warning: This method assumes that the data structure is acyclical. + * + * + * @return a printable, displayable, portable, transmittable representation of the object, beginning with { (left + * brace) and ending with } (right brace). + */ + @Override + public String toString() { + try { + return this.toString(-1); + } catch (JSONException e) { + return null; + } + } + + /** + * Make a pretty-printed JSON text of this JSONObject. + * + *

+ * If indentFactor > 0 and the {@link JSONObject} has only one key, then the object will be output on a single line: + *

{@code {"key": 1}}
+ * + *

+ * If an object has 2 or more keys, then it will be output across multiple lines:

{
+    *  "key1": 1,
+    *  "key2": "value 2",
+    *  "key3": 3
+    * }
+ *

+ * + * Warning: This method assumes that the data structure is acyclical. + * + * + * @param indentFactor The number of spaces to add to each level of indentation. + * @return a printable, displayable, portable, transmittable representation of the object, beginning with { (left + * brace) and ending with } (right brace). + * @throws JSONException If the object contains an invalid number. + */ + @Override + public String toString(int indentFactor) throws JSONException { + StringWriter w = new StringWriter(); + JSONOptions options; + if (indentFactor == -1) { + options = JSONOptions.getDefault(); + } else { + options = JSONOptions.createOptions().setIndentFactor(indentFactor); + } + synchronized (w.getBuffer()) { + return this.write(w, 0, options).toString(); + } + } + + /** + * Make a JSON text of an Object value. If the object has an value.toJSONString() method, then that method will be used to produce the JSON text. + * The method is required to produce a strictly conforming text. If the object does not contain a toJSONString method (which is the most common + * case), then a text will be produced by other means. If the value is an array or Collection, then a JSONArray will be made from it and its + * toJSONString method will be called. If the value is a MAP, then a JSONObject will be made from it and its toJSONString method will be called. + * Otherwise, the value's toString method will be called, and the result will be quoted. + * + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param value The value to be serialized. + * @return a printable, displayable, transmittable representation of the object, beginning with { (left brace) and + * ending with } (right brace). + * @throws JSONException If the value is or contains an invalid number. + */ + public static String valueToString(Object value) throws JSONException { + // moves the implementation to JSONWriter as: + // 1. It makes more sense to be part of the writer class + // 2. For Android support this method is not available. By implementing it in the Writer + // Android users can use the writer with the built in Android JSONObject implementation. + return JSONWriter.valueToString(value); + } + + /** + * Wrap an object, if necessary. If the object is null, return the NULL object. If it is an array or collection, wrap it in a + * JSONArray. If it is a map, wrap it in a JSONObject. If it is a standard property (Double, String, et al) then it is already wrapped. Otherwise, + * if it comes from one of the java packages, turn it into a string. And if it doesn't, try to wrap it in a JSONObject. If the wrapping fails, then + * null is returned. + * + * @param object The object to wrap + * @return The wrapped value + */ + public static Object wrap(Object object) { + try { + if (object == null) { + return NULL; + } + if (object instanceof JSONObject || object instanceof JSONArray + || NULL.equals(object) || object instanceof JSONString + || object instanceof Byte || object instanceof Character + || object instanceof Short || object instanceof Integer + || object instanceof Long || object instanceof Boolean + || object instanceof Float || object instanceof Double + || object instanceof String || object instanceof BigInteger + || object instanceof BigDecimal || object instanceof Enum) { + return object; + } + + if (object instanceof Collection) { + Collection coll = (Collection) object; + return new JSONArray(coll); + } + if (object.getClass().isArray()) { + return new JSONArray(object); + } + if (object instanceof Map) { + Map map = (Map) object; + return new JSONObject(map); + } + Package objectPackage = object.getClass().getPackage(); + String objectPackageName = objectPackage != null ? objectPackage + .getName() : ""; + if (objectPackageName.startsWith("java.") + || objectPackageName.startsWith("javax.") + || object.getClass().getClassLoader() == null) { + return object.toString(); + } + return new JSONObject(object); + } catch (JSONException exception) { + return null; + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For compactness, no whitespace is added. + *

+ * + * Warning: This method assumes that the data structure is acyclical. + * + * + * @param writer the writer + * @return The writer. + * @throws JSONException + */ + @Override + public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0); + } + + static final Writer writeValue(Writer writer, Object value, int indent, JSONOptions options) throws JSONException, IOException { + return writeValue(writer, value, indent, false, options); + } + + static final Writer writeValue(Writer writer, Object value, int indent, boolean isValue, JSONOptions options) throws JSONException, IOException { + options = JSONOptions.getOptions(options); + if (value == null) { + writer.write("null"); + } else if (value instanceof JSONObject.Null) { + writer.write("null"); + } else if (value instanceof JSONString) { + Object o; + try { + o = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + writer.write(o != null ? o.toString() : quote(value.toString())); + } else if (value instanceof Number) { + // not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary + final String numberAsString = numberToString((Number) value, options.isKeepingDecimalPoints()); + try { + // Use the BigDecimal constructor for its parser to validate the format. + @SuppressWarnings("unused") + BigDecimal testNum = new BigDecimal(numberAsString); + // Close enough to a JSON number that we will use it unquoted + writer.write(numberAsString); + } catch (NumberFormatException ex) { + // The Number value is not a valid JSON number. + // Instead we will quote it as a string + quote(numberAsString, writer); + } + } else if (value instanceof Boolean) { + writer.write(value.toString()); + } else if (value instanceof Enum) { + writer.write(quote(((Enum) value).name())); + } else if (value instanceof JSONObject) { + ((JSONObject) value).write(writer, indent, isValue, options); + } else if (value instanceof JSONArray) { + ((JSONArray) value).write(writer, indent, options); + } else if (value instanceof Map) { + Map map = (Map) value; + new JSONObject(map).write(writer, indent, isValue, options); + } else if (value instanceof Collection) { + Collection coll = (Collection) value; + new JSONArray(coll).write(writer, indent, options); + } else if (value.getClass().isArray()) { + new JSONArray(value).write(writer, indent, options); + } else { + quote(value.toString(), writer); + } + return writer; + } + + static final void indent(Writer writer, int indent) throws IOException { + for (int i = 0; i < indent; i += 1) { + writer.write(' '); + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer, without keeping the keys natural order. + * + * + * @param writer Writes the serialized JSON + * @param indent The indentation of the top level + * @return The writer + * @throws JSONException if the JSONObject was unable to write + */ + @Override + public Writer write(Writer writer, int indent) throws JSONException { + return write(writer, indent, null); + } + + private boolean shouldIndent(int length, JSONOptions options) { + if (length == 1) { + switch (options.getIndentationType()) { + case JSONOptions.INDENT_ALL: + return true; + case JSONOptions.INDENT_DEFAULT: + return false; + case JSONOptions.INDENT_OBJECTS: + Object value = map.values().iterator().next(); + return value instanceof JSONElement; + default: + return false; + } + } else { + return true; + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. + * + *

+ * If indentFactor > 0 and the {@link JSONObject} has only one key, then the object will be output on a single line: + *

{@code {"key": 1}}
+ * + *

+ * If an object has 2 or more keys, then it will be output across multiple lines:

{
+    *  "key1": 1,
+    *  "key2": "value 2",
+    *  "key3": 3
+    * }
+ *

+ * + * Warning: This method assumes that the data structure is acyclical. + * + * + * @param writer Writes the serialized JSON + * @param indent The indentation of the top level + * @param isValue true to write a value + * @param options the JSON configuration options + * @return The writer + * @throws JSONException if the JSONObject was unable to write + */ + public Writer write(Writer writer, int indent, boolean isValue, JSONOptions options) throws JSONException { + try { + options = JSONOptions.getOptions(options); + int indentFactor = options.getIndentFactor(); + boolean commanate = false; + Map theMap; + if (options.isKeepingKeysNaturalOrder()) { + theMap = new TreeMap<>(map); + } else { + theMap = map; + } + final int length = this.length(); + if (isValue && indent > 0) { + indent(writer, 1); + } else { + indent(writer, indent); + } + writer.write('{'); + + if (length != 0) { + boolean shouldIndent = shouldIndent(length, options); + if (!shouldIndent) { + final Entry entry = theMap.entrySet().iterator().next(); + final String key = entry.getKey(); + writer.write(quote(key)); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + try { + writeValue(writer, entry.getValue(), indent, options); + } catch (IOException | JSONException e) { + throw new JSONException("Unable to write JSONObject value for key: " + key, e); + } + } else { + int newindent = indent + indentFactor; + for (final Entry entry : theMap.entrySet()) { + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, newindent); + final String key = entry.getKey(); + writer.write(quote(key)); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + try { + newindent = indent + indentFactor; + writeValue(writer, entry.getValue(), newindent, true, options); + } catch (IOException | JSONException e) { + throw new JSONException("Unable to write JSONObject value for key: " + key, e); + } + commanate = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, indent); + } + } + writer.write('}'); + return writer; + } catch (IOException exception) { + throw new JSONException(exception); + } + } + + /** + * 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 be + * converted. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a java.util.Map containing the entries of this object + */ + public Map toMap() { + Map results = new HashMap<>(); + for (Entry entry : this.entrySet()) { + Object value; + if (entry.getValue() == null || NULL.equals(entry.getValue())) { + value = null; + } else if (entry.getValue() instanceof JSONObject) { + value = ((JSONObject) entry.getValue()).toMap(); + } else if (entry.getValue() instanceof JSONArray) { + value = ((JSONArray) entry.getValue()).toList(); + } else { + value = entry.getValue(); + } + results.put(entry.getKey(), value); + } + return results; + } +} diff --git a/src/org/json/JSONOptions.java b/src/org/json/JSONOptions.java new file mode 100644 index 000000000..58fd350f4 --- /dev/null +++ b/src/org/json/JSONOptions.java @@ -0,0 +1,399 @@ +/* +Copyright (C) 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +/** + * Stores options for reading or serializing JSON content. + * + * It is possible to specifically set the options to use when saving JSON content (or + * reading through the {@link FileUtils} class). For example: + *

    + *
  • {@link FileUtils#toFile(org.json.JSONElement, java.io.File, org.json.JSONOptions)}
  • + *
+ * + * By default saving or reading will use a default options which content can be modified. For example: + *
    + *
  • {@link FileUtils#toFile(org.json.JSONElement, java.io.File)}
  • + *
+ * + *

Options

+ *

Charset Option

+ * It is possible to specify the charset to read or write JSON files. By default the charset is UTF-8. + * + *

key ordering Option

+ * It is possible to ensure that the keys are written according to their natural order (String order). By + * default this option is set to false. + * + * You must be aware that the ordering of the keys has no meaning for JSON files. It can only be useful to set this option to true if you want the + * result to be easily human-readable. + * + *

Allowing comments Option

+ * It is possible to allow javascript comments whe reading JSON content. By default + * comments are not allowed. + * + *

Decimal points Option

+ * It is possible to keep decimal points when serializing float or double numbers. By default + * float or double numbers which are equal to int values will not have their decimal points + * when serializing. + * + *

Indentation type Option

+ * By default the result is not indented. The possible types are: + *
    + *
  • {@link #INDENT_DEFAULT}: the default indentation type
  • + *
  • {@link #INDENT_ALL}: all fields are indented
  • + *
  • {@link #INDENT_OBJECTS}: only objects and arrays fields are indented
  • + *
+ * + *

Indent factor Option

+ * The ident factor is the number of spaces to add to each level of indentation. By default + * it is equal to 0. + * + *

Keep XML Strings Option

+ * Set if values will not be coerced into boolean or numeric values and will instead be left as strings. This is only valid for XML parsing. By default + * it is false. + * + * @since 1.7.2 + */ +public class JSONOptions { + private static JSONOptions STATIC_OPTIONS = null; + /** + * The default indentation type. + */ + public static final short INDENT_DEFAULT = 0; + /** + * The object indentation type. Only objects fields are indented. + */ + public static final short INDENT_OBJECTS = 1; + /** + * The "All" indentation type. All fields are indented. + */ + public static final short INDENT_ALL = 2; + private boolean keepDecimalPoints = false; + private boolean keysNaturalOrder = false; + private short resultIndentationType = INDENT_DEFAULT; + private boolean acceptComments = false; + private boolean keepXMLStrings = false; + private int indentFactor = 0; + private String charset = "UTF-8"; + + public JSONOptions() { + } + + /** + * Return the default options. The default options will be used when reading or + * writing JSON content without specifying the options explicitly. For example: + * {@link FileUtils#toFile(org.json.JSONElement, java.io.File)}. + * + * @return the default options + */ + public static JSONOptions getDefault() { + if (STATIC_OPTIONS == null) { + STATIC_OPTIONS = new JSONOptions(); + } + return STATIC_OPTIONS; + } + + /** + * Reset the default options. + * + * @return the default options + */ + public static JSONOptions resetDefault() { + STATIC_OPTIONS = getDefault(); + STATIC_OPTIONS.reset(); + return STATIC_OPTIONS; + } + + /** + * Reset this options values. + */ + public void reset() { + keepDecimalPoints = false; + keysNaturalOrder = false; + resultIndentationType = INDENT_DEFAULT; + acceptComments = false; + keepXMLStrings = false; + indentFactor = 0; + charset = "UTF-8"; + } + + /** + * Return a new JSONOptions with default parameters values. + * + * @return the JSONOptions + */ + public static JSONOptions createOptions() { + return new JSONOptions(); + } + + /** + * Return a not null JSONOptions. If the provided options is not null, return this JSONOptions. Else return the default JSONOptions, creating + * it if necessary. + * + * @param options the provided JSONOptions (may be null) + * @return the returned JSONOptions (will never be null) + */ + public static JSONOptions getOptions(JSONOptions options) { + if (options == null) { + options = getDefault(); + } + return options; + } + + /** + * Set if decimal points must be kept in the serialized results for decimal values, for the default options. + * + * @param keepDecimalPoints true if decimal points must be kept in the results for decimal values + * @return the default options + */ + public static JSONOptions setDefaultKeepDecimalPoints(boolean keepDecimalPoints) { + JSONOptions options = JSONOptions.getDefault(); + options.keepDecimalPoints = keepDecimalPoints; + return options; + } + + /** + * Set if decimal points must be kept in the serialized results for decimal values. It is false by default, so trailing decimal points will be + * removed. + * + * @param keepDecimalPoints true if decimal points must be kept in the results for decimal values + * @return this + */ + public JSONOptions keepDecimalPoints(boolean keepDecimalPoints) { + this.keepDecimalPoints = keepDecimalPoints; + return this; + } + + /** + * Return true if decimal points must be kept in the serialized results for decimal values. It is false by default, so trailing decimal points will be + * removed. + * + * @return true if decimal points must be kept in the serialized results for decimal values. It is false by default, so trailing decimal points will be + * removed + */ + public boolean isKeepingDecimalPoints() { + return keepDecimalPoints; + } + + /** + * Set the charset to use when reading or writing JSON content, for the default options. + * + * @param charset true the charset + * @return the default options + */ + public static JSONOptions setDefaultCharSetName(String charset) { + JSONOptions options = JSONOptions.getDefault(); + options.charset = charset; + return options; + } + + /** + * Set the charset to use when reading or writing JSON content. + * + * @param charset true the charset + * @return this + */ + public JSONOptions setCharSetName(String charset) { + this.charset = charset; + return this; + } + + /** + * Return the charset to use when reading or writing JSON content. + * + * @return the charset + */ + public String getCharset() { + return charset; + } + + /** + * Set if javascript comments should be accepted, for the default options.. + * + * @param accept true if javascript comments should be accepted + * @return the default options + */ + public static JSONOptions setDefaultAcceptJavascriptComments(boolean accept) { + JSONOptions options = JSONOptions.getDefault(); + options.acceptComments = accept; + return options; + } + + /** + * Set if javascript comments should be accepted. + * + * @param accept true if javascript comments should be accepted + * @return this + */ + public JSONOptions acceptJavascriptComments(boolean accept) { + this.acceptComments = accept; + return this; + } + + /** + * Return true if javascript comments should be accepted. + * + * @return true if javascript comments should be accepted + */ + public boolean isAcceptingJavascriptComments() { + return acceptComments; + } + + /** + * Set if the keys natural order should be kept, for the default options. + * + * @param keysNaturalOrder true if the keys natural order must be kept + * @return the default options + */ + public static JSONOptions setDefaultKeepKeysNaturalOrder(boolean keysNaturalOrder) { + JSONOptions options = JSONOptions.getDefault(); + options.keysNaturalOrder = keysNaturalOrder; + return options; + } + + /** + * Set if the keys natural order should be kept. + * + * @param keysNaturalOrder true if the keys natural order must be kept + * @return this + */ + public JSONOptions keepKeysNaturalOrder(boolean keysNaturalOrder) { + this.keysNaturalOrder = keysNaturalOrder; + return this; + } + + /** + * Return true if the keys natural order should be kept. + * + * @return true if the keys natural order should be kept + */ + public boolean isKeepingKeysNaturalOrder() { + return keysNaturalOrder; + } + + /** + * Set if values will not be coerced into boolean or numeric values and will instead be left as strings, for the default options. This is only + * valid for XML parsing. + * + * @param keepXMLStrings true if values will not be coerced into boolean or numeric values and will instead be left as strings + * @return the default options + */ + public static JSONOptions setDefaultKeepXMLStrings(boolean keepXMLStrings) { + JSONOptions options = JSONOptions.getDefault(); + options.keepXMLStrings = keepXMLStrings; + return options; + } + + /** + * Set if values will not be coerced into boolean or numeric values and will instead be left as strings. This is only valid for XML parsing. + * + * @param keepXMLStrings true if values will not be coerced into boolean or numeric values and will instead be left as strings + * @return this + */ + public JSONOptions keepXMLStrings(boolean keepXMLStrings) { + this.keepXMLStrings = keepXMLStrings; + return this; + } + + /** + * Return true if values will not be coerced into boolean or numeric values and will instead be left as strings. This is only valid for XML parsing. + * + * @return true if values will not be coerced into boolean or numeric values and will instead be left as strings + */ + public boolean isKeepingXMLStrings() { + return keepXMLStrings; + } + + /** + * Set the number of spaces to add to each level of indentation, for the default options. + * + * @param indentFactor the indent factor + * @return the default options + */ + public static JSONOptions setDefaultIndentFactor(int indentFactor) { + JSONOptions options = JSONOptions.getDefault(); + options.indentFactor = indentFactor; + return options; + } + + /** + * Set the number of spaces to add to each level of indentation. + * + * @param indentFactor the indent factor + * @return this + */ + public JSONOptions setIndentFactor(int indentFactor) { + this.indentFactor = indentFactor; + return this; + } + + /** + * Return the number of spaces to add to each level of indentation. + * + * @return the indent factor + */ + public int getIndentFactor() { + return indentFactor; + } + + /** + * Set the result indentation type, for the default options. The possible types are: + *
    + *
  • {@link #INDENT_DEFAULT}: the default indentation type
  • + *
  • {@link #INDENT_ALL}: all fields are indented
  • + *
  • {@link #INDENT_OBJECTS}: only objects and arrays fields are indented
  • + *
+ * + * @param indentType the result indentation type + * @return the default options + */ + public static JSONOptions setDefaultIndentationType(short indentType) { + JSONOptions options = JSONOptions.getDefault(); + options.resultIndentationType = indentType; + return options; + } + + /** + * Set the result indentation type. The possible types are: + *
    + *
  • {@link #INDENT_DEFAULT}: the default indentation type
  • + *
  • {@link #INDENT_ALL}: all fields are indented
  • + *
  • {@link #INDENT_OBJECTS}: only objects and arrays fields are indented
  • + *
+ * + * @param indentType the result indentation type + * @return this + */ + public JSONOptions setIndentationType(short indentType) { + this.resultIndentationType = indentType; + return this; + } + + /** + * Return the result indentation type. + * + * @return the result indentation type + */ + public short getIndentationType() { + return resultIndentationType; + } +} diff --git a/src/org/json/JSONPointer.java b/src/org/json/JSONPointer.java new file mode 100644 index 000000000..a7b3dbc2a --- /dev/null +++ b/src/org/json/JSONPointer.java @@ -0,0 +1,290 @@ +package org.json; + +import static java.lang.String.format; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/* +Copyright (c) 2002 JSON.org +Copyright (c) 2019 Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +/** + * A JSON Pointer is a simple query language defined for JSON documents by + *
RFC 6901. + * + * In a nutshell, JSONPointer allows the user to navigate into a JSON document + * using strings, and retrieve targeted objects, like a simple form of XPATH. + * Path segments are separated by the '/' char, which signifies the root of + * the document when it appears as the first char of the string. Array + * elements are navigated using ordinals, counting from 0. JSONPointer strings + * may be extended to any arbitrary number of segments. If the navigation + * is successful, the matched item is returned. A matched item may be a + * JSONObject, a JSONArray, or a JSON value. If the JSONPointer string building + * fails, an appropriate exception is thrown. If the navigation fails to find + * a match, a JSONPointerException is thrown. + * + * @author JSON.org + * @version 1.4 + */ +public class JSONPointer { + + // used for URL encoding and decoding + private static final String ENCODING = "utf-8"; + + /** + * This class allows the user to build a JSONPointer in steps, using + * exactly one segment in each step. + */ + public static class Builder { + + // Segments for the eventual JSONPointer string + private final List refTokens = new ArrayList<>(); + + /** + * Creates a {@code JSONPointer} instance using the tokens previously set using the + * {@link #append(String)} method calls. + */ + public JSONPointer build() { + return new JSONPointer(this.refTokens); + } + + /** + * Adds an arbitrary token to the list of reference tokens. It can be any non-null value. + * + * Unlike in the case of JSON string or URI fragment representation of JSON pointers, the + * argument of this method MUST NOT be escaped. If you want to query the property called + * {@code "a~b"} then you should simply pass the {@code "a~b"} string as-is, there is no + * need to escape it as {@code "a~0b"}. + * + * @param token the new token to be appended to the list + * @return {@code this} + * @throws NullPointerException if {@code token} is null + */ + public Builder append(String token) { + if (token == null) { + throw new NullPointerException("token cannot be null"); + } + this.refTokens.add(token); + return this; + } + + /** + * Adds an integer to the reference token list. Although not necessarily, mostly this token will + * denote an array index. + * + * @param arrayIndex the array index to be added to the token list + * @return {@code this} + */ + public Builder append(int arrayIndex) { + this.refTokens.add(String.valueOf(arrayIndex)); + return this; + } + } + + /** + * Static factory method for {@link Builder}. Example usage: + * + *

+    * JSONPointer pointer = JSONPointer.builder()
+    *       .append("obj")
+    *       .append("other~key").append("another/key")
+    *       .append("\"")
+    *       .append(0)
+    *       .build();
+    * 
+ * + * @return a builder instance which can be used to construct a {@code JSONPointer} instance by chained + * {@link Builder#append(String)} calls. + */ + public static Builder builder() { + return new Builder(); + } + + // Segments for the JSONPointer string + private final List refTokens; + + /** + * Pre-parses and initializes a new {@code JSONPointer} instance. If you want to + * evaluate the same JSON Pointer on different JSON documents then it is recommended + * to keep the {@code JSONPointer} instances due to performance considerations. + * + * @param pointer the JSON String or URI Fragment representation of the JSON pointer. + * @throws IllegalArgumentException if {@code pointer} is not a valid JSON pointer + */ + public JSONPointer(final String pointer) { + if (pointer == null) { + throw new NullPointerException("pointer cannot be null"); + } + if (pointer.isEmpty() || pointer.equals("#")) { + this.refTokens = Collections.emptyList(); + return; + } + String refs; + if (pointer.startsWith("#/")) { + refs = pointer.substring(2); + try { + refs = URLDecoder.decode(refs, ENCODING); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } else if (pointer.startsWith("/")) { + refs = pointer.substring(1); + } else { + throw new IllegalArgumentException("a JSON pointer should start with '/' or '#/'"); + } + this.refTokens = new ArrayList<>(); + int slashIdx = -1; + int prevSlashIdx = 0; + do { + prevSlashIdx = slashIdx + 1; + slashIdx = refs.indexOf('/', prevSlashIdx); + if (prevSlashIdx == slashIdx || prevSlashIdx == refs.length()) { + // found 2 slashes in a row ( obj//next ) + // or single slash at the end of a string ( obj/test/ ) + this.refTokens.add(""); + } else if (slashIdx >= 0) { + final String token = refs.substring(prevSlashIdx, slashIdx); + this.refTokens.add(unescape(token)); + } else { + // last item after separator, or no separator at all. + final String token = refs.substring(prevSlashIdx); + this.refTokens.add(unescape(token)); + } + } while (slashIdx >= 0); + // using split does not take into account consecutive separators or "ending nulls" + //for (String token : refs.split("/")) { + // this.refTokens.add(unescape(token)); + //} + } + + public JSONPointer(List refTokens) { + this.refTokens = new ArrayList<>(refTokens); + } + + private String unescape(String token) { + return token.replace("~1", "/").replace("~0", "~").replace("\\\"", "\"").replace("\\\\", "\\"); + } + + /** + * Evaluates this JSON Pointer on the given {@code document}. The {@code document} + * is usually a {@link JSONObject} or a {@link JSONArray} instance, but the empty + * JSON Pointer ({@code ""}) can be evaluated on any JSON values and in such case the + * returned value will be {@code document} itself. + * + * @param document the JSON document which should be the subject of querying. + * @return the result of the evaluation + * @throws JSONPointerException if an error occurs during evaluation + */ + public Object queryFrom(Object document) throws JSONPointerException { + if (this.refTokens.isEmpty()) { + return document; + } + Object current = document; + for (String token : this.refTokens) { + if (current instanceof JSONObject) { + current = ((JSONObject) current).opt(unescape(token)); + } else if (current instanceof JSONArray) { + current = readByIndexToken(current, token); + } else { + throw new JSONPointerException(format( + "value [%s] is not an array or object therefore its key %s cannot be resolved", current, + token)); + } + } + return current; + } + + /** + * Matches a JSONArray element by ordinal position + * + * @param current the JSONArray to be evaluated + * @param indexToken the array index in string form + * @return the matched object. If no matching item is found a + * @throws JSONPointerException is thrown if the index is out of bounds + */ + private Object readByIndexToken(Object current, String indexToken) throws JSONPointerException { + try { + int index = Integer.parseInt(indexToken); + JSONArray currentArr = (JSONArray) current; + if (index >= currentArr.length()) { + throw new JSONPointerException(format("index %d is out of bounds - the array has %d elements", index, + currentArr.length())); + } + try { + return currentArr.get(index); + } catch (JSONException e) { + throw new JSONPointerException("Error reading value at index position " + index, e); + } + } catch (NumberFormatException e) { + throw new JSONPointerException(format("%s is not an array index", indexToken), e); + } + } + + /** + * Returns a string representing the JSONPointer path value using string + * representation + */ + @Override + public String toString() { + StringBuilder rval = new StringBuilder(""); + for (String token : this.refTokens) { + rval.append('/').append(escape(token)); + } + return rval.toString(); + } + + /** + * Escapes path segment values to an unambiguous form. + * The escape char to be inserted is '~'. The chars to be escaped + * are ~, which maps to ~0, and /, which maps to ~1. Backslashes + * and double quote chars are also escaped. + * + * @param token the JSONPointer segment value to be escaped + * @return the escaped value for the token + */ + private String escape(String token) { + return token.replace("~", "~0").replace("/", "~1").replace("\\", "\\\\").replace("\"", "\\\""); + } + + /** + * Returns a string representing the JSONPointer path value using URI + * fragment identifier representation + */ + public String toURIFragment() { + try { + StringBuilder rval = new StringBuilder("#"); + for (String token : this.refTokens) { + rval.append('/').append(URLEncoder.encode(token, ENCODING)); + } + return rval.toString(); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/JSONPointerException.java b/src/org/json/JSONPointerException.java similarity index 100% rename from JSONPointerException.java rename to src/org/json/JSONPointerException.java diff --git a/JSONPropertyIgnore.java b/src/org/json/JSONPropertyIgnore.java similarity index 100% rename from JSONPropertyIgnore.java rename to src/org/json/JSONPropertyIgnore.java diff --git a/JSONPropertyName.java b/src/org/json/JSONPropertyName.java similarity index 100% rename from JSONPropertyName.java rename to src/org/json/JSONPropertyName.java diff --git a/JSONString.java b/src/org/json/JSONString.java similarity index 100% rename from JSONString.java rename to src/org/json/JSONString.java diff --git a/JSONStringer.java b/src/org/json/JSONStringer.java similarity index 100% rename from JSONStringer.java rename to src/org/json/JSONStringer.java diff --git a/src/org/json/JSONTokener.java b/src/org/json/JSONTokener.java new file mode 100644 index 000000000..fe664a0eb --- /dev/null +++ b/src/org/json/JSONTokener.java @@ -0,0 +1,522 @@ +package org.json; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; + +/* +Copyright (c) 2002 JSON.org +Copyright (c) 2019 Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +/** + * A JSONTokener takes a source string and extracts characters and tokens from + * it. It is used by the JSONObject and JSONArray constructors to parse + * JSON source strings. + * + * @author JSON.org + * @version 1.4 + */ +public class JSONTokener { + /** current read character position on the current line. */ + private long character; + /** flag to indicate if the end of the input has been found. */ + private boolean eof; + /** current read index of the input. */ + private long index; + /** current line of the input. */ + private long line; + /** previous character read from the input. */ + private char previous; + /** Reader for the input. */ + private final Reader reader; + /** flag to indicate that a previous character was requested. */ + private boolean usePrevious; + /** the number of characters read in the previous line. */ + private long characterPreviousLine; + + /** + * Construct a JSONTokener from a Reader. The caller must close the Reader. + * + * @param reader A reader. + */ + public JSONTokener(Reader reader) { + this.reader = reader.markSupported() ? reader : new BufferedReader(reader); + this.eof = false; + this.usePrevious = false; + this.previous = 0; + this.index = 0; + this.character = 1; + this.characterPreviousLine = 0; + 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)); + } + + /** + * Construct a JSONTokener from a string. + * + * @param s A source string. + */ + public JSONTokener(String s) { + this(new StringReader(s)); + } + + /** + * Back up one character. This provides a sort of lookahead capability, + * so that you can test for a digit or letter before attempting to parse + * the next number or identifier. + * + * @throws JSONException Thrown if trying to step back more than 1 step + * or if already at the start of the string + */ + public void back() throws JSONException { + if (this.usePrevious || this.index <= 0) { + throw new JSONException("Stepping back two steps is not supported"); + } + this.decrementIndexes(); + this.usePrevious = true; + this.eof = false; + } + + /** + * Decrements the indexes for the {@link #back()} method based on the previous character read. + */ + private void decrementIndexes() { + this.index--; + if (this.previous == '\r' || this.previous == '\n') { + this.line--; + this.character = this.characterPreviousLine; + } else if (this.character > 0) { + this.character--; + } + } + + /** + * Get the hex value of a character (base16). + * + * @param c A character between '0' and '9' or between 'A' and 'F' or + * between 'a' and 'f'. + * @return An int between 0 and 15, or -1 if c was not a hex digit. + */ + public static int dehexchar(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'A' && c <= 'F') { + return c - ('A' - 10); + } + if (c >= 'a' && c <= 'f') { + return c - ('a' - 10); + } + return -1; + } + + /** + * 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() { + return this.eof && !this.usePrevious; + } + + /** + * Determine if the source string still contains characters that next() + * can consume. + * + * @return true if not yet at the end of the source. + * @throws JSONException thrown if there is an error stepping forward + * or backward while checking for more data. + */ + public boolean more() throws JSONException { + if (this.usePrevious) { + return true; + } + try { + this.reader.mark(1); + } catch (IOException e) { + throw new JSONException("Unable to preserve stream position", e); + } + try { + // -1 is EOF, but next() can not consume the null character '\0' + if (this.reader.read() <= 0) { + this.eof = true; + return false; + } + this.reader.reset(); + } catch (IOException e) { + throw new JSONException("Unable to read the next character from the stream", e); + } + return true; + } + + /** + * Get the next character in the source string. + * + * @return The next character, or 0 if past the end of the source string. + * @throws JSONException Thrown if there is an error reading the source string. + */ + public char next() throws JSONException { + int c; + if (this.usePrevious) { + this.usePrevious = false; + c = this.previous; + } else { + try { + c = this.reader.read(); + } catch (IOException exception) { + throw new JSONException(exception); + } + } + if (c <= 0) { // End of stream + this.eof = true; + return 0; + } + this.incrementIndexes(c); + this.previous = (char) c; + return this.previous; + } + + /** + * Increments the internal indexes according to the previous character + * read and the character passed as the current character. + * + * @param c the current character read. + */ + private void incrementIndexes(int c) { + if (c > 0) { + this.index++; + if (c == '\r') { + this.line++; + this.characterPreviousLine = this.character; + this.character = 0; + } else if (c == '\n') { + if (this.previous != '\r') { + this.line++; + this.characterPreviousLine = this.character; + } + this.character = 0; + } else { + this.character++; + } + } + } + + /** + * Consume the next character, and check that it matches a specified + * character. + * + * @param c The character to match. + * @return The character. + * @throws JSONException if the character does not match. + */ + public char next(char c) throws JSONException { + char n = this.next(); + if (n != c) { + if (n > 0) { + throw this.syntaxError("Expected '" + c + "' and instead saw '" + n + "'"); + } + throw this.syntaxError("Expected '" + c + "' and instead saw ''"); + } + return n; + } + + /** + * Get the next n characters. + * + * @param n The number of characters to take. + * @return A string of n characters. + * @throws JSONException Substring bounds error if there are not n characters remaining in the source string. + */ + public String next(int n) throws JSONException { + if (n == 0) { + return ""; + } + + char[] chars = new char[n]; + int pos = 0; + + while (pos < n) { + chars[pos] = this.next(); + if (this.end()) { + throw this.syntaxError("Substring bounds error"); + } + pos += 1; + } + return new String(chars); + } + + /** + * Get the next char in the string, skipping whitespace. + * + * @throws JSONException Thrown if there is an error reading the source string. + * @return A character, or 0 if there are no more characters. + */ + public char nextClean() throws JSONException { + for (;;) { + char c = this.next(); + if (c == 0 || c > ' ') { + return c; + } + } + } + + /** + * Return the characters up to the next close quote character. + * Backslash processing is done. The formal JSON format does not + * allow strings in single quotes, but an implementation is allowed to + * accept them. + * + * @param quote The quoting character, either " (double quote) or + * ' (single quote). + * @return A String. + * @throws JSONException Unterminated string. + */ + public String nextString(char quote) throws JSONException { + char c; + StringBuilder sb = new StringBuilder(); + for (;;) { + c = this.next(); + switch (c) { + case 0: + case '\n': + case '\r': + throw this.syntaxError("Unterminated string"); + case '\\': + c = this.next(); + switch (c) { + case 'b': + sb.append('\b'); + break; + case 't': + sb.append('\t'); + break; + case 'n': + sb.append('\n'); + break; + case 'f': + sb.append('\f'); + break; + case 'r': + sb.append('\r'); + break; + case 'u': + try { + sb.append((char) Integer.parseInt(this.next(4), 16)); + } catch (NumberFormatException e) { + throw this.syntaxError("Illegal escape.", e); + } + break; + case '"': + case '\'': + case '\\': + case '/': + sb.append(c); + break; + default: + throw this.syntaxError("Illegal escape."); + } + break; + default: + if (c == quote) { + return sb.toString(); + } + sb.append(c); + } + } + } + + /** + * Get the text up but not including the specified character or the + * end of line, whichever comes first. + * + * @param delimiter A delimiter character. + * @return A string. + * @throws JSONException Thrown if there is an error while searching + * for the delimiter + */ + public String nextTo(char delimiter) throws JSONException { + StringBuilder sb = new StringBuilder(); + for (;;) { + char c = this.next(); + if (c == delimiter || c == 0 || c == '\n' || c == '\r') { + if (c != 0) { + this.back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + /** + * Get the text up but not including one of the specified delimiter + * characters or the end of line, whichever comes first. + * + * @param delimiters A set of delimiter characters. + * @return A string, trimmed. + * @throws JSONException Thrown if there is an error while searching + * for the delimiter + */ + public String nextTo(String delimiters) throws JSONException { + char c; + StringBuilder sb = new StringBuilder(); + for (;;) { + c = this.next(); + if (delimiters.indexOf(c) >= 0 || c == 0 + || c == '\n' || c == '\r') { + if (c != 0) { + this.back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + /** + * Get the next value. The value can be a Boolean, Double, Integer, + * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object. + * + * @throws JSONException If syntax error. + * + * @return An object. + */ + public Object nextValue() throws JSONException { + char c = this.nextClean(); + String string; + + switch (c) { + case '"': + case '\'': + return this.nextString(c); + case '{': + this.back(); + return new JSONObject(this); + case '[': + this.back(); + return new JSONArray(this); + } + + /* + * Handle unquoted text. This could be the values true, false, or + * null, or it can be a number. An implementation (such as this one) + * is allowed to also accept non-standard forms. + * + * Accumulate characters until we reach the end of the text or a + * formatting character. + */ + StringBuilder sb = new StringBuilder(); + while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { + sb.append(c); + c = this.next(); + } + this.back(); + + string = sb.toString().trim(); + if ("".equals(string)) { + throw this.syntaxError("Missing value"); + } + return JSONObject.stringToValue(string); + } + + /** + * Skip characters until the next character is the requested character. + * If the requested character is not found, no characters are skipped. + * + * @param to A character to skip to. + * @return The requested character, or zero if the requested character + * is not found. + * @throws JSONException Thrown if there is an error while searching + * for the to character + */ + public char skipTo(char to) throws JSONException { + char c; + try { + long startIndex = this.index; + long startCharacter = this.character; + long startLine = this.line; + this.reader.mark(1000000); + do { + c = this.next(); + if (c == 0) { + // in some readers, reset() may throw an exception if + // the remaining portion of the input is greater than + // the mark size (1,000,000 above). + this.reader.reset(); + this.index = startIndex; + this.character = startCharacter; + this.line = startLine; + return 0; + } + } while (c != to); + this.reader.mark(1); + } catch (IOException exception) { + throw new JSONException(exception); + } + this.back(); + return c; + } + + /** + * Make a JSONException to signal a syntax error. + * + * @param message The error message. + * @return A JSONException object, suitable for throwing + */ + public JSONException syntaxError(String message) { + return new JSONException(message + this.toString()); + } + + /** + * Make a JSONException to signal a syntax error. + * + * @param message The error message. + * @param causedBy The throwable that caused the error. + * @return A JSONException object, suitable for throwing + */ + public JSONException syntaxError(String message, Throwable causedBy) { + return new JSONException(message + this.toString(), causedBy); + } + + /** + * Make a printable string of this JSONTokener. + * + * @return " at {index} [character {character} line {line}]" + */ + @Override + public String toString() { + return " at " + this.index + " [character " + this.character + " line " + + this.line + "]"; + } +} diff --git a/JSONWriter.java b/src/org/json/JSONWriter.java similarity index 100% rename from JSONWriter.java rename to src/org/json/JSONWriter.java diff --git a/src/org/json/Main.java b/src/org/json/Main.java new file mode 100644 index 000000000..1b868c512 --- /dev/null +++ b/src/org/json/Main.java @@ -0,0 +1,49 @@ +/* +Copyright (c) 2018 Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +package org.json; + +import java.io.IOException; +import java.net.URL; +import java.util.PropertyResourceBundle; + +/** + * Main class, only used to give some informations about the version of the library on the command line. + * + * @since 1.0 + */ +public class Main { + public static void main(String[] args) { + URL url = Main.class.getResource("json.properties"); + try { + PropertyResourceBundle prb = new PropertyResourceBundle(url.openStream()); + String version = prb.getString("version"); + String date = prb.getString("date"); + System.out.println("JSON version " + version + " build on " + date); + System.out.println("Distributed under the JSON License"); + System.out.println("(This is a fork of https://github.com/stleary/JSON-java)"); + } catch (IOException ex) { + ex.printStackTrace(); + } + } +} diff --git a/Property.java b/src/org/json/Property.java similarity index 100% rename from Property.java rename to src/org/json/Property.java diff --git a/src/org/json/XML.java b/src/org/json/XML.java new file mode 100644 index 000000000..0b371cad0 --- /dev/null +++ b/src/org/json/XML.java @@ -0,0 +1,663 @@ +package org.json; + +/* +Copyright (c) 2015 JSON.org +Copyright (c) 2018, 2019 Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +import java.io.Reader; +import java.io.StringReader; +import java.util.Iterator; + +/** + * This provides static methods to convert an XML text into a JSONObject, and to + * covert a JSONObject into an XML text. + * + * @author JSON.org + * @version 1.4 + */ +public class XML { + /** The Character '&'. */ + public static final Character AMP = '&'; + + /** The Character '''. */ + public static final Character APOS = '\''; + + /** The Character '!'. */ + public static final Character BANG = '!'; + + /** The Character '='. */ + public static final Character EQ = '='; + + /** The Character '>'. */ + public static final Character GT = '>'; + + /** The Character '<'. */ + public static final Character LT = '<'; + + /** The Character '?'. */ + public static final Character QUEST = '?'; + + /** The Character '"'. */ + public static final Character QUOT = '"'; + + /** The Character '/'. */ + public static final Character SLASH = '/'; + + /** + * Creates an iterator for navigating Code Points in a string instead of + * characters. Once Java7 support is dropped, this can be replaced with + * + * string.codePoints() + * + * which is available in Java8 and above. + * + * @see http://stackoverflow.com/a/21791059/6030888 + */ + private static Iterable codePointIterator(final String string) { + return new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + private int nextIndex = 0; + private int length = string.length(); + + @Override + public boolean hasNext() { + return this.nextIndex < this.length; + } + + @Override + public Integer next() { + int result = string.codePointAt(this.nextIndex); + this.nextIndex += Character.charCount(result); + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + + /** + * Replace special characters with XML escapes: + * + *
+    * & (ampersand) is replaced by &amp;
+    * < (less than) is replaced by &lt;
+    * > (greater than) is replaced by &gt;
+    * " (double quote) is replaced by &quot;
+    * ' (single quote / apostrophe) is replaced by &apos;
+    * 
+ * + * @param string + * The string to be escaped. + * @return The escaped string. + */ + public static String escape(String string) { + StringBuilder sb = new StringBuilder(string.length()); + for (final int cp : codePointIterator(string)) { + switch (cp) { + case '&': + sb.append("&"); + break; + case '<': + sb.append("<"); + break; + case '>': + sb.append(">"); + break; + case '"': + sb.append("""); + break; + case '\'': + sb.append("'"); + break; + default: + if (mustEscape(cp)) { + sb.append("&#x"); + sb.append(Integer.toHexString(cp)); + sb.append(';'); + } else { + sb.appendCodePoint(cp); + } + } + } + return sb.toString(); + } + + /** + * @param cp code point to test + * @return true if the code point is not valid for an XML + */ + private static boolean mustEscape(int cp) { + /* Valid range from https://www.w3.org/TR/REC-xml/#charsets + * + * #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] + * + * any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. + */ + // isISOControl is true when (cp >= 0 && cp <= 0x1F) || (cp >= 0x7F && cp <= 0x9F) + // all ISO control characters are out of range except tabs and new lines + return (Character.isISOControl(cp) + && cp != 0x9 + && cp != 0xA + && cp != 0xD) || !( // valid the range of acceptable characters that aren't control + (cp >= 0x20 && cp <= 0xD7FF) + || (cp >= 0xE000 && cp <= 0xFFFD) + || (cp >= 0x10000 && cp <= 0x10FFFF)); + } + + /** + * Removes XML escapes from the string. + * + * @param string + * string to remove escapes from + * @return string with converted entities + */ + public static String unescape(String string) { + StringBuilder sb = new StringBuilder(string.length()); + for (int i = 0, length = string.length(); i < length; i++) { + char c = string.charAt(i); + if (c == '&') { + final int semic = string.indexOf(';', i); + if (semic > i) { + final String entity = string.substring(i + 1, semic); + sb.append(XMLTokener.unescapeEntity(entity)); + // skip past the entity we just parsed. + i += entity.length() + 1; + } else { + // this shouldn't happen in most cases since the parser + // errors on unclosed entries. + sb.append(c); + } + } else { + // not part of an entity + sb.append(c); + } + } + return sb.toString(); + } + + /** + * Throw an exception if the string contains whitespace. Whitespace is not + * allowed in tagNames and attributes. + * + * @param string + * A string. + * @throws JSONException Thrown if the string contains whitespace or is empty. + */ + public static void noSpace(String string) throws JSONException { + int i, length = string.length(); + if (length == 0) { + throw new JSONException("Empty string."); + } + for (i = 0; i < length; i += 1) { + if (Character.isWhitespace(string.charAt(i))) { + throw new JSONException("'" + string + "' contains a space character."); + } + } + } + + /** + * Scan the content following the named tag, attaching it to the context. + * + * @param x + * The XMLTokener containing the source string. + * @param context + * The JSONObject that will include the new material. + * @param name + * The tag name. + * @return true if the close tag is processed. + * @throws JSONException + */ + private static boolean parse(XMLTokener x, JSONObject context, String name, boolean keepStrings) throws JSONException { + char c; + int i; + JSONObject jsonobject; + String string; + String tagName; + Object token; + + // Test for and skip past these forms: + // + // + // + // + // Report errors for these forms: + // <> + // <= + // << + token = x.nextToken(); + + // "); + return false; + } + x.back(); + } else if (c == '[') { + token = x.nextToken(); + if ("CDATA".equals(token)) { + if (x.next() == '[') { + string = x.nextCDATA(); + if (string.length() > 0) { + context.accumulate("content", string); + } + return false; + } + } + throw x.syntaxError("Expected 'CDATA['"); + } + i = 1; + do { + token = x.nextMeta(); + if (token == null) { + throw x.syntaxError("Missing '>' after ' 0); + return false; + } else if (token == QUEST) { + + // "); + return false; + } else if (token == SLASH) { + + // Close tag + if (x.nextToken() != GT) { + throw x.syntaxError("Misshaped tag"); + } + if (jsonobject.length() > 0) { + context.accumulate(tagName, jsonobject); + } else { + context.accumulate(tagName, ""); + } + return false; + + } else if (token == GT) { + // Content, between <...> and + for (;;) { + token = x.nextContent(); + if (token == null) { + if (tagName != null) { + throw x.syntaxError("Unclosed tag " + tagName); + } + return false; + } else if (token instanceof String) { + string = (String) token; + if (string.length() > 0) { + jsonobject.accumulate("content", + keepStrings ? string : stringToValue(string)); + } + + } else if (token == LT) { + // Nested element + if (parse(x, jsonobject, tagName, keepStrings)) { + if (jsonobject.length() == 0) { + context.accumulate(tagName, ""); + } else if (jsonobject.length() == 1 + && jsonobject.opt("content") != null) { + context.accumulate(tagName, + jsonobject.opt("content")); + } else { + context.accumulate(tagName, jsonobject); + } + return false; + } + } + } + } else { + throw x.syntaxError("Misshaped tag"); + } + } + } + } + + /** + * 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. + public static Object stringToValue(String string) { + if (string.equals("")) { + return string; + } + if (string.equalsIgnoreCase("true")) { + return Boolean.TRUE; + } + if (string.equalsIgnoreCase("false")) { + return Boolean.FALSE; + } + if (string.equalsIgnoreCase("null")) { + 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 { + // if we want full Big Number support this block can be replaced with: + // return stringToNumber(string); + if (string.indexOf('.') > -1 || string.indexOf('e') > -1 || string.indexOf('E') > -1 || "-0".equals(string)) { + Double d = Double.valueOf(string); + if (!d.isInfinite() && !d.isNaN()) { + return d; + } + } else { + Long myLong = Long.valueOf(string); + if (string.equals(myLong.toString())) { + if (myLong.longValue() == myLong.intValue()) { + return myLong.intValue(); + } + return myLong; + } + } + } catch (NumberFormatException ignore) { + } + } + return string; + } + + /** + * 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 <[ [ ]]> + * are ignored. + * + * @param string + * The source string. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static JSONObject toJSONObject(String string) throws JSONException { + return toJSONObject(string, false); + } + + /** + * 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 <[ [ ]]> + * are ignored. + * + * @param reader The XML source reader. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static JSONObject toJSONObject(Reader reader) throws JSONException { + return toJSONObject(reader, false); + } + + /** + * 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 <[ [ ]]> + * are ignored. + * + * All values are converted as strings, for 1, 01, 29.0 will not be coerced to + * numbers but will instead be the exact value as seen in the XML document. + * + * @param reader The XML source reader. + * @param keepStrings If true, then values will not be coerced into boolean + * or numeric values and will instead be left as strings + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static JSONObject toJSONObject(Reader reader, boolean keepStrings) throws JSONException { + JSONObject jo = new JSONObject(); + XMLTokener x = new XMLTokener(reader); + while (x.more()) { + x.skipPast("<"); + if (x.more()) { + parse(x, jo, null, keepStrings); + } + } + return jo; + } + + /** + * 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 <[ [ ]]> + * are ignored. + * + * All values are converted as strings, for 1, 01, 29.0 will not be coerced to + * numbers but will instead be the exact value as seen in the XML document. + * + * @param string The source string. + * @param keepStrings If true, then values will not be coerced into boolean or numeric values and will instead be left as strings + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { + return toJSONObject(new StringReader(string), keepStrings); + } + + /** + * Convert a JSONObject into a well-formed, element-normal XML string. + * + * @param object + * A JSONObject. + * @return A string. + * @throws JSONException Thrown if there is an error parsing the string + */ + public static String toString(Object object) throws JSONException { + return toString(object, null); + } + + /** + * Convert a JSONObject into a well-formed, element-normal XML string. + * + * @param object + * A JSONObject. + * @param tagName + * The optional name of the enclosing tag. + * @return A string. + * @throws JSONException Thrown if there is an error parsing the string + */ + public static String toString(final Object object, final String tagName) + throws JSONException { + StringBuilder sb = new StringBuilder(); + JSONArray ja; + JSONObject jo; + String string; + + if (object instanceof JSONObject) { + + // Emit + if (tagName != null) { + sb.append('<'); + sb.append(tagName); + sb.append('>'); + } + + // Loop thru the keys. + // don't use the new entrySet accessor to maintain Android Support + jo = (JSONObject) object; + for (final String key : jo.keySet()) { + Object value = jo.opt(key); + if (value == null) { + value = ""; + } else if (value.getClass().isArray()) { + value = new JSONArray(value); + } + + // Emit content in body + if ("content".equals(key)) { + if (value instanceof JSONArray) { + ja = (JSONArray) value; + int jaLength = ja.length(); + // don't use the new iterator API to maintain support for Android + for (int i = 0; i < jaLength; i++) { + if (i > 0) { + sb.append('\n'); + } + Object val = ja.opt(i); + sb.append(escape(val.toString())); + } + } else { + sb.append(escape(value.toString())); + } + + // Emit an array of similar keys + } else if (value instanceof JSONArray) { + ja = (JSONArray) value; + int jaLength = ja.length(); + // don't use the new iterator API to maintain support for Android + for (int i = 0; i < jaLength; i++) { + Object val = ja.opt(i); + if (val instanceof JSONArray) { + sb.append('<'); + sb.append(key); + sb.append('>'); + sb.append(toString(val)); + sb.append("'); + } else { + sb.append(toString(val, key)); + } + } + } else if ("".equals(value)) { + sb.append('<'); + sb.append(key); + sb.append("/>"); + + // Emit a new tag + } else { + sb.append(toString(value, key)); + } + } + if (tagName != null) { + + // Emit the close tag + sb.append("'); + } + return sb.toString(); + + } + + if (object != null && (object instanceof JSONArray || object.getClass().isArray())) { + if (object.getClass().isArray()) { + ja = new JSONArray(object); + } else { + ja = (JSONArray) object; + } + int jaLength = ja.length(); + // don't use the new iterator API to maintain support for Android + for (int i = 0; i < jaLength; i++) { + Object val = ja.opt(i); + // XML does not have good support for arrays. If an array + // appears in a place where XML is lacking, synthesize an + // element. + sb.append(toString(val, tagName == null ? "array" : tagName)); + } + return sb.toString(); + } + + string = (object == null) ? "null" : escape(object.toString()); + return (tagName == null) ? "\"" + string + "\"" + : (string.length() == 0) ? "<" + tagName + "/>" : "<" + tagName + ">" + string + ""; + + } +} diff --git a/src/org/json/XMLTokener.java b/src/org/json/XMLTokener.java new file mode 100644 index 000000000..0a7b14804 --- /dev/null +++ b/src/org/json/XMLTokener.java @@ -0,0 +1,403 @@ +package org.json; + +/* +Copyright (c) 2002 JSON.org +Copyright (c) 2019 Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +import java.io.Reader; +import java.util.HashMap; + +/** + * The XMLTokener extends the JSONTokener to provide additional methods + * for the parsing of XML texts. + * + * @author JSON.org + * @version 1.4 + */ +public class XMLTokener extends JSONTokener { + /** The table of entity values. It initially contains Character values for + * amp, apos, gt, lt, quot. + */ + public static final HashMap entity; + + static { + entity = new HashMap<>(8); + entity.put("amp", XML.AMP); + entity.put("apos", XML.APOS); + entity.put("gt", XML.GT); + entity.put("lt", XML.LT); + entity.put("quot", XML.QUOT); + } + + /** + * Construct an XMLTokener from a Reader. + * + * @param r A source reader. + */ + public XMLTokener(Reader r) { + super(r); + } + + /** + * Construct an XMLTokener from a string. + * + * @param s A source string. + */ + public XMLTokener(String s) { + super(s); + } + + /** + * Get the text in the CDATA block. + * + * @return The string up to the ]]>. + * @throws JSONException If the ]]> is not found. + */ + public String nextCDATA() throws JSONException { + char c; + int i; + StringBuilder sb = new StringBuilder(); + while (more()) { + c = next(); + sb.append(c); + i = sb.length() - 3; + if (i >= 0 && sb.charAt(i) == ']' + && sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') { + sb.setLength(i); + return sb.toString(); + } + } + throw syntaxError("Unclosed CDATA"); + } + + /** + * Get the next XML outer token, trimming whitespace. There are two kinds + * of tokens: the '<' character which begins a markup tag, and the content + * text between markup tags. + * + * @return A string, or a '<' Character, or null if there is no more + * source text. + * @throws JSONException + */ + public Object nextContent() throws JSONException { + char c; + StringBuilder sb; + do { + c = next(); + } while (Character.isWhitespace(c)); + if (c == 0) { + return null; + } + if (c == '<') { + return XML.LT; + } + sb = new StringBuilder(); + for (;;) { + if (c == 0) { + return sb.toString().trim(); + } + if (c == '<') { + back(); + return sb.toString().trim(); + } + if (c == '&') { + sb.append(nextEntity(c)); + } else { + sb.append(c); + } + c = next(); + } + } + + /** + * Return the next entity. These entities are translated to Characters: + * & ' > < ". + * + * @param ampersand An ampersand character. + * @return A Character or an entity String if the entity is not recognized. + * @throws JSONException If missing ';' in XML entity. + */ + public Object nextEntity(char ampersand) throws JSONException { + StringBuilder sb = new StringBuilder(); + for (;;) { + char c = next(); + if (Character.isLetterOrDigit(c) || c == '#') { + sb.append(Character.toLowerCase(c)); + } else if (c == ';') { + break; + } else { + throw syntaxError("Missing ';' in XML entity: &" + sb); + } + } + String string = sb.toString(); + return unescapeEntity(string); + } + + /** + * Unescapes an XML entity encoding; + * + * @param e entity (only the actual entity value, not the preceding & or ending ; + * @return + */ + static String unescapeEntity(String e) { + // 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') { + // hex encoded unicode + cp = Integer.parseInt(e.substring(2), 16); + } else { + // decimal encoded unicode + cp = Integer.parseInt(e.substring(1)); + } + return new String(new int[] { cp }, 0, 1); + } + Character knownEntity = entity.get(e); + if (knownEntity == null) { + // we don't know the entity so keep it encoded + return '&' + e + ';'; + } + return knownEntity.toString(); + } + + /** + * Returns the next XML meta token. This is used for skipping over + * and structures. + * + * @return Syntax characters (< > / = ! ?) are returned as + * Character, and strings and names are returned as Boolean. We don't care + * what the values actually are. + * @throws JSONException If a string is not properly closed or if the XML + * is badly structured. + */ + public Object nextMeta() throws JSONException { + char c; + char q; + do { + c = next(); + } while (Character.isWhitespace(c)); + switch (c) { + case 0: + throw syntaxError("Misshaped meta tag"); + case '<': + return XML.LT; + case '>': + return XML.GT; + case '/': + return XML.SLASH; + case '=': + return XML.EQ; + case '!': + return XML.BANG; + case '?': + return XML.QUEST; + case '"': + case '\'': + q = c; + for (;;) { + c = next(); + if (c == 0) { + throw syntaxError("Unterminated string"); + } + if (c == q) { + return Boolean.TRUE; + } + } + default: + for (;;) { + c = next(); + if (Character.isWhitespace(c)) { + return Boolean.TRUE; + } + switch (c) { + case 0: + case '<': + case '>': + case '/': + case '=': + case '!': + case '?': + case '"': + case '\'': + back(); + return Boolean.TRUE; + } + } + } + } + + /** + * Get the next XML Token. These tokens are found inside of angle + * brackets. It may be one of these characters: / &t; = ! ? or it + * may be a string wrapped in single quotes or double quotes, or it may be a + * name. + * + * @return a String or a Character. + * @throws JSONException If the XML is not well formed. + */ + public Object nextToken() throws JSONException { + char c; + char q; + StringBuilder sb; + do { + c = next(); + } while (Character.isWhitespace(c)); + switch (c) { + case 0: + throw syntaxError("Misshaped element"); + case '<': + throw syntaxError("Misplaced '<'"); + case '>': + return XML.GT; + case '/': + return XML.SLASH; + case '=': + return XML.EQ; + case '!': + return XML.BANG; + case '?': + return XML.QUEST; + +// Quoted string + case '"': + case '\'': + q = c; + sb = new StringBuilder(); + for (;;) { + c = next(); + if (c == 0) { + throw syntaxError("Unterminated string"); + } + if (c == q) { + return sb.toString(); + } + if (c == '&') { + sb.append(nextEntity(c)); + } else { + sb.append(c); + } + } + default: + +// Name + sb = new StringBuilder(); + for (;;) { + sb.append(c); + c = next(); + if (Character.isWhitespace(c)) { + return sb.toString(); + } + switch (c) { + case 0: + return sb.toString(); + case '>': + case '/': + case '=': + case '!': + case '?': + case '[': + case ']': + back(); + return sb.toString(); + case '<': + case '"': + case '\'': + throw syntaxError("Bad character in a name"); + } + } + } + } + + /** + * Skip characters until past the requested string. + * If it is not found, we are left at the end of the source with a result of false. + * + * @param to A string to skip past. + */ + // The Android implementation of JSONTokener has a public method of public void skipPast(String to) + // even though ours does not have that method, to have API compatibility, our method in the subclass + // should match. + public void skipPast(String to) { + boolean b; + char c; + int i; + int j; + int offset = 0; + int length = to.length(); + char[] circle = new char[length]; + + /* + * First fill the circle buffer with as many characters as are in the + * to string. If we reach an early end, bail. + */ + for (i = 0; i < length; i += 1) { + c = next(); + if (c == 0) { + return; + } + circle[i] = c; + } + + /* We will loop, possibly for all of the remaining characters. */ + for (;;) { + j = offset; + b = true; + + /* Compare the circle buffer with the to string. */ + for (i = 0; i < length; i += 1) { + if (circle[j] != to.charAt(i)) { + b = false; + break; + } + j += 1; + if (j >= length) { + j -= length; + } + } + + /* If we exit the loop with b intact, then victory is ours. */ + if (b) { + return; + } + + /* Get the next character. If there isn't one, then defeat is ours. */ + c = next(); + if (c == 0) { + return; + } + /* + * Shove the character in the circle buffer and advance the + * circle offset. The offset is mod n. + */ + circle[offset] = c; + offset += 1; + if (offset >= length) { + offset -= length; + } + } + } +} diff --git a/src/org/json/json.properties b/src/org/json/json.properties new file mode 100644 index 000000000..d18ce04db --- /dev/null +++ b/src/org/json/json.properties @@ -0,0 +1,2 @@ +version=1.7.7 +date=23/01/2024 diff --git a/src/org/json/package.html b/src/org/json/package.html new file mode 100644 index 000000000..ba76a6745 --- /dev/null +++ b/src/org/json/package.html @@ -0,0 +1,10 @@ + + + + + + + + The json package. The code is copied from the https://github.com/stleary/JSON-java project. + + diff --git a/src/overview.html b/src/overview.html new file mode 100644 index 000000000..d3768dcac --- /dev/null +++ b/src/overview.html @@ -0,0 +1,93 @@ + + + + + + + + This is a fork of the JSON project in github (https://github.com/stleary/JSON-java) for the 20180813 version. + +

+ Compared to this implementation: +

    +
  • this is a Netbeans project
  • +
  • JSONObject and JSONArray both have an equals and hashCode method
  • +
  • a FileUtils class with static method has been added to simply the generation to/from a File / a JSON object
  • +
  • The supported Java version is Java 8
  • +
+ +

Overview

+ JSON is a light-weight, language independent, data interchange format. See http://www.JSON.org/ + +

+ The files in this package implement JSON encoders/decoders in Java. + It also includes the capability to convert between JSON and XML, HTTP + headers, Cookies, and CDL. + +

+ This is a reference implementation. There is a large number of JSON packages + in Java. Perhaps someday the Java community will standardize on one. Until + then, choose carefully. + +

+ The license includes this restriction: "The software shall be used for good, + not evil." If your conscience cannot live with that, then choose a different + package. + +

+ The package compiles on Java 1.8. + +

Code structure

+
    +
  • JSONObject.java: The JSONObject can parse text from a String or a JSONTokener + to produce a map-like object. The object provides methods for manipulating its + contents, and for producing a JSON compliant object serialization.
  • +
  • JSONArray.java: The JSONArray can parse text from a String or a JSONTokener + to produce a vector-like object. The object provides methods for manipulating + its contents, and for producing a JSON compliant array serialization.
  • +
  • JSONTokener.java: The JSONTokener breaks a text into a sequence of individual + tokens. It can be constructed from a String, Reader, or InputStream
  • +
  • JSONException.java: The JSONException is the standard exception type thrown by this package.
  • +
  • JSONPointer.java: Implementation of [JSON Pointer (RFC 6901)](https://tools.ietf.org/html/rfc6901). Supports + JSON Pointers both in the form of string representation and URI fragment representation.
  • +
  • JSONPropertyIgnore.java: Annotation class that can be used on Java Bean getter methods. + When used on a bean method that would normally be serialized into a JSONObject, it + overrides the getter-to-key-name logic and forces the property to be excluded from the + resulting JSONObject.
  • +
  • JSONPropertyName.java: Annotation class that can be used on Java Bean getter methods. + When used on a bean method that would normally be serialized into a JSONObject, it + overrides the getter-to-key-name logic and uses the value of the annotation. The Bean + processor will look through the class hierarchy. This means you can use the annotation on + a base class or interface and the value of the annotation will be used even if the getter + is overridden in a child class.
  • +
  • JSONString.java: The JSONString interface requires a toJSONString method, + allowing an object to provide its own serialization.
  • +
  • JSONStringer.java: The JSONStringer provides a convenient facility for + building JSON strings.
  • +
  • JSONWriter.java: The JSONWriter provides a convenient facility for building + JSON text through a writer.
  • +
  • CDL.java: CDL provides support for converting between JSON and comma delimited lists.
  • +
  • Cookie.java: Cookie provides support for converting between JSON and cookies.
  • +
  • CookieList.java: CookieList provides support for converting between JSON and cookie lists.
  • +
  • HTTP.java: HTTP provides support for converting between JSON and HTTP headers.
  • +
  • XML.java: XML provides support for converting between JSON and XML.
  • +
+ +

About numeric types

+ Numeric types in this package comply with + [ECMA-404: The JSON Data Interchange Format](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) and + [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange Format](https://tools.ietf.org/html/rfc7159#section-6). + This package fully supports `Integer`, `Long`, and `Double` Java types. Partial support + for `BigInteger` and `BigDecimal` values in `JSONObject` and `JSONArray` objects is provided + in the form of `get()`, `opt()`, and `put()` API methods. + +

+ In compliance with RFC7159 page 10 section 9, the parser is more lax with what is valid + JSON than the Generator. For Example, the tab character (U+0009) is allowed when reading + JSON Text strings, but when output by the Generator, tab is properly converted to \t in + the string. Other instances may occur where reading invalid JSON text does not cause an + error to be generated. Malformed JSON Texts such as missing end " (quote) on strings or + invalid number formats (1.2e6.3) will cause errors as such documents can not be read + reliably. + + diff --git a/test/org/json/FileUtils2Test.java b/test/org/json/FileUtils2Test.java new file mode 100644 index 000000000..708fed571 --- /dev/null +++ b/test/org/json/FileUtils2Test.java @@ -0,0 +1,115 @@ +/* +Copyright (C) 2020, 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertEquals; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @version 1.7.2 + */ +public class FileUtils2Test { + + public FileUtils2Test() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of toFile method, of class FileUtils. + */ + @Test + public void testFromFile() throws Exception { + System.out.println("FileUtils2Test : testFromFile"); + + URL url = this.getClass().getResource("testJSONArray.json"); + JSONArray array = FileUtils.toJSONArray(url); + assertEquals("Array length", 3, array.length()); + Object o = array.get(0); + assertEquals("First index", "cookie", o); + } + + /** + * Test of toFile method. + */ + @Test + public void testToFile() throws IOException { + System.out.println("FileUtils2Test : testToFile"); + JSONArray array = new JSONArray(); + array.put("cookie"); + array.put("fish"); + array.put("chips"); + + File file = File.createTempFile("json", ".json"); + FileUtils.toFile(array, file); + array = FileUtils.toJSONArray(file); + file.delete(); + + assertEquals("Array length", 3, array.length()); + Object o = array.get(0); + assertEquals("First index", "cookie", o); + } + + /** + * Test of toURL method. + */ + @Test + public void testToURL() throws IOException { + System.out.println("FileUtils2Test : testToURL"); + JSONArray array = new JSONArray(); + array.put("cookie"); + array.put("fish"); + array.put("chips"); + + File file = File.createTempFile("json", ".json"); + URL url = file.toURI().toURL(); + FileUtils.toURL(array, url); + array = FileUtils.toJSONArray(file); + file.delete(); + + assertEquals("Array length", 3, array.length()); + Object o = array.get(0); + assertEquals("First index", "cookie", o); + } +} diff --git a/test/org/json/FileUtils3Test.java b/test/org/json/FileUtils3Test.java new file mode 100644 index 000000000..96f3e4ef1 --- /dev/null +++ b/test/org/json/FileUtils3Test.java @@ -0,0 +1,175 @@ +/* +Copyright (C) 2020, 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @version 1.7.2 + */ +public class FileUtils3Test { + + public FileUtils3Test() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of toJSONObject method, of class FileUtils. + */ + @Test + public void testFromFile() throws Exception { + System.out.println("FileUtils3Test : testFromFile"); + + URL url = this.getClass().getResource("testJSON7.json"); + JSONObject jsonObj = FileUtils.toJSONObject(url); + + boolean isMarried = jsonObj.getBoolean("married"); + assertFalse("married", isMarried); + int age = jsonObj.getInt("age"); + assertEquals("age", 76, age); + String name = jsonObj.getString("name"); + assertNull("name", name); + + JSONObject jsonObjField = jsonObj.getJSONObject("passport"); + assertNotNull("passport", jsonObjField); + int id = jsonObjField.getInt("id"); + assertEquals("id", 100001, id); + String nationality = jsonObjField.getString("nationality"); + assertEquals("nationality", "American", nationality); + } + + /** + * Test of toFile method. + */ + @Test + public void testToFile() throws IOException { + System.out.println("FileUtilsTest : testToFile"); + JSONObject jsonObj = new JSONObject(); + jsonObj.putNULL("name"); + jsonObj.put("birthday", "1940-02-10"); + jsonObj.put("age", 76); + jsonObj.put("married", false); + + // Cannot set null directly + jsonObj.putNULL("car"); + + jsonObj.put("favorite_foods", new String[] { "cookie", "fish", "chips" }); + + JSONObject passportJsonObj = new JSONObject(); + passportJsonObj.put("id", 100001); + passportJsonObj.put("nationality", "American"); + jsonObj.put("passport", passportJsonObj); + + File file = File.createTempFile("json", ".json"); + FileUtils.toFile(jsonObj, file); + jsonObj = FileUtils.toJSONObject(file); + file.delete(); + + boolean isMarried = jsonObj.getBoolean("married"); + assertFalse("married", isMarried); + int age = jsonObj.getInt("age"); + assertEquals("age", 76, age); + String name = jsonObj.getString("name"); + assertNull("name", name); + String car = jsonObj.getString("car"); + assertNull("car", car); + + JSONObject jsonObjField = jsonObj.getJSONObject("passport"); + assertNotNull("passport", jsonObjField); + int id = jsonObjField.getInt("id"); + assertEquals("id", 100001, id); + String nationality = jsonObjField.getString("nationality"); + assertEquals("nationality", "American", nationality); + } + + /** + * Test of toURL method. + */ + @Test + public void testToURL() throws IOException { + System.out.println("FileUtilsTest : testToURL"); + JSONObject jsonObj = new JSONObject(); + jsonObj.putNULL("name"); + jsonObj.put("birthday", "1940-02-10"); + jsonObj.put("age", 76); + jsonObj.put("married", false); + + // Cannot set null directly + jsonObj.putNULL("car"); + + jsonObj.put("favorite_foods", new String[] { "cookie", "fish", "chips" }); + + JSONObject passportJsonObj = new JSONObject(); + passportJsonObj.put("id", 100001); + passportJsonObj.put("nationality", "American"); + jsonObj.put("passport", passportJsonObj); + + File file = File.createTempFile("json", ".json"); + URL url = file.toURI().toURL(); + FileUtils.toURL(jsonObj, url); + file.delete(); + jsonObj = FileUtils.toJSONObject(url); + + boolean isMarried = jsonObj.getBoolean("married"); + assertFalse("married", isMarried); + int age = jsonObj.getInt("age"); + assertEquals("age", 76, age); + String name = jsonObj.getString("name"); + assertNull("name", name); + String car = jsonObj.getString("car"); + assertNull("car", car); + + JSONObject jsonObjField = jsonObj.getJSONObject("passport"); + assertNotNull("passport", jsonObjField); + int id = jsonObjField.getInt("id"); + assertEquals("id", 100001, id); + String nationality = jsonObjField.getString("nationality"); + assertEquals("nationality", "American", nationality); + } +} diff --git a/test/org/json/FileUtils4Test.java b/test/org/json/FileUtils4Test.java new file mode 100644 index 000000000..b84dd6c90 --- /dev/null +++ b/test/org/json/FileUtils4Test.java @@ -0,0 +1,122 @@ +/* +Copyright (C) 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertTrue; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @version 1.7.3 + */ +public class FileUtils4Test { + + public FileUtils4Test() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of toFile method. + */ + @Test + public void testToFile() throws IOException { + System.out.println("FileUtils4Test : testToFile"); + JSONOptions.getDefault().keepKeysNaturalOrder(true); + JSONObject jsonObj = new JSONObject(); + jsonObj.put("name", "Bob"); + jsonObj.put("age", 23); + JSONObject socialSecJSon = new JSONObject(); + socialSecJSon.put("gender", "Male"); + socialSecJSon.put("number", 100000); + JSONArray children = new JSONArray(); + jsonObj.put("children", children); + JSONObject child = new JSONObject(); + child.put("name", "Paul"); + children.put(child); + child = new JSONObject(); + child.put("name", "Suzanne"); + children.put(child); + + File file = File.createTempFile("json", ".json"); + FileUtils.toFile(jsonObj, file); + + URL expected = this.getClass().getResource("expected/expected1.json"); + URL result = file.toURI().toURL(); + boolean isEqual = TestUtilities.checkContent(expected, result); + assertTrue("The file must be equal", isEqual); + file.delete(); + } + + /** + * Test of toFile method, using a variable. + */ + @Test + public void testToFile2() throws IOException { + System.out.println("FileUtils4Test : testToFile2"); + JSONOptions.getDefault().keepKeysNaturalOrder(true); + JSONObject jsonObj = new JSONObject(); + jsonObj.put("name", "Bob"); + jsonObj.put("age", 23); + JSONObject socialSecJSon = new JSONObject(); + socialSecJSon.put("gender", "Male"); + socialSecJSon.put("number", 100000); + JSONArray children = new JSONArray(); + jsonObj.put("children", children); + JSONObject child = new JSONObject(); + child.put("name", "Paul"); + children.put(child); + child = new JSONObject(); + child.put("name", "Suzanne"); + children.put(child); + + File file = File.createTempFile("json", ".json"); + FileUtils.toFile(jsonObj, file, "a"); + + URL expected = this.getClass().getResource("expected/expected2.json"); + URL result = file.toURI().toURL(); + boolean isEqual = TestUtilities.checkContent(expected, result); + assertTrue("The file must be equal", isEqual); + file.delete(); + } +} diff --git a/test/org/json/FileUtils5Test.java b/test/org/json/FileUtils5Test.java new file mode 100644 index 000000000..a9c50d01c --- /dev/null +++ b/test/org/json/FileUtils5Test.java @@ -0,0 +1,92 @@ +/* +Copyright (C) 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertTrue; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @since 1.7.2 + */ +public class FileUtils5Test { + + public FileUtils5Test() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of toFile method. + */ + @Test + public void testToFile() throws IOException { + System.out.println("FileUtils5Test : testToFile"); + JSONOptions.getDefault() + .keepKeysNaturalOrder(true) + .setIndentFactor(2); + JSONObject jsonObj = new JSONObject(); + jsonObj.put("name", "Bob"); + jsonObj.put("age", 23); + JSONObject socialSecJSon = new JSONObject(); + socialSecJSon.put("gender", "Male"); + socialSecJSon.put("number", 100000); + JSONArray children = new JSONArray(); + jsonObj.put("children", children); + JSONObject child = new JSONObject(); + child.put("name", "Paul"); + children.put(child); + child = new JSONObject(); + child.put("name", "Suzanne"); + children.put(child); + + File file = File.createTempFile("json", ".json"); + FileUtils.toFile(jsonObj, file); + + URL expected = this.getClass().getResource("expected/expected3.json"); + URL result = file.toURI().toURL(); + boolean isEqual = TestUtilities.checkContent(expected, result); + assertTrue("The file must be equal", isEqual); + file.delete(); + } +} diff --git a/test/org/json/FileUtils6BTest.java b/test/org/json/FileUtils6BTest.java new file mode 100644 index 000000000..9fbca5aa5 --- /dev/null +++ b/test/org/json/FileUtils6BTest.java @@ -0,0 +1,96 @@ +/* +Copyright (C) 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertTrue; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @since 1.7.2 + */ +public class FileUtils6BTest { + + public FileUtils6BTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of toFile method. + */ + @Test + public void testToFile2() throws IOException { + System.out.println("FileUtils6Test : testToFile2"); + JSONOptions.getDefault() + .keepKeysNaturalOrder(true).setIndentFactor(2) + .setIndentationType(JSONOptions.INDENT_DEFAULT); + JSONObject rootObj = new JSONObject(); + + JSONObject personObj = new JSONObject(); + personObj.put("name", "Bob"); + personObj.put("age", 23); + rootObj.put("person", personObj); + + JSONObject socialSecJSon = new JSONObject(); + socialSecJSon.put("gender", "Male"); + socialSecJSon.put("number", 100000); + JSONArray children = new JSONArray(); + personObj.put("children", children); + JSONObject child = new JSONObject(); + child.put("name", "Paul"); + children.put(child); + child = new JSONObject(); + child.put("name", "Suzanne"); + children.put(child); + + File file = File.createTempFile("json", ".json"); + FileUtils.toFile(rootObj, file); + + URL expected = this.getClass().getResource("expected/expected6.json"); + URL result = file.toURI().toURL(); + boolean isEqual = TestUtilities.checkContent(expected, result); + assertTrue("The file must be equal", isEqual); + file.delete(); + } +} diff --git a/test/org/json/FileUtils6Test.java b/test/org/json/FileUtils6Test.java new file mode 100644 index 000000000..f44f2d171 --- /dev/null +++ b/test/org/json/FileUtils6Test.java @@ -0,0 +1,135 @@ +/* +Copyright (C) 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertTrue; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @since 1.7.2 + */ +public class FileUtils6Test { + + public FileUtils6Test() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of toFile method. + */ + @Test + public void testToFile() throws IOException { + System.out.println("FileUtils6Test : testToFile"); + JSONOptions.getDefault() + .keepKeysNaturalOrder(true).setIndentFactor(2) + .setIndentationType(JSONOptions.INDENT_OBJECTS); + JSONObject rootObj = new JSONObject(); + + JSONObject personObj = new JSONObject(); + personObj.put("name", "Bob"); + personObj.put("age", 23); + rootObj.put("person", personObj); + + JSONObject socialSecJSon = new JSONObject(); + socialSecJSon.put("gender", "Male"); + socialSecJSon.put("number", 100000); + JSONArray children = new JSONArray(); + personObj.put("children", children); + JSONObject child = new JSONObject(); + child.put("name", "Paul"); + children.put(child); + child = new JSONObject(); + child.put("name", "Suzanne"); + children.put(child); + + File file = File.createTempFile("json", ".json"); + FileUtils.toFile(rootObj, file); + + URL expected = this.getClass().getResource("expected/expected4.json"); + URL result = file.toURI().toURL(); + boolean isEqual = TestUtilities.checkContent(expected, result); + assertTrue("The file must be equal", isEqual); + file.delete(); + } + + /** + * Test of toFile method. + */ + @Test + public void testToFile2() throws IOException { + System.out.println("FileUtils6Test : testToFile2"); + JSONOptions.getDefault() + .keepKeysNaturalOrder(true) + .setIndentationType(JSONOptions.INDENT_ALL) + .setIndentFactor(2); + JSONObject rootObj = new JSONObject(); + + JSONObject personObj = new JSONObject(); + personObj.put("name", "Bob"); + personObj.put("age", 23); + rootObj.put("person", personObj); + + JSONObject socialSecJSon = new JSONObject(); + socialSecJSon.put("gender", "Male"); + socialSecJSon.put("number", 100000); + JSONArray children = new JSONArray(); + personObj.put("children", children); + JSONObject child = new JSONObject(); + child.put("name", "Paul"); + children.put(child); + child = new JSONObject(); + child.put("name", "Suzanne"); + children.put(child); + + File file = File.createTempFile("json", ".json"); + FileUtils.toFile(rootObj, file); + + URL expected = this.getClass().getResource("expected/expected5.json"); + URL result = file.toURI().toURL(); + boolean isEqual = TestUtilities.checkContent(expected, result); + assertTrue("The file must be equal", isEqual); + file.delete(); + } +} diff --git a/test/org/json/FileUtils7Test.java b/test/org/json/FileUtils7Test.java new file mode 100644 index 000000000..4eedd7015 --- /dev/null +++ b/test/org/json/FileUtils7Test.java @@ -0,0 +1,91 @@ +/* +Copyright (C) 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertTrue; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @since 1.7.3 + */ +public class FileUtils7Test { + + public FileUtils7Test() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of toFile method. + */ + @Test + public void testToFile() throws IOException { + System.out.println("FileUtils6Test : testToFile"); + JSONOptions.getDefault() + .keepKeysNaturalOrder(true).setIndentFactor(2) + .setIndentationType(JSONOptions.INDENT_OBJECTS); + JSONObject rootObj = new JSONObject(); + + JSONObject personObj = new JSONObject(); + personObj.put("name", "Bob"); + personObj.put("age", 23); + rootObj.put("person", personObj); + + JSONObject socialSecJSon = new JSONObject(); + socialSecJSon.put("gender", "Male"); + socialSecJSon.put("number", 100000); + JSONObject child = new JSONObject(); + child.put("name", "Paul"); + personObj.put("child", child); + + File file = File.createTempFile("json", ".json"); + FileUtils.toFile(rootObj, file); + + URL expected = this.getClass().getResource("expected/expected7.json"); + URL result = file.toURI().toURL(); + boolean isEqual = TestUtilities.checkContent(expected, result); + assertTrue("The file must be equal", isEqual); + file.delete(); + } +} diff --git a/test/org/json/FileUtils8Test.java b/test/org/json/FileUtils8Test.java new file mode 100644 index 000000000..f0ca58331 --- /dev/null +++ b/test/org/json/FileUtils8Test.java @@ -0,0 +1,122 @@ +/* +Copyright (C) 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertTrue; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @since 1.7.4 + */ +public class FileUtils8Test { + + public FileUtils8Test() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of toFile method. + */ + @Test + public void testToFile() throws IOException { + System.out.println("FileUtils8Test : testToFile"); + JSONOptions.getDefault() + .keepKeysNaturalOrder(true).setIndentFactor(2) + .setIndentationType(JSONOptions.INDENT_OBJECTS); + JSONObject rootObj = new JSONObject(); + + JSONObject personObj = new JSONObject(); + personObj.put("name", "Bob"); + personObj.put("age", 23); + rootObj.put("person", personObj); + + JSONArray children = new JSONArray(); + personObj.put("children", children); + children.put(1); + children.put(2); + children.put(3); + + File file = File.createTempFile("json", ".json"); + FileUtils.toFile(rootObj, file); + + URL expected = this.getClass().getResource("expected/expected8.json"); + URL result = file.toURI().toURL(); + boolean isEqual = TestUtilities.checkContent(expected, result); + assertTrue("The file must be equal", isEqual); + file.delete(); + } + + /** + * Test of toFile method. + */ + @Test + public void testToFile2() throws IOException { + System.out.println("FileUtils8Test : testToFile2"); + JSONOptions.getDefault() + .keepKeysNaturalOrder(true).setIndentFactor(2) + .setIndentationType(JSONOptions.INDENT_OBJECTS); + JSONObject rootObj = new JSONObject(); + + JSONObject personObj = new JSONObject(); + personObj.put("name", "Bob"); + personObj.put("age", 23); + rootObj.put("person", personObj); + + JSONArray children = new JSONArray(); + personObj.put("children", children); + children.put("A"); + children.put("B"); + children.put("C"); + + File file = File.createTempFile("json", ".json"); + FileUtils.toFile(rootObj, file); + + URL expected = this.getClass().getResource("expected/expected9.json"); + URL result = file.toURI().toURL(); + boolean isEqual = TestUtilities.checkContent(expected, result); + assertTrue("The file must be equal", isEqual); + file.delete(); + } +} diff --git a/test/org/json/FileUtils9Test.java b/test/org/json/FileUtils9Test.java new file mode 100644 index 000000000..0e882b822 --- /dev/null +++ b/test/org/json/FileUtils9Test.java @@ -0,0 +1,177 @@ +/* +Copyright (C) 2024 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Check using InputStream to get JSON content. + * + * @since 1.7.7 + */ +public class FileUtils9Test { + + public FileUtils9Test() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of toJSONObject method. + */ + @Test + public void testFromFile() throws Exception { + System.out.println("FileUtils9Test : testFromFile"); + + URL url = this.getClass().getResource("testJSON.json"); + InputStream stream = url.openStream(); + JSONObject jsonObj = FileUtils.toJSONObject(stream); + + boolean isMarried = jsonObj.getBoolean("married"); + assertFalse("married", isMarried); + int age = jsonObj.getInt("age"); + assertEquals("age", 76, age); + String name = jsonObj.getString("name"); + assertEquals("name", "Tom", name); + + JSONObject jsonObjField = jsonObj.getJSONObject("passport"); + assertNotNull("passport", jsonObjField); + int id = jsonObjField.getInt("id"); + assertEquals("id", 100001, id); + String nationality = jsonObjField.getString("nationality"); + assertEquals("nationality", "American", nationality); + } + + /** + * Test of toFile method, of class FileUtils. + */ + @Test + public void testFromFile2() throws Exception { + System.out.println("FileUtils9Test : testFromFile2"); + + URL url = this.getClass().getResource("testJSONArray.json"); + InputStream stream = url.openStream(); + JSONArray array = FileUtils.toJSONArray(stream); + assertEquals("Array length", 3, array.length()); + Object o = array.get(0); + assertEquals("First index", "cookie", o); + } + + /** + * Test of toJSONObject method. + */ + @Test + public void testFromFile3() throws Exception { + System.out.println("FileUtils9Test : testFromFile3"); + + URL url = this.getClass().getResource("testJSON.json"); + InputStream stream = url.openStream(); + JSONElement jsonElt = FileUtils.toJSONElement(stream); + assertTrue("JSONElement must be a JSONObject", jsonElt instanceof JSONObject); + JSONObject jsonObj = (JSONObject) jsonElt; + + boolean isMarried = jsonObj.getBoolean("married"); + assertFalse("married", isMarried); + int age = jsonObj.getInt("age"); + assertEquals("age", 76, age); + String name = jsonObj.getString("name"); + assertEquals("name", "Tom", name); + + JSONObject jsonObjField = jsonObj.getJSONObject("passport"); + assertNotNull("passport", jsonObjField); + int id = jsonObjField.getInt("id"); + assertEquals("id", 100001, id); + String nationality = jsonObjField.getString("nationality"); + assertEquals("nationality", "American", nationality); + } + + /** + * Test of toJSONObject method. + */ + @Test + public void testToJSONObjectWithComments() throws IOException { + System.out.println("FileUtils9Test : testToJSONObjectWithComments"); + JSONOptions.getDefault().acceptJavascriptComments(true); + JSONOptions.getDefault().keepKeysNaturalOrder(true); + URL url = this.getClass().getResource("comments/comments2.json"); + InputStream stream = url.openStream(); + JSONObject jsonObj = FileUtils.toJSONObject(stream); + + int age = jsonObj.getInt("age"); + assertEquals("Age", 23, age); + + String name = jsonObj.getString("name"); + assertEquals("name", "Bob", name); + + JSONArray array = jsonObj.getJSONArray("children"); + assertNotNull("children array must exist", array); + assertEquals("array length", 2, array.length()); + } + + /** + * Test of toJSONObject method. + */ + @Test + public void testToJSONObjectWithComments2() throws IOException { + System.out.println("FileUtils9Test : testToJSONObjectWithComments2"); + JSONOptions.getDefault().acceptJavascriptComments(true); + JSONOptions.getDefault().keepKeysNaturalOrder(true); + URL url = this.getClass().getResource("comments/comments2.json"); + InputStream stream = url.openStream(); + JSONObject jsonObj = FileUtils.toJSONObject(stream); + + int age = jsonObj.getInt("age"); + assertEquals("Age", 23, age); + + String name = jsonObj.getString("name"); + assertEquals("name", "Bob", name); + + JSONArray array = jsonObj.getJSONArray("children"); + assertNotNull("children array must exist", array); + assertEquals("array length", 2, array.length()); + } +} diff --git a/test/org/json/FileUtilsComments2Test.java b/test/org/json/FileUtilsComments2Test.java new file mode 100644 index 000000000..384ba8e60 --- /dev/null +++ b/test/org/json/FileUtilsComments2Test.java @@ -0,0 +1,105 @@ +/* +Copyright (C) 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Tests with comments. + * + * @since 1.7.2 + */ +public class FileUtilsComments2Test { + + public FileUtilsComments2Test() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of toJSONObject method. + */ + @Test + public void testToJSONObjectWithComments() throws IOException { + System.out.println("FileUtilsComments2Test : testToJSONObjectWithComments"); + JSONOptions.getDefault().acceptJavascriptComments(true); + JSONOptions.getDefault().keepKeysNaturalOrder(true); + URL url = this.getClass().getResource("comments/comments2.json"); + File file = new File(url.getFile()); + JSONObject jsonObj = FileUtils.toJSONObject(file); + + int age = jsonObj.getInt("age"); + assertEquals("Age", 23, age); + + String name = jsonObj.getString("name"); + assertEquals("name", "Bob", name); + + JSONArray array = jsonObj.getJSONArray("children"); + assertNotNull("children array must exist", array); + assertEquals("array length", 2, array.length()); + } + + /** + * Test of toJSONObject method. + */ + @Test + public void testToJSONObjectWithComments2() throws IOException { + System.out.println("FileUtilsComments2Test : testToJSONObjectWithComments2"); + JSONOptions.getDefault().acceptJavascriptComments(true); + JSONOptions.getDefault().keepKeysNaturalOrder(true); + URL url = this.getClass().getResource("comments/comments2.json"); + JSONObject jsonObj = FileUtils.toJSONObject(url); + + int age = jsonObj.getInt("age"); + assertEquals("Age", 23, age); + + String name = jsonObj.getString("name"); + assertEquals("name", "Bob", name); + + JSONArray array = jsonObj.getJSONArray("children"); + assertNotNull("children array must exist", array); + assertEquals("array length", 2, array.length()); + } +} diff --git a/test/org/json/FileUtilsCommentsTest.java b/test/org/json/FileUtilsCommentsTest.java new file mode 100644 index 000000000..6b27a6776 --- /dev/null +++ b/test/org/json/FileUtilsCommentsTest.java @@ -0,0 +1,105 @@ +/* +Copyright (C) 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Tests with comments. + * + * @since 1.7.2 + */ +public class FileUtilsCommentsTest { + + public FileUtilsCommentsTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of toJSONObject method. + */ + @Test + public void testToJSONObjectWithComments() throws IOException { + System.out.println("FileUtilsCommentsTest : testToJSONObjectWithComments"); + JSONOptions.getDefault().acceptJavascriptComments(true); + JSONOptions.getDefault().keepKeysNaturalOrder(true); + URL url = this.getClass().getResource("comments/comments1.json"); + File file = new File(url.getFile()); + JSONObject jsonObj = FileUtils.toJSONObject(file); + + int age = jsonObj.getInt("age"); + assertEquals("Age", 23, age); + + String name = jsonObj.getString("name"); + assertEquals("name", "Bob", name); + + JSONArray array = jsonObj.getJSONArray("children"); + assertNotNull("children array must exist", array); + assertEquals("array length", 2, array.length()); + } + + /** + * Test of toJSONObject method. + */ + @Test + public void testToJSONObjectWithComments2() throws IOException { + System.out.println("FileUtilsCommentsTest : testToJSONObjectWithComments2"); + JSONOptions.getDefault().acceptJavascriptComments(true); + JSONOptions.getDefault().keepKeysNaturalOrder(true); + URL url = this.getClass().getResource("comments/comments1.json"); + JSONObject jsonObj = FileUtils.toJSONObject(url); + + int age = jsonObj.getInt("age"); + assertEquals("Age", 23, age); + + String name = jsonObj.getString("name"); + assertEquals("name", "Bob", name); + + JSONArray array = jsonObj.getJSONArray("children"); + assertNotNull("children array must exist", array); + assertEquals("array length", 2, array.length()); + } +} diff --git a/test/org/json/FileUtilsTest.java b/test/org/json/FileUtilsTest.java new file mode 100644 index 000000000..ccddf85b9 --- /dev/null +++ b/test/org/json/FileUtilsTest.java @@ -0,0 +1,170 @@ +/* +Copyright (C) 2018, 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @version 1.7.2 + */ +public class FileUtilsTest { + + public FileUtilsTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of toJSONObject method. + */ + @Test + public void testFromFile() throws Exception { + System.out.println("FileUtilsTest : testFromFile"); + + URL url = this.getClass().getResource("testJSON.json"); + JSONObject jsonObj = FileUtils.toJSONObject(url); + + boolean isMarried = jsonObj.getBoolean("married"); + assertFalse("married", isMarried); + int age = jsonObj.getInt("age"); + assertEquals("age", 76, age); + String name = jsonObj.getString("name"); + assertEquals("name", "Tom", name); + + JSONObject jsonObjField = jsonObj.getJSONObject("passport"); + assertNotNull("passport", jsonObjField); + int id = jsonObjField.getInt("id"); + assertEquals("id", 100001, id); + String nationality = jsonObjField.getString("nationality"); + assertEquals("nationality", "American", nationality); + } + + /** + * Test of toFile method. + */ + @Test + public void testToFile() throws IOException { + System.out.println("FileUtilsTest : testToFile"); + JSONObject jsonObj = new JSONObject(); + jsonObj.put("name", "Tom"); + jsonObj.put("birthday", "1940-02-10"); + jsonObj.put("age", 76); + jsonObj.put("married", false); + + // Cannot set null directly + jsonObj.put("car", JSONObject.NULL); + + jsonObj.put("favorite_foods", new String[] { "cookie", "fish", "chips" }); + + JSONObject passportJsonObj = new JSONObject(); + passportJsonObj.put("id", 100001); + passportJsonObj.put("nationality", "American"); + jsonObj.put("passport", passportJsonObj); + + File file = File.createTempFile("json", ".json"); + FileUtils.toFile(jsonObj, file); + jsonObj = FileUtils.toJSONObject(file); + file.delete(); + + boolean isMarried = jsonObj.getBoolean("married"); + assertFalse("married", isMarried); + int age = jsonObj.getInt("age"); + assertEquals("age", 76, age); + String name = jsonObj.getString("name"); + assertEquals("name", "Tom", name); + + JSONObject jsonObjField = jsonObj.getJSONObject("passport"); + assertNotNull("passport", jsonObjField); + int id = jsonObjField.getInt("id"); + assertEquals("id", 100001, id); + String nationality = jsonObjField.getString("nationality"); + assertEquals("nationality", "American", nationality); + } + + /** + * Test of toURL method. + */ + @Test + public void testToURL() throws IOException { + System.out.println("FileUtilsTest : testToURL"); + JSONObject jsonObj = new JSONObject(); + jsonObj.put("name", "Tom"); + jsonObj.put("birthday", "1940-02-10"); + jsonObj.put("age", 76); + jsonObj.put("married", false); + + // Cannot set null directly + jsonObj.put("car", JSONObject.NULL); + + jsonObj.put("favorite_foods", new String[] { "cookie", "fish", "chips" }); + + JSONObject passportJsonObj = new JSONObject(); + passportJsonObj.put("id", 100001); + passportJsonObj.put("nationality", "American"); + jsonObj.put("passport", passportJsonObj); + + File file = File.createTempFile("json", ".json"); + URL url = file.toURI().toURL(); + FileUtils.toURL(jsonObj, url); + file.delete(); + jsonObj = FileUtils.toJSONObject(file); + + boolean isMarried = jsonObj.getBoolean("married"); + assertFalse("married", isMarried); + int age = jsonObj.getInt("age"); + assertEquals("age", 76, age); + String name = jsonObj.getString("name"); + assertEquals("name", "Tom", name); + + JSONObject jsonObjField = jsonObj.getJSONObject("passport"); + assertNotNull("passport", jsonObjField); + int id = jsonObjField.getInt("id"); + assertEquals("id", 100001, id); + String nationality = jsonObjField.getString("nationality"); + assertEquals("nationality", "American", nationality); + } +} diff --git a/test/org/json/JSONElementTest.java b/test/org/json/JSONElementTest.java new file mode 100644 index 000000000..16f6be2db --- /dev/null +++ b/test/org/json/JSONElementTest.java @@ -0,0 +1,81 @@ +/* +Copyright (C) 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; +import java.io.Writer; +import java.net.URL; + +/** + * + * @since 1.7.5 + */ +public class JSONElementTest { + + public JSONElementTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + /** + * Test of testFromString method, of class JSONElement. + */ + @Test + public void testFromString() { + System.out.println("JSONElementTest : testFromString"); + URL url = this.getClass().getResource("testJSON.json"); + String content = TestUtilities.getContent(url); + JSONElement element = JSONElement.fromString(content); + assertTrue("JSONElement must be a JSONObject", element instanceof JSONObject); + } + + /** + * Test of testFromString method, of class JSONElement. + */ + @Test + public void testFromString2() { + System.out.println("JSONElementTest : testFromString2"); + URL url = this.getClass().getResource("testJSONArray.json"); + String content = TestUtilities.getContent(url); + JSONElement element = JSONElement.fromString(content); + assertTrue("JSONElement must be a JSONArray", element instanceof JSONArray); + } + +} diff --git a/test/org/json/JSONObject2Test.java b/test/org/json/JSONObject2Test.java new file mode 100644 index 000000000..8786ca04d --- /dev/null +++ b/test/org/json/JSONObject2Test.java @@ -0,0 +1,92 @@ +/* +Copyright (C) 2018 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertEquals; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @since 1.2 + */ +public class JSONObject2Test { + + public JSONObject2Test() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of creation method, of class JSONObject. + */ + @Test + public void testGetString() { + System.out.println("JSONObject2Test : testGetString"); + + JSONObject jsonObj = new JSONObject(); + jsonObj.put("name", "Tom"); + jsonObj.put("birthday", "1940-02-10"); + jsonObj.put("age", 76); + jsonObj.put("married", false); + + assertEquals("int field", 76, jsonObj.get("age")); + assertEquals("int field", "76", jsonObj.getString("age")); + assertEquals("int field", "Tom", jsonObj.getString("name")); + } + + /** + * Test of creation method, of class JSONObject. + */ + @Test + public void testCreate() { + System.out.println("JSONObject2Test : testCreate"); + + JSONObject jsonObj = JSONObject.create() + .put("name", "Tom") + .put("birthday", "1940-02-10") + .put("age", 76) + .put("married", false); + + assertEquals("int field", 76, jsonObj.get("age")); + assertEquals("int field", "76", jsonObj.getString("age")); + assertEquals("int field", "Tom", jsonObj.getString("name")); + } + +} diff --git a/test/org/json/JSONObject3Test.java b/test/org/json/JSONObject3Test.java new file mode 100644 index 000000000..23d8a72e8 --- /dev/null +++ b/test/org/json/JSONObject3Test.java @@ -0,0 +1,71 @@ +/* +Copyright (C) 2019 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertNull; +import java.io.IOException; +import java.net.URL; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @version 1.7.2 + */ +public class JSONObject3Test { + + public JSONObject3Test() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of creation method, of class JSONObject. + */ + @Test + public void testGetArray() throws IOException { + System.out.println("JSONObject3Test : testGetArray"); + + URL url = this.getClass().getResource("testJSON6.json"); + JSONObject jsonObj = FileUtils.toJSONObject(url); + + JSONArray array = jsonObj.optJSONArray("favorite_foods"); + assertNull("favorite_foods is null", array); + } +} diff --git a/test/org/json/JSONObject4Test.java b/test/org/json/JSONObject4Test.java new file mode 100644 index 000000000..2416bb34d --- /dev/null +++ b/test/org/json/JSONObject4Test.java @@ -0,0 +1,99 @@ +/* +Copyright (C) 2020 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @version 1.7.5 + */ +public class JSONObject4Test { + + public JSONObject4Test() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of creation method, of class JSONObject. + */ + @Test + public void testCreate() { + System.out.println("JSONObject4Test : testCreate"); + + JSONObject jsonObj = new JSONObject(); + jsonObj.put("name", "Tom"); + jsonObj.put("birthday", "1940-02-10"); + jsonObj.put("age", 76); + jsonObj.put("married", false); + + // Cannot set null directly + jsonObj.putNULL("car"); + + jsonObj.put("favorite_foods", new String[] { "cookie", "fish", "chips" }); + + JSONObject passportJsonObj = new JSONObject(); + passportJsonObj.put("id", 100001); + passportJsonObj.put("nationality", "American"); + jsonObj.put("passport", passportJsonObj); + + boolean isMarried = jsonObj.getBoolean("married"); + assertFalse("married", isMarried); + int age = jsonObj.getInt("age"); + assertEquals("age", 76, age); + String name = jsonObj.getString("name"); + assertEquals("name", "Tom", name); + String car = jsonObj.getString("car"); + assertNull("car", car); + + JSONObject jsonObjField = jsonObj.getJSONObject("passport"); + assertNotNull("passport", jsonObjField); + int id = jsonObjField.getInt("id"); + assertEquals("id", 100001, id); + String nationality = jsonObjField.getString("nationality"); + assertEquals("nationality", "American", nationality); + } + +} diff --git a/test/org/json/JSONObject5Test.java b/test/org/json/JSONObject5Test.java new file mode 100644 index 000000000..d238a8554 --- /dev/null +++ b/test/org/json/JSONObject5Test.java @@ -0,0 +1,78 @@ +/* +Copyright (C) 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import org.junit.After; +import org.junit.AfterClass; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Get JSON elements from Strings. + * + * @since 1.7.5 + */ +public class JSONObject5Test { + + public JSONObject5Test() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of creation method, of class JSONObject. + */ + @Test + public void testGetFromString() { + System.out.println("JSONObject5Test : testGetFromString"); + String str = "{\"age\":23,\"children\":[{\"name\":\"Paul\"},{\"name\":\"Suzanne\"}],\"name\":\"Bob\"}"; + JSONElement element = JSONElement.fromString(str); + assertTrue("JSONElement must be a JSONObject", element instanceof JSONObject); + } + + /** + * Test of creation method, of class JSONObject. + */ + @Test + public void testGetFromString2() { + System.out.println("JSONObject5Test : testGetFromString2"); + String str = "[{\"name\": \"Paul\"}, {\"name\": \"Suzanne\"}]"; + JSONElement element = JSONElement.fromString(str); + assertTrue("JSONElement must be a JSONArray", element instanceof JSONArray); + } +} diff --git a/test/org/json/JSONObjectEmptyTest.java b/test/org/json/JSONObjectEmptyTest.java new file mode 100644 index 000000000..8ca53357a --- /dev/null +++ b/test/org/json/JSONObjectEmptyTest.java @@ -0,0 +1,87 @@ +/* +Copyright (C) 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertEquals; +import org.junit.After; +import org.junit.AfterClass; +import static org.junit.Assert.fail; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Get JSON elements from Strings. + * + * @since 1.7.5 + */ +public class JSONObjectEmptyTest { + + public JSONObjectEmptyTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of creation method, of class JSONObject. Must have a syntax error. + */ + @Test + public void testGetFromEmptyString() { + System.out.println("JSONObjectEmptyTest : testGetFromEmptyString"); + try { + JSONObject object = new JSONObject(" "); + fail("We should have a JSONException"); + } catch (JSONException e) { + assertEquals("A JSONObject text must begin with '{' at 1 [character 2 line 1]", e.getMessage()); + // we should go here + } + } + + /** + * Test of creation method, of class JSONArray. Must have a syntax error. + */ + @Test + public void testGetFromEmptyString2() { + System.out.println("JSONObjectEmptyTest : testGetFromEmptyString"); + try { + JSONArray object = new JSONArray(" "); + fail("We should have a JSONException"); + } catch (JSONException e) { + assertEquals("A JSONArray text must start with '[' at 1 [character 2 line 1]", e.getMessage()); + // we should go here + } + } +} diff --git a/test/org/json/JSONObjectNumbers2Test.java b/test/org/json/JSONObjectNumbers2Test.java new file mode 100644 index 000000000..ad9f47b62 --- /dev/null +++ b/test/org/json/JSONObjectNumbers2Test.java @@ -0,0 +1,97 @@ +/* +Copyright (C) 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertEquals; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @since 1.7.2 + */ +public class JSONObjectNumbers2Test { + + public JSONObjectNumbers2Test() { + } + + @BeforeClass + public static void setUpClass() { + JSONOptions.getDefault().keepDecimalPoints(true); + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of numberToString method, of class JSONObject. + */ + @Test + public void testNumberToStringFloat() { + System.out.println("JSONObjectNumbers2Test : testNumberToStringFloat"); + float f = 1.5f; + String result = JSONObject.numberToString(f, true); + assertEquals("Result", "1.5", result); + + f = 1f; + result = JSONObject.numberToString(f, true); + assertEquals("Result", "1.0", result); + } + + /** + * Test of numberToString method, of class JSONObject. + */ + @Test + public void testNumberToStringDouble() { + System.out.println("JSONObjectNumbers2Test : testNumberToStringDouble"); + double d = 1.5d; + String result = JSONObject.numberToString(d, true); + assertEquals("Result", "1.5", result); + + d = 1f; + result = JSONObject.numberToString(d, true); + assertEquals("Result", "1.0", result); + } + + /** + * Test of numberToString method, of class JSONObject. + */ + @Test + public void testNumberToStringInt() { + System.out.println("JSONObjectNumbers2Test : testNumberToStringInt"); + int i = 1; + String result = JSONObject.numberToString(i); + assertEquals("Result", "1", result); + } +} diff --git a/test/org/json/JSONObjectNumbers3Test.java b/test/org/json/JSONObjectNumbers3Test.java new file mode 100644 index 000000000..0dcbccd8f --- /dev/null +++ b/test/org/json/JSONObjectNumbers3Test.java @@ -0,0 +1,97 @@ +/* +Copyright (C) 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertEquals; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @since 1.7.2 + */ +public class JSONObjectNumbers3Test { + + public JSONObjectNumbers3Test() { + } + + @BeforeClass + public static void setUpClass() { + JSONOptions.getDefault().keepDecimalPoints(true); + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of numberToString method, of class JSONObject. + */ + @Test + public void testNumberToStringFloat() { + System.out.println("JSONObjectNumbers3Test : testNumberToStringFloat"); + float f = 1.5f; + String result = JSONObject.numberToString(f); + assertEquals("Result", "1.5", result); + + f = 1f; + result = JSONObject.numberToString(f); + assertEquals("Result", "1", result); + } + + /** + * Test of numberToString method, of class JSONObject. + */ + @Test + public void testNumberToStringDouble() { + System.out.println("JSONObjectNumbers3Test : testNumberToStringDouble"); + double d = 1.5d; + String result = JSONObject.numberToString(d); + assertEquals("Result", "1.5", result); + + d = 1f; + result = JSONObject.numberToString(d); + assertEquals("Result", "1", result); + } + + /** + * Test of numberToString method, of class JSONObject. + */ + @Test + public void testNumberToStringInt() { + System.out.println("JSONObjectNumbers3Test : testNumberToStringInt"); + int i = 1; + String result = JSONObject.numberToString(i); + assertEquals("Result", "1", result); + } +} diff --git a/test/org/json/JSONObjectNumbersTest.java b/test/org/json/JSONObjectNumbersTest.java new file mode 100644 index 000000000..324f70c67 --- /dev/null +++ b/test/org/json/JSONObjectNumbersTest.java @@ -0,0 +1,96 @@ +/* +Copyright (C) 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertEquals; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @since 1.7.2 + */ +public class JSONObjectNumbersTest { + + public JSONObjectNumbersTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of numberToString method, of class JSONObject. + */ + @Test + public void testNumberToStringFloat() { + System.out.println("JSONObjectNumbersTest : testNumberToStringFloat"); + float f = 1.5f; + String result = JSONObject.numberToString(f); + assertEquals("Result", "1.5", result); + + f = 1f; + result = JSONObject.numberToString(f); + assertEquals("Result", "1", result); + } + + /** + * Test of numberToString method, of class JSONObject. + */ + @Test + public void testNumberToStringDouble() { + System.out.println("JSONObjectNumbersTest : testNumberToStringDouble"); + double d = 1.5d; + String result = JSONObject.numberToString(d); + assertEquals("Result", "1.5", result); + + d = 1f; + result = JSONObject.numberToString(d); + assertEquals("Result", "1", result); + } + + /** + * Test of numberToString method, of class JSONObject. + */ + @Test + public void testNumberToStringInt() { + System.out.println("JSONObjectNumbersTest : testNumberToStringInt"); + int i = 1; + String result = JSONObject.numberToString(i); + assertEquals("Result", "1", result); + } +} diff --git a/test/org/json/JSONObjectTest.java b/test/org/json/JSONObjectTest.java new file mode 100644 index 000000000..e6a9cdf76 --- /dev/null +++ b/test/org/json/JSONObjectTest.java @@ -0,0 +1,162 @@ +/* +Copyright (C) 2018 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import java.io.IOException; +import java.net.URL; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @since 1.1 + */ +public class JSONObjectTest { + + public JSONObjectTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of creation method, of class JSONObject. + */ + @Test + public void testCreate() { + System.out.println("JSONObjectTest : testCreate"); + + JSONObject jsonObj = new JSONObject(); + jsonObj.put("name", "Tom"); + jsonObj.put("birthday", "1940-02-10"); + jsonObj.put("age", 76); + jsonObj.put("married", false); + + // Cannot set null directly + jsonObj.put("car", JSONObject.NULL); + + jsonObj.put("favorite_foods", new String[] { "cookie", "fish", "chips" }); + + JSONObject passportJsonObj = new JSONObject(); + passportJsonObj.put("id", 100001); + passportJsonObj.put("nationality", "American"); + jsonObj.put("passport", passportJsonObj); + + boolean isMarried = jsonObj.getBoolean("married"); + assertFalse("married", isMarried); + int age = jsonObj.getInt("age"); + assertEquals("age", 76, age); + String name = jsonObj.getString("name"); + assertEquals("name", "Tom", name); + + JSONObject jsonObjField = jsonObj.getJSONObject("passport"); + assertNotNull("passport", jsonObjField); + int id = jsonObjField.getInt("id"); + assertEquals("id", 100001, id); + String nationality = jsonObjField.getString("nationality"); + assertEquals("nationality", "American", nationality); + } + + /** + * Test of Equals method, of class JSONObject. + */ + @Test + public void testEquals() throws IOException { + System.out.println("JSONObjectTest : testEquals"); + + URL url = this.getClass().getResource("testJSON.json"); + JSONObject jsonObj = FileUtils.toJSONObject(url); + + URL url2 = this.getClass().getResource("testJSON2.json"); + JSONObject jsonObj2 = FileUtils.toJSONObject(url2); + + assertEquals("must be equal", jsonObj, jsonObj2); + } + + /** + * Test of Equals method, of class JSONObject. + */ + @Test + public void testEquals2() throws IOException { + System.out.println("JSONObjectTest : testEquals2"); + + URL url = this.getClass().getResource("testJSON.json"); + JSONObject jsonObj = FileUtils.toJSONObject(url); + + URL url2 = this.getClass().getResource("testJSON3.json"); + JSONObject jsonObj2 = FileUtils.toJSONObject(url2); + + assertFalse("must not be equal", jsonObj.equals(jsonObj2)); + } + + /** + * Test of Equals method, of class JSONObject. + */ + @Test + public void testEquals3() throws IOException { + System.out.println("JSONObjectTest : testEquals3"); + + URL url = this.getClass().getResource("testJSON.json"); + JSONObject jsonObj = FileUtils.toJSONObject(url); + + URL url2 = this.getClass().getResource("testJSON4.json"); + JSONObject jsonObj2 = FileUtils.toJSONObject(url2); + + assertFalse("must not be equal", jsonObj.equals(jsonObj2)); + } + + /** + * Test of Equals method, of class JSONObject. + */ + @Test + public void testEquals4() throws IOException { + System.out.println("JSONObjectTest : testEquals4"); + + URL url = this.getClass().getResource("testJSON.json"); + JSONObject jsonObj = FileUtils.toJSONObject(url); + + URL url2 = this.getClass().getResource("testJSON5.json"); + JSONObject jsonObj2 = FileUtils.toJSONObject(url2); + + assertEquals("must be equal", jsonObj, jsonObj2); + } + +} diff --git a/test/org/json/JSONWriter2Test.java b/test/org/json/JSONWriter2Test.java new file mode 100644 index 000000000..553bf753d --- /dev/null +++ b/test/org/json/JSONWriter2Test.java @@ -0,0 +1,100 @@ +/* +Copyright (C) 2019 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import java.io.File; +import java.io.IOException; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @since 1.4 + */ +public class JSONWriter2Test { + + public JSONWriter2Test() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of FileUtils.toFile(JSONObject, File). + */ + @Test + public void testWrite() throws IOException { + System.out.println("JSONWriter2Test : testWrite"); + JSONObject jsonObj = new JSONObject(); + jsonObj.put("name", "Tom"); + jsonObj.put("birthday", "1940-02-10"); + jsonObj.put("age", 76); + jsonObj.put("married", false); + + // Cannot set null directly + jsonObj.put("car", JSONObject.NULL); + + jsonObj.put("favorite_foods", new String[] { "cookie", "fish", "chips" }); + + JSONObject passportJsonObj = new JSONObject(); + passportJsonObj.put("id", 100001); + passportJsonObj.put("nationality", "American"); + jsonObj.put("passport", passportJsonObj); + + File file = File.createTempFile("json", ".json"); + FileUtils.toFile(jsonObj, file); + + jsonObj = FileUtils.toJSONObject(file); + boolean isMarried = jsonObj.getBoolean("married"); + assertFalse("married", isMarried); + int age = jsonObj.getInt("age"); + assertEquals("age", 76, age); + String name = jsonObj.getString("name"); + assertEquals("name", "Tom", name); + + JSONObject jsonObjField = jsonObj.getJSONObject("passport"); + assertNotNull("passport", jsonObjField); + int id = jsonObjField.getInt("id"); + assertEquals("id", 100001, id); + String nationality = jsonObjField.getString("nationality"); + assertEquals("nationality", "American", nationality); + } +} diff --git a/test/org/json/JSONWriter3Test.java b/test/org/json/JSONWriter3Test.java new file mode 100644 index 000000000..3a87fc96f --- /dev/null +++ b/test/org/json/JSONWriter3Test.java @@ -0,0 +1,102 @@ +/* +Copyright (C) 2019 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @since 1.4 + */ +public class JSONWriter3Test { + + public JSONWriter3Test() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of FileUtils.toURL(JSONObject, URL). + */ + @Test + public void testWrite() throws IOException { + System.out.println("JSONWriter3Test : testWrite"); + JSONObject jsonObj = new JSONObject(); + jsonObj.put("name", "Tom"); + jsonObj.put("birthday", "1940-02-10"); + jsonObj.put("age", 76); + jsonObj.put("married", false); + + // Cannot set null directly + jsonObj.put("car", JSONObject.NULL); + + jsonObj.put("favorite_foods", new String[] { "cookie", "fish", "chips" }); + + JSONObject passportJsonObj = new JSONObject(); + passportJsonObj.put("id", 100001); + passportJsonObj.put("nationality", "American"); + jsonObj.put("passport", passportJsonObj); + + File file = File.createTempFile("json", ".json"); + URL url = file.toURI().toURL(); + FileUtils.toURL(jsonObj, url); + + jsonObj = FileUtils.toJSONObject(url); + boolean isMarried = jsonObj.getBoolean("married"); + assertFalse("married", isMarried); + int age = jsonObj.getInt("age"); + assertEquals("age", 76, age); + String name = jsonObj.getString("name"); + assertEquals("name", "Tom", name); + + JSONObject jsonObjField = jsonObj.getJSONObject("passport"); + assertNotNull("passport", jsonObjField); + int id = jsonObjField.getInt("id"); + assertEquals("id", 100001, id); + String nationality = jsonObjField.getString("nationality"); + assertEquals("nationality", "American", nationality); + } +} diff --git a/test/org/json/JSONWriterTest.java b/test/org/json/JSONWriterTest.java new file mode 100644 index 000000000..f04bdb0ba --- /dev/null +++ b/test/org/json/JSONWriterTest.java @@ -0,0 +1,108 @@ +/* +Copyright (C) 2018, 2019 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @version 1.4 + */ +public class JSONWriterTest { + + public JSONWriterTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of FileUtils.toJSONObject(File). + */ + @Test + public void testWrite() throws IOException { + System.out.println("JSONWriterTest : testWrite"); + JSONObject jsonObj = new JSONObject(); + jsonObj.put("name", "Tom"); + jsonObj.put("birthday", "1940-02-10"); + jsonObj.put("age", 76); + jsonObj.put("married", false); + + // Cannot set null directly + jsonObj.put("car", JSONObject.NULL); + + jsonObj.put("favorite_foods", new String[] { "cookie", "fish", "chips" }); + + JSONObject passportJsonObj = new JSONObject(); + passportJsonObj.put("id", 100001); + passportJsonObj.put("nationality", "American"); + jsonObj.put("passport", passportJsonObj); + + File file = File.createTempFile("json", ".json"); + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + jsonObj.write(writer); + writer.flush(); + jsonObj = FileUtils.toJSONObject(file); + + boolean isMarried = jsonObj.getBoolean("married"); + assertFalse("married", isMarried); + int age = jsonObj.getInt("age"); + assertEquals("age", 76, age); + String name = jsonObj.getString("name"); + assertEquals("name", "Tom", name); + + JSONObject jsonObjField = jsonObj.getJSONObject("passport"); + assertNotNull("passport", jsonObjField); + int id = jsonObjField.getInt("id"); + assertEquals("id", 100001, id); + String nationality = jsonObjField.getString("nationality"); + assertEquals("nationality", "American", nationality); + } catch (IOException e) { + fail("Could not write JSON content"); + } + } +} diff --git a/test/org/json/TestUtilities.java b/test/org/json/TestUtilities.java new file mode 100644 index 000000000..c49378e82 --- /dev/null +++ b/test/org/json/TestUtilities.java @@ -0,0 +1,64 @@ +/* +Copyright (C) 2021 by Herve Girod + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +package org.json; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; + +/** + * + * @since 1.7.2 + */ +public class TestUtilities { + + public static boolean checkContent(URL url1, URL url2) { + String content1 = getContent(url1); + String content2 = getContent(url2); + return content1.equals(content2); + + } + + public static String getContent(URL url) { + StringBuilder buf = new StringBuilder(); + try { + try ( + BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) { + // write the output to stdout + boolean first = true; + String line; + while ((line = reader.readLine()) != null) { + if (first) { + first = false; + } else { + buf.append("\n"); + } + buf.append(line); + } + reader.close(); + } + } catch (IOException e) { + } + return buf.toString(); + } +} diff --git a/test/org/json/comments/comments1.json b/test/org/json/comments/comments1.json new file mode 100644 index 000000000..42d377e90 --- /dev/null +++ b/test/org/json/comments/comments1.json @@ -0,0 +1,10 @@ +{ + /* this is a comment + */ + "age": 23, + "children": [ + {"name": "Paul"}, + {"name": "Suzanne"} + ], + "name": "Bob" +} \ No newline at end of file diff --git a/test/org/json/comments/comments2.json b/test/org/json/comments/comments2.json new file mode 100644 index 000000000..9af4d4a31 --- /dev/null +++ b/test/org/json/comments/comments2.json @@ -0,0 +1,11 @@ +{ + /* this is a comment + */ + "age": 23, + "children": [ + /* this is a second comment */ + {"name": "Paul"}, + {"name": "Suzanne"} + ], + "name": "Bob" +} \ No newline at end of file diff --git a/test/org/json/expected/expected1.json b/test/org/json/expected/expected1.json new file mode 100644 index 000000000..a0394d619 --- /dev/null +++ b/test/org/json/expected/expected1.json @@ -0,0 +1 @@ +{"age":23,"children":[{"name":"Paul"},{"name":"Suzanne"}],"name":"Bob"} \ No newline at end of file diff --git a/test/org/json/expected/expected2.json b/test/org/json/expected/expected2.json new file mode 100644 index 000000000..74d22dc72 --- /dev/null +++ b/test/org/json/expected/expected2.json @@ -0,0 +1 @@ +var a = {"age":23,"children":[{"name":"Paul"},{"name":"Suzanne"}],"name":"Bob"} \ No newline at end of file diff --git a/test/org/json/expected/expected3.json b/test/org/json/expected/expected3.json new file mode 100644 index 000000000..fc29cba95 --- /dev/null +++ b/test/org/json/expected/expected3.json @@ -0,0 +1,8 @@ +{ + "age": 23, + "children": [ + {"name": "Paul"}, + {"name": "Suzanne"} + ], + "name": "Bob" +} \ No newline at end of file diff --git a/test/org/json/expected/expected4.json b/test/org/json/expected/expected4.json new file mode 100644 index 000000000..3070d66df --- /dev/null +++ b/test/org/json/expected/expected4.json @@ -0,0 +1,10 @@ +{ + "person": { + "age": 23, + "children": [ + {"name": "Paul"}, + {"name": "Suzanne"} + ], + "name": "Bob" + } +} \ No newline at end of file diff --git a/test/org/json/expected/expected5.json b/test/org/json/expected/expected5.json new file mode 100644 index 000000000..c411ff3de --- /dev/null +++ b/test/org/json/expected/expected5.json @@ -0,0 +1,14 @@ +{ + "person": { + "age": 23, + "children": [ + { + "name": "Paul" + }, + { + "name": "Suzanne" + } + ], + "name": "Bob" + } +} \ No newline at end of file diff --git a/test/org/json/expected/expected6.json b/test/org/json/expected/expected6.json new file mode 100644 index 000000000..2683a531c --- /dev/null +++ b/test/org/json/expected/expected6.json @@ -0,0 +1,8 @@ +{"person": { + "age": 23, + "children": [ + {"name": "Paul"}, + {"name": "Suzanne"} + ], + "name": "Bob" +}} \ No newline at end of file diff --git a/test/org/json/expected/expected7.json b/test/org/json/expected/expected7.json new file mode 100644 index 000000000..e8a79f780 --- /dev/null +++ b/test/org/json/expected/expected7.json @@ -0,0 +1,7 @@ +{ + "person": { + "age": 23, + "child": {"name": "Paul"}, + "name": "Bob" + } +} \ No newline at end of file diff --git a/test/org/json/expected/expected8.json b/test/org/json/expected/expected8.json new file mode 100644 index 000000000..b20cbf1cf --- /dev/null +++ b/test/org/json/expected/expected8.json @@ -0,0 +1,7 @@ +{ + "person": { + "age": 23, + "children": [1, 2, 3], + "name": "Bob" + } +} \ No newline at end of file diff --git a/test/org/json/expected/expected9.json b/test/org/json/expected/expected9.json new file mode 100644 index 000000000..34d39a6f3 --- /dev/null +++ b/test/org/json/expected/expected9.json @@ -0,0 +1,7 @@ +{ + "person": { + "age": 23, + "children": ["A", "B", "C"], + "name": "Bob" + } +} \ No newline at end of file diff --git a/test/org/json/stleary/CDLTest.java b/test/org/json/stleary/CDLTest.java new file mode 100644 index 000000000..9a441dac7 --- /dev/null +++ b/test/org/json/stleary/CDLTest.java @@ -0,0 +1,298 @@ +package org.json.stleary; + +import static org.junit.Assert.*; +import org.json.CDL; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Test; + +/** + * Tests for CDL.java. + * CDL provides an application level API, but it is not used by the + * reference app. To test it, strings will be converted to JSON-Java classes + * and then converted back. + */ +public class CDLTest { + + /** + * String of lines where the column names are in the first row, + * and all subsequent rows are values. All keys and values should be legal. + */ + String lines = new String( + "Col 1, Col 2, \tCol 3, Col 4, Col 5, Col 6, Col 7\n" + + "val1, val2, val3, val4, val5, val6, val7\n" + + "1, 2, 3, 4\t, 5, 6, 7\n" + + "true, false, true, true, false, false, false\n" + + "0.23, 57.42, 5e27, -234.879, 2.34e5, 0.0, 9e-3\n" + + "\"va\tl1\", \"v\bal2\", \"val3\", \"val\f4\", \"val5\", va\'l6, val7\n" + ); + + /** + * CDL.toJSONArray() adds all values asstrings, with no filtering or + * conversions. For testing, this means that the expected JSONObject + * values all must be quoted in the cases where the JSONObject parsing + * might normally convert the value into a non-string. + */ + String expectedLines = new String( + "[{Col 1:val1, Col 2:val2, Col 3:val3, Col 4:val4, Col 5:val5, Col 6:val6, Col 7:val7}, " + + "{Col 1:\"1\", Col 2:\"2\", Col 3:\"3\", Col 4:\"4\", Col 5:\"5\", Col 6:\"6\", Col 7:\"7\"}, " + + "{Col 1:\"true\", Col 2:\"false\", Col 3:\"true\", Col 4:\"true\", Col 5:\"false\", Col 6:\"false\", Col 7:\"false\"}, " + + "{Col 1:\"0.23\", Col 2:\"57.42\", Col 3:\"5e27\", Col 4:\"-234.879\", Col 5:\"2.34e5\", Col 6:\"0.0\", Col 7:\"9e-3\"}, " + + "{Col 1:\"va\tl1\", Col 2:\"v\bal2\", Col 3:val3, Col 4:\"val\f4\", Col 5:val5, Col 6:va\'l6, Col 7:val7}]"); + + /** + * Attempts to create a JSONArray from a null string. + * Expect a NullPointerException. + */ + @Test(expected = NullPointerException.class) + public void exceptionOnNullString() { + String nullStr = null; + CDL.toJSONArray(nullStr); + } + + /** + * Attempts to create a JSONArray from a string with unbalanced quotes + * in column title line. Expects a JSONException. + */ + @Test + public void unbalancedQuoteInName() { + String badLine = "Col1, \"Col2\nVal1, Val2"; + try { + CDL.toJSONArray(badLine); + fail("Expecting an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Missing close quote '\"'. at 12 [character 0 line 2]", + e.getMessage()); + } + } + + /** + * Attempts to create a JSONArray from a string with unbalanced quotes + * in value line. Expects a JSONException. + */ + @Test + public void unbalancedQuoteInValue() { + String badLine = "Col1, Col2\n\"Val1, Val2"; + try { + CDL.toJSONArray(badLine); + fail("Expecting an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Missing close quote '\"'. at 22 [character 11 line 2]", + e.getMessage()); + + } + } + + /** + * Attempts to create a JSONArray from a string with null char + * in column title line. Expects a JSONException. + */ + @Test + public void nullInName() { + String badLine = "C\0ol1, Col2\nVal1, Val2"; + try { + CDL.toJSONArray(badLine); + fail("Expecting an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Bad character 'o' (111). at 2 [character 3 line 1]", + e.getMessage()); + + } + } + + /** + * Attempt to create a JSONArray with unbalanced quotes and a properly escaped doubled quote. + * Expects a JSONException. + */ + @Test + public void unbalancedEscapedQuote() { + String badLine = "Col1, Col2\n\"Val1, \"\"Val2\"\""; + try { + CDL.toJSONArray(badLine); + fail("Expecting an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Missing close quote '\"'. at 26 [character 15 line 2]", + e.getMessage()); + + } + } + + /** + * Assert that there is no error for a single escaped quote within a properly embedded quote. + */ + @Test + public void singleEscapedQuote() { + String singleEscape = "Col1, Col2\nVal1, \"\"\"Val2\""; + JSONArray jsonArray = CDL.toJSONArray(singleEscape); + + String cdlStr = CDL.toString(jsonArray); + assertTrue(cdlStr.contains("Col1")); + assertTrue(cdlStr.contains("Col2")); + assertTrue(cdlStr.contains("Val1")); + assertTrue(cdlStr.contains("\"Val2")); + } + + /** + * Assert that there is no error for a single escaped quote within a properly + * embedded quote when not the last value. + */ + @Test + public void singleEscapedQuoteMiddleString() { + String singleEscape = "Col1, Col2\nVal1, \"\"\"Val2\"\nVal 3,Val 4"; + JSONArray jsonArray = CDL.toJSONArray(singleEscape); + + String cdlStr = CDL.toString(jsonArray); + assertTrue(cdlStr.contains("Col1")); + assertTrue(cdlStr.contains("Col2")); + assertTrue(cdlStr.contains("Val1")); + assertTrue(cdlStr.contains("\"Val2")); + } + + /** + * Attempt to create a JSONArray with an escape quote and no enclosing quotes. + * Expects a JSONException. + */ + @Test + public void badEscapedQuote() { + String badLine = "Col1, Col2\nVal1, \"\"Val2"; + + try { + CDL.toJSONArray(badLine); + fail("Expecting an exception"); + } catch (JSONException e) { + System.out.println("Message" + e.getMessage()); + assertEquals("Expecting an exception message", + "Bad character 'V' (86). at 20 [character 9 line 2]", + e.getMessage()); + + } + + } + + /** + * call toString with a null array + */ + @Test(expected = NullPointerException.class) + public void nullJSONArrayToString() { + CDL.toString((JSONArray) null); + } + + /** + * Create a JSONArray from an empty string + */ + @Test + public void emptyString() { + String emptyStr = ""; + JSONArray jsonArray = CDL.toJSONArray(emptyStr); + assertTrue("CDL should return null when the input string is empty", + jsonArray == null); + } + + /** + * Create a JSONArray with only 1 row + */ + @Test + public void onlyColumnNames() { + String columnNameStr = "col1, col2, col3"; + JSONArray jsonArray = CDL.toJSONArray(columnNameStr); + assertNull("CDL should return null when only 1 row is given", + jsonArray); + } + + /** + * Create a JSONArray from string containing only whitespace and commas + */ + @Test + public void emptyLinesToJSONArray() { + String str = " , , , \n , , , "; + JSONArray jsonArray = CDL.toJSONArray(str); + assertNull("JSONArray should be null for no content", + jsonArray); + } + + /** + * call toString with a null array + */ + @Test + public void emptyJSONArrayToString() { + JSONArray jsonArray = new JSONArray(); + String str = CDL.toString(jsonArray); + assertNull("CDL should return null for toString(null)", + str); + } + + /** + * call toString with a null arrays for names and values + */ + @Test + public void nullJSONArraysToString() { + String str = CDL.toString(null, null); + assertNull("CDL should return null for toString(null)", + str); + } + + /** + * Given a JSONArray that was not built by CDL, some chars may be + * found that would otherwise be filtered out by CDL. + */ + @Test + public void checkSpecialChars() { + JSONArray jsonArray = new JSONArray(); + JSONObject jsonObject = new JSONObject(); + jsonArray.put(jsonObject); + // \r will be filtered from name + jsonObject.put("Col \r1", "V1"); + // \r will be filtered from value + jsonObject.put("Col 2", "V2\r"); + assertTrue("expected length should be 1", jsonArray.length() == 1); + String cdlStr = CDL.toString(jsonArray); + jsonObject = jsonArray.getJSONObject(0); + assertTrue(cdlStr.contains("\"Col 1\"")); + assertTrue(cdlStr.contains("Col 2")); + assertTrue(cdlStr.contains("V1")); + assertTrue(cdlStr.contains("\"V2\"")); + } + + /** + * Create a JSONArray from a string of lines + */ + @Test + public void textToJSONArray() { + JSONArray jsonArray = CDL.toJSONArray(lines); + JSONArray expectedJsonArray = new JSONArray(expectedLines); + Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray); + } + + /** + * Create a JSONArray from a JSONArray of titles and a + * string of value lines + */ + @Test + public void jsonArrayToJSONArray() { + String nameArrayStr = "[Col1, Col2]"; + String values = "V1, V2"; + JSONArray nameJSONArray = new JSONArray(nameArrayStr); + JSONArray jsonArray = CDL.toJSONArray(nameJSONArray, values); + JSONArray expectedJsonArray = new JSONArray("[{Col1:V1,Col2:V2}]"); + Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray); + } + + /** + * Create a JSONArray from a string of lines, + * then convert to string and then back to JSONArray + */ + @Test + public void textToJSONArrayAndBackToString() { + JSONArray jsonArray = CDL.toJSONArray(lines); + String jsonStr = CDL.toString(jsonArray); + JSONArray finalJsonArray = CDL.toJSONArray(jsonStr); + JSONArray expectedJsonArray = new JSONArray(expectedLines); + Util.compareActualVsExpectedJsonArrays(finalJsonArray, expectedJsonArray); + } + +} diff --git a/test/org/json/stleary/CookieTest.java b/test/org/json/stleary/CookieTest.java new file mode 100644 index 000000000..013d5ef0d --- /dev/null +++ b/test/org/json/stleary/CookieTest.java @@ -0,0 +1,222 @@ +package org.json.stleary; + +import static org.junit.Assert.*; +import org.json.*; +import org.junit.Test; + +/** + * HTTP cookie specification: RFC6265 + *

+ * At its most basic, a cookie is a name=value pair. The value may be subdivided + * into other cookies, but that is not tested here. The cookie may also include + * certain named attributes, delimited by semicolons. + *

+ * The Cookie.toString() method emits certain attributes if present: expires, + * domain, path, secure. All but secure are name-value pairs. Other attributes + * are not included in the toString() output. + *

+ * A JSON-Java encoded cookie escapes '+', '%', '=', ';' with %hh values. + */ +public class CookieTest { + + /** + * Attempts to create a JSONObject from a null string. + * Expects a NullPointerException. + */ + @Test(expected = NullPointerException.class) + public void nullCookieException() { + String cookieStr = null; + Cookie.toJSONObject(cookieStr); + } + + /** + * Attempts to create a JSONObject from a cookie string with + * no '=' char. + * Expects a JSONException. + */ + @Test + public void malFormedNameValueException() { + String cookieStr = "thisCookieHasNoEqualsChar"; + try { + Cookie.toJSONObject(cookieStr); + fail("Expecting an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Expected '=' and instead saw '' at 25 [character 26 line 1]", + e.getMessage()); + } + } + + /** + * Attempts to create a JSONObject from a cookie string + * with embedded ';' char. + * Expects a JSONException. + */ + @Test + public void malFormedAttributeException() { + String cookieStr = "this=Cookie;myAttribute"; + try { + Cookie.toJSONObject(cookieStr); + fail("Expecting an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Missing '=' in cookie parameter. at 23 [character 24 line 1]", + e.getMessage()); + } + } + + /** + * Attempts to create a JSONObject from an empty cookie string.
+ * Note: Cookie throws an exception, but CookieList does not.
+ * Expects a JSONException + */ + @Test + public void emptyStringCookieException() { + String cookieStr = ""; + try { + Cookie.toJSONObject(cookieStr); + fail("Expecting an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Expected '=' and instead saw '' at 0 [character 1 line 1]", + e.getMessage()); + } + } + + /** + * Cookie from a simple name/value pair with no delimiter + */ + @Test + public void simpleCookie() { + String cookieStr = "SID=31d4d96e407aad42"; + String expectedCookieStr = "{\"name\":\"SID\",\"value\":\"31d4d96e407aad42\"}"; + JSONObject jsonObject = Cookie.toJSONObject(cookieStr); + JSONObject expectedJsonObject = new JSONObject(expectedCookieStr); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); + } + + /** + * Store a cookie with all of the supported attributes in a + * JSONObject. The secure attribute, which has no value, is treated + * as a boolean. + */ + @Test + public void multiPartCookie() { + String cookieStr + = "PH=deleted; " + + " expires=Wed, 19-Mar-2014 17:53:53 GMT;" + + "path=/; " + + " domain=.yahoo.com;" + + "secure"; + String expectedCookieStr + = "{" + + "\"name\":\"PH\"," + + "\"value\":\"deleted\"," + + "\"path\":\"/\"," + + "\"expires\":\"Wed, 19-Mar-2014 17:53:53 GMT\"," + + "\"domain\":\".yahoo.com\"," + + "\"secure\":true" + + "}"; + JSONObject jsonObject = Cookie.toJSONObject(cookieStr); + JSONObject expectedJsonObject = new JSONObject(expectedCookieStr); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); + } + + /** + * Cookie.toString() will omit the non-standard "thiswont=beIncluded" + * attribute, but the attribute is still stored in the JSONObject. + * This test confirms both behaviors. + */ + @Test + public void convertCookieToString() { + String cookieStr + = "PH=deleted; " + + " expires=Wed, 19-Mar-2014 17:53:53 GMT;" + + "path=/; " + + " domain=.yahoo.com;" + + "thisWont=beIncluded;" + + "secure"; + String expectedCookieStr + = "{\"path\":\"/\"," + + "\"expires\":\"Wed, 19-Mar-2014 17:53:53 GMT\"," + + "\"domain\":\".yahoo.com\"," + + "\"name\":\"PH\"," + + "\"secure\":true," + + "\"value\":\"deleted\"}"; + // Add the nonstandard attribute to the expected cookie string + String expectedDirectCompareCookieStr + = expectedCookieStr.replaceAll("\\{", "\\{\"thisWont\":\"beIncluded\","); + // convert all strings into JSONObjects + JSONObject jsonObject = Cookie.toJSONObject(cookieStr); + JSONObject expectedJsonObject = new JSONObject(expectedCookieStr); + JSONObject expectedDirectCompareJsonObject + = new JSONObject(expectedDirectCompareCookieStr); + // emit the string + String cookieToStr = Cookie.toString(jsonObject); + // create a final JSONObject from the string + JSONObject finalJsonObject = Cookie.toJSONObject(cookieToStr); + // JSONObject should contain the nonstandard string + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedDirectCompareJsonObject); + // JSONObject -> string -> JSONObject should not contain the nonstandard string + Util.compareActualVsExpectedJsonObjects(finalJsonObject, expectedJsonObject); + } + + /** + * A string may be URL-encoded when converting to JSONObject. + * If found, '+' is converted to ' ', and %hh hex strings are converted + * to their ascii char equivalents. This test confirms the decoding + * behavior. + */ + @Test + public void convertEncodedCookieToString() { + String cookieStr + = "PH=deleted; " + + " expires=Wed,+19-Mar-2014+17:53:53+GMT;" + + "path=/%2Bthis/is%26/a/spec%3Bsegment%3D; " + + " domain=.yahoo.com;" + + "secure"; + String expectedCookieStr + = "{\"path\":\"/+this/is&/a/spec;segment=\"," + + "\"expires\":\"Wed, 19-Mar-2014 17:53:53 GMT\"," + + "\"domain\":\".yahoo.com\"," + + "\"name\":\"PH\"," + + "\"secure\":true," + + "\"value\":\"deleted\"}"; + JSONObject jsonObject = Cookie.toJSONObject(cookieStr); + JSONObject expectedJsonObject = new JSONObject(expectedCookieStr); + String cookieToStr = Cookie.toString(jsonObject); + JSONObject finalJsonObject = Cookie.toJSONObject(cookieToStr); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(finalJsonObject, expectedJsonObject); + } + + /** + * A public API method performs a URL encoding for selected chars + * in a string. Control chars, '+', '%', '=', ';' are all encoded + * as %hh hex strings. The string is also trimmed. + * This test confirms that behavior. + */ + @Test + public void escapeString() { + String str = " +%\r\n\t\b%=;;; "; + String expectedStr = "%2b%25%0d%0a%09%08%25%3d%3b%3b%3b"; + String actualStr = Cookie.escape(str); + assertTrue("expect escape() to encode correctly. Actual: " + actualStr + + " expected: " + expectedStr, expectedStr.equals(actualStr)); + } + + /** + * A public API method performs URL decoding for strings. + * '+' is converted to space and %hh hex strings are converted to + * their ascii equivalent values. The string is not trimmed. + * This test confirms that behavior. + */ + @Test + public void unescapeString() { + String str = " +%2b%25%0d%0a%09%08%25%3d%3b%3b%3b+ "; + String expectedStr = " +%\r\n\t\b%=;;; "; + String actualStr = Cookie.unescape(str); + assertTrue("expect unescape() to decode correctly. Actual: " + actualStr + + " expected: " + expectedStr, expectedStr.equals(actualStr)); + } +} diff --git a/test/org/json/stleary/HTTPTest.java b/test/org/json/stleary/HTTPTest.java new file mode 100644 index 000000000..53f5cfcb1 --- /dev/null +++ b/test/org/json/stleary/HTTPTest.java @@ -0,0 +1,194 @@ +package org.json.stleary; + +import static org.junit.Assert.*; +import org.json.*; +import org.junit.Test; + +/** + * Unit tests for JSON-Java HTTP.java. See RFC7230. + */ +public class HTTPTest { + + /** + * Attempt to call HTTP.toJSONObject() with a null string + * Expects a NUllPointerException. + */ + @Test(expected = NullPointerException.class) + public void nullHTTPException() { + String httpStr = null; + HTTP.toJSONObject(httpStr); + } + + /** + * Attempt to call HTTP.toJSONObject() with a string containing + * an empty object. Expects a JSONException. + */ + @Test + public void notEnoughHTTPException() { + String httpStr = "{}"; + JSONObject jsonObject = new JSONObject(httpStr); + try { + HTTP.toString(jsonObject); + assertTrue("Expected to throw exception", false); + } catch (JSONException e) { + assertTrue("Expecting an exception message", + "Not enough material for an HTTP header.".equals(e.getMessage())); + } + } + + /** + * Calling HTTP.toJSONObject() with an empty string will result in a + * populated JSONObject with keys but no values for Request-URI, Method, + * and HTTP-Version. + */ + @Test + public void emptyStringHTTPRequest() { + String httpStr = ""; + String expectedHTTPStr = "{\"Request-URI\":\"\",\"Method\":\"\",\"HTTP-Version\":\"\"}"; + JSONObject jsonObject = HTTP.toJSONObject(httpStr); + JSONObject expectedJsonObject = new JSONObject(expectedHTTPStr); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); + } + + /** + * Call HTTP.toJSONObject() with a Request-URI, Method, + * and HTTP-Version. + */ + @Test + public void simpleHTTPRequest() { + String httpStr = "GET /hello.txt HTTP/1.1"; + String expectedHTTPStr + = "{\"Request-URI\":\"/hello.txt\",\"Method\":\"GET\",\"HTTP-Version\":\"HTTP/1.1\"}"; + JSONObject jsonObject = HTTP.toJSONObject(httpStr); + JSONObject expectedJsonObject = new JSONObject(expectedHTTPStr); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); + } + + /** + * Call HTTP.toJSONObject() with a response string containing a + * HTTP-Version, Status-Code, and Reason. + */ + @Test + public void simpleHTTPResponse() { + String httpStr = "HTTP/1.1 200 OK"; + String expectedHTTPStr + = "{\"HTTP-Version\":\"HTTP/1.1\",\"Status-Code\":\"200\",\"Reason-Phrase\":\"OK\"}"; + JSONObject jsonObject = HTTP.toJSONObject(httpStr); + JSONObject expectedJsonObject = new JSONObject(expectedHTTPStr); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); + } + + /** + * Call HTTP.toJSONObject() with a full request string including + * request headers. + */ + @Test + public void extendedHTTPRequest() { + String httpStr + = "POST /enlighten/calais.asmx HTTP/1.1\n" + + "Host: api.opencalais.com\n" + + "Content-Type: text/xml; charset=utf-8\n" + + "Content-Length: 100\n" + + "SOAPAction: \"http://clearforest.com/Enlighten\""; + String expectedHTTPStr + = "{" + + "\"Request-URI\":\"/enlighten/calais.asmx\"," + + "\"Host\":\"api.opencalais.com\"," + + "\"Method\":\"POST\"," + + "\"HTTP-Version\":\"HTTP/1.1\"," + + "\"Content-Length\":\"100\"," + + "\"Content-Type\":\"text/xml; charset=utf-8\"}"; + JSONObject jsonObject = HTTP.toJSONObject(httpStr); + JSONObject expectedJsonObject = new JSONObject(expectedHTTPStr); + /** + * Not too easy for JSONObject to parse a string with embedded quotes. + * For the sake of the test, add it here. + */ + expectedJsonObject.put("SOAPAction", "\"http://clearforest.com/Enlighten\""); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); + } + + /** + * Call HTTP.toJSONObject() with a full response string including + * response headers. + */ + @Test + public void extendedHTTPResponse() { + String httpStr + = "HTTP/1.1 200 OK\n" + + "Content-Type: text/xml; charset=utf-8\n" + + "Content-Length: 100\n"; + String expectedHTTPStr + = "{\"HTTP-Version\":\"HTTP/1.1\"," + + "\"Status-Code\":\"200\"," + + "\"Content-Length\":\"100\"," + + "\"Reason-Phrase\":\"OK\"," + + "\"Content-Type\":\"text/xml; charset=utf-8\"}"; + JSONObject jsonObject = HTTP.toJSONObject(httpStr); + JSONObject expectedJsonObject = new JSONObject(expectedHTTPStr); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); + } + + /** + * Call HTTP.toJSONObject() with a full POST request string including + * response headers, then convert it back into an HTTP string. + */ + @Test + public void convertHTTPRequestToString() { + String httpStr + = "POST /enlighten/calais.asmx HTTP/1.1\n" + + "Host: api.opencalais.com\n" + + "Content-Type: text/xml; charset=utf-8\n" + + "Content-Length: 100"; + String expectedHTTPStr + = "{" + + "\"Request-URI\":\"/enlighten/calais.asmx\"," + + "\"Host\":\"api.opencalais.com\"," + + "\"Method\":\"POST\"," + + "\"HTTP-Version\":\"HTTP/1.1\"," + + "\"Content-Length\":\"100\"," + + "\"Content-Type\":\"text/xml; charset=utf-8\"}"; + JSONObject jsonObject = HTTP.toJSONObject(httpStr); + JSONObject expectedJsonObject = new JSONObject(expectedHTTPStr); + String httpToStr = HTTP.toString(jsonObject); + /** + * JSONObject objects to crlfs and any trailing chars. + * For the sake of the test, simplify the resulting string + */ + httpToStr = httpToStr.replaceAll("(" + HTTP.CRLF + HTTP.CRLF + ")", ""); + httpToStr = httpToStr.replaceAll(HTTP.CRLF, "\n"); + JSONObject finalJsonObject = HTTP.toJSONObject(httpToStr); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(finalJsonObject, expectedJsonObject); + } + + /** + * Call HTTP.toJSONObject() with a full response string including + * response headers, then convert it back into an HTTP string. + */ + @Test + public void convertHTTPResponseToString() { + String httpStr + = "HTTP/1.1 200 OK\n" + + "Content-Type: text/xml; charset=utf-8\n" + + "Content-Length: 100\n"; + String expectedHTTPStr + = "{\"HTTP-Version\":\"HTTP/1.1\"," + + "\"Status-Code\":\"200\"," + + "\"Content-Length\":\"100\"," + + "\"Reason-Phrase\":\"OK\"," + + "\"Content-Type\":\"text/xml; charset=utf-8\"}"; + JSONObject jsonObject = HTTP.toJSONObject(httpStr); + JSONObject expectedJsonObject = new JSONObject(expectedHTTPStr); + String httpToStr = HTTP.toString(jsonObject); + /** + * JSONObject objects to crlfs and any trailing chars. + * For the sake of the test, simplify the resulting string + */ + httpToStr = httpToStr.replaceAll("(" + HTTP.CRLF + HTTP.CRLF + ")", ""); + httpToStr = httpToStr.replaceAll(HTTP.CRLF, "\n"); + JSONObject finalJsonObject = HTTP.toJSONObject(httpToStr); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(finalJsonObject, expectedJsonObject); + } +} diff --git a/test/org/json/stleary/JSONObjectLocaleTest.java b/test/org/json/stleary/JSONObjectLocaleTest.java new file mode 100644 index 000000000..97879dba8 --- /dev/null +++ b/test/org/json/stleary/JSONObjectLocaleTest.java @@ -0,0 +1,54 @@ +package org.json.stleary; + +import static org.junit.Assert.*; +import java.util.*; +import org.json.*; +import org.json.stleary.data.MyLocaleBean; +import org.junit.*; + +/** + * Note: This file is saved as UTF-8. Do not save as ASCII or the tests will + * fail. + * + */ +public class JSONObjectLocaleTest { + /** + * JSONObject built from a bean with locale-specific keys. + * In the Turkish alphabet, there are 2 versions of the letter "i". + * 'eh' I ı (dotless i) + * 'ee' İ i (dotted i) + * A problem can occur when parsing the public get methods for a bean. + * If the method starts with getI... then the key name will be lowercased + * to 'i' in English, and 'ı' in Turkish. + * We want the keys to be consistent regardless of locale, so JSON-Java + * lowercase operations are made to be locale-neutral by specifying + * Locale.ROOT. This causes 'I' to be universally lowercased to 'i' + * regardless of the locale currently in effect. + */ + @Test + 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")); + } +} diff --git a/test/org/json/stleary/JSONPointerTest.java b/test/org/json/stleary/JSONPointerTest.java new file mode 100644 index 000000000..b5f2892e0 --- /dev/null +++ b/test/org/json/stleary/JSONPointerTest.java @@ -0,0 +1,356 @@ +package org.json.stleary; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.io.InputStream; +import org.json.JSONArray; +import org.json.JSONObject; +import org.json.JSONPointer; +import org.json.JSONPointerException; +import org.json.JSONTokener; +import org.junit.Test; + +public class JSONPointerTest { + + private static final JSONObject document; + + static { + @SuppressWarnings("resource") + InputStream resourceAsStream = JSONPointerTest.class.getResourceAsStream("jsonpointer-testdoc.json"); + if (resourceAsStream == null) { + throw new ExceptionInInitializerError("Unable to locate test file. Please check your development environment configuration"); + } + document = new JSONObject(new JSONTokener(resourceAsStream)); + } + + private Object query(String pointer) { + return new JSONPointer(pointer).queryFrom(document); + } + + @Test + public void emptyPointer() { + assertSame(document, query("")); + } + + @Test(expected = NullPointerException.class) + public void nullPointer() { + new JSONPointer((String) null); + } + + @Test + public void objectPropertyQuery() { + assertSame(document.get("foo"), query("/foo")); + } + + @Test + public void arrayIndexQuery() { + assertSame(document.getJSONArray("foo").get(0), query("/foo/0")); + } + + @Test(expected = JSONPointerException.class) + public void stringPropOfArrayFailure() { + query("/foo/bar"); + } + + @Test + public void queryByEmptyKey() { + assertSame(document.get(""), query("/")); + } + + @Test + public void queryByEmptyKeySubObject() { + assertSame(document.getJSONObject("obj").getJSONObject(""), query("/obj/")); + } + + @Test + public void queryByEmptyKeySubObjectSubOject() { + assertSame( + document.getJSONObject("obj").getJSONObject("").get(""), + query("/obj//") + ); + } + + @Test + public void queryByEmptyKeySubObjectValue() { + assertSame( + document.getJSONObject("obj").getJSONObject("").get("subKey"), + query("/obj//subKey") + ); + } + + @Test + public void slashEscaping() { + assertSame(document.get("a/b"), query("/a~1b")); + } + + @Test + public void tildeEscaping() { + assertSame(document.get("m~n"), query("/m~0n")); + } + + @Test + public void backslashEscaping() { + assertSame(document.get("i\\j"), query("/i\\\\j")); + } + + @Test + public void quotationEscaping() { + assertSame(document.get("k\"l"), query("/k\\\\\\\"l")); + } + + @Test + public void whitespaceKey() { + assertSame(document.get(" "), query("/ ")); + } + + @Test + public void uriFragmentNotation() { + assertSame(document.get("foo"), query("#/foo")); + } + + @Test + public void uriFragmentNotationRoot() { + assertSame(document, query("#")); + } + + @Test + public void uriFragmentPercentHandling() { + assertSame(document.get("c%d"), query("#/c%25d")); + assertSame(document.get("e^f"), query("#/e%5Ef")); + assertSame(document.get("g|h"), query("#/g%7Ch")); + assertSame(document.get("m~n"), query("#/m~0n")); + } + + @Test(expected = IllegalArgumentException.class) + public void syntaxError() { + new JSONPointer("key"); + } + + @Test(expected = JSONPointerException.class) + public void arrayIndexFailure() { + query("/foo/2"); + } + + @Test(expected = JSONPointerException.class) + public void primitiveFailure() { + query("/obj/key/failure"); + } + + @Test + public void builderTest() { + JSONPointer pointer = JSONPointer.builder() + .append("obj") + .append("other~key").append("another/key") + .append(0) + .build(); + assertEquals("val", pointer.queryFrom(document)); + } + + @Test(expected = NullPointerException.class) + public void nullToken() { + JSONPointer.builder().append(null); + } + + @Test + public void toStringEscaping() { + JSONPointer pointer = JSONPointer.builder() + .append("obj") + .append("other~key").append("another/key") + .append("\"") + .append(0) + .build(); + assertEquals("/obj/other~0key/another~1key/\\\"/0", pointer.toString()); + } + + @Test + public void emptyPointerToString() { + assertEquals("", new JSONPointer("").toString()); + } + + @Test + public void toURIFragment() { + assertEquals("#/c%25d", new JSONPointer("/c%d").toURIFragment()); + assertEquals("#/e%5Ef", new JSONPointer("/e^f").toURIFragment()); + assertEquals("#/g%7Ch", new JSONPointer("/g|h").toURIFragment()); + assertEquals("#/m%7En", new JSONPointer("/m~n").toURIFragment()); + } + + @Test + public void tokenListIsCopiedInConstructor() { + JSONPointer.Builder b = JSONPointer.builder().append("key1"); + JSONPointer jp1 = b.build(); + b.append("key2"); + JSONPointer jp2 = b.build(); + if (jp1.toString().equals(jp2.toString())) { + fail("Oops, my pointers are sharing a backing array"); + } + } + + /** + * Coverage for JSONObject query(String) + */ + @Test + public void queryFromJSONObject() { + String str = "{" + + "\"stringKey\":\"hello world!\"," + + "\"arrayKey\":[0,1,2]," + + "\"objectKey\": {" + + "\"a\":\"aVal\"," + + "\"b\":\"bVal\"" + + "}" + + "}"; + JSONObject jsonObject = new JSONObject(str); + Object obj = jsonObject.query("/stringKey"); + assertTrue("Expected 'hello world!'", "hello world!".equals(obj)); + obj = jsonObject.query("/arrayKey/1"); + assertTrue("Expected 1", Integer.valueOf(1).equals(obj)); + obj = jsonObject.query("/objectKey/b"); + assertTrue("Expected bVal", "bVal".equals(obj)); + try { + obj = jsonObject.query("/a/b/c"); + assertTrue("Expected JSONPointerException", false); + } catch (JSONPointerException e) { + assertTrue("Expected bad key/value exception", + "value [null] is not an array or object therefore its key b cannot be resolved". + equals(e.getMessage())); + } + } + + /** + * Coverage for JSONObject query(JSONPointer) + */ + @Test + public void queryFromJSONObjectUsingPointer() { + String str = "{" + + "\"stringKey\":\"hello world!\"," + + "\"arrayKey\":[0,1,2]," + + "\"objectKey\": {" + + "\"a\":\"aVal\"," + + "\"b\":\"bVal\"" + + "}" + + "}"; + JSONObject jsonObject = new JSONObject(str); + Object obj = jsonObject.query(new JSONPointer("/stringKey")); + assertTrue("Expected 'hello world!'", "hello world!".equals(obj)); + obj = jsonObject.query(new JSONPointer("/arrayKey/1")); + assertTrue("Expected 1", Integer.valueOf(1).equals(obj)); + obj = jsonObject.query(new JSONPointer("/objectKey/b")); + assertTrue("Expected bVal", "bVal".equals(obj)); + try { + obj = jsonObject.query(new JSONPointer("/a/b/c")); + assertTrue("Expected JSONPointerException", false); + } catch (JSONPointerException e) { + assertTrue("Expected bad key/value exception", + "value [null] is not an array or object therefore its key b cannot be resolved". + equals(e.getMessage())); + } + } + + /** + * Coverage for JSONObject optQuery(JSONPointer) + */ + @Test + public void optQueryFromJSONObjectUsingPointer() { + String str = "{" + + "\"stringKey\":\"hello world!\"," + + "\"arrayKey\":[0,1,2]," + + "\"objectKey\": {" + + "\"a\":\"aVal\"," + + "\"b\":\"bVal\"" + + "}" + + "}"; + JSONObject jsonObject = new JSONObject(str); + Object obj = jsonObject.optQuery(new JSONPointer("/stringKey")); + assertTrue("Expected 'hello world!'", "hello world!".equals(obj)); + obj = jsonObject.optQuery(new JSONPointer("/arrayKey/1")); + assertTrue("Expected 1", Integer.valueOf(1).equals(obj)); + obj = jsonObject.optQuery(new JSONPointer("/objectKey/b")); + assertTrue("Expected bVal", "bVal".equals(obj)); + obj = jsonObject.optQuery(new JSONPointer("/a/b/c")); + assertTrue("Expected null", obj == null); + } + + /** + * Coverage for JSONArray query(String) + */ + @Test + public void queryFromJSONArray() { + String str = "[" + + "\"hello world!\"," + + "[0,1,2]," + + "{" + + "\"a\":\"aVal\"," + + "\"b\":\"bVal\"" + + "}" + + "]"; + JSONArray jsonArray = new JSONArray(str); + Object obj = jsonArray.query("/0"); + assertTrue("Expected 'hello world!'", "hello world!".equals(obj)); + obj = jsonArray.query("/1/1"); + assertTrue("Expected 1", Integer.valueOf(1).equals(obj)); + obj = jsonArray.query("/2/b"); + assertTrue("Expected bVal", "bVal".equals(obj)); + try { + obj = jsonArray.query("/a/b/c"); + assertTrue("Expected JSONPointerException", false); + } catch (JSONPointerException e) { + assertTrue("Expected bad index exception", + "a is not an array index".equals(e.getMessage())); + } + } + + /** + * Coverage for JSONArray query(JSONPointer) + */ + @Test + public void queryFromJSONArrayUsingPointer() { + String str = "[" + + "\"hello world!\"," + + "[0,1,2]," + + "{" + + "\"a\":\"aVal\"," + + "\"b\":\"bVal\"" + + "}" + + "]"; + JSONArray jsonArray = new JSONArray(str); + Object obj = jsonArray.query(new JSONPointer("/0")); + assertTrue("Expected 'hello world!'", "hello world!".equals(obj)); + obj = jsonArray.query(new JSONPointer("/1/1")); + assertTrue("Expected 1", Integer.valueOf(1).equals(obj)); + obj = jsonArray.query(new JSONPointer("/2/b")); + assertTrue("Expected bVal", "bVal".equals(obj)); + try { + obj = jsonArray.query(new JSONPointer("/a/b/c")); + assertTrue("Expected JSONPointerException", false); + } catch (JSONPointerException e) { + assertTrue("Expected bad index exception", + "a is not an array index".equals(e.getMessage())); + } + } + + /** + * Coverage for JSONArray optQuery(JSONPointer) + */ + @Test + public void optQueryFromJSONArrayUsingPointer() { + String str = "[" + + "\"hello world!\"," + + "[0,1,2]," + + "{" + + "\"a\":\"aVal\"," + + "\"b\":\"bVal\"" + + "}" + + "]"; + JSONArray jsonArray = new JSONArray(str); + Object obj = jsonArray.optQuery(new JSONPointer("/0")); + assertTrue("Expected 'hello world!'", "hello world!".equals(obj)); + obj = jsonArray.optQuery(new JSONPointer("/1/1")); + assertTrue("Expected 1", Integer.valueOf(1).equals(obj)); + obj = jsonArray.optQuery(new JSONPointer("/2/b")); + assertTrue("Expected bVal", "bVal".equals(obj)); + obj = jsonArray.optQuery(new JSONPointer("/a/b/c")); + assertTrue("Expected null", obj == null); + } +} diff --git a/test/org/json/stleary/JSONStringTest.java b/test/org/json/stleary/JSONStringTest.java new file mode 100644 index 000000000..6746ea3fd --- /dev/null +++ b/test/org/json/stleary/JSONStringTest.java @@ -0,0 +1,369 @@ +package org.json.stleary; + +import static org.junit.Assert.*; +import java.io.IOException; +import java.io.StringWriter; +import java.util.*; +import org.json.*; +import org.junit.Test; + +/** + * Tests for JSONString implementations, and the difference between + * {@link JSONObject#valueToString} and {@link JSONObject#writeValue}. + */ +public class JSONStringTest { + + /** + * This tests the JSONObject.writeValue() method. We can't test directly + * due to it being a package-protected method. Instead, we can call + * JSONArray.write(), which delegates the writing of each entry to + * writeValue(). + */ + @Test + public void writeValues() throws Exception { + JSONArray jsonArray = new JSONArray(); + jsonArray.put((Object) null); + + StringWriter writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[null]".equals(output)); + + jsonArray = new JSONArray(); + jsonArray.put(JSONObject.NULL); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[null]".equals(output)); + + jsonArray = new JSONArray(); + jsonArray.put(new JSONObject()); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[{}]".equals(output)); + + jsonArray = new JSONArray(); + jsonArray.put(new JSONArray()); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[[]]".equals(output)); + + jsonArray = new JSONArray(); + Map singleMap = Collections.singletonMap("key1", "value1"); + jsonArray.put((Object) singleMap); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[{\"key1\":\"value1\"}]".equals(output)); + + jsonArray = new JSONArray(); + List singleList = Collections.singletonList("entry1"); + jsonArray.put((Object) singleList); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[[\"entry1\"]]".equals(output)); + + jsonArray = new JSONArray(); + int[] intArray = new int[] { 1, 2, 3 }; + jsonArray.put(intArray); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[[1, 2, 3]]".equals(output)); + + jsonArray = new JSONArray(); + jsonArray.put(24); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[24]".equals(output)); + + jsonArray = new JSONArray(); + jsonArray.put("string value"); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[\"string value\"]".equals(output)); + + jsonArray = new JSONArray(); + jsonArray.put(true); + } finally { + writer.close(); + } + writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[true]".equals(output)); + } finally { + writer.close(); + } + + } + + /** + * This tests the JSONObject.valueToString() method. These should be + * identical to the values above, except for the enclosing [ and ]. + */ + @SuppressWarnings("boxing") + @Test + public void valuesToString() throws Exception { + + String output = JSONObject.valueToString(null); + assertTrue("String values should be equal", "null".equals(output)); + + output = JSONObject.valueToString(JSONObject.NULL); + assertTrue("String values should be equal", "null".equals(output)); + + output = JSONObject.valueToString(new JSONObject()); + assertTrue("String values should be equal", "{}".equals(output)); + + output = JSONObject.valueToString(new JSONArray()); + assertTrue("String values should be equal", "[]".equals(output)); + + Map singleMap = Collections.singletonMap("key1", "value1"); + output = JSONObject.valueToString(singleMap); + assertTrue("String values should be equal", "{\"key1\":\"value1\"}".equals(output)); + + List singleList = Collections.singletonList("entry1"); + output = JSONObject.valueToString(singleList); + assertTrue("String values should be equal", "[\"entry1\"]".equals(output)); + + int[] intArray = new int[] { 1, 2, 3 }; + output = JSONObject.valueToString(intArray); + assertTrue("String values should be equal", "[1, 2, 3]".equals(output)); + + output = JSONObject.valueToString(24); + assertTrue("String values should be equal", "24".equals(output)); + + output = JSONObject.valueToString("string value"); + assertTrue("String values should be equal", "\"string value\"".equals(output)); + + output = JSONObject.valueToString(true); + assertTrue("String values should be equal", "true".equals(output)); + + } + + /** + * Test what happens when toJSONString() returns a well-formed JSON value. + * This is the usual case. + */ + @Test + public void testJSONStringValue() throws Exception { + JSONStringValue jsonString = new JSONStringValue(); + JSONArray jsonArray = new JSONArray(); + + jsonArray.put(jsonString); + + StringWriter writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[\"the JSON string value\"]".equals(output)); + + output = JSONObject.valueToString(jsonString); + assertTrue("String values should be equal", "\"the JSON string value\"".equals(output)); + } finally { + writer.close(); + } + } + + /** + * Test what happens when toJSONString() returns null. In one case, + * use the object's toString() method. In the other, throw a JSONException. + */ + @Test + public void testJSONNullStringValue() throws Exception { + JSONNullStringValue jsonString = new JSONNullStringValue(); + JSONArray jsonArray = new JSONArray(); + + jsonArray.put(jsonString); + + StringWriter writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[\"the toString value\"]".equals(output)); + + // The only different between writeValue() and valueToString(): + // in this case, valueToString throws a JSONException + try { + output = JSONObject.valueToString(jsonString); + fail("Expected an exception, got a String value"); + } catch (Exception e) { + assertTrue("Expected JSONException", e instanceof JSONException); + assertTrue("Exception message does not match", "Bad value from toJSONString: null".equals(e.getMessage())); + } + } finally { + writer.close(); + } + } + + /** + * Test what happens when toJSONString() returns an exception. In both + * cases, a JSONException is thrown, with the cause and message set from + * the original exception. + */ + @Test + public void testJSONStringExceptionValue() throws IOException { + JSONStringExceptionValue jsonString = new JSONStringExceptionValue(); + JSONArray jsonArray = new JSONArray(); + + jsonArray.put(jsonString); + + StringWriter writer = new StringWriter(); + try { + jsonArray.write(writer).toString(); + fail("Expected an exception, got a String value"); + } catch (JSONException e) { + assertEquals("Unable to write JSONArray value at index: 0", e.getMessage()); + } catch (Exception e) { + fail("Expected JSONException"); + } finally { + writer.close(); + } + + try { + JSONObject.valueToString(jsonString); + fail("Expected an exception, got a String value"); + } catch (JSONException e) { + assertTrue("Exception message does not match", "the exception value".equals(e.getMessage())); + } catch (Exception e) { + fail("Expected JSONException"); + } + } + + /** + * Test what happens when a Java object's toString() returns a String value. + * This is the usual case. + */ + @Test + public void testStringValue() throws Exception { + StringValue nonJsonString = new StringValue(); + JSONArray jsonArray = new JSONArray(); + + jsonArray.put(nonJsonString); + + StringWriter writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[\"the toString value for StringValue\"]".equals(output)); + + output = JSONObject.valueToString(nonJsonString); + assertTrue("String values should be equal", "\"the toString value for StringValue\"".equals(output)); + } finally { + writer.close(); + } + } + + /** + * Test what happens when a Java object's toString() returns null. + * Defaults to empty string. + */ + @Test + public void testNullStringValue() throws Exception { + NullStringValue nonJsonString = new NullStringValue(); + JSONArray jsonArray = new JSONArray(); + + jsonArray.put(nonJsonString); + + StringWriter writer = new StringWriter(); + try { + String output = jsonArray.write(writer).toString(); + assertTrue("String values should be equal", "[\"\"]".equals(output)); + + output = JSONObject.valueToString(nonJsonString); + assertTrue("String values should be equal", "\"\"".equals(output)); + } finally { + writer.close(); + } + } + + /** + * A JSONString that returns a valid JSON string value. + */ + private static final class JSONStringValue implements JSONString { + + @Override + public String toJSONString() { + return "\"the JSON string value\""; + } + + @Override + public String toString() { + return "the toString value for JSONStringValue"; + } + } + + /** + * A JSONString that returns null when calling toJSONString(). + */ + private static final class JSONNullStringValue implements JSONString { + + @Override + public String toJSONString() { + return null; + } + + @Override + public String toString() { + return "the toString value"; + } + } + + /** + * A JSONString that throw an exception when calling toJSONString(). + */ + private static final class JSONStringExceptionValue implements JSONString { + + @Override + public String toJSONString() { + throw new IllegalStateException("the exception value"); + } + + @Override + public String toString() { + return "the toString value for JSONStringExceptionValue"; + } + } + + public static final class StringValue { + + @Override + public String toString() { + return "the toString value for StringValue"; + } + } + + public static final class NullStringValue { + + @Override + public String toString() { + return null; + } + } +} diff --git a/test/org/json/stleary/JSONTokenerTest.java b/test/org/json/stleary/JSONTokenerTest.java new file mode 100644 index 000000000..73a8e2bab --- /dev/null +++ b/test/org/json/stleary/JSONTokenerTest.java @@ -0,0 +1,203 @@ +package org.json.stleary; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import org.json.JSONException; +import org.json.JSONTokener; +import org.junit.Test; + +/** + * Test specific to the {@link org.json.JSONTokener} class. + * + * @author John Aylward + * + */ +public class JSONTokenerTest { + + /** + * verify that back() fails as expected. + * + * @throws IOException thrown if something unexpected happens. + */ + @Test + public void verifyBackFailureZeroIndex() throws IOException { + try (Reader reader = new StringReader("some test string")) { + final JSONTokener tokener = new JSONTokener(reader); + try { + // this should fail since the index is 0; + tokener.back(); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Stepping back two steps is not supported", e.getMessage()); + } catch (Exception e) { + fail("Unknown Exception type " + e.getClass().getCanonicalName() + " with message " + e.getMessage()); + } + + } + } + + /** + * verify that back() fails as expected. + * + * @throws IOException thrown if something unexpected happens. + */ + @Test + public void verifyBackFailureDoubleBack() throws IOException { + try (Reader reader = new StringReader("some test string")) { + final JSONTokener tokener = new JSONTokener(reader); + tokener.next(); + tokener.back(); + try { + // this should fail since the index is 0; + tokener.back(); + fail("Expected an exception"); + } catch (JSONException e) { + assertEquals("Stepping back two steps is not supported", e.getMessage()); + } catch (Exception e) { + fail("Unknown Exception type " + e.getClass().getCanonicalName() + " with message " + e.getMessage()); + } + } + } + + /** + * Tests the failure of the skipTo method with a buffered reader. Preferably + * we'd like this not to fail but at this time we don't have a good recovery. + * + * @throws IOException thrown if something unexpected happens. + */ + @Test + public void testSkipToFailureWithBufferedReader() throws IOException { + final byte[] superLongBuffer = new byte[1000001]; + // fill our buffer + for (int i = 0; i < superLongBuffer.length; i++) { + superLongBuffer[i] = 'A'; + } + try (Reader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(superLongBuffer)))) { + final JSONTokener tokener = new JSONTokener(reader); + try { + // this should fail since the internal markAhead buffer is only 1,000,000 + // but 'B' doesn't exist in our buffer that is 1,000,001 in size + tokener.skipTo('B'); + fail("Expected exception"); + } catch (JSONException e) { + assertEquals("Mark invalid", e.getMessage()); + } catch (Exception e) { + fail("Unknown Exception type " + e.getClass().getCanonicalName() + " with message " + e.getMessage()); + } + } + } + + /** + * Tests the success of the skipTo method with a String reader. + * + * @throws IOException thrown if something unexpected happens. + */ + @Test + public void testSkipToSuccessWithStringReader() throws IOException { + final StringBuilder superLongBuffer = new StringBuilder(1000001); + // fill our buffer + for (int i = 0; i < superLongBuffer.length(); i++) { + superLongBuffer.append('A'); + } + try (Reader reader = new StringReader(superLongBuffer.toString())) { + final JSONTokener tokener = new JSONTokener(reader); + try { + // this should not fail since the internal markAhead is ignored for StringReaders + tokener.skipTo('B'); + } catch (Exception e) { + fail("Unknown Exception type " + e.getClass().getCanonicalName() + " with message " + e.getMessage()); + } + } + } + + /** + * Verify that next and back are working properly and tracking the correct positions + * with different new line combinations. + */ + @Test + public void testNextBackComboWithNewLines() { + final String testString = "this is\nA test\r\nWith some different\rNew Lines"; + // ^ ^ ^ ^ + // index positions 0 8 16 36 + final JSONTokener tokener = new JSONTokener(testString); + assertEquals(" at 0 [character 1 line 1]", tokener.toString()); + assertEquals('t', tokener.next()); + assertEquals(" at 1 [character 2 line 1]", tokener.toString()); + tokener.skipTo('\n'); + assertEquals("skipTo() improperly modifying indexes", " at 7 [character 8 line 1]", tokener.toString()); + assertEquals('\n', tokener.next()); + assertEquals(" at 8 [character 0 line 2]", tokener.toString()); + assertEquals('A', tokener.next()); + assertEquals(" at 9 [character 1 line 2]", tokener.toString()); + tokener.back(); + assertEquals(" at 8 [character 0 line 2]", tokener.toString()); + tokener.skipTo('\r'); + assertEquals("skipTo() improperly modifying indexes", " at 14 [character 6 line 2]", tokener.toString()); + // verify \r\n combo doesn't increment the line twice + assertEquals('\r', tokener.next()); + assertEquals(" at 15 [character 0 line 3]", tokener.toString()); + assertEquals('\n', tokener.next()); + assertEquals(" at 16 [character 0 line 3]", tokener.toString()); + // verify stepping back after reading the \n of an \r\n combo doesn't increment the line incorrectly + tokener.back(); + assertEquals(" at 15 [character 6 line 2]", tokener.toString()); + assertEquals('\n', tokener.next()); + assertEquals(" at 16 [character 0 line 3]", tokener.toString()); + assertEquals('W', tokener.next()); + assertEquals(" at 17 [character 1 line 3]", tokener.toString()); + assertEquals('i', tokener.next()); + assertEquals(" at 18 [character 2 line 3]", tokener.toString()); + tokener.skipTo('\r'); + assertEquals("skipTo() improperly modifying indexes", " at 35 [character 19 line 3]", tokener.toString()); + assertEquals('\r', tokener.next()); + assertEquals(" at 36 [character 0 line 4]", tokener.toString()); + tokener.back(); + assertEquals(" at 35 [character 19 line 3]", tokener.toString()); + assertEquals('\r', tokener.next()); + assertEquals(" at 36 [character 0 line 4]", tokener.toString()); + assertEquals('N', tokener.next()); + assertEquals(" at 37 [character 1 line 4]", tokener.toString()); + + // verify we get the same data just walking though, no calls to back + final JSONTokener t2 = new JSONTokener(testString); + for (int i = 0; i < 7; i++) { + assertTrue(t2.toString().startsWith(" at " + i + " ")); + assertEquals(testString.charAt(i), t2.next()); + } + assertEquals(" at 7 [character 8 line 1]", t2.toString()); + assertEquals(testString.charAt(7), t2.next()); + assertEquals(" at 8 [character 0 line 2]", t2.toString()); + for (int i = 8; i < 14; i++) { + assertTrue(t2.toString().startsWith(" at " + i + " ")); + assertEquals(testString.charAt(i), t2.next()); + } + assertEquals(" at 14 [character 6 line 2]", t2.toString()); + assertEquals('\r', t2.next()); + assertEquals(" at 15 [character 0 line 3]", t2.toString()); + assertEquals('\n', t2.next()); + assertEquals(" at 16 [character 0 line 3]", t2.toString()); + assertEquals('W', t2.next()); + assertEquals(" at 17 [character 1 line 3]", t2.toString()); + for (int i = 17; i < 37; i++) { + assertTrue(t2.toString().startsWith(" at " + i + " ")); + assertEquals(testString.charAt(i), t2.next()); + } + assertEquals(" at 37 [character 1 line 4]", t2.toString()); + for (int i = 37; i < testString.length(); i++) { + assertTrue(t2.toString().startsWith(" at " + i + " ")); + assertEquals(testString.charAt(i), t2.next()); + } + assertEquals(" at " + testString.length() + " [character 9 line 4]", t2.toString()); + // end of the input + assertEquals(0, t2.next()); + assertFalse(t2.more()); + } +} diff --git a/test/org/json/stleary/PropertyTest.java b/test/org/json/stleary/PropertyTest.java new file mode 100644 index 000000000..a0d19e2de --- /dev/null +++ b/test/org/json/stleary/PropertyTest.java @@ -0,0 +1,95 @@ +package org.json.stleary; + +import static org.junit.Assert.*; +import java.util.*; +import org.json.*; +import org.junit.Test; + +/** + * Tests for JSON-Java Property + */ +public class PropertyTest { + + /** + * JSONObject from null properties object should + * result in an empty JSONObject. + */ + @Test + public void shouldHandleNullProperties() { + Properties properties = null; + JSONObject jsonObject = Property.toJSONObject(properties); + assertTrue("jsonObject should be empty", jsonObject.isEmpty()); + } + + /** + * JSONObject from empty properties object should + * result in an empty JSONObject. + */ + @Test + public void shouldHandleEmptyProperties() { + Properties properties = new Properties(); + JSONObject jsonObject = Property.toJSONObject(properties); + assertTrue("jsonObject should be empty", jsonObject.isEmpty()); + } + + /** + * JSONObject from simple properties object. + */ + @Test + public void shouldHandleProperties() { + Properties properties = new Properties(); + + properties.put("Illinois", "Springfield"); + properties.put("Missouri", "Jefferson City"); + properties.put("Washington", "Olympia"); + properties.put("California", "Sacramento"); + properties.put("Indiana", "Indianapolis"); + + JSONObject jsonObject = Property.toJSONObject(properties); + + assertTrue("jsonObject should contain 5 items", jsonObject.length() == 5); + assertTrue("jsonObject should contain Illinois property", + "Springfield".equals(jsonObject.get("Illinois"))); + assertTrue("jsonObject should contain Missouri property", + "Jefferson City".equals(jsonObject.get("Missouri"))); + assertTrue("jsonObject should contain Washington property", + "Olympia".equals(jsonObject.get("Washington"))); + assertTrue("jsonObject should contain California property", + "Sacramento".equals(jsonObject.get("California"))); + assertTrue("jsonObject should contain Indiana property", + "Indianapolis".equals(jsonObject.get("Indiana"))); + } + + /** + * Null JSONObject toProperties() should result in an empty + * Properties object. + */ + @Test + public void shouldHandleNullJSONProperty() { + JSONObject jsonObject = null; + Properties properties = Property.toProperties(jsonObject); + assertTrue("properties should be empty", + properties.size() == 0); + } + + /** + * Properties should convert to JSONObject, and back to + * Properties without changing. + */ + @Test + public void shouldHandleJSONProperty() { + Properties properties = new Properties(); + + properties.put("Illinois", "Springfield"); + properties.put("Missouri", "Jefferson City"); + properties.put("Washington", "Olympia"); + properties.put("California", "Sacramento"); + properties.put("Indiana", "Indianapolis"); + + JSONObject jsonObject = Property.toJSONObject(properties); + Properties jsonProperties = Property.toProperties(jsonObject); + + assertTrue("property objects should match", + properties.equals(jsonProperties)); + } +} diff --git a/test/org/json/stleary/Util.java b/test/org/json/stleary/Util.java new file mode 100644 index 000000000..6b1b8e8a7 --- /dev/null +++ b/test/org/json/stleary/Util.java @@ -0,0 +1,99 @@ +package org.json.stleary; + +import static org.junit.Assert.*; +import java.util.*; +import org.json.*; + +/** + * These are helpful utility methods that perform basic comparisons + * between various objects. In most cases, the comparisons are not + * order-dependent, or else the order is known. + */ +public class Util { + + /** + * Compares two JSONArrays for equality. + * The arrays need not be in the same order. + * + * @param jsonArray created by the code to be tested + * @param expectedJsonArray created specifically for comparing + */ + public static void compareActualVsExpectedJsonArrays(JSONArray jsonArray, + JSONArray expectedJsonArray) { + assertTrue("jsonArray lengths should be equal", + jsonArray.length() == expectedJsonArray.length()); + for (int i = 0; i < jsonArray.length(); ++i) { + Object value = jsonArray.get(i); + Object expectedValue = expectedJsonArray.get(i); + compareActualVsExpectedObjects(value, expectedValue); + } + } + + /** + * Compares two JSONObjects for equality. The objects need not be + * in the same order + * + * @param jsonObject created by the code to be tested + * @param expectedJsonObject created specifically for comparing + */ + public static void compareActualVsExpectedJsonObjects( + JSONObject jsonObject, JSONObject expectedJsonObject) { + assertTrue("jsonObjects should have the same length", + jsonObject.length() == expectedJsonObject.length()); + Iterator keys = jsonObject.keys(); + while (keys.hasNext()) { + String key = keys.next(); + Object value = jsonObject.get(key); + Object expectedValue = expectedJsonObject.get(key); + compareActualVsExpectedObjects(value, expectedValue); + } + } + + /** + * Compare two objects for equality. Might be JSONArray, JSONObject, + * or something else. + * + * @param value created by the code to be tested + * @param expectedValue created specifically for comparing + * @param key key to the jsonObject entry to be compared + */ + private static void compareActualVsExpectedObjects(Object value, + Object expectedValue) { + if (value instanceof JSONObject && expectedValue instanceof JSONObject) { + // Compare JSONObjects + JSONObject jsonObject = (JSONObject) value; + JSONObject expectedJsonObject = (JSONObject) expectedValue; + compareActualVsExpectedJsonObjects( + jsonObject, expectedJsonObject); + } else if (value instanceof JSONArray && expectedValue instanceof JSONArray) { + // Compare JSONArrays + JSONArray jsonArray = (JSONArray) value; + JSONArray expectedJsonArray = (JSONArray) expectedValue; + compareActualVsExpectedJsonArrays( + jsonArray, expectedJsonArray); + } else { + /** + * Compare all other types using toString(). First, the types must + * also be equal, unless both are Number type. Certain helper + * classes (e.g. XML) may create Long instead of Integer for small + * int values. + */ + if (!(value instanceof Number && expectedValue instanceof Number)) { + // Non-Number and non-matching types + assertTrue("object types should be equal for actual: " + + value.toString() + " (" + + value.getClass().toString() + ") expected: " + + expectedValue.toString() + " (" + + expectedValue.getClass().toString() + ")", + value.getClass().toString().equals( + expectedValue.getClass().toString())); + } + /** + * Same types or both Numbers, compare by toString() + */ + assertTrue("string values should be equal for actual: " + + value.toString() + " expected: " + expectedValue.toString(), + value.toString().equals(expectedValue.toString())); + } + } +} diff --git a/test/org/json/stleary/data/MyLocaleBean.java b/test/org/json/stleary/data/MyLocaleBean.java new file mode 100644 index 000000000..b2a5a0b78 --- /dev/null +++ b/test/org/json/stleary/data/MyLocaleBean.java @@ -0,0 +1,14 @@ +package org.json.stleary.data; + +public class MyLocaleBean { + private final String id = "beanId"; + private final String i = "beanI"; + + public String getId() { + return id; + } + + public String getI() { + return i; + } +} diff --git a/test/org/json/stleary/jsonpointer-testdoc.json b/test/org/json/stleary/jsonpointer-testdoc.json new file mode 100644 index 000000000..657ccdd34 --- /dev/null +++ b/test/org/json/stleary/jsonpointer-testdoc.json @@ -0,0 +1,28 @@ +{ + "foo": + [ + "bar", + "baz" + ], + "": 0, + "a/b": 1, + "c%d": 2, + "e^f": 3, + "g|h": 4, + "i\\j": 5, + "k\"l": 6, + " ": 7, + "m~n": 8, + "obj" : { + "key" : "value", + "other~key" : { + "another/key" : [ + "val" + ] + }, + "" : { + "" : "empty key of an object with an empty key", + "subKey" : "Some other value" + } + } +} \ No newline at end of file diff --git a/test/org/json/stleary/package.html b/test/org/json/stleary/package.html new file mode 100644 index 000000000..7436633b4 --- /dev/null +++ b/test/org/json/stleary/package.html @@ -0,0 +1,10 @@ + + + + + + + + Unit tests copied from the https://github.com/stleary/JSON-Java-unit-test/ master. + + diff --git a/test/org/json/testJSON.json b/test/org/json/testJSON.json new file mode 100644 index 000000000..a1e2db764 --- /dev/null +++ b/test/org/json/testJSON.json @@ -0,0 +1 @@ +{"birthday":"1940-02-10","favorite_foods":["cookie","fish","chips"],"passport":{"nationality":"American","id":100001},"car":null,"name":"Tom","married":false,"age":76} \ No newline at end of file diff --git a/test/org/json/testJSON2.json b/test/org/json/testJSON2.json new file mode 100644 index 000000000..a1e2db764 --- /dev/null +++ b/test/org/json/testJSON2.json @@ -0,0 +1 @@ +{"birthday":"1940-02-10","favorite_foods":["cookie","fish","chips"],"passport":{"nationality":"American","id":100001},"car":null,"name":"Tom","married":false,"age":76} \ No newline at end of file diff --git a/test/org/json/testJSON3.json b/test/org/json/testJSON3.json new file mode 100644 index 000000000..8b20424c5 --- /dev/null +++ b/test/org/json/testJSON3.json @@ -0,0 +1 @@ +{"birthday":"1940-02-10","favorite_foods":["cookie","fish","chips2"],"passport":{"nationality":"American","id":100001},"car":null,"name":"Tom","married":false,"age":76} \ No newline at end of file diff --git a/test/org/json/testJSON4.json b/test/org/json/testJSON4.json new file mode 100644 index 000000000..466109036 --- /dev/null +++ b/test/org/json/testJSON4.json @@ -0,0 +1 @@ +{"birthday":"1940-02-10","favorite_foods":["cookie","fish","chips"],"passport":{"nationality":"American","id":100001},"car":null,"name":"Tom","married":false,"age":78} \ No newline at end of file diff --git a/test/org/json/testJSON5.json b/test/org/json/testJSON5.json new file mode 100644 index 000000000..cc843f573 --- /dev/null +++ b/test/org/json/testJSON5.json @@ -0,0 +1 @@ +{"birthday":"1940-02-10","favorite_foods":["cookie","fish","chips"],"passport":{"nationality":"American","id":100001},"car":null,"name":"Tom","married":false,"age":76.0} \ No newline at end of file diff --git a/test/org/json/testJSON6.json b/test/org/json/testJSON6.json new file mode 100644 index 000000000..a15f05682 --- /dev/null +++ b/test/org/json/testJSON6.json @@ -0,0 +1 @@ +{"birthday":"1940-02-10","favorite_foods":null,"passport":{"nationality":"American","id":100001},"car":null,"name":"Tom","married":false,"age":76.0} \ No newline at end of file diff --git a/test/org/json/testJSON7.json b/test/org/json/testJSON7.json new file mode 100644 index 000000000..bfa8569b8 --- /dev/null +++ b/test/org/json/testJSON7.json @@ -0,0 +1 @@ +{"birthday":"1940-02-10","favorite_foods":["cookie","fish","chips"],"passport":{"nationality":"American","id":100001},"car":null,"name":null,"married":false,"age":76} \ No newline at end of file diff --git a/test/org/json/testJSONArray.json b/test/org/json/testJSONArray.json new file mode 100644 index 000000000..49987c2f9 --- /dev/null +++ b/test/org/json/testJSONArray.json @@ -0,0 +1 @@ +["cookie","fish","chips"] \ No newline at end of file