Merge pull request #1001 from marilynel/master

addressing sonarqube concerns in JSONObject
This commit is contained in:
Sean Leary 2025-07-31 20:54:08 -05:00 committed by GitHub
commit 78137d389d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -79,17 +79,6 @@ public class JSONObject {
*/ */
private static final class Null { private static final class Null {
/**
* There is only intended to be a single instance of the NULL object,
* so the clone method returns itself.
*
* @return NULL.
*/
@Override
protected final Object clone() {
return this;
}
/** /**
* A Null object is equal to the null value and to itself. * A Null object is equal to the null value and to itself.
* *
@ -180,7 +169,7 @@ public class JSONObject {
for (int i = 0; i < names.length; i += 1) { for (int i = 0; i < names.length; i += 1) {
try { try {
this.putOnce(names[i], jo.opt(names[i])); this.putOnce(names[i], jo.opt(names[i]));
} catch (Exception ignore) { } catch (Exception ignore) { // exception thrown for missing key
} }
} }
} }
@ -211,93 +200,134 @@ public class JSONObject {
*/ */
public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException { public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this(); this();
char c;
String key;
Object obj;
boolean isInitial = x.getPrevious() == 0; boolean isInitial = x.getPrevious() == 0;
if (x.nextClean() != '{') { if (x.nextClean() != '{') {
throw x.syntaxError("A JSONObject text must begin with '{'"); throw x.syntaxError("A JSONObject text must begin with '{'");
} }
for (;;) { for (;;) {
c = x.nextClean(); if (parseJSONObject(x, jsonParserConfiguration, isInitial)) {
switch (c) {
case 0:
throw x.syntaxError("A JSONObject text must end with '}'");
case '}':
if (isInitial && jsonParserConfiguration.isStrictMode() && x.nextClean() != 0) {
throw x.syntaxError("Strict mode error: Unparsed characters found at end of input text");
}
return; return;
}
}
}
/**
* Parses entirety of JSON object
*
* @param jsonTokener Parses text as tokens
* @param jsonParserConfiguration Variable to pass parser custom configuration for json parsing.
* @param isInitial True if start of document, else false
* @return True if done building object, else false
*/
private boolean parseJSONObject(JSONTokener jsonTokener, JSONParserConfiguration jsonParserConfiguration, boolean isInitial) {
Object obj;
String key;
boolean doneParsing = false;
char c = jsonTokener.nextClean();
switch (c) {
case 0:
throw jsonTokener.syntaxError("A JSONObject text must end with '}'");
case '}':
if (isInitial && jsonParserConfiguration.isStrictMode() && jsonTokener.nextClean() != 0) {
throw jsonTokener.syntaxError("Strict mode error: Unparsed characters found at end of input text");
}
return true;
default: default:
obj = x.nextSimpleValue(c); obj = jsonTokener.nextSimpleValue(c);
key = obj.toString(); key = obj.toString();
}
checkKeyForStrictMode(jsonTokener, jsonParserConfiguration, obj);
// The key is followed by ':'.
c = jsonTokener.nextClean();
if (c != ':') {
throw jsonTokener.syntaxError("Expected a ':' after a key");
}
// Use syntaxError(..) to include error location
if (key != null) {
// Check if key exists
boolean keyExists = this.opt(key) != null;
if (keyExists && !jsonParserConfiguration.isOverwriteDuplicateKey()) {
throw jsonTokener.syntaxError("Duplicate key \"" + key + "\"");
} }
if (jsonParserConfiguration != null && jsonParserConfiguration.isStrictMode()) { Object value = jsonTokener.nextValue();
if(obj instanceof Boolean) { // Only add value if non-null
throw x.syntaxError(String.format("Strict mode error: key '%s' cannot be boolean", key)); if (value != null) {
} this.put(key, value);
if(obj == JSONObject.NULL) {
throw x.syntaxError(String.format("Strict mode error: key '%s' cannot be null", key));
}
if(obj instanceof Number) {
throw x.syntaxError(String.format("Strict mode error: key '%s' cannot be number", key));
}
} }
}
// The key is followed by ':'. // Pairs are separated by ','.
if (parseEndOfKeyValuePair(jsonTokener, jsonParserConfiguration, isInitial)) {
doneParsing = true;
}
c = x.nextClean(); return doneParsing;
if (c != ':') { }
throw x.syntaxError("Expected a ':' after a key");
}
// Use syntaxError(..) to include error location /**
* Checks for valid end of key:value pair
if (key != null) { * @param jsonTokener Parses text as tokens
// Check if key exists * @param jsonParserConfiguration Variable to pass parser custom configuration for json parsing.
boolean keyExists = this.opt(key) != null; * @param isInitial True if end of JSON object, else false
if (keyExists && !jsonParserConfiguration.isOverwriteDuplicateKey()) { * @return
throw x.syntaxError("Duplicate key \"" + key + "\""); */
} private static boolean parseEndOfKeyValuePair(JSONTokener jsonTokener, JSONParserConfiguration jsonParserConfiguration, boolean isInitial) {
switch (jsonTokener.nextClean()) {
Object value = x.nextValue();
// Only add value if non-null
if (value != null) {
this.put(key, value);
}
}
// Pairs are separated by ','.
switch (x.nextClean()) {
case ';': case ';':
// In strict mode semicolon is not a valid separator // In strict mode semicolon is not a valid separator
if (jsonParserConfiguration.isStrictMode()) { if (jsonParserConfiguration.isStrictMode()) {
throw x.syntaxError("Strict mode error: Invalid character ';' found"); throw jsonTokener.syntaxError("Strict mode error: Invalid character ';' found");
} }
break;
case ',': case ',':
if (x.nextClean() == '}') { if (jsonTokener.nextClean() == '}') {
// trailing commas are not allowed in strict mode // trailing commas are not allowed in strict mode
if (jsonParserConfiguration.isStrictMode()) { if (jsonParserConfiguration.isStrictMode()) {
throw x.syntaxError("Strict mode error: Expected another object element"); throw jsonTokener.syntaxError("Strict mode error: Expected another object element");
} }
return; // End of JSON object
return true;
} }
if (x.end()) { if (jsonTokener.end()) {
throw x.syntaxError("A JSONObject text must end with '}'"); throw jsonTokener.syntaxError("A JSONObject text must end with '}'");
} }
x.back(); jsonTokener.back();
break; break;
case '}': case '}':
if (isInitial && jsonParserConfiguration.isStrictMode() && x.nextClean() != 0) { if (isInitial && jsonParserConfiguration.isStrictMode() && jsonTokener.nextClean() != 0) {
throw x.syntaxError("Strict mode error: Unparsed characters found at end of input text"); throw jsonTokener.syntaxError("Strict mode error: Unparsed characters found at end of input text");
} }
return; // End of JSON object
return true;
default: default:
throw x.syntaxError("Expected a ',' or '}'"); throw jsonTokener.syntaxError("Expected a ',' or '}'");
}
// Not at end of JSON object
return false;
}
/**
* Throws error if key violates strictMode
* @param jsonTokener Parses text as tokens
* @param jsonParserConfiguration Variable to pass parser custom configuration for json parsing.
* @param obj Value to be checked
*/
private static void checkKeyForStrictMode(JSONTokener jsonTokener, JSONParserConfiguration jsonParserConfiguration, Object obj) {
if (jsonParserConfiguration != null && jsonParserConfiguration.isStrictMode()) {
if(obj instanceof Boolean) {
throw jsonTokener.syntaxError(String.format("Strict mode error: key '%s' cannot be boolean", obj.toString()));
}
if(obj == JSONObject.NULL) {
throw jsonTokener.syntaxError(String.format("Strict mode error: key '%s' cannot be null", obj.toString()));
}
if(obj instanceof Number) {
throw jsonTokener.syntaxError(String.format("Strict mode error: key '%s' cannot be number", obj.toString()));
} }
} }
} }