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