Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 96 additions & 66 deletions src/main/java/org/json/JSONObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,6 @@ public class JSONObject {
*/
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.
*
Expand Down Expand Up @@ -180,7 +169,7 @@ public JSONObject(JSONObject jo, String ... names) {
for (int i = 0; i < names.length; i += 1) {
try {
this.putOnce(names[i], jo.opt(names[i]));
} catch (Exception ignore) {
} catch (Exception ignore) { // exception thrown for missing key
}
}
}
Expand Down Expand Up @@ -211,93 +200,134 @@ public JSONObject(JSONTokener x) throws JSONException {
*/
public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration) throws JSONException {
this();
char c;
String key;
Object obj;

boolean isInitial = x.getPrevious() == 0;

if (x.nextClean() != '{') {
throw x.syntaxError("A JSONObject text must begin with '{'");
}
for (;;) {
c = x.nextClean();
switch (c) {
if (parseJSONObject(x, jsonParserConfiguration, isInitial)) {
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 x.syntaxError("A JSONObject text must end with '}'");
throw jsonTokener.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");
if (isInitial && jsonParserConfiguration.isStrictMode() && jsonTokener.nextClean() != 0) {
throw jsonTokener.syntaxError("Strict mode error: Unparsed characters found at end of input text");
}
return;
return true;
default:
obj = x.nextSimpleValue(c);
obj = jsonTokener.nextSimpleValue(c);
key = obj.toString();
}
}

if (jsonParserConfiguration != null && jsonParserConfiguration.isStrictMode()) {
if(obj instanceof Boolean) {
throw x.syntaxError(String.format("Strict mode error: key '%s' cannot be boolean", key));
}
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));
}
}
checkKeyForStrictMode(jsonTokener, jsonParserConfiguration, obj);

// The key is followed by ':'.
// The key is followed by ':'.
c = jsonTokener.nextClean();
if (c != ':') {
throw jsonTokener.syntaxError("Expected a ':' after a key");
}

c = x.nextClean();
if (c != ':') {
throw x.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 + "\"");
}

// Use syntaxError(..) to include error location

if (key != null) {
// Check if key exists
boolean keyExists = this.opt(key) != null;
if (keyExists && !jsonParserConfiguration.isOverwriteDuplicateKey()) {
throw x.syntaxError("Duplicate key \"" + key + "\"");
}

Object value = x.nextValue();
// Only add value if non-null
if (value != null) {
this.put(key, value);
}
Object value = jsonTokener.nextValue();
// Only add value if non-null
if (value != null) {
this.put(key, value);
}
}

// Pairs are separated by ','.
// Pairs are separated by ','.
if (parseEndOfKeyValuePair(jsonTokener, jsonParserConfiguration, isInitial)) {
doneParsing = true;
}

switch (x.nextClean()) {
return doneParsing;
}

/**
* Checks for valid end of key:value pair
* @param jsonTokener Parses text as tokens
* @param jsonParserConfiguration Variable to pass parser custom configuration for json parsing.
* @param isInitial True if end of JSON object, else false
* @return
*/
private static boolean parseEndOfKeyValuePair(JSONTokener jsonTokener, JSONParserConfiguration jsonParserConfiguration, boolean isInitial) {
switch (jsonTokener.nextClean()) {
case ';':
// In strict mode semicolon is not a valid separator
if (jsonParserConfiguration.isStrictMode()) {
throw x.syntaxError("Strict mode error: Invalid character ';' found");
throw jsonTokener.syntaxError("Strict mode error: Invalid character ';' found");
}
break;
case ',':
if (x.nextClean() == '}') {
if (jsonTokener.nextClean() == '}') {
// trailing commas are not allowed in strict mode
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()) {
throw x.syntaxError("A JSONObject text must end with '}'");
if (jsonTokener.end()) {
throw jsonTokener.syntaxError("A JSONObject text must end with '}'");
}
x.back();
jsonTokener.back();
break;
case '}':
if (isInitial && jsonParserConfiguration.isStrictMode() && x.nextClean() != 0) {
throw x.syntaxError("Strict mode error: Unparsed characters found at end of input text");
if (isInitial && jsonParserConfiguration.isStrictMode() && jsonTokener.nextClean() != 0) {
throw jsonTokener.syntaxError("Strict mode error: Unparsed characters found at end of input text");
}
return;
// End of JSON object
return true;
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()));
}
}
}
Expand Down