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