feat(#871-strictMode): enhanced and simplified strictMode logic

This commit is contained in:
rikkarth 2024-03-30 10:15:10 +00:00
parent 49de92224d
commit 372f5caac4
No known key found for this signature in database
GPG Key ID: 11E5F28B0AED6AC7
2 changed files with 84 additions and 127 deletions

View File

@ -225,14 +225,7 @@ public class JSONObject {
case '}': case '}':
return; return;
default: default:
String keyToValidate = getKeyToValidate(x, c, jsonParserConfiguration.isStrictMode()); key = x.nextSimpleValue(c, jsonParserConfiguration.isStrictMode()).toString();
boolean keyHasQuotes = keyToValidate.startsWith("\"") && keyToValidate.endsWith("\"");
if (jsonParserConfiguration.isStrictMode() && !keyHasQuotes) {
throw new JSONException("Key is not surrounded by quotes: " + keyToValidate);
}
key = keyToValidate;
} }
// The key is followed by ':'. // The key is followed by ':'.
@ -285,11 +278,6 @@ public class JSONObject {
} }
return x.nextValue(); return x.nextValue();
} }
private String getKeyToValidate(JSONTokener x, char c, boolean strictMode) {
return x.nextSimpleValue(c, strictMode).toString();
}
/** /**
* Construct a JSONObject from a Map. * Construct a JSONObject from a Map.
* *

View File

@ -284,62 +284,73 @@ 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. * @throws JSONException Unterminated string or unbalanced quotes if strictMode == true.
*/ */
public String nextString(char quote) throws JSONException { public String nextString(char quote, boolean strictMode) throws JSONException {
char c; char c;
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (;;) { for (;;) {
c = this.next(); c = this.next();
switch (c) { switch (c) {
case 0: case 0:
case '\n': case '\n':
case '\r': case '\r':
throw this.syntaxError("Unterminated string"); throw this.syntaxError("Unterminated string");
case '\\': case '\\':
c = this.next(); c = this.next();
switch (c) { switch (c) {
case 'b': case 'b':
sb.append('\b'); sb.append('\b');
break; break;
case 't': case 't':
sb.append('\t'); sb.append('\t');
break; break;
case 'n': case 'n':
sb.append('\n'); sb.append('\n');
break; break;
case 'f': case 'f':
sb.append('\f'); sb.append('\f');
break; break;
case 'r': case 'r':
sb.append('\r'); sb.append('\r');
break; break;
case 'u': case 'u':
try { try {
sb.append((char)Integer.parseInt(this.next(4), 16)); sb.append((char) Integer.parseInt(this.next(4), 16));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw this.syntaxError("Illegal escape.", e); throw this.syntaxError("Illegal escape.", e);
}
break;
case '"':
case '\'':
case '\\':
case '/':
sb.append(c);
break;
default:
throw this.syntaxError("Illegal escape.");
} }
break; break;
case '"':
case '\'':
case '\\':
case '/':
sb.append(c);
break;
default: default:
throw this.syntaxError("Illegal escape."); if (strictMode && c == '\"' && quote != c) {
} throw this.syntaxError(String.format(
break; "Field contains unbalanced quotes. Starts with %s but ends with double quote.", quote));
default: }
if (c == quote) {
return sb.toString(); if (strictMode && c == '\'' && quote != c) {
} throw this.syntaxError(String.format(
sb.append(c); "Field contains unbalanced quotes. Starts with %s but ends with single quote.", quote));
}
if (c == quote) {
return sb.toString();
}
sb.append(c);
} }
} }
} }
@ -423,50 +434,10 @@ public class JSONTokener {
this.back(); this.back();
return getJsonArray(); return getJsonArray();
default: default:
return getValue(c, strictMode); return nextSimpleValue(c, strictMode);
} }
} }
/**
* This method is used to get the next value.
*
* @param c The next character in the JSONTokener.
* @param strictMode If true, the method will strictly adhere to the JSON syntax, throwing a JSONException if the
* value is not surrounded by quotes.
* @return An object which is the next value in the JSONTokener.
* @throws JSONException If the value is not surrounded by quotes when strictMode is true.
*/
private Object getValue(char c, boolean strictMode) {
if (strictMode) {
Object valueToValidate = nextSimpleValue(c, true);
boolean isNumeric = checkIfValueIsNumeric(valueToValidate);
if (isNumeric) {
return valueToValidate;
}
boolean hasQuotes = valueIsWrappedByQuotes(valueToValidate);
if (!hasQuotes) {
throw new JSONException("Value is not surrounded by quotes: " + valueToValidate);
}
return valueToValidate;
}
return nextSimpleValue(c);
}
private boolean checkIfValueIsNumeric(Object valueToValidate) {
for (char c : valueToValidate.toString().toCharArray()) {
if (!Character.isDigit(c)) {
return false;
}
}
return true;
}
/** /**
* 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.
@ -502,38 +473,12 @@ public class JSONTokener {
} }
} }
/**
* This method checks if the provided value is wrapped by quotes.
*
* @param valueToValidate The value to be checked. It is converted to a string before checking.
* @return A boolean indicating whether the value is wrapped by quotes. It returns true if the value is wrapped by
* either single or double quotes.
*/
private boolean valueIsWrappedByQuotes(Object valueToValidate) {
String stringToValidate = valueToValidate.toString();
boolean isWrappedByDoubleQuotes = isWrappedByQuotes(stringToValidate, "\"");
boolean isWrappedBySingleQuotes = isWrappedByQuotes(stringToValidate, "'");
return isWrappedByDoubleQuotes || isWrappedBySingleQuotes;
}
private boolean isWrappedByQuotes(String valueToValidate, String quoteType) {
return valueToValidate.startsWith(quoteType) && valueToValidate.endsWith(quoteType);
}
Object nextSimpleValue(char c) {
return nextSimpleValue(c, false);
}
Object nextSimpleValue(char c, boolean strictMode) { Object nextSimpleValue(char c, boolean strictMode) {
if (c == '"' || c == '\'') { if (c == '"' || c == '\'') {
String str = this.nextString(c); return this.nextString(c, strictMode);
if (strictMode) {
return String.format("\"%s\"", str);
}
return str;
} }
return parsedUnquotedText(c); return parsedUnquotedText(c, strictMode);
} }
/** /**
@ -545,7 +490,7 @@ public class JSONTokener {
* @return The parsed object. * @return The parsed object.
* @throws JSONException If the parsed string is empty. * @throws JSONException If the parsed string is empty.
*/ */
private Object parsedUnquotedText(char c) { 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);
@ -556,12 +501,36 @@ public class JSONTokener {
} }
String string = sb.toString().trim(); String string = sb.toString().trim();
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()) { 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.