Skip to content

Commit

Permalink
feat(stleary#871-strictMode): add allowSingleQuote option, add enhanc…
Browse files Browse the repository at this point in the history
…ements and simplification
  • Loading branch information
rikkarth committed Mar 30, 2024
1 parent d2cb38d commit c0918c2
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 30 deletions.
6 changes: 1 addition & 5 deletions src/main/java/org/json/JSONArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,7 @@ public JSONArray(JSONTokener x, JSONParserConfiguration jsonParserConfiguration)
this.myArrayList.add(JSONObject.NULL);
} else {
x.back();
if (jsonParserConfiguration.isStrictMode()) {
this.myArrayList.add(x.nextValue(true));
} else {
this.myArrayList.add(x.nextValue());
}
this.myArrayList.add(x.nextValue(jsonParserConfiguration));
}
switch (x.nextClean()) {
case 0:
Expand Down
10 changes: 2 additions & 8 deletions src/main/java/org/json/JSONObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration
case '}':
return;
default:
key = x.nextSimpleValue(c, jsonParserConfiguration.isStrictMode()).toString();
key = x.nextSimpleValue(c, jsonParserConfiguration).toString();
}

// The key is followed by ':'.
Expand All @@ -244,7 +244,7 @@ public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration
throw x.syntaxError("Duplicate key \"" + key + "\"");
}

Object value = getValue(x, jsonParserConfiguration.isStrictMode());
Object value = x.nextValue(jsonParserConfiguration);
// Only add value if non-null
if (value != null) {
this.put(key, value);
Expand Down Expand Up @@ -272,12 +272,6 @@ public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration
}
}

private Object getValue(JSONTokener x, boolean strictMode) {
if (strictMode) {
return x.nextValue(true);
}
return x.nextValue();
}
/**
* Construct a JSONObject from a Map.
*
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/org/json/JSONParserConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public class JSONParserConfiguration extends ParserConfiguration {
*/
private boolean strictMode;

/**
* Allows Single Quotes when strictMode is true. Has no effect if strictMode is false.
*/
private boolean allowSingleQuotes;

/**
* Configuration with the default values.
*/
Expand Down Expand Up @@ -92,6 +97,24 @@ public JSONParserConfiguration withStrictMode(final boolean mode) {
return clone;
}

/**
* Allows single quotes mode configuration for JSON parser when strictMode is on.
* <p>
* If this option is set to true when strict Mode is enabled, the parser will allow single quoted fields.
* <p>
* This option is false by default.
*
* @param mode a boolean value indicating whether single quotes should be allowed or not
* @return a new JSONParserConfiguration instance with the updated strict mode setting
*/
public JSONParserConfiguration allowSingleQuotes(final boolean mode) {
JSONParserConfiguration clone = this.clone();
clone.strictMode = this.strictMode;
clone.allowSingleQuotes = mode;

return clone;
}

/**
* The parser's behavior when meeting duplicate keys, controls whether the parser should
* overwrite duplicate keys or not.
Expand All @@ -115,4 +138,14 @@ public boolean isOverwriteDuplicateKey() {
public boolean isStrictMode() {
return this.strictMode;
}

/**
* Retrieves the allow single quotes option.
* <p>
* Allow Single Quotes, when enabled during strict mode, instructs the parser to allow single quoted JSON fields.
* The parser will not throw a JSONException if compliant single quoted fields are found in the JSON structure.
*
* @return the current allow single quotes setting.
*/
public boolean isAllowSingleQuotes() {return this.allowSingleQuotes;}
}
34 changes: 19 additions & 15 deletions src/main/java/org/json/JSONTokener.java
Original file line number Diff line number Diff line change
Expand Up @@ -412,48 +412,45 @@ public String nextTo(String delimiters) throws JSONException {
* @throws JSONException If syntax error.
*/
public Object nextValue() throws JSONException {
return nextValue(false);
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 strictMode If true, the method will strictly adhere to the JSON syntax, throwing a JSONException for any
* deviations.
* @param jsonParserConfiguration which carries options such as strictMode and allowSingleQuotes, 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(boolean strictMode) throws JSONException {
public Object nextValue(JSONParserConfiguration jsonParserConfiguration) throws JSONException {
char c = this.nextClean();
switch (c) {
case '{':
this.back();
return getJsonObject(strictMode);
return getJsonObject(jsonParserConfiguration);
case '[':
this.back();
return getJsonArray();
default:
return nextSimpleValue(c, strictMode);
return nextSimpleValue(c, jsonParserConfiguration);
}
}

/**
* This method is used to get a JSONObject from the JSONTokener. The strictMode parameter controls the behavior of
* the method when parsing the JSONObject.
*
* @param strictMode If true, the method will strictly adhere to the JSON syntax, throwing a JSONException for any
* deviations.
* @param jsonParserConfiguration which carries options such as strictMode and allowSingleQuotes, 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(boolean strictMode) {
private JSONObject getJsonObject(JSONParserConfiguration jsonParserConfiguration) {
try {
if (strictMode) {
return new JSONObject(this, new JSONParserConfiguration().withStrictMode(true));
}

return new JSONObject(this);
return new JSONObject(this, jsonParserConfiguration);
} catch (StackOverflowError e) {
throw new JSONException("JSON Array or Object depth too large to process.", e);
}
Expand All @@ -473,7 +470,14 @@ private JSONArray getJsonArray() {
}
}

Object nextSimpleValue(char c, boolean strictMode) {
Object nextSimpleValue(char c, JSONParserConfiguration jsonParserConfiguration) {
boolean strictMode = jsonParserConfiguration.isStrictMode();
boolean allowSingleQuotes = jsonParserConfiguration.isAllowSingleQuotes();

if(strictMode && !allowSingleQuotes && c == '\''){
throw this.syntaxError("Single quote wrap not allowed in strict mode");
}

if (c == '"' || c == '\'') {
return this.nextString(c, strictMode);
}
Expand Down
5 changes: 3 additions & 2 deletions src/test/java/org/json/junit/JSONParserConfigurationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,9 @@ public void givenInvalidInputArray_testStrictModeFalse_shouldNotThrowAnyExceptio
}

@Test
public void givenUnbalancedQuotes_testStrictModeTrue_shouldThrowJsonExceptionWtihConcreteErrorDescription() {
public void givenUnbalancedQuotes_testStrictModeTrueAndAllowSingleQuotes_shouldThrowJsonExceptionWtihConcreteErrorDescription() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.withStrictMode(true);
.withStrictMode(true).allowSingleQuotes(true);

String testCaseOne = "[\"abc', \"test\"]";
String testCaseTwo = "['abc\", \"test\"]";
Expand Down Expand Up @@ -198,6 +198,7 @@ private List<String> getNonCompliantJSONList() {
return Arrays.asList(
"[1,2];[3,4]",
"[test]",
"[{'testSingleQuote': 'testSingleQuote'}]",
"[1, 2,3]:[4,5]",
"[{test: implied}]",
"[{\"test\": implied}]",
Expand Down

0 comments on commit c0918c2

Please sign in to comment.