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