Revert "Merge pull request #877 from rikkarth/feat/871-strictMode"

This reverts commit d02ac0f2a35f8c8ba56230bc4b67275010d4d617, reversing
changes made to cfd47615d0b9a2392945d198d7fd820bbbc17437.
This commit is contained in:
Sean Leary 2024-11-03 09:50:08 -06:00
parent 61dc2644d8
commit 215ec9bb9c
6 changed files with 109 additions and 764 deletions

View File

@ -75,31 +75,19 @@ public class JSONArray implements Iterable<Object> {
} }
/** /**
* Constructs a JSONArray from a JSONTokener. * Construct a JSONArray from a JSONTokener.
* <p>
* This constructor reads the JSONTokener to parse a JSON array. It uses the default JSONParserConfiguration.
* *
* @param x A JSONTokener * @param x
* @throws JSONException If there is a syntax error. * A JSONTokener
* @throws JSONException
* If there is a syntax error.
*/ */
public JSONArray(JSONTokener x) throws JSONException { public JSONArray(JSONTokener x) throws JSONException {
this(x, new JSONParserConfiguration());
}
/**
* Constructs a JSONArray from a JSONTokener and a JSONParserConfiguration.
* JSONParserConfiguration contains strictMode turned off (false) by default.
*
* @param x A JSONTokener instance from which the JSONArray is constructed.
* @param jsonParserConfiguration A JSONParserConfiguration instance that controls the behavior of the parser.
* @throws JSONException If a syntax error occurs during the construction of the JSONArray.
*/
public JSONArray(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this(); this();
if (x.nextClean() != '[') { if (x.nextClean() != '[') {
throw x.syntaxError("A JSONArray text must start with '['"); throw x.syntaxError("A JSONArray text must start with '['");
} }
char nextChar = x.nextClean(); char nextChar = x.nextClean();
if (nextChar == 0) { if (nextChar == 0) {
// array is unclosed. No ']' found, instead EOF // array is unclosed. No ']' found, instead EOF
@ -113,34 +101,27 @@ public class JSONArray implements Iterable<Object> {
this.myArrayList.add(JSONObject.NULL); this.myArrayList.add(JSONObject.NULL);
} else { } else {
x.back(); x.back();
this.myArrayList.add(x.nextValue(jsonParserConfiguration)); this.myArrayList.add(x.nextValue());
} }
switch (x.nextClean()) { switch (x.nextClean()) {
case 0: 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 // array is unclosed. No ']' found, instead EOF
throw x.syntaxError("Expected a ',' or ']'"); throw x.syntaxError("Expected a ',' or ']'");
case ',': }
nextChar = x.nextClean(); if (nextChar == ']') {
if (nextChar == 0) {
// array is unclosed. No ']' found, instead EOF
throw x.syntaxError("Expected a ',' or ']'");
}
if (nextChar == ']') {
return;
}
x.back();
break;
case ']':
if (jsonParserConfiguration.isStrictMode()) {
nextChar = x.nextClean();
if (nextChar != 0) {
throw x.syntaxError("invalid character found after end of array: " + nextChar);
}
}
return; return;
default: }
throw x.syntaxError("Expected a ',' or ']'"); x.back();
break;
case ']':
return;
default:
throw x.syntaxError("Expected a ',' or ']'");
} }
} }
} }
@ -157,19 +138,7 @@ public class JSONArray implements Iterable<Object> {
* If there is a syntax error. * If there is a syntax error.
*/ */
public JSONArray(String source) throws JSONException { public JSONArray(String source) throws JSONException {
this(new JSONTokener(source), new JSONParserConfiguration()); this(new JSONTokener(source));
}
/**
* Constructs a JSONArray from a source JSON text and a JSONParserConfiguration.
*
* @param source A string that begins with <code>[</code>&nbsp;<small>(left bracket)</small> and
* ends with <code>]</code> &nbsp;<small>(right bracket)</small>.
* @param jsonParserConfiguration A JSONParserConfiguration instance that controls the behavior of the parser.
* @throws JSONException If there is a syntax error.
*/
public JSONArray(String source, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this(new JSONTokener(source), jsonParserConfiguration);
} }
/** /**
@ -398,7 +367,7 @@ public class JSONArray implements Iterable<Object> {
/** /**
* Get the enum value associated with an index. * Get the enum value associated with an index.
* *
* @param <E> * @param <E>
* Enum Type * Enum Type
* @param clazz * @param clazz
@ -586,7 +555,7 @@ public class JSONArray implements Iterable<Object> {
if (len == 0) { if (len == 0) {
return ""; return "";
} }
StringBuilder sb = new StringBuilder( StringBuilder sb = new StringBuilder(
JSONObject.valueToString(this.myArrayList.get(0))); JSONObject.valueToString(this.myArrayList.get(0)));
@ -900,7 +869,7 @@ public class JSONArray implements Iterable<Object> {
/** /**
* Get the enum value associated with a key. * Get the enum value associated with a key.
* *
* @param <E> * @param <E>
* Enum Type * Enum Type
* @param clazz * @param clazz
@ -915,7 +884,7 @@ public class JSONArray implements Iterable<Object> {
/** /**
* Get the enum value associated with a key. * Get the enum value associated with a key.
* *
* @param <E> * @param <E>
* Enum Type * Enum Type
* @param clazz * @param clazz
@ -948,8 +917,8 @@ public class JSONArray implements Iterable<Object> {
} }
/** /**
* Get the optional BigInteger value associated with an index. The * Get the optional BigInteger value associated with an index. The
* defaultValue is returned if there is no value for the index, or if 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. * value is not a number and cannot be converted to a number.
* *
* @param index * @param index
@ -964,8 +933,8 @@ public class JSONArray implements Iterable<Object> {
} }
/** /**
* Get the optional BigDecimal value associated with an index. The * Get the optional BigDecimal value associated with an index. The
* defaultValue is returned if there is no value for the index, or if 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. If the value * value is not a number and cannot be converted to a number. If the value
* is float or double, the {@link BigDecimal#BigDecimal(double)} * is float or double, the {@link BigDecimal#BigDecimal(double)}
* constructor will be used. See notes on the constructor for conversion * constructor will be used. See notes on the constructor for conversion
@ -1134,7 +1103,7 @@ public class JSONArray implements Iterable<Object> {
if (val instanceof Number){ if (val instanceof Number){
return (Number) val; return (Number) val;
} }
if (val instanceof String) { if (val instanceof String) {
try { try {
return JSONObject.stringToNumber((String) val); return JSONObject.stringToNumber((String) val);
@ -1211,7 +1180,7 @@ public class JSONArray implements Iterable<Object> {
public JSONArray put(double value) throws JSONException { public JSONArray put(double value) throws JSONException {
return this.put(Double.valueOf(value)); return this.put(Double.valueOf(value));
} }
/** /**
* Append a float value. This increases the array's length by one. * Append a float value. This increases the array's length by one.
* *
@ -1466,19 +1435,19 @@ public class JSONArray implements Iterable<Object> {
* *
* @param collection * @param collection
* A Collection. * A Collection.
* @return this. * @return this.
*/ */
public JSONArray putAll(Collection<?> collection) { public JSONArray putAll(Collection<?> collection) {
this.addAll(collection, false); this.addAll(collection, false);
return this; return this;
} }
/** /**
* Put an Iterable's elements in to the JSONArray. * Put an Iterable's elements in to the JSONArray.
* *
* @param iter * @param iter
* An Iterable. * An Iterable.
* @return this. * @return this.
*/ */
public JSONArray putAll(Iterable<?> iter) { public JSONArray putAll(Iterable<?> iter) {
this.addAll(iter, false); this.addAll(iter, false);
@ -1490,7 +1459,7 @@ public class JSONArray implements Iterable<Object> {
* *
* @param array * @param array
* A JSONArray. * A JSONArray.
* @return this. * @return this.
*/ */
public JSONArray putAll(JSONArray array) { public JSONArray putAll(JSONArray array) {
// directly copy the elements from the source array to this one // directly copy the elements from the source array to this one
@ -1505,7 +1474,7 @@ public class JSONArray implements Iterable<Object> {
* @param array * @param array
* Array. If the parameter passed is null, or not an array or Iterable, an * Array. If the parameter passed is null, or not an array or Iterable, an
* exception will be thrown. * exception will be thrown.
* @return this. * @return this.
* *
* @throws JSONException * @throws JSONException
* If not an array, JSONArray, Iterable or if an value is non-finite number. * If not an array, JSONArray, Iterable or if an value is non-finite number.
@ -1516,9 +1485,9 @@ public class JSONArray implements Iterable<Object> {
this.addAll(array, false); this.addAll(array, false);
return this; return this;
} }
/** /**
* Creates a JSONPointer using an initialization string and tries to * Creates a JSONPointer using an initialization string and tries to
* match it to an item within this JSONArray. For example, given a * match it to an item within this JSONArray. For example, given a
* JSONArray initialized with this document: * JSONArray initialized with this document:
* <pre> * <pre>
@ -1526,7 +1495,7 @@ public class JSONArray implements Iterable<Object> {
* {"b":"c"} * {"b":"c"}
* ] * ]
* </pre> * </pre>
* and this JSONPointer string: * and this JSONPointer string:
* <pre> * <pre>
* "/0/b" * "/0/b"
* </pre> * </pre>
@ -1539,9 +1508,9 @@ public class JSONArray implements Iterable<Object> {
public Object query(String jsonPointer) { public Object query(String jsonPointer) {
return query(new JSONPointer(jsonPointer)); return query(new JSONPointer(jsonPointer));
} }
/** /**
* Uses a user initialized JSONPointer and tries to * Uses a user initialized JSONPointer and tries to
* match it to an item within this JSONArray. For example, given a * match it to an item within this JSONArray. For example, given a
* JSONArray initialized with this document: * JSONArray initialized with this document:
* <pre> * <pre>
@ -1549,7 +1518,7 @@ public class JSONArray implements Iterable<Object> {
* {"b":"c"} * {"b":"c"}
* ] * ]
* </pre> * </pre>
* and this JSONPointer: * and this JSONPointer:
* <pre> * <pre>
* "/0/b" * "/0/b"
* </pre> * </pre>
@ -1562,11 +1531,11 @@ public class JSONArray implements Iterable<Object> {
public Object query(JSONPointer jsonPointer) { public Object query(JSONPointer jsonPointer) {
return jsonPointer.queryFrom(this); return jsonPointer.queryFrom(this);
} }
/** /**
* Queries and returns a value from this object using {@code jsonPointer}, or * Queries and returns a value from this object using {@code jsonPointer}, or
* returns null if the query fails due to a missing key. * returns null if the query fails due to a missing key.
* *
* @param jsonPointer the string representation of the JSON pointer * @param jsonPointer the string representation of the JSON pointer
* @return the queried value or {@code null} * @return the queried value or {@code null}
* @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax
@ -1574,11 +1543,11 @@ public class JSONArray implements Iterable<Object> {
public Object optQuery(String jsonPointer) { public Object optQuery(String jsonPointer) {
return optQuery(new JSONPointer(jsonPointer)); return optQuery(new JSONPointer(jsonPointer));
} }
/** /**
* Queries and returns a value from this object using {@code jsonPointer}, or * Queries and returns a value from this object using {@code jsonPointer}, or
* returns null if the query fails due to a missing key. * returns null if the query fails due to a missing key.
* *
* @param jsonPointer The JSON pointer * @param jsonPointer The JSON pointer
* @return the queried value or {@code null} * @return the queried value or {@code null}
* @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax
@ -1698,11 +1667,11 @@ public class JSONArray implements Iterable<Object> {
/** /**
* Make a pretty-printed JSON text of this JSONArray. * Make a pretty-printed JSON text of this JSONArray.
* *
* <p>If <pre> {@code indentFactor > 0}</pre> and the {@link JSONArray} has only * <p>If <pre> {@code indentFactor > 0}</pre> and the {@link JSONArray} has only
* one element, then the array will be output on a single line: * one element, then the array will be output on a single line:
* <pre>{@code [1]}</pre> * <pre>{@code [1]}</pre>
* *
* <p>If an array has 2 or more elements, then it will be output across * <p>If an array has 2 or more elements, then it will be output across
* multiple lines: <pre>{@code * multiple lines: <pre>{@code
* [ * [
@ -1714,7 +1683,7 @@ public class JSONArray implements Iterable<Object> {
* <p><b> * <p><b>
* Warning: This method assumes that the data structure is acyclical. * Warning: This method assumes that the data structure is acyclical.
* </b> * </b>
* *
* @param indentFactor * @param indentFactor
* The number of spaces to add to each level of indentation. * The number of spaces to add to each level of indentation.
* @return a printable, displayable, transmittable representation of the * @return a printable, displayable, transmittable representation of the
@ -1748,11 +1717,11 @@ public class JSONArray implements Iterable<Object> {
/** /**
* Write the contents of the JSONArray as JSON text to a writer. * Write the contents of the JSONArray as JSON text to a writer.
* *
* <p>If <pre>{@code indentFactor > 0}</pre> and the {@link JSONArray} has only * <p>If <pre>{@code indentFactor > 0}</pre> and the {@link JSONArray} has only
* one element, then the array will be output on a single line: * one element, then the array will be output on a single line:
* <pre>{@code [1]}</pre> * <pre>{@code [1]}</pre>
* *
* <p>If an array has 2 or more elements, then it will be output across * <p>If an array has 2 or more elements, then it will be output across
* multiple lines: <pre>{@code * multiple lines: <pre>{@code
* [ * [
@ -1978,7 +1947,7 @@ public class JSONArray implements Iterable<Object> {
"JSONArray initial value should be a string or collection or array."); "JSONArray initial value should be a string or collection or array.");
} }
} }
/** /**
* Create a new JSONException in a common format for incorrect conversions. * Create a new JSONException in a common format for incorrect conversions.
* @param idx index of the item * @param idx index of the item

View File

@ -220,12 +220,12 @@ public class JSONObject {
for (;;) { for (;;) {
c = x.nextClean(); c = x.nextClean();
switch (c) { switch (c) {
case 0: case 0:
throw x.syntaxError("A JSONObject text must end with '}'"); throw x.syntaxError("A JSONObject text must end with '}'");
case '}': case '}':
return; return;
default: default:
key = x.nextSimpleValue(c, jsonParserConfiguration).toString(); key = x.nextSimpleValue(c).toString();
} }
// The key is followed by ':'. // The key is followed by ':'.
@ -244,7 +244,7 @@ public class JSONObject {
throw x.syntaxError("Duplicate key \"" + key + "\""); throw x.syntaxError("Duplicate key \"" + key + "\"");
} }
Object value = x.nextValue(jsonParserConfiguration); Object value = x.nextValue();
// Only add value if non-null // Only add value if non-null
if (value != null) { if (value != null) {
this.put(key, value); this.put(key, value);
@ -1247,7 +1247,7 @@ public class JSONObject {
static BigDecimal objectToBigDecimal(Object val, BigDecimal defaultValue) { static BigDecimal objectToBigDecimal(Object val, BigDecimal defaultValue) {
return objectToBigDecimal(val, defaultValue, true); return objectToBigDecimal(val, defaultValue, true);
} }
/** /**
* @param val value to convert * @param val value to convert
* @param defaultValue default value to return is the conversion doesn't work or is null. * @param defaultValue default value to return is the conversion doesn't work or is null.

View File

@ -4,25 +4,11 @@ package org.json;
* Configuration object for the JSON parser. The configuration is immutable. * Configuration object for the JSON parser. The configuration is immutable.
*/ */
public class JSONParserConfiguration extends ParserConfiguration { public class JSONParserConfiguration extends ParserConfiguration {
/** Original Configuration of the JSON Parser. */
public static final JSONParserConfiguration ORIGINAL = new JSONParserConfiguration();
/** Original configuration of the JSON Parser except that values are kept as strings. */
public static final JSONParserConfiguration KEEP_STRINGS = new JSONParserConfiguration().withKeepStrings(true);
/** /**
* Used to indicate whether to overwrite duplicate key or not. * Used to indicate whether to overwrite duplicate key or not.
*/ */
private boolean overwriteDuplicateKey; private boolean overwriteDuplicateKey;
/**
* This flag, when set to true, instructs the parser to throw a JSONException if it encounters an invalid character
* immediately following the final ']' character in the input. This is useful for ensuring strict adherence to the
* JSON syntax, as any characters after the final closing bracket of a JSON array are considered invalid.
*/
private boolean strictMode;
/** /**
* Configuration with the default values. * Configuration with the default values.
*/ */
@ -72,24 +58,6 @@ public class JSONParserConfiguration extends ParserConfiguration {
return clone; return clone;
} }
/**
* Sets the strict mode configuration for the JSON parser.
* <p>
* When strict mode is enabled, the parser will throw a JSONException if it encounters an invalid character
* immediately following the final ']' character in the input. This is useful for ensuring strict adherence to the
* JSON syntax, as any characters after the final closing bracket of a JSON array are considered invalid.
*
* @param mode a boolean value indicating whether strict mode should be enabled or not
* @return a new JSONParserConfiguration instance with the updated strict mode setting
*/
public JSONParserConfiguration withStrictMode(final boolean mode) {
JSONParserConfiguration clone = this.clone();
clone.strictMode = mode;
return clone;
}
/** /**
* The parser's behavior when meeting duplicate keys, controls whether the parser should * The parser's behavior when meeting duplicate keys, controls whether the parser should
* overwrite duplicate keys or not. * overwrite duplicate keys or not.
@ -99,18 +67,4 @@ public class JSONParserConfiguration extends ParserConfiguration {
public boolean isOverwriteDuplicateKey() { public boolean isOverwriteDuplicateKey() {
return this.overwriteDuplicateKey; return this.overwriteDuplicateKey;
} }
/**
* Retrieves the current strict mode setting of the JSON parser.
* <p>
* Strict mode, when enabled, instructs the parser to throw a JSONException if it encounters an invalid character
* immediately following the final ']' character in the input. This ensures strict adherence to the JSON syntax, as
* any characters after the final closing bracket of a JSON array are considered invalid.
*
* @return the current strict mode setting. True if strict mode is enabled, false otherwise.
*/
public boolean isStrictMode() {
return this.strictMode;
}
} }

View File

@ -284,14 +284,13 @@ public class JSONTokener {
* Backslash processing is done. The formal JSON format does not * Backslash processing is done. The formal JSON format does not
* allow strings in single quotes, but an implementation is allowed to * allow strings in single quotes, but an implementation is allowed to
* accept them. * accept them.
* If strictMode is true, this implementation will not accept unbalanced quotes (e.g will not accept <code>"test'</code>)
* @param quote The quoting character, either * @param quote The quoting character, either
* <code>"</code>&nbsp;<small>(double quote)</small> or * <code>"</code>&nbsp;<small>(double quote)</small> or
* <code>'</code>&nbsp;<small>(single quote)</small>. * <code>'</code>&nbsp;<small>(single quote)</small>.
* @return A String. * @return A String.
* @throws JSONException Unterminated string or unbalanced quotes if strictMode == true. * @throws JSONException Unterminated string.
*/ */
public String nextString(char quote, boolean strictMode) throws JSONException { public String nextString(char quote) throws JSONException {
char c; char c;
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (;;) { for (;;) {
@ -339,21 +338,11 @@ public class JSONTokener {
throw this.syntaxError("Illegal escape. Escape sequence \\" + c + " is not valid."); throw this.syntaxError("Illegal escape. Escape sequence \\" + c + " is not valid.");
} }
break; break;
default: default:
if (strictMode && c == '\"' && quote != c) { if (c == quote) {
throw this.syntaxError(String.format( return sb.toString();
"Field contains unbalanced quotes. Starts with %s but ends with double quote.", quote)); }
} sb.append(c);
if (strictMode && c == '\'' && quote != c) {
throw this.syntaxError(String.format(
"Field contains unbalanced quotes. Starts with %s but ends with single quote.", quote));
}
if (c == quote) {
return sb.toString();
}
sb.append(c);
} }
} }
} }
@ -408,103 +397,51 @@ public class JSONTokener {
/** /**
* Get the next value. The value can be a Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the * Get the next value. The value can be a Boolean, Double, Integer,
* JSONObject.NULL object. * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
* @throws JSONException If syntax error.
* *
* @return An object. * @return An object.
* @throws JSONException If syntax error.
*/ */
public Object nextValue() throws JSONException { public Object nextValue() throws JSONException {
return nextValue(new JSONParserConfiguration());
}
/**
* Get the next value. The value can be a Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the
* JSONObject.NULL object. The strictMode parameter controls the behavior of the method when parsing the value.
*
* @param jsonParserConfiguration which carries options such as strictMode, these methods will
* strictly adhere to the JSON syntax, throwing a JSONException for any deviations.
* @return An object.
* @throws JSONException If syntax error.
*/
public Object nextValue(JSONParserConfiguration jsonParserConfiguration) throws JSONException {
char c = this.nextClean(); char c = this.nextClean();
switch (c) { switch (c) {
case '{': case '{':
this.back(); this.back();
try { try {
return new JSONObject(this, jsonParserConfiguration); return new JSONObject(this);
} catch (StackOverflowError e) { } catch (StackOverflowError e) {
throw new JSONException("JSON Array or Object depth too large to process.", e); throw new JSONException("JSON Array or Object depth too large to process.", e);
} }
case '[': case '[':
this.back(); this.back();
try { try {
return new JSONArray(this); return new JSONArray(this);
} catch (StackOverflowError e) { } catch (StackOverflowError e) {
throw new JSONException("JSON Array or Object depth too large to process.", e); throw new JSONException("JSON Array or Object depth too large to process.", e);
} }
default:
return nextSimpleValue(c, jsonParserConfiguration);
} }
return nextSimpleValue(c);
} }
/** Object nextSimpleValue(char c) {
* This method is used to get a JSONObject from the JSONTokener. The strictMode parameter controls the behavior of String string;
* the method when parsing the JSONObject.
*
* @param jsonParserConfiguration which carries options such as strictMode, these methods will
* strictly adhere to the JSON syntax, throwing a JSONException for any deviations.
* deviations.
* @return A JSONObject which is the next value in the JSONTokener.
* @throws JSONException If the JSONObject or JSONArray depth is too large to process.
*/
private JSONObject getJsonObject(JSONParserConfiguration jsonParserConfiguration) {
try {
return new JSONObject(this, jsonParserConfiguration);
} catch (StackOverflowError e) {
throw new JSONException("JSON Array or Object depth too large to process.", e);
}
}
/** switch (c) {
* This method is used to get a JSONArray from the JSONTokener. case '"':
* case '\'':
* @return A JSONArray which is the next value in the JSONTokener. return this.nextString(c);
* @throws JSONException If the JSONArray depth is too large to process.
*/
private JSONArray getJsonArray() {
try {
return new JSONArray(this);
} catch (StackOverflowError e) {
throw new JSONException("JSON Array or Object depth too large to process.", e);
}
}
Object nextSimpleValue(char c, JSONParserConfiguration jsonParserConfiguration) {
boolean strictMode = jsonParserConfiguration.isStrictMode();
if(strictMode && c == '\''){
throw this.syntaxError("Single quote wrap not allowed in strict mode");
} }
if (c == '"' || c == '\'') { /*
return this.nextString(c, strictMode); * 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.
*/
return parsedUnquotedText(c, strictMode);
}
/**
* Parses unquoted text from the JSON input. This could be the values true, false, or null, or it can be a number.
* Non-standard forms are also accepted. Characters are accumulated until the end of the text or a formatting
* character is reached.
*
* @param c The starting character.
* @return The parsed object.
* @throws JSONException If the parsed string is empty.
*/
private Object parsedUnquotedText(char c, boolean strictMode) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
sb.append(c); sb.append(c);
@ -514,37 +451,13 @@ public class JSONTokener {
this.back(); this.back();
} }
String string = sb.toString().trim(); string = sb.toString().trim();
if ("".equals(string)) {
if (strictMode) {
boolean isBooleanOrNumeric = checkIfValueIsBooleanOrNumeric(string);
if (isBooleanOrNumeric) {
return string;
}
throw new JSONException(String.format("Value is not surrounded by quotes: %s", string));
}
if (string.isEmpty()) {
throw this.syntaxError("Missing value"); throw this.syntaxError("Missing value");
} }
return JSONObject.stringToValue(string); return JSONObject.stringToValue(string);
} }
private boolean checkIfValueIsBooleanOrNumeric(Object valueToValidate) {
String stringToValidate = valueToValidate.toString();
if (stringToValidate.equals("true") || stringToValidate.equals("false")) {
return true;
}
try {
Double.parseDouble(stringToValidate);
return true;
} catch (NumberFormatException e) {
return false;
}
}
/** /**
* Skip characters until the next character is the requested character. * Skip characters until the next character is the requested character.

View File

@ -1,24 +1,14 @@
package org.json.junit; package org.json.junit;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.json.JSONParserConfiguration; import org.json.JSONParserConfiguration;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class JSONParserConfigurationTest { public class JSONParserConfigurationTest {
private static final String TEST_SOURCE = "{\"key\": \"value1\", \"key\": \"value2\"}"; private static final String TEST_SOURCE = "{\"key\": \"value1\", \"key\": \"value2\"}";
@Test(expected = JSONException.class) @Test(expected = JSONException.class)
@ -29,162 +19,16 @@ public class JSONParserConfigurationTest {
@Test @Test
public void testOverwrite() { public void testOverwrite() {
JSONObject jsonObject = new JSONObject(TEST_SOURCE, JSONObject jsonObject = new JSONObject(TEST_SOURCE,
new JSONParserConfiguration().withOverwriteDuplicateKey(true)); new JSONParserConfiguration().withOverwriteDuplicateKey(true));
assertEquals("duplicate key should be overwritten", "value2", jsonObject.getString("key")); assertEquals("duplicate key should be overwritten", "value2", jsonObject.getString("key"));
} }
@Test
public void givenInvalidInputArrays_testStrictModeTrue_shouldThrowJsonException() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.withStrictMode(true);
List<String> strictModeInputTestCases = getNonCompliantJSONList();
strictModeInputTestCases.forEach(
testCase -> assertThrows("expected non-compliant array but got instead: " + testCase, JSONException.class,
() -> new JSONArray(testCase, jsonParserConfiguration)));
}
@Test
public void givenCompliantJSONArrayFile_testStrictModeTrue_shouldNotThrowAnyException() throws IOException {
try (Stream<String> lines = Files.lines(Paths.get("src/test/resources/compliantJsonArray.json"))) {
String compliantJsonArrayAsString = lines.collect(Collectors.joining());
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.withStrictMode(true);
new JSONArray(compliantJsonArrayAsString, jsonParserConfiguration);
}
}
@Test
public void givenInvalidInputArrays_testStrictModeFalse_shouldNotThrowAnyException() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.withStrictMode(false);
List<String> strictModeInputTestCases = getNonCompliantJSONList();
strictModeInputTestCases.forEach(testCase -> new JSONArray(testCase, jsonParserConfiguration));
}
@Test
public void givenInvalidInputArray_testStrictModeTrue_shouldThrowInvalidCharacterErrorMessage() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.withStrictMode(true);
String testCase = "[1,2];[3,4]";
JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
JSONException.class, () -> new JSONArray(testCase, jsonParserConfiguration));
assertEquals("invalid character found after end of array: ; at 6 [character 7 line 1]", je.getMessage());
}
@Test
public void givenInvalidInputArrayWithNumericStrings_testStrictModeTrue_shouldThrowInvalidCharacterErrorMessage() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.withStrictMode(true);
String testCase = "[\"1\",\"2\"];[3,4]";
JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
JSONException.class, () -> new JSONArray(testCase, jsonParserConfiguration));
assertEquals("invalid character found after end of array: ; at 10 [character 11 line 1]", je.getMessage());
}
@Test
public void givenInvalidInputArray_testStrictModeTrue_shouldThrowValueNotSurroundedByQuotesErrorMessage() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.withStrictMode(true);
String testCase = "[{\"test\": implied}]";
JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
JSONException.class, () -> new JSONArray(testCase, jsonParserConfiguration));
assertEquals("Value is not surrounded by quotes: implied", je.getMessage());
}
@Test
public void givenInvalidInputArray_testStrictModeFalse_shouldNotThrowAnyException() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.withStrictMode(false);
String testCase = "[{\"test\": implied}]";
new JSONArray(testCase, jsonParserConfiguration);
}
@Test
public void givenNonCompliantQuotes_testStrictModeTrue_shouldThrowJsonExceptionWithConcreteErrorDescription() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.withStrictMode(true);
String testCaseOne = "[\"abc', \"test\"]";
String testCaseTwo = "['abc\", \"test\"]";
String testCaseThree = "['abc']";
String testCaseFour = "[{'testField': \"testValue\"}]";
JSONException jeOne = assertThrows(JSONException.class,
() -> new JSONArray(testCaseOne, jsonParserConfiguration));
JSONException jeTwo = assertThrows(JSONException.class,
() -> new JSONArray(testCaseTwo, jsonParserConfiguration));
JSONException jeThree = assertThrows(JSONException.class,
() -> new JSONArray(testCaseThree, jsonParserConfiguration));
JSONException jeFour = assertThrows(JSONException.class,
() -> new JSONArray(testCaseFour, jsonParserConfiguration));
assertEquals(
"Field contains unbalanced quotes. Starts with \" but ends with single quote. at 6 [character 7 line 1]",
jeOne.getMessage());
assertEquals(
"Single quote wrap not allowed in strict mode at 2 [character 3 line 1]",
jeTwo.getMessage());
assertEquals(
"Single quote wrap not allowed in strict mode at 2 [character 3 line 1]",
jeThree.getMessage());
assertEquals(
"Single quote wrap not allowed in strict mode at 3 [character 4 line 1]",
jeFour.getMessage());
}
@Test
public void givenUnbalancedQuotes_testStrictModeFalse_shouldThrowJsonException() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.withStrictMode(false);
String testCaseOne = "[\"abc', \"test\"]";
String testCaseTwo = "['abc\", \"test\"]";
JSONException jeOne = assertThrows(JSONException.class,
() -> new JSONArray(testCaseOne, jsonParserConfiguration));
JSONException jeTwo = assertThrows(JSONException.class,
() -> new JSONArray(testCaseTwo, jsonParserConfiguration));
assertEquals("Expected a ',' or ']' at 10 [character 11 line 1]", jeOne.getMessage());
assertEquals("Unterminated string. Character with int code 0 is not allowed within a quoted string. at 15 [character 16 line 1]", jeTwo.getMessage());
}
@Test
public void givenInvalidInputArray_testStrictModeTrue_shouldThrowKeyNotSurroundedByQuotesErrorMessage() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.withStrictMode(true);
String testCase = "[{test: implied}]";
JSONException je = assertThrows("expected non-compliant array but got instead: " + testCase,
JSONException.class, () -> new JSONArray(testCase, jsonParserConfiguration));
assertEquals(String.format("Value is not surrounded by quotes: %s", "test"), je.getMessage());
}
@Test @Test
public void verifyDuplicateKeyThenMaxDepth() { public void verifyDuplicateKeyThenMaxDepth() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration() JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.withOverwriteDuplicateKey(true) .withOverwriteDuplicateKey(true)
.withMaxNestingDepth(42); .withMaxNestingDepth(42);
assertEquals(42, jsonParserConfiguration.getMaxNestingDepth()); assertEquals(42, jsonParserConfiguration.getMaxNestingDepth());
assertTrue(jsonParserConfiguration.isOverwriteDuplicateKey()); assertTrue(jsonParserConfiguration.isOverwriteDuplicateKey());
@ -193,28 +37,10 @@ public class JSONParserConfigurationTest {
@Test @Test
public void verifyMaxDepthThenDuplicateKey() { public void verifyMaxDepthThenDuplicateKey() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration() JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.withMaxNestingDepth(42) .withMaxNestingDepth(42)
.withOverwriteDuplicateKey(true); .withOverwriteDuplicateKey(true);
assertTrue(jsonParserConfiguration.isOverwriteDuplicateKey()); assertTrue(jsonParserConfiguration.isOverwriteDuplicateKey());
assertEquals(42, jsonParserConfiguration.getMaxNestingDepth()); assertEquals(42, jsonParserConfiguration.getMaxNestingDepth());
} }
/**
* This method contains short but focused use-case samples and is exclusively used to test strictMode unit tests in
* this class.
*
* @return List with JSON strings.
*/
private List<String> getNonCompliantJSONList() {
return Arrays.asList(
"[1,2];[3,4]",
"[test]",
"[{'testSingleQuote': 'testSingleQuote'}]",
"[1, 2,3]:[4,5]",
"[{test: implied}]",
"[{\"test\": implied}]",
"[{\"number\":\"7990154836330\",\"color\":'c'},{\"number\":8784148854580,\"color\":RosyBrown},{\"number\":\"5875770107113\",\"color\":\"DarkSeaGreen\"}]",
"[{test: \"implied\"}]");
}
} }

View File

@ -1,317 +0,0 @@
[
{
"_id": "6606c27d2ab4a0102d49420a",
"index": 0,
"guid": "441331fb-84d1-4873-a649-3814621a0370",
"isActive": true,
"balance": "$2,691.63",
"picture": "http://example.abc/32x32",
"age": 26,
"eyeColor": "blue",
"name": "abc",
"gender": "female",
"company": "example",
"email": "abc@def.com",
"phone": "+1 (123) 456-7890",
"address": "123 Main St",
"about": "Laborum magna tempor officia irure cillum nulla incididunt Lorem dolor veniam elit cupidatat amet. Veniam veniam exercitation nulla consectetur officia esse ex sunt nulla nisi ea cillum nisi reprehenderit. Qui aliquip reprehenderit aliqua aliquip aliquip anim sit magna nostrud dolore veniam velit elit aliquip.\r\n",
"registered": "2016-07-22T03:18:11 -01:00",
"latitude": -21.544934,
"longitude": 72.765495,
"tags": [
"consectetur",
"minim",
"sunt",
"in",
"ut",
"velit",
"anim"
],
"friends": [
{
"id": 0,
"name": "abc def"
},
{
"id": 1,
"name": "ghi jkl"
},
{
"id": 2,
"name": "mno pqr"
}
],
"greeting": "Hello, abc! You have 10 unread messages.",
"favoriteFruit": "banana"
},
{
"_id": "6606c27d0a45df5121fb765f",
"index": 1,
"guid": "fd774715-de85-44b9-b498-c214d8f68d9f",
"isActive": true,
"balance": "$2,713.96",
"picture": "http://placehold.it/32x32",
"age": 27,
"eyeColor": "green",
"name": "def",
"gender": "female",
"company": "sample",
"email": "def@abc.com",
"phone": "+1 (123) 456-78910",
"address": "1234 Main St",
"about": "Ea id cupidatat eiusmod culpa. Nulla consequat esse elit enim et pariatur eiusmod ipsum. Consequat eu non reprehenderit in.\r\n",
"registered": "2015-04-06T07:54:22 -01:00",
"latitude": 83.512347,
"longitude": -9.368739,
"tags": [
"excepteur",
"non",
"nostrud",
"laboris",
"laboris",
"qui",
"aute"
],
"friends": [
{
"id": 0,
"name": "sample example"
},
{
"id": 1,
"name": "test name"
},
{
"id": 2,
"name": "aaa aaaa"
}
],
"greeting": "Hello, test! You have 7 unread messages.",
"favoriteFruit": "apple"
},
{
"_id": "6606c27dfb3a0e4e7e7183d3",
"index": 2,
"guid": "688b0c36-98e0-4ee7-86b8-863638d79b5f",
"isActive": false,
"balance": "$3,514.35",
"picture": "http://placehold.it/32x32",
"age": 32,
"eyeColor": "green",
"name": "test",
"gender": "female",
"company": "test",
"email": "test@test.com",
"phone": "+1 (123) 456-7890",
"address": "123 Main St",
"about": "Mollit officia adipisicing ex nisi non Lorem sunt quis est. Irure exercitation duis ipsum qui ullamco eu ea commodo occaecat minim proident. Incididunt nostrud ex cupidatat eiusmod mollit anim irure culpa. Labore voluptate voluptate labore nisi sit eu. Dolor sit proident velit dolor deserunt labore sit ipsum incididunt eiusmod reprehenderit voluptate. Duis anim velit officia laboris consequat officia dolor sint dolor nisi ex.\r\n",
"registered": "2021-11-02T12:50:05 -00:00",
"latitude": -82.969939,
"longitude": 86.415645,
"tags": [
"aliquip",
"et",
"est",
"nulla",
"nulla",
"tempor",
"adipisicing"
],
"friends": [
{
"id": 0,
"name": "test"
},
{
"id": 1,
"name": "sample"
},
{
"id": 2,
"name": "example"
}
],
"greeting": "Hello, test! You have 1 unread messages.",
"favoriteFruit": "strawberry"
},
{
"_id": "6606c27d204bc2327fc9ba23",
"index": 3,
"guid": "be970cba-306e-4cbd-be08-c265a43a61fa",
"isActive": true,
"balance": "$3,691.63",
"picture": "http://placehold.it/32x32",
"age": 35,
"eyeColor": "brown",
"name": "another test",
"gender": "male",
"company": "TEST",
"email": "anothertest@anothertest.com",
"phone": "+1 (321) 987-6543",
"address": "123 Example Main St",
"about": "Do proident consectetur minim quis. In adipisicing culpa Lorem fugiat cillum exercitation velit velit. Non voluptate laboris deserunt veniam et sint consectetur irure aliqua quis eiusmod consectetur elit id. Ex sint do anim Lorem excepteur eu nulla.\r\n",
"registered": "2020-06-25T04:55:25 -01:00",
"latitude": 63.614955,
"longitude": -109.299405,
"tags": [
"irure",
"esse",
"non",
"mollit",
"laborum",
"adipisicing",
"ad"
],
"friends": [
{
"id": 0,
"name": "test"
},
{
"id": 1,
"name": "sample"
},
{
"id": 2,
"name": "example"
}
],
"greeting": "Hello, another test! You have 5 unread messages.",
"favoriteFruit": "apple"
},
{
"_id": "6606c27df63eb5f390cb9989",
"index": 4,
"guid": "2c3e5115-758d-468e-99c5-c9afa26e1f9f",
"isActive": true,
"balance": "$1,047.20",
"picture": "http://test.it/32x32",
"age": 30,
"eyeColor": "green",
"name": "Test Name",
"gender": "female",
"company": "test",
"email": "testname@testname.com",
"phone": "+1 (999) 999-9999",
"address": "999 Test Main St",
"about": "Voluptate exercitation tempor consectetur velit magna ea occaecat cupidatat consectetur anim aute. Aliquip est aute ipsum laboris non irure qui consectetur tempor quis do ea Lorem. Cupidatat exercitation ad culpa aliqua amet commodo mollit reprehenderit exercitation adipisicing amet et laborum pariatur.\r\n",
"registered": "2023-01-19T02:43:18 -00:00",
"latitude": 14.15208,
"longitude": 170.411535,
"tags": [
"dolor",
"qui",
"cupidatat",
"aliqua",
"laboris",
"reprehenderit",
"sint"
],
"friends": [
{
"id": 0,
"name": "test"
},
{
"id": 1,
"name": "sample"
},
{
"id": 2,
"name": "example"
}
],
"greeting": "Hello, test! You have 6 unread messages.",
"favoriteFruit": "apple"
},
{
"_id": "6606c27d01d19fa29853d59c",
"index": 5,
"guid": "816cda74-5d4b-498f-9724-20f340d5f5bf",
"isActive": false,
"balance": "$2,628.74",
"picture": "http://testing.it/32x32",
"age": 28,
"eyeColor": "green",
"name": "Testing",
"gender": "female",
"company": "test",
"email": "testing@testing.com",
"phone": "+1 (888) 888-8888",
"address": "123 Main St",
"about": "Cupidatat non ut nulla qui excepteur in minim non et nulla fugiat. Dolor quis laborum occaecat veniam dolor ullamco deserunt amet veniam dolor quis proident tempor laboris. In cillum duis ut quis. Aliqua cupidatat magna proident velit tempor veniam et consequat laborum ex dolore qui. Incididunt deserunt magna minim Lorem consectetur.\r\n",
"registered": "2017-10-14T11:14:08 -01:00",
"latitude": -5.345728,
"longitude": -9.706491,
"tags": [
"officia",
"velit",
"laboris",
"qui",
"cupidatat",
"cupidatat",
"ad"
],
"friends": [
{
"id": 0,
"name": "test"
},
{
"id": 1,
"name": "sample"
},
{
"id": 2,
"name": "example"
}
],
"greeting": "Hello, testing! You have 2 unread messages.",
"favoriteFruit": "strawberry"
},
{
"_id": "6606c27d803003cede1d6deb",
"index": 6,
"guid": "4ee550bc-0920-4104-b3ce-ebf9db6a803f",
"isActive": true,
"balance": "$1,709.31",
"picture": "http://sample.it/32x32",
"age": 31,
"eyeColor": "blue",
"name": "Sample Name",
"gender": "female",
"company": "Sample",
"email": "sample@sample.com",
"phone": "+1 (777) 777-7777",
"address": "123 Main St",
"about": "Lorem ex proident ipsum ullamco velit sit nisi eiusmod cillum. Id tempor irure culpa nisi sit non qui veniam non ut. Aliquip reprehenderit excepteur mollit quis excepteur ex sit. Quis do eu veniam do ullamco occaecat eu cupidatat nisi laborum tempor minim fugiat pariatur. Ex in nulla ex velit.\r\n",
"registered": "2019-04-08T03:54:36 -01:00",
"latitude": -70.660321,
"longitude": 71.547525,
"tags": [
"consequat",
"veniam",
"pariatur",
"aliqua",
"cillum",
"eu",
"officia"
],
"friends": [
{
"id": 0,
"name": "Test"
},
{
"id": 1,
"name": "Sample"
},
{
"id": 2,
"name": "Example"
}
],
"greeting": "Hello, Sample! You have 6 unread messages.",
"favoriteFruit": "apple"
}
]