Skip to content

Commit 372f5ca

Browse files
author
rikkarth
committed
feat(#871-strictMode): enhanced and simplified strictMode logic
1 parent 49de922 commit 372f5ca

2 files changed

Lines changed: 84 additions & 127 deletions

File tree

src/main/java/org/json/JSONObject.java

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,7 @@ public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration
225225
case '}':
226226
return;
227227
default:
228-
String keyToValidate = getKeyToValidate(x, c, jsonParserConfiguration.isStrictMode());
229-
boolean keyHasQuotes = keyToValidate.startsWith("\"") && keyToValidate.endsWith("\"");
230-
231-
if (jsonParserConfiguration.isStrictMode() && !keyHasQuotes) {
232-
throw new JSONException("Key is not surrounded by quotes: " + keyToValidate);
233-
}
234-
235-
key = keyToValidate;
228+
key = x.nextSimpleValue(c, jsonParserConfiguration.isStrictMode()).toString();
236229
}
237230

238231
// The key is followed by ':'.
@@ -285,11 +278,6 @@ private Object getValue(JSONTokener x, boolean strictMode) {
285278
}
286279
return x.nextValue();
287280
}
288-
289-
private String getKeyToValidate(JSONTokener x, char c, boolean strictMode) {
290-
return x.nextSimpleValue(c, strictMode).toString();
291-
}
292-
293281
/**
294282
* Construct a JSONObject from a Map.
295283
*

src/main/java/org/json/JSONTokener.java

Lines changed: 83 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -284,62 +284,73 @@ public char nextClean() throws JSONException {
284284
* Backslash processing is done. The formal JSON format does not
285285
* allow strings in single quotes, but an implementation is allowed to
286286
* accept them.
287+
* If strictMode is true, this implementation will not accept unbalanced quotes (e.g will not accept <code>"test'</code>)
287288
* @param quote The quoting character, either
288289
* <code>"</code>&nbsp;<small>(double quote)</small> or
289290
* <code>'</code>&nbsp;<small>(single quote)</small>.
290-
* @return A String.
291-
* @throws JSONException Unterminated string.
291+
* @return A String.
292+
* @throws JSONException Unterminated string or unbalanced quotes if strictMode == true.
292293
*/
293-
public String nextString(char quote) throws JSONException {
294+
public String nextString(char quote, boolean strictMode) throws JSONException {
294295
char c;
295296
StringBuilder sb = new StringBuilder();
296297
for (;;) {
297298
c = this.next();
298299
switch (c) {
299-
case 0:
300-
case '\n':
301-
case '\r':
302-
throw this.syntaxError("Unterminated string");
303-
case '\\':
304-
c = this.next();
305-
switch (c) {
306-
case 'b':
307-
sb.append('\b');
308-
break;
309-
case 't':
310-
sb.append('\t');
311-
break;
312-
case 'n':
313-
sb.append('\n');
314-
break;
315-
case 'f':
316-
sb.append('\f');
317-
break;
318-
case 'r':
319-
sb.append('\r');
320-
break;
321-
case 'u':
322-
try {
323-
sb.append((char)Integer.parseInt(this.next(4), 16));
324-
} catch (NumberFormatException e) {
325-
throw this.syntaxError("Illegal escape.", e);
326-
}
327-
break;
328-
case '"':
329-
case '\'':
300+
case 0:
301+
case '\n':
302+
case '\r':
303+
throw this.syntaxError("Unterminated string");
330304
case '\\':
331-
case '/':
332-
sb.append(c);
305+
c = this.next();
306+
switch (c) {
307+
case 'b':
308+
sb.append('\b');
309+
break;
310+
case 't':
311+
sb.append('\t');
312+
break;
313+
case 'n':
314+
sb.append('\n');
315+
break;
316+
case 'f':
317+
sb.append('\f');
318+
break;
319+
case 'r':
320+
sb.append('\r');
321+
break;
322+
case 'u':
323+
try {
324+
sb.append((char) Integer.parseInt(this.next(4), 16));
325+
} catch (NumberFormatException e) {
326+
throw this.syntaxError("Illegal escape.", e);
327+
}
328+
break;
329+
case '"':
330+
case '\'':
331+
case '\\':
332+
case '/':
333+
sb.append(c);
334+
break;
335+
default:
336+
throw this.syntaxError("Illegal escape.");
337+
}
333338
break;
334339
default:
335-
throw this.syntaxError("Illegal escape.");
336-
}
337-
break;
338-
default:
339-
if (c == quote) {
340-
return sb.toString();
341-
}
342-
sb.append(c);
340+
if (strictMode && c == '\"' && quote != c) {
341+
throw this.syntaxError(String.format(
342+
"Field contains unbalanced quotes. Starts with %s but ends with double quote.", quote));
343+
}
344+
345+
if (strictMode && c == '\'' && quote != c) {
346+
throw this.syntaxError(String.format(
347+
"Field contains unbalanced quotes. Starts with %s but ends with single quote.", quote));
348+
}
349+
350+
if (c == quote) {
351+
return sb.toString();
352+
}
353+
sb.append(c);
343354
}
344355
}
345356
}
@@ -423,50 +434,10 @@ public Object nextValue(boolean strictMode) throws JSONException {
423434
this.back();
424435
return getJsonArray();
425436
default:
426-
return getValue(c, strictMode);
437+
return nextSimpleValue(c, strictMode);
427438
}
428439
}
429440

430-
/**
431-
* This method is used to get the next value.
432-
*
433-
* @param c The next character in the JSONTokener.
434-
* @param strictMode If true, the method will strictly adhere to the JSON syntax, throwing a JSONException if the
435-
* value is not surrounded by quotes.
436-
* @return An object which is the next value in the JSONTokener.
437-
* @throws JSONException If the value is not surrounded by quotes when strictMode is true.
438-
*/
439-
private Object getValue(char c, boolean strictMode) {
440-
if (strictMode) {
441-
Object valueToValidate = nextSimpleValue(c, true);
442-
443-
boolean isNumeric = checkIfValueIsNumeric(valueToValidate);
444-
445-
if (isNumeric) {
446-
return valueToValidate;
447-
}
448-
449-
boolean hasQuotes = valueIsWrappedByQuotes(valueToValidate);
450-
451-
if (!hasQuotes) {
452-
throw new JSONException("Value is not surrounded by quotes: " + valueToValidate);
453-
}
454-
455-
return valueToValidate;
456-
}
457-
458-
return nextSimpleValue(c);
459-
}
460-
461-
private boolean checkIfValueIsNumeric(Object valueToValidate) {
462-
for (char c : valueToValidate.toString().toCharArray()) {
463-
if (!Character.isDigit(c)) {
464-
return false;
465-
}
466-
}
467-
return true;
468-
}
469-
470441
/**
471442
* This method is used to get a JSONObject from the JSONTokener. The strictMode parameter controls the behavior of
472443
* the method when parsing the JSONObject.
@@ -502,38 +473,12 @@ private JSONArray getJsonArray() {
502473
}
503474
}
504475

505-
/**
506-
* This method checks if the provided value is wrapped by quotes.
507-
*
508-
* @param valueToValidate The value to be checked. It is converted to a string before checking.
509-
* @return A boolean indicating whether the value is wrapped by quotes. It returns true if the value is wrapped by
510-
* either single or double quotes.
511-
*/
512-
private boolean valueIsWrappedByQuotes(Object valueToValidate) {
513-
String stringToValidate = valueToValidate.toString();
514-
boolean isWrappedByDoubleQuotes = isWrappedByQuotes(stringToValidate, "\"");
515-
boolean isWrappedBySingleQuotes = isWrappedByQuotes(stringToValidate, "'");
516-
return isWrappedByDoubleQuotes || isWrappedBySingleQuotes;
517-
}
518-
519-
private boolean isWrappedByQuotes(String valueToValidate, String quoteType) {
520-
return valueToValidate.startsWith(quoteType) && valueToValidate.endsWith(quoteType);
521-
}
522-
523-
Object nextSimpleValue(char c) {
524-
return nextSimpleValue(c, false);
525-
}
526-
527476
Object nextSimpleValue(char c, boolean strictMode) {
528477
if (c == '"' || c == '\'') {
529-
String str = this.nextString(c);
530-
if (strictMode) {
531-
return String.format("\"%s\"", str);
532-
}
533-
return str;
478+
return this.nextString(c, strictMode);
534479
}
535480

536-
return parsedUnquotedText(c);
481+
return parsedUnquotedText(c, strictMode);
537482
}
538483

539484
/**
@@ -545,7 +490,7 @@ Object nextSimpleValue(char c, boolean strictMode) {
545490
* @return The parsed object.
546491
* @throws JSONException If the parsed string is empty.
547492
*/
548-
private Object parsedUnquotedText(char c) {
493+
private Object parsedUnquotedText(char c, boolean strictMode) {
549494
StringBuilder sb = new StringBuilder();
550495
while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
551496
sb.append(c);
@@ -556,12 +501,36 @@ private Object parsedUnquotedText(char c) {
556501
}
557502

558503
String string = sb.toString().trim();
504+
505+
if (strictMode) {
506+
boolean isBooleanOrNumeric = checkIfValueIsBooleanOrNumeric(string);
507+
508+
if (isBooleanOrNumeric) {
509+
return string;
510+
}
511+
512+
throw new JSONException(String.format("Value is not surrounded by quotes: %s", string));
513+
}
514+
559515
if (string.isEmpty()) {
560516
throw this.syntaxError("Missing value");
561517
}
562518
return JSONObject.stringToValue(string);
563519
}
564520

521+
private boolean checkIfValueIsBooleanOrNumeric(Object valueToValidate) {
522+
String stringToValidate = valueToValidate.toString();
523+
if (stringToValidate.equals("true") || stringToValidate.equals("false")) {
524+
return true;
525+
}
526+
527+
try {
528+
Double.parseDouble(stringToValidate);
529+
return true;
530+
} catch (NumberFormatException e) {
531+
return false;
532+
}
533+
}
565534

566535
/**
567536
* Skip characters until the next character is the requested character.

0 commit comments

Comments
 (0)