feat(#871-strictMode): add allowSingleQuote option, add enhancements and simplification

This commit is contained in:
rikkarth 2024-03-30 11:06:30 +00:00
parent d2cb38dba7
commit c0918c2428
No known key found for this signature in database
GPG Key ID: 11E5F28B0AED6AC7
5 changed files with 58 additions and 30 deletions

View File

@ -113,11 +113,7 @@ public class JSONArray implements Iterable<Object> {
this.myArrayList.add(JSONObject.NULL); this.myArrayList.add(JSONObject.NULL);
} else { } else {
x.back(); x.back();
if (jsonParserConfiguration.isStrictMode()) { this.myArrayList.add(x.nextValue(jsonParserConfiguration));
this.myArrayList.add(x.nextValue(true));
} else {
this.myArrayList.add(x.nextValue());
}
} }
switch (x.nextClean()) { switch (x.nextClean()) {
case 0: case 0:

View File

@ -225,7 +225,7 @@ public class JSONObject {
case '}': case '}':
return; return;
default: default:
key = x.nextSimpleValue(c, jsonParserConfiguration.isStrictMode()).toString(); key = x.nextSimpleValue(c, jsonParserConfiguration).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 = getValue(x, jsonParserConfiguration.isStrictMode()); Object value = x.nextValue(jsonParserConfiguration);
// 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);
@ -272,12 +272,6 @@ public class JSONObject {
} }
} }
private Object getValue(JSONTokener x, boolean strictMode) {
if (strictMode) {
return x.nextValue(true);
}
return x.nextValue();
}
/** /**
* Construct a JSONObject from a Map. * Construct a JSONObject from a Map.
* *

View File

@ -25,6 +25,11 @@ public class JSONParserConfiguration extends ParserConfiguration {
*/ */
private boolean strictMode; 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. * Configuration with the default values.
*/ */
@ -92,6 +97,24 @@ public class JSONParserConfiguration extends ParserConfiguration {
return clone; 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 * The parser's behavior when meeting duplicate keys, controls whether the parser should
* overwrite duplicate keys or not. * overwrite duplicate keys or not.
@ -115,4 +138,14 @@ public class JSONParserConfiguration extends ParserConfiguration {
public boolean isStrictMode() { public boolean isStrictMode() {
return this.strictMode; 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;}
} }

View File

@ -412,29 +412,29 @@ public class JSONTokener {
* @throws JSONException If syntax error. * @throws JSONException If syntax error.
*/ */
public Object nextValue() throws JSONException { 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 * 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. * 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 * @param jsonParserConfiguration which carries options such as strictMode and allowSingleQuotes, these methods will
* deviations. * strictly adhere to the JSON syntax, throwing a JSONException for any deviations.
* @return An object. * @return An object.
* @throws JSONException If syntax error. * @throws JSONException If syntax error.
*/ */
public Object nextValue(boolean strictMode) throws JSONException { 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();
return getJsonObject(strictMode); return getJsonObject(jsonParserConfiguration);
case '[': case '[':
this.back(); this.back();
return getJsonArray(); return getJsonArray();
default: default:
return nextSimpleValue(c, strictMode); return nextSimpleValue(c, jsonParserConfiguration);
} }
} }
@ -442,18 +442,15 @@ public class JSONTokener {
* This method is used to get a JSONObject from the JSONTokener. The strictMode parameter controls the behavior of * This method is used to get a JSONObject from the JSONTokener. The strictMode parameter controls the behavior of
* the method when parsing the JSONObject. * the method when parsing the JSONObject.
* *
* @param strictMode If true, the method will strictly adhere to the JSON syntax, throwing a JSONException for any * @param jsonParserConfiguration which carries options such as strictMode and allowSingleQuotes, these methods will
* deviations. * strictly adhere to the JSON syntax, throwing a JSONException for any deviations.
* deviations.
* @return A JSONObject which is the next value in the JSONTokener. * @return A JSONObject which is the next value in the JSONTokener.
* @throws JSONException If the JSONObject or JSONArray depth is too large to process. * @throws JSONException If the JSONObject or JSONArray depth is too large to process.
*/ */
private JSONObject getJsonObject(boolean strictMode) { private JSONObject getJsonObject(JSONParserConfiguration jsonParserConfiguration) {
try { try {
if (strictMode) { return new JSONObject(this, jsonParserConfiguration);
return new JSONObject(this, new JSONParserConfiguration().withStrictMode(true));
}
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);
} }
@ -473,7 +470,14 @@ public class JSONTokener {
} }
} }
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 == '\'') { if (c == '"' || c == '\'') {
return this.nextString(c, strictMode); return this.nextString(c, strictMode);
} }

View File

@ -118,9 +118,9 @@ public class JSONParserConfigurationTest {
} }
@Test @Test
public void givenUnbalancedQuotes_testStrictModeTrue_shouldThrowJsonExceptionWtihConcreteErrorDescription() { public void givenUnbalancedQuotes_testStrictModeTrueAndAllowSingleQuotes_shouldThrowJsonExceptionWtihConcreteErrorDescription() {
JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration() JSONParserConfiguration jsonParserConfiguration = new JSONParserConfiguration()
.withStrictMode(true); .withStrictMode(true).allowSingleQuotes(true);
String testCaseOne = "[\"abc', \"test\"]"; String testCaseOne = "[\"abc', \"test\"]";
String testCaseTwo = "['abc\", \"test\"]"; String testCaseTwo = "['abc\", \"test\"]";
@ -198,6 +198,7 @@ public class JSONParserConfigurationTest {
return Arrays.asList( return Arrays.asList(
"[1,2];[3,4]", "[1,2];[3,4]",
"[test]", "[test]",
"[{'testSingleQuote': 'testSingleQuote'}]",
"[1, 2,3]:[4,5]", "[1, 2,3]:[4,5]",
"[{test: implied}]", "[{test: implied}]",
"[{\"test\": implied}]", "[{\"test\": implied}]",