@@ -79,17 +79,6 @@ public class JSONObject {
7979 */
8080 private static final class Null {
8181
82- /**
83- * There is only intended to be a single instance of the NULL object,
84- * so the clone method returns itself.
85- *
86- * @return NULL.
87- */
88- @ Override
89- protected final Object clone () {
90- return this ;
91- }
92-
9382 /**
9483 * A Null object is equal to the null value and to itself.
9584 *
@@ -180,7 +169,7 @@ public JSONObject(JSONObject jo, String ... names) {
180169 for (int i = 0 ; i < names .length ; i += 1 ) {
181170 try {
182171 this .putOnce (names [i ], jo .opt (names [i ]));
183- } catch (Exception ignore ) {
172+ } catch (Exception ignore ) { // exception thrown for missing key
184173 }
185174 }
186175 }
@@ -211,93 +200,134 @@ public JSONObject(JSONTokener x) throws JSONException {
211200 */
212201 public JSONObject (JSONTokener x , JSONParserConfiguration jsonParserConfiguration ) throws JSONException {
213202 this ();
214- char c ;
215- String key ;
216- Object obj ;
217-
218203 boolean isInitial = x .getPrevious () == 0 ;
219204
220205 if (x .nextClean () != '{' ) {
221206 throw x .syntaxError ("A JSONObject text must begin with '{'" );
222207 }
223208 for (;;) {
224- c = x .nextClean ();
225- switch (c ) {
209+ if (parseJSONObject (x , jsonParserConfiguration , isInitial )) {
210+ return ;
211+ }
212+ }
213+ }
214+
215+ /**
216+ * Parses entirety of JSON object
217+ *
218+ * @param jsonTokener Parses text as tokens
219+ * @param jsonParserConfiguration Variable to pass parser custom configuration for json parsing.
220+ * @param isInitial True if start of document, else false
221+ * @return True if done building object, else false
222+ */
223+ private boolean parseJSONObject (JSONTokener jsonTokener , JSONParserConfiguration jsonParserConfiguration , boolean isInitial ) {
224+ Object obj ;
225+ String key ;
226+ boolean doneParsing = false ;
227+ char c = jsonTokener .nextClean ();
228+
229+ switch (c ) {
226230 case 0 :
227- throw x .syntaxError ("A JSONObject text must end with '}'" );
231+ throw jsonTokener .syntaxError ("A JSONObject text must end with '}'" );
228232 case '}' :
229- if (isInitial && jsonParserConfiguration .isStrictMode () && x .nextClean () != 0 ) {
230- throw x .syntaxError ("Strict mode error: Unparsed characters found at end of input text" );
233+ if (isInitial && jsonParserConfiguration .isStrictMode () && jsonTokener .nextClean () != 0 ) {
234+ throw jsonTokener .syntaxError ("Strict mode error: Unparsed characters found at end of input text" );
231235 }
232- return ;
236+ return true ;
233237 default :
234- obj = x .nextSimpleValue (c );
238+ obj = jsonTokener .nextSimpleValue (c );
235239 key = obj .toString ();
236- }
240+ }
237241
238- if (jsonParserConfiguration != null && jsonParserConfiguration .isStrictMode ()) {
239- if (obj instanceof Boolean ) {
240- throw x .syntaxError (String .format ("Strict mode error: key '%s' cannot be boolean" , key ));
241- }
242- if (obj == JSONObject .NULL ) {
243- throw x .syntaxError (String .format ("Strict mode error: key '%s' cannot be null" , key ));
244- }
245- if (obj instanceof Number ) {
246- throw x .syntaxError (String .format ("Strict mode error: key '%s' cannot be number" , key ));
247- }
248- }
242+ checkKeyForStrictMode (jsonTokener , jsonParserConfiguration , obj );
249243
250- // The key is followed by ':'.
244+ // The key is followed by ':'.
245+ c = jsonTokener .nextClean ();
246+ if (c != ':' ) {
247+ throw jsonTokener .syntaxError ("Expected a ':' after a key" );
248+ }
251249
252- c = x .nextClean ();
253- if (c != ':' ) {
254- throw x .syntaxError ("Expected a ':' after a key" );
250+ // Use syntaxError(..) to include error location
251+ if (key != null ) {
252+ // Check if key exists
253+ boolean keyExists = this .opt (key ) != null ;
254+ if (keyExists && !jsonParserConfiguration .isOverwriteDuplicateKey ()) {
255+ throw jsonTokener .syntaxError ("Duplicate key \" " + key + "\" " );
255256 }
256257
257- // Use syntaxError(..) to include error location
258-
259- if (key != null ) {
260- // Check if key exists
261- boolean keyExists = this .opt (key ) != null ;
262- if (keyExists && !jsonParserConfiguration .isOverwriteDuplicateKey ()) {
263- throw x .syntaxError ("Duplicate key \" " + key + "\" " );
264- }
265-
266- Object value = x .nextValue ();
267- // Only add value if non-null
268- if (value != null ) {
269- this .put (key , value );
270- }
258+ Object value = jsonTokener .nextValue ();
259+ // Only add value if non-null
260+ if (value != null ) {
261+ this .put (key , value );
271262 }
263+ }
272264
273- // Pairs are separated by ','.
265+ // Pairs are separated by ','.
266+ if (parseEndOfKeyValuePair (jsonTokener , jsonParserConfiguration , isInitial )) {
267+ doneParsing = true ;
268+ }
274269
275- switch (x .nextClean ()) {
270+ return doneParsing ;
271+ }
272+
273+ /**
274+ * Checks for valid end of key:value pair
275+ * @param jsonTokener Parses text as tokens
276+ * @param jsonParserConfiguration Variable to pass parser custom configuration for json parsing.
277+ * @param isInitial True if end of JSON object, else false
278+ * @return
279+ */
280+ private static boolean parseEndOfKeyValuePair (JSONTokener jsonTokener , JSONParserConfiguration jsonParserConfiguration , boolean isInitial ) {
281+ switch (jsonTokener .nextClean ()) {
276282 case ';' :
277283 // In strict mode semicolon is not a valid separator
278284 if (jsonParserConfiguration .isStrictMode ()) {
279- throw x .syntaxError ("Strict mode error: Invalid character ';' found" );
285+ throw jsonTokener .syntaxError ("Strict mode error: Invalid character ';' found" );
280286 }
287+ break ;
281288 case ',' :
282- if (x .nextClean () == '}' ) {
289+ if (jsonTokener .nextClean () == '}' ) {
283290 // trailing commas are not allowed in strict mode
284291 if (jsonParserConfiguration .isStrictMode ()) {
285- throw x .syntaxError ("Strict mode error: Expected another object element" );
292+ throw jsonTokener .syntaxError ("Strict mode error: Expected another object element" );
286293 }
287- return ;
294+ // End of JSON object
295+ return true ;
288296 }
289- if (x .end ()) {
290- throw x .syntaxError ("A JSONObject text must end with '}'" );
297+ if (jsonTokener .end ()) {
298+ throw jsonTokener .syntaxError ("A JSONObject text must end with '}'" );
291299 }
292- x .back ();
300+ jsonTokener .back ();
293301 break ;
294302 case '}' :
295- if (isInitial && jsonParserConfiguration .isStrictMode () && x .nextClean () != 0 ) {
296- throw x .syntaxError ("Strict mode error: Unparsed characters found at end of input text" );
303+ if (isInitial && jsonParserConfiguration .isStrictMode () && jsonTokener .nextClean () != 0 ) {
304+ throw jsonTokener .syntaxError ("Strict mode error: Unparsed characters found at end of input text" );
297305 }
298- return ;
306+ // End of JSON object
307+ return true ;
299308 default :
300- throw x .syntaxError ("Expected a ',' or '}'" );
309+ throw jsonTokener .syntaxError ("Expected a ',' or '}'" );
310+ }
311+ // Not at end of JSON object
312+ return false ;
313+ }
314+
315+ /**
316+ * Throws error if key violates strictMode
317+ * @param jsonTokener Parses text as tokens
318+ * @param jsonParserConfiguration Variable to pass parser custom configuration for json parsing.
319+ * @param obj Value to be checked
320+ */
321+ private static void checkKeyForStrictMode (JSONTokener jsonTokener , JSONParserConfiguration jsonParserConfiguration , Object obj ) {
322+ if (jsonParserConfiguration != null && jsonParserConfiguration .isStrictMode ()) {
323+ if (obj instanceof Boolean ) {
324+ throw jsonTokener .syntaxError (String .format ("Strict mode error: key '%s' cannot be boolean" , obj .toString ()));
325+ }
326+ if (obj == JSONObject .NULL ) {
327+ throw jsonTokener .syntaxError (String .format ("Strict mode error: key '%s' cannot be null" , obj .toString ()));
328+ }
329+ if (obj instanceof Number ) {
330+ throw jsonTokener .syntaxError (String .format ("Strict mode error: key '%s' cannot be number" , obj .toString ()));
301331 }
302332 }
303333 }
0 commit comments