• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.json;
18 
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Iterator;
22 import java.util.LinkedHashMap;
23 import java.util.Map;
24 import java.util.Objects;
25 import java.util.Set;
26 
27 // Note: this class was written without inspecting the non-free org.json sourcecode.
28 
29 /**
30  * A modifiable set of name/value mappings. Names are unique, non-null strings.
31  * Values may be any mix of {@link JSONObject JSONObjects}, {@link JSONArray
32  * JSONArrays}, Strings, Booleans, Integers, Longs, Doubles or {@link #NULL}.
33  * Values may not be {@code null}, {@link Double#isNaN() NaNs}, {@link
34  * Double#isInfinite() infinities}, or of any type not listed here.
35  *
36  * <p>This class can coerce values to another type when requested.
37  * <ul>
38  *   <li>When the requested type is a boolean, strings will be coerced using a
39  *       case-insensitive comparison to "true" and "false".
40  *   <li>When the requested type is a double, other {@link Number} types will
41  *       be coerced using {@link Number#doubleValue() doubleValue}. Strings
42  *       that can be coerced using {@link Double#valueOf(String)} will be.
43  *   <li>When the requested type is an int, other {@link Number} types will
44  *       be coerced using {@link Number#intValue() intValue}. Strings
45  *       that can be coerced using {@link Double#valueOf(String)} will be,
46  *       and then cast to int.
47  *   <li><a name="lossy">When the requested type is a long, other {@link Number} types will
48  *       be coerced using {@link Number#longValue() longValue}. Strings
49  *       that can be coerced using {@link Double#valueOf(String)} will be,
50  *       and then cast to long. This two-step conversion is lossy for very
51  *       large values. For example, the string "9223372036854775806" yields the
52  *       long 9223372036854775807.</a>
53  *   <li>When the requested type is a String, other non-null values will be
54  *       coerced using {@link String#valueOf(Object)}. Although null cannot be
55  *       coerced, the sentinel value {@link JSONObject#NULL} is coerced to the
56  *       string "null".
57  * </ul>
58  *
59  * <p>This class can look up both mandatory and optional values:
60  * <ul>
61  *   <li>Use <code>get<i>Type</i>()</code> to retrieve a mandatory value. This
62  *       fails with a {@code JSONException} if the requested name has no value
63  *       or if the value cannot be coerced to the requested type.
64  *   <li>Use <code>opt<i>Type</i>()</code> to retrieve an optional value. This
65  *       returns a system- or user-supplied default if the requested name has no
66  *       value or if the value cannot be coerced to the requested type.
67  * </ul>
68  *
69  * <p><strong>Warning:</strong> this class represents null in two incompatible
70  * ways: the standard Java {@code null} reference, and the sentinel value {@link
71  * JSONObject#NULL}. In particular, calling {@code put(name, null)} removes the
72  * named entry from the object but {@code put(name, JSONObject.NULL)} stores an
73  * entry whose value is {@code JSONObject.NULL}.
74  *
75  * <p>Instances of this class are not thread safe. Although this class is
76  * nonfinal, it was not designed for inheritance and should not be subclassed.
77  * In particular, self-use by overrideable methods is not specified. See
78  * <i>Effective Java</i> Item 17, "Design and Document or inheritance or else
79  * prohibit it" for further information.
80  */
81 public class JSONObject {
82 
83     private static final Double NEGATIVE_ZERO = -0d;
84 
85     /**
86      * A sentinel value used to explicitly define a name with no value. Unlike
87      * {@code null}, names with this value:
88      * <ul>
89      *   <li>show up in the {@link #names} array
90      *   <li>show up in the {@link #keys} iterator
91      *   <li>return {@code true} for {@link #has(String)}
92      *   <li>do not throw on {@link #get(String)}
93      *   <li>are included in the encoded JSON string.
94      * </ul>
95      *
96      * <p>This value violates the general contract of {@link Object#equals} by
97      * returning true when compared to {@code null}. Its {@link #toString}
98      * method returns "null".
99      */
100     public static final Object NULL = new Object() {
101         @Override public boolean equals(Object o) {
102             return o == this || o == null; // API specifies this broken equals implementation
103         }
104         // at least make the broken equals(null) consistent with Objects.hashCode(null).
105         @Override public int hashCode() { return Objects.hashCode(null); }
106         @Override public String toString() {
107             return "null";
108         }
109     };
110 
111     private final LinkedHashMap<String, Object> nameValuePairs;
112 
113     /**
114      * Creates a {@code JSONObject} with no name/value mappings.
115      */
JSONObject()116     public JSONObject() {
117         nameValuePairs = new LinkedHashMap<String, Object>();
118     }
119 
120     /**
121      * Creates a new {@code JSONObject} by copying all name/value mappings from
122      * the given map.
123      *
124      * @param copyFrom a map whose keys are of type {@link String} and whose
125      *     values are of supported types.
126      * @throws NullPointerException if any of the map's keys are null.
127      */
128     /* (accept a raw type for API compatibility) */
JSONObject(Map copyFrom)129     public JSONObject(Map copyFrom) {
130         this();
131         Map<?, ?> contentsTyped = (Map<?, ?>) copyFrom;
132         for (Map.Entry<?, ?> entry : contentsTyped.entrySet()) {
133             /*
134              * Deviate from the original by checking that keys are non-null and
135              * of the proper type. (We still defer validating the values).
136              */
137             String key = (String) entry.getKey();
138             if (key == null) {
139                 throw new NullPointerException("key == null");
140             }
141             nameValuePairs.put(key, wrap(entry.getValue()));
142         }
143     }
144 
145     /**
146      * Creates a new {@code JSONObject} with name/value mappings from the next
147      * object in the tokener.
148      *
149      * @param readFrom a tokener whose nextValue() method will yield a
150      *     {@code JSONObject}.
151      * @throws JSONException if the parse fails or doesn't yield a
152      *     {@code JSONObject}.
153      */
JSONObject(JSONTokener readFrom)154     public JSONObject(JSONTokener readFrom) throws JSONException {
155         /*
156          * Getting the parser to populate this could get tricky. Instead, just
157          * parse to temporary JSONObject and then steal the data from that.
158          */
159         Object object = readFrom.nextValue();
160         if (object instanceof JSONObject) {
161             this.nameValuePairs = ((JSONObject) object).nameValuePairs;
162         } else {
163             throw JSON.typeMismatch(object, "JSONObject");
164         }
165     }
166 
167     /**
168      * Creates a new {@code JSONObject} with name/value mappings from the JSON
169      * string.
170      *
171      * @param json a JSON-encoded string containing an object.
172      * @throws JSONException if the parse fails or doesn't yield a {@code
173      *     JSONObject}.
174      */
JSONObject(String json)175     public JSONObject(String json) throws JSONException {
176         this(new JSONTokener(json));
177     }
178 
179     /**
180      * Creates a new {@code JSONObject} by copying mappings for the listed names
181      * from the given object. Names that aren't present in {@code copyFrom} will
182      * be skipped.
183      */
JSONObject(JSONObject copyFrom, String[] names)184     public JSONObject(JSONObject copyFrom, String[] names) throws JSONException {
185         this();
186         for (String name : names) {
187             Object value = copyFrom.opt(name);
188             if (value != null) {
189                 nameValuePairs.put(name, value);
190             }
191         }
192     }
193 
194     /**
195      * Returns the number of name/value mappings in this object.
196      */
length()197     public int length() {
198         return nameValuePairs.size();
199     }
200 
201     /**
202      * Maps {@code name} to {@code value}, clobbering any existing name/value
203      * mapping with the same name.
204      *
205      * @return this object.
206      */
put(String name, boolean value)207     public JSONObject put(String name, boolean value) throws JSONException {
208         nameValuePairs.put(checkName(name), value);
209         return this;
210     }
211 
212     /**
213      * Maps {@code name} to {@code value}, clobbering any existing name/value
214      * mapping with the same name.
215      *
216      * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
217      *     {@link Double#isInfinite() infinities}.
218      * @return this object.
219      */
put(String name, double value)220     public JSONObject put(String name, double value) throws JSONException {
221         nameValuePairs.put(checkName(name), JSON.checkDouble(value));
222         return this;
223     }
224 
225     /**
226      * Maps {@code name} to {@code value}, clobbering any existing name/value
227      * mapping with the same name.
228      *
229      * @return this object.
230      */
put(String name, int value)231     public JSONObject put(String name, int value) throws JSONException {
232         nameValuePairs.put(checkName(name), value);
233         return this;
234     }
235 
236     /**
237      * Maps {@code name} to {@code value}, clobbering any existing name/value
238      * mapping with the same name.
239      *
240      * @return this object.
241      */
put(String name, long value)242     public JSONObject put(String name, long value) throws JSONException {
243         nameValuePairs.put(checkName(name), value);
244         return this;
245     }
246 
247     /**
248      * Maps {@code name} to {@code value}, clobbering any existing name/value
249      * mapping with the same name. If the value is {@code null}, any existing
250      * mapping for {@code name} is removed.
251      *
252      * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
253      *     Integer, Long, Double, {@link #NULL}, or {@code null}. May not be
254      *     {@link Double#isNaN() NaNs} or {@link Double#isInfinite()
255      *     infinities}.
256      * @return this object.
257      */
put(String name, Object value)258     public JSONObject put(String name, Object value) throws JSONException {
259         if (value == null) {
260             nameValuePairs.remove(name);
261             return this;
262         }
263         if (value instanceof Number) {
264             // deviate from the original by checking all Numbers, not just floats & doubles
265             JSON.checkDouble(((Number) value).doubleValue());
266         }
267         nameValuePairs.put(checkName(name), value);
268         return this;
269     }
270 
271     /**
272      * Equivalent to {@code put(name, value)} when both parameters are non-null;
273      * does nothing otherwise.
274      */
putOpt(String name, Object value)275     public JSONObject putOpt(String name, Object value) throws JSONException {
276         if (name == null || value == null) {
277             return this;
278         }
279         return put(name, value);
280     }
281 
282     /**
283      * Appends {@code value} to the array already mapped to {@code name}. If
284      * this object has no mapping for {@code name}, this inserts a new mapping.
285      * If the mapping exists but its value is not an array, the existing
286      * and new values are inserted in order into a new array which is itself
287      * mapped to {@code name}. In aggregate, this allows values to be added to a
288      * mapping one at a time.
289      *
290      * <p> Note that {@code append(String, Object)} provides better semantics.
291      * In particular, the mapping for {@code name} will <b>always</b> be a
292      * {@link JSONArray}. Using {@code accumulate} will result in either a
293      * {@link JSONArray} or a mapping whose type is the type of {@code value}
294      * depending on the number of calls to it.
295      *
296      * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
297      *     Integer, Long, Double, {@link #NULL} or null. May not be {@link
298      *     Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}.
299      */
300     // TODO: Change {@code append) to {@link #append} when append is
301     // unhidden.
accumulate(String name, Object value)302     public JSONObject accumulate(String name, Object value) throws JSONException {
303         Object current = nameValuePairs.get(checkName(name));
304         if (current == null) {
305             return put(name, value);
306         }
307 
308         if (current instanceof JSONArray) {
309             JSONArray array = (JSONArray) current;
310             array.checkedPut(value);
311         } else {
312             JSONArray array = new JSONArray();
313             array.checkedPut(current);
314             array.checkedPut(value);
315             nameValuePairs.put(name, array);
316         }
317         return this;
318     }
319 
320     /**
321      * Appends values to the array mapped to {@code name}. A new {@link JSONArray}
322      * mapping for {@code name} will be inserted if no mapping exists. If the existing
323      * mapping for {@code name} is not a {@link JSONArray}, a {@link JSONException}
324      * will be thrown.
325      *
326      * @throws JSONException if {@code name} is {@code null} or if the mapping for
327      *         {@code name} is non-null and is not a {@link JSONArray}.
328      *
329      * @hide
330      */
append(String name, Object value)331     public JSONObject append(String name, Object value) throws JSONException {
332         Object current = nameValuePairs.get(checkName(name));
333 
334         final JSONArray array;
335         if (current instanceof JSONArray) {
336             array = (JSONArray) current;
337         } else if (current == null) {
338             JSONArray newArray = new JSONArray();
339             nameValuePairs.put(name, newArray);
340             array = newArray;
341         } else {
342             throw new JSONException("Key " + name + " is not a JSONArray");
343         }
344 
345         array.checkedPut(value);
346 
347         return this;
348     }
349 
checkName(String name)350     String checkName(String name) throws JSONException {
351         if (name == null) {
352             throw new JSONException("Names must be non-null");
353         }
354         return name;
355     }
356 
357     /**
358      * Removes the named mapping if it exists; does nothing otherwise.
359      *
360      * @return the value previously mapped by {@code name}, or null if there was
361      *     no such mapping.
362      */
remove(String name)363     public Object remove(String name) {
364         return nameValuePairs.remove(name);
365     }
366 
367     /**
368      * Returns true if this object has no mapping for {@code name} or if it has
369      * a mapping whose value is {@link #NULL}.
370      */
isNull(String name)371     public boolean isNull(String name) {
372         Object value = nameValuePairs.get(name);
373         return value == null || value == NULL;
374     }
375 
376     /**
377      * Returns true if this object has a mapping for {@code name}. The mapping
378      * may be {@link #NULL}.
379      */
has(String name)380     public boolean has(String name) {
381         return nameValuePairs.containsKey(name);
382     }
383 
384     /**
385      * Returns the value mapped by {@code name}, or throws if no such mapping exists.
386      *
387      * @throws JSONException if no such mapping exists.
388      */
get(String name)389     public Object get(String name) throws JSONException {
390         Object result = nameValuePairs.get(name);
391         if (result == null) {
392             throw new JSONException("No value for " + name);
393         }
394         return result;
395     }
396 
397     /**
398      * Returns the value mapped by {@code name}, or null if no such mapping
399      * exists.
400      */
opt(String name)401     public Object opt(String name) {
402         return nameValuePairs.get(name);
403     }
404 
405     /**
406      * Returns the value mapped by {@code name} if it exists and is a boolean or
407      * can be coerced to a boolean, or throws otherwise.
408      *
409      * @throws JSONException if the mapping doesn't exist or cannot be coerced
410      *     to a boolean.
411      */
getBoolean(String name)412     public boolean getBoolean(String name) throws JSONException {
413         Object object = get(name);
414         Boolean result = JSON.toBoolean(object);
415         if (result == null) {
416             throw JSON.typeMismatch(name, object, "boolean");
417         }
418         return result;
419     }
420 
421     /**
422      * Returns the value mapped by {@code name} if it exists and is a boolean or
423      * can be coerced to a boolean, or false otherwise.
424      */
optBoolean(String name)425     public boolean optBoolean(String name) {
426         return optBoolean(name, false);
427     }
428 
429     /**
430      * Returns the value mapped by {@code name} if it exists and is a boolean or
431      * can be coerced to a boolean, or {@code fallback} otherwise.
432      */
optBoolean(String name, boolean fallback)433     public boolean optBoolean(String name, boolean fallback) {
434         Object object = opt(name);
435         Boolean result = JSON.toBoolean(object);
436         return result != null ? result : fallback;
437     }
438 
439     /**
440      * Returns the value mapped by {@code name} if it exists and is a double or
441      * can be coerced to a double, or throws otherwise.
442      *
443      * @throws JSONException if the mapping doesn't exist or cannot be coerced
444      *     to a double.
445      */
getDouble(String name)446     public double getDouble(String name) throws JSONException {
447         Object object = get(name);
448         Double result = JSON.toDouble(object);
449         if (result == null) {
450             throw JSON.typeMismatch(name, object, "double");
451         }
452         return result;
453     }
454 
455     /**
456      * Returns the value mapped by {@code name} if it exists and is a double or
457      * can be coerced to a double, or {@code NaN} otherwise.
458      */
optDouble(String name)459     public double optDouble(String name) {
460         return optDouble(name, Double.NaN);
461     }
462 
463     /**
464      * Returns the value mapped by {@code name} if it exists and is a double or
465      * can be coerced to a double, or {@code fallback} otherwise.
466      */
optDouble(String name, double fallback)467     public double optDouble(String name, double fallback) {
468         Object object = opt(name);
469         Double result = JSON.toDouble(object);
470         return result != null ? result : fallback;
471     }
472 
473     /**
474      * Returns the value mapped by {@code name} if it exists and is an int or
475      * can be coerced to an int, or throws otherwise.
476      *
477      * @throws JSONException if the mapping doesn't exist or cannot be coerced
478      *     to an int.
479      */
getInt(String name)480     public int getInt(String name) throws JSONException {
481         Object object = get(name);
482         Integer result = JSON.toInteger(object);
483         if (result == null) {
484             throw JSON.typeMismatch(name, object, "int");
485         }
486         return result;
487     }
488 
489     /**
490      * Returns the value mapped by {@code name} if it exists and is an int or
491      * can be coerced to an int, or 0 otherwise.
492      */
optInt(String name)493     public int optInt(String name) {
494         return optInt(name, 0);
495     }
496 
497     /**
498      * Returns the value mapped by {@code name} if it exists and is an int or
499      * can be coerced to an int, or {@code fallback} otherwise.
500      */
optInt(String name, int fallback)501     public int optInt(String name, int fallback) {
502         Object object = opt(name);
503         Integer result = JSON.toInteger(object);
504         return result != null ? result : fallback;
505     }
506 
507     /**
508      * Returns the value mapped by {@code name} if it exists and is a long or
509      * can be coerced to a long, or throws otherwise.
510      * Note that JSON represents numbers as doubles,
511      * so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via JSON.
512      *
513      * @throws JSONException if the mapping doesn't exist or cannot be coerced
514      *     to a long.
515      */
getLong(String name)516     public long getLong(String name) throws JSONException {
517         Object object = get(name);
518         Long result = JSON.toLong(object);
519         if (result == null) {
520             throw JSON.typeMismatch(name, object, "long");
521         }
522         return result;
523     }
524 
525     /**
526      * Returns the value mapped by {@code name} if it exists and is a long or
527      * can be coerced to a long, or 0 otherwise. Note that JSON represents numbers as doubles,
528      * so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via JSON.
529      */
optLong(String name)530     public long optLong(String name) {
531         return optLong(name, 0L);
532     }
533 
534     /**
535      * Returns the value mapped by {@code name} if it exists and is a long or
536      * can be coerced to a long, or {@code fallback} otherwise. Note that JSON represents
537      * numbers as doubles, so this is <a href="#lossy">lossy</a>; use strings to transfer
538      * numbers via JSON.
539      */
optLong(String name, long fallback)540     public long optLong(String name, long fallback) {
541         Object object = opt(name);
542         Long result = JSON.toLong(object);
543         return result != null ? result : fallback;
544     }
545 
546     /**
547      * Returns the value mapped by {@code name} if it exists, coercing it if
548      * necessary, or throws if no such mapping exists.
549      *
550      * @throws JSONException if no such mapping exists.
551      */
getString(String name)552     public String getString(String name) throws JSONException {
553         Object object = get(name);
554         String result = JSON.toString(object);
555         if (result == null) {
556             throw JSON.typeMismatch(name, object, "String");
557         }
558         return result;
559     }
560 
561     /**
562      * Returns the value mapped by {@code name} if it exists, coercing it if
563      * necessary, or the empty string if no such mapping exists.
564      */
optString(String name)565     public String optString(String name) {
566         return optString(name, "");
567     }
568 
569     /**
570      * Returns the value mapped by {@code name} if it exists, coercing it if
571      * necessary, or {@code fallback} if no such mapping exists.
572      */
optString(String name, String fallback)573     public String optString(String name, String fallback) {
574         Object object = opt(name);
575         String result = JSON.toString(object);
576         return result != null ? result : fallback;
577     }
578 
579     /**
580      * Returns the value mapped by {@code name} if it exists and is a {@code
581      * JSONArray}, or throws otherwise.
582      *
583      * @throws JSONException if the mapping doesn't exist or is not a {@code
584      *     JSONArray}.
585      */
getJSONArray(String name)586     public JSONArray getJSONArray(String name) throws JSONException {
587         Object object = get(name);
588         if (object instanceof JSONArray) {
589             return (JSONArray) object;
590         } else {
591             throw JSON.typeMismatch(name, object, "JSONArray");
592         }
593     }
594 
595     /**
596      * Returns the value mapped by {@code name} if it exists and is a {@code
597      * JSONArray}, or null otherwise.
598      */
optJSONArray(String name)599     public JSONArray optJSONArray(String name) {
600         Object object = opt(name);
601         return object instanceof JSONArray ? (JSONArray) object : null;
602     }
603 
604     /**
605      * Returns the value mapped by {@code name} if it exists and is a {@code
606      * JSONObject}, or throws otherwise.
607      *
608      * @throws JSONException if the mapping doesn't exist or is not a {@code
609      *     JSONObject}.
610      */
getJSONObject(String name)611     public JSONObject getJSONObject(String name) throws JSONException {
612         Object object = get(name);
613         if (object instanceof JSONObject) {
614             return (JSONObject) object;
615         } else {
616             throw JSON.typeMismatch(name, object, "JSONObject");
617         }
618     }
619 
620     /**
621      * Returns the value mapped by {@code name} if it exists and is a {@code
622      * JSONObject}, or null otherwise.
623      */
optJSONObject(String name)624     public JSONObject optJSONObject(String name) {
625         Object object = opt(name);
626         return object instanceof JSONObject ? (JSONObject) object : null;
627     }
628 
629     /**
630      * Returns an array with the values corresponding to {@code names}. The
631      * array contains null for names that aren't mapped. This method returns
632      * null if {@code names} is either null or empty.
633      */
toJSONArray(JSONArray names)634     public JSONArray toJSONArray(JSONArray names) throws JSONException {
635         JSONArray result = new JSONArray();
636         if (names == null) {
637             return null;
638         }
639         int length = names.length();
640         if (length == 0) {
641             return null;
642         }
643         for (int i = 0; i < length; i++) {
644             String name = JSON.toString(names.opt(i));
645             result.put(opt(name));
646         }
647         return result;
648     }
649 
650     /**
651      * Returns an iterator of the {@code String} names in this object. The
652      * returned iterator supports {@link Iterator#remove() remove}, which will
653      * remove the corresponding mapping from this object. If this object is
654      * modified after the iterator is returned, the iterator's behavior is
655      * undefined. The order of the keys is undefined.
656      */
keys()657     public Iterator<String> keys() {
658         return nameValuePairs.keySet().iterator();
659     }
660 
661     /**
662      * Returns the set of {@code String} names in this object. The returned set
663      * is a view of the keys in this object. {@link Set#remove(Object)} will remove
664      * the corresponding mapping from this object and set iterator behaviour
665      * is undefined if this object is modified after it is returned.
666      *
667      * See {@link #keys()}.
668      *
669      * @hide.
670      */
keySet()671     public Set<String> keySet() {
672         return nameValuePairs.keySet();
673     }
674 
675     /**
676      * Returns an array containing the string names in this object. This method
677      * returns null if this object contains no mappings.
678      */
names()679     public JSONArray names() {
680         return nameValuePairs.isEmpty()
681                 ? null
682                 : new JSONArray(new ArrayList<String>(nameValuePairs.keySet()));
683     }
684 
685     /**
686      * Encodes this object as a compact JSON string, such as:
687      * <pre>{"query":"Pizza","locations":[94043,90210]}</pre>
688      */
toString()689     @Override public String toString() {
690         try {
691             JSONStringer stringer = new JSONStringer();
692             writeTo(stringer);
693             return stringer.toString();
694         } catch (JSONException e) {
695             return null;
696         }
697     }
698 
699     /**
700      * Encodes this object as a human readable JSON string for debugging, such
701      * as:
702      * <pre>
703      * {
704      *     "query": "Pizza",
705      *     "locations": [
706      *         94043,
707      *         90210
708      *     ]
709      * }</pre>
710      *
711      * @param indentSpaces the number of spaces to indent for each level of
712      *     nesting.
713      */
toString(int indentSpaces)714     public String toString(int indentSpaces) throws JSONException {
715         JSONStringer stringer = new JSONStringer(indentSpaces);
716         writeTo(stringer);
717         return stringer.toString();
718     }
719 
writeTo(JSONStringer stringer)720     void writeTo(JSONStringer stringer) throws JSONException {
721         stringer.object();
722         for (Map.Entry<String, Object> entry : nameValuePairs.entrySet()) {
723             stringer.key(entry.getKey()).value(entry.getValue());
724         }
725         stringer.endObject();
726     }
727 
728     /**
729      * Encodes the number as a JSON string.
730      *
731      * @param number a finite value. May not be {@link Double#isNaN() NaNs} or
732      *     {@link Double#isInfinite() infinities}.
733      */
numberToString(Number number)734     public static String numberToString(Number number) throws JSONException {
735         if (number == null) {
736             throw new JSONException("Number must be non-null");
737         }
738 
739         double doubleValue = number.doubleValue();
740         JSON.checkDouble(doubleValue);
741 
742         // the original returns "-0" instead of "-0.0" for negative zero
743         if (number.equals(NEGATIVE_ZERO)) {
744             return "-0";
745         }
746 
747         long longValue = number.longValue();
748         if (doubleValue == (double) longValue) {
749             return Long.toString(longValue);
750         }
751 
752         return number.toString();
753     }
754 
755     /**
756      * Encodes {@code data} as a JSON string. This applies quotes and any
757      * necessary character escaping.
758      *
759      * @param data the string to encode. Null will be interpreted as an empty
760      *     string.
761      */
quote(String data)762     public static String quote(String data) {
763         if (data == null) {
764             return "\"\"";
765         }
766         try {
767             JSONStringer stringer = new JSONStringer();
768             stringer.open(JSONStringer.Scope.NULL, "");
769             stringer.value(data);
770             stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, "");
771             return stringer.toString();
772         } catch (JSONException e) {
773             throw new AssertionError();
774         }
775     }
776 
777     /**
778      * Wraps the given object if necessary.
779      *
780      * <p>If the object is null or , returns {@link #NULL}.
781      * If the object is a {@code JSONArray} or {@code JSONObject}, no wrapping is necessary.
782      * If the object is {@code NULL}, no wrapping is necessary.
783      * If the object is an array or {@code Collection}, returns an equivalent {@code JSONArray}.
784      * If the object is a {@code Map}, returns an equivalent {@code JSONObject}.
785      * If the object is a primitive wrapper type or {@code String}, returns the object.
786      * Otherwise if the object is from a {@code java} package, returns the result of {@code toString}.
787      * If wrapping fails, returns null.
788      */
wrap(Object o)789     public static Object wrap(Object o) {
790         if (o == null) {
791             return NULL;
792         }
793         if (o instanceof JSONArray || o instanceof JSONObject) {
794             return o;
795         }
796         if (o.equals(NULL)) {
797             return o;
798         }
799         try {
800             if (o instanceof Collection) {
801                 return new JSONArray((Collection) o);
802             } else if (o.getClass().isArray()) {
803                 return new JSONArray(o);
804             }
805             if (o instanceof Map) {
806                 return new JSONObject((Map) o);
807             }
808             if (o instanceof Boolean ||
809                 o instanceof Byte ||
810                 o instanceof Character ||
811                 o instanceof Double ||
812                 o instanceof Float ||
813                 o instanceof Integer ||
814                 o instanceof Long ||
815                 o instanceof Short ||
816                 o instanceof String) {
817                 return o;
818             }
819             if (o.getClass().getPackage().getName().startsWith("java.")) {
820                 return o.toString();
821             }
822         } catch (Exception ignored) {
823         }
824         return null;
825     }
826 }
827