From c0918c2428fd094b3939f27e9d781824e2b5a951 Mon Sep 17 00:00:00 2001 From: rikkarth Date: Sat, 30 Mar 2024 11:06:30 +0000 Subject: [PATCH] feat(#871-strictMode): add allowSingleQuote option, add enhancements and simplification --- src/main/java/org/json/JSONArray.java | 6 +--- src/main/java/org/json/JSONObject.java | 10 ++---- .../org/json/JSONParserConfiguration.java | 33 ++++++++++++++++++ src/main/java/org/json/JSONTokener.java | 34 +++++++++++-------- .../junit/JSONParserConfigurationTest.java | 5 +-- 5 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/json/JSONArray.java b/src/main/java/org/json/JSONArray.java index 1e2c32c..ded271e 100644 --- a/src/main/java/org/json/JSONArray.java +++ b/src/main/java/org/json/JSONArray.java @@ -113,11 +113,7 @@ public class JSONArray implements Iterable { 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: diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index f8f6e18..642e967 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -225,7 +225,7 @@ public class JSONObject { case '}': return; default: - key = x.nextSimpleValue(c, jsonParserConfiguration.isStrictMode()).toString(); + key = x.nextSimpleValue(c, jsonParserConfiguration).toString(); } // The key is followed by ':'. @@ -244,7 +244,7 @@ public class JSONObject { 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); @@ -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. * diff --git a/src/main/java/org/json/JSONParserConfiguration.java b/src/main/java/org/json/JSONParserConfiguration.java index 850f202..3b32eab 100644 --- a/src/main/java/org/json/JSONParserConfiguration.java +++ b/src/main/java/org/json/JSONParserConfiguration.java @@ -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. */ @@ -92,6 +97,24 @@ public class JSONParserConfiguration extends ParserConfiguration { return clone; } + /** + * Allows single quotes mode configuration for JSON parser when strictMode is on. + *

+ * If this option is set to true when strict Mode is enabled, the parser will allow single quoted fields. + *

+ * 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. @@ -115,4 +138,14 @@ public class JSONParserConfiguration extends ParserConfiguration { public boolean isStrictMode() { return this.strictMode; } + + /** + * Retrieves the allow single quotes option. + *

+ * 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;} } diff --git a/src/main/java/org/json/JSONTokener.java b/src/main/java/org/json/JSONTokener.java index c91628a..36352ad 100644 --- a/src/main/java/org/json/JSONTokener.java +++ b/src/main/java/org/json/JSONTokener.java @@ -412,29 +412,29 @@ public class JSONTokener { * @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); } } @@ -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 * 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); } @@ -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 == '\'') { return this.nextString(c, strictMode); } diff --git a/src/test/java/org/json/junit/JSONParserConfigurationTest.java b/src/test/java/org/json/junit/JSONParserConfigurationTest.java index dd23c70..64723c7 100644 --- a/src/test/java/org/json/junit/JSONParserConfigurationTest.java +++ b/src/test/java/org/json/junit/JSONParserConfigurationTest.java @@ -118,9 +118,9 @@ public class JSONParserConfigurationTest { } @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\"]"; @@ -198,6 +198,7 @@ public class JSONParserConfigurationTest { return Arrays.asList( "[1,2];[3,4]", "[test]", + "[{'testSingleQuote': 'testSingleQuote'}]", "[1, 2,3]:[4,5]", "[{test: implied}]", "[{\"test\": implied}]",