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, 3rd edition</i> Item 19, "Design and Document for 86 * inheritance or else 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 {@link #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 */ accumulate(@onNull String name, @Nullable Object value)309 @NonNull public JSONObject accumulate(@NonNull String name, @Nullable Object value) throws JSONException { 310 Object current = nameValuePairs.get(checkName(name)); 311 if (current == null) { 312 return put(name, value); 313 } 314 315 if (current instanceof JSONArray) { 316 JSONArray array = (JSONArray) current; 317 array.checkedPut(value); 318 } else { 319 JSONArray array = new JSONArray(); 320 array.checkedPut(current); 321 array.checkedPut(value); 322 nameValuePairs.put(name, array); 323 } 324 return this; 325 } 326 327 /** 328 * Appends values to the array mapped to {@code name}. A new {@link JSONArray} 329 * mapping for {@code name} will be inserted if no mapping exists. If the existing 330 * mapping for {@code name} is not a {@link JSONArray}, a {@link JSONException} 331 * will be thrown. 332 * 333 * @throws JSONException if {@code name} is {@code null} or if the mapping for 334 * {@code name} is non-null and is not a {@link JSONArray}. 335 */ append(@onNull String name, @Nullable Object value)336 @NonNull public JSONObject append(@NonNull String name, @Nullable Object value) throws JSONException { 337 Object current = nameValuePairs.get(checkName(name)); 338 339 final JSONArray array; 340 if (current instanceof JSONArray) { 341 array = (JSONArray) current; 342 } else if (current == null) { 343 JSONArray newArray = new JSONArray(); 344 nameValuePairs.put(name, newArray); 345 array = newArray; 346 } else { 347 throw new JSONException("Key " + name + " is not a JSONArray"); 348 } 349 350 array.checkedPut(value); 351 352 return this; 353 } 354 355 @UnsupportedAppUsage checkName(String name)356 String checkName(String name) throws JSONException { 357 if (name == null) { 358 throw new JSONException("Names must be non-null"); 359 } 360 return name; 361 } 362 363 /** 364 * Removes the named mapping if it exists; does nothing otherwise. 365 * 366 * @return the value previously mapped by {@code name}, or null if there was 367 * no such mapping. 368 */ remove(@ullable String name)369 @Nullable public Object remove(@Nullable String name) { 370 return nameValuePairs.remove(name); 371 } 372 373 /** 374 * Returns true if this object has no mapping for {@code name} or if it has 375 * a mapping whose value is {@link #NULL}. 376 */ isNull(@ullable String name)377 public boolean isNull(@Nullable String name) { 378 Object value = nameValuePairs.get(name); 379 return value == null || value == NULL; 380 } 381 382 /** 383 * Returns true if this object has a mapping for {@code name}. The mapping 384 * may be {@link #NULL}. 385 */ has(@ullable String name)386 public boolean has(@Nullable String name) { 387 return nameValuePairs.containsKey(name); 388 } 389 390 /** 391 * Returns the value mapped by {@code name}, or throws if no such mapping exists. 392 * 393 * @throws JSONException if no such mapping exists. 394 */ get(@onNull String name)395 @NonNull public Object get(@NonNull String name) throws JSONException { 396 Object result = nameValuePairs.get(name); 397 if (result == null) { 398 throw new JSONException("No value for " + name); 399 } 400 return result; 401 } 402 403 /** 404 * Returns the value mapped by {@code name}, or null if no such mapping 405 * exists. 406 */ opt(@ullable String name)407 @Nullable public Object opt(@Nullable String name) { 408 return nameValuePairs.get(name); 409 } 410 411 /** 412 * Returns the value mapped by {@code name} if it exists and is a boolean or 413 * can be coerced to a boolean, or throws otherwise. 414 * 415 * @throws JSONException if the mapping doesn't exist or cannot be coerced 416 * to a boolean. 417 */ getBoolean(@onNull String name)418 public boolean getBoolean(@NonNull String name) throws JSONException { 419 Object object = get(name); 420 Boolean result = JSON.toBoolean(object); 421 if (result == null) { 422 throw JSON.typeMismatch(name, object, "boolean"); 423 } 424 return result; 425 } 426 427 /** 428 * Returns the value mapped by {@code name} if it exists and is a boolean or 429 * can be coerced to a boolean, or false otherwise. 430 */ optBoolean(@ullable String name)431 public boolean optBoolean(@Nullable String name) { 432 return optBoolean(name, false); 433 } 434 435 /** 436 * Returns the value mapped by {@code name} if it exists and is a boolean or 437 * can be coerced to a boolean, or {@code fallback} otherwise. 438 */ optBoolean(@ullable String name, boolean fallback)439 public boolean optBoolean(@Nullable String name, boolean fallback) { 440 Object object = opt(name); 441 Boolean result = JSON.toBoolean(object); 442 return result != null ? result : fallback; 443 } 444 445 /** 446 * Returns the value mapped by {@code name} if it exists and is a double or 447 * can be coerced to a double, or throws otherwise. 448 * 449 * @throws JSONException if the mapping doesn't exist or cannot be coerced 450 * to a double. 451 */ getDouble(@onNull String name)452 public double getDouble(@NonNull String name) throws JSONException { 453 Object object = get(name); 454 Double result = JSON.toDouble(object); 455 if (result == null) { 456 throw JSON.typeMismatch(name, object, "double"); 457 } 458 return result; 459 } 460 461 /** 462 * Returns the value mapped by {@code name} if it exists and is a double or 463 * can be coerced to a double, or {@code NaN} otherwise. 464 */ optDouble(@ullable String name)465 public double optDouble(@Nullable String name) { 466 return optDouble(name, Double.NaN); 467 } 468 469 /** 470 * Returns the value mapped by {@code name} if it exists and is a double or 471 * can be coerced to a double, or {@code fallback} otherwise. 472 */ optDouble(@ullable String name, double fallback)473 public double optDouble(@Nullable String name, double fallback) { 474 Object object = opt(name); 475 Double result = JSON.toDouble(object); 476 return result != null ? result : fallback; 477 } 478 479 /** 480 * Returns the value mapped by {@code name} if it exists and is an int or 481 * can be coerced to an int, or throws otherwise. 482 * 483 * @throws JSONException if the mapping doesn't exist or cannot be coerced 484 * to an int. 485 */ getInt(@onNull String name)486 public int getInt(@NonNull String name) throws JSONException { 487 Object object = get(name); 488 Integer result = JSON.toInteger(object); 489 if (result == null) { 490 throw JSON.typeMismatch(name, object, "int"); 491 } 492 return result; 493 } 494 495 /** 496 * Returns the value mapped by {@code name} if it exists and is an int or 497 * can be coerced to an int, or 0 otherwise. 498 */ optInt(@ullable String name)499 public int optInt(@Nullable String name) { 500 return optInt(name, 0); 501 } 502 503 /** 504 * Returns the value mapped by {@code name} if it exists and is an int or 505 * can be coerced to an int, or {@code fallback} otherwise. 506 */ optInt(@ullable String name, int fallback)507 public int optInt(@Nullable String name, int fallback) { 508 Object object = opt(name); 509 Integer result = JSON.toInteger(object); 510 return result != null ? result : fallback; 511 } 512 513 /** 514 * Returns the value mapped by {@code name} if it exists and is a long or 515 * can be coerced to a long, or throws otherwise. 516 * Note that JSON represents numbers as doubles, 517 * so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via JSON. 518 * 519 * @throws JSONException if the mapping doesn't exist or cannot be coerced 520 * to a long. 521 */ getLong(@onNull String name)522 public long getLong(@NonNull String name) throws JSONException { 523 Object object = get(name); 524 Long result = JSON.toLong(object); 525 if (result == null) { 526 throw JSON.typeMismatch(name, object, "long"); 527 } 528 return result; 529 } 530 531 /** 532 * Returns the value mapped by {@code name} if it exists and is a long or 533 * can be coerced to a long, or 0 otherwise. Note that JSON represents numbers as doubles, 534 * so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via JSON. 535 */ optLong(@ullable String name)536 public long optLong(@Nullable String name) { 537 return optLong(name, 0L); 538 } 539 540 /** 541 * Returns the value mapped by {@code name} if it exists and is a long or 542 * can be coerced to a long, or {@code fallback} otherwise. Note that JSON represents 543 * numbers as doubles, so this is <a href="#lossy">lossy</a>; use strings to transfer 544 * numbers via JSON. 545 */ optLong(@ullable String name, long fallback)546 public long optLong(@Nullable String name, long fallback) { 547 Object object = opt(name); 548 Long result = JSON.toLong(object); 549 return result != null ? result : fallback; 550 } 551 552 /** 553 * Returns the value mapped by {@code name} if it exists, coercing it if 554 * necessary, or throws if no such mapping exists. 555 * 556 * @throws JSONException if no such mapping exists. 557 */ getString(@onNull String name)558 @NonNull public String getString(@NonNull String name) throws JSONException { 559 Object object = get(name); 560 String result = JSON.toString(object); 561 if (result == null) { 562 throw JSON.typeMismatch(name, object, "String"); 563 } 564 return result; 565 } 566 567 /** 568 * Returns the value mapped by {@code name} if it exists, coercing it if 569 * necessary, or the empty string if no such mapping exists. 570 */ optString(@ullable String name)571 @NonNull public String optString(@Nullable String name) { 572 return optString(name, ""); 573 } 574 575 /** 576 * Returns the value mapped by {@code name} if it exists, coercing it if 577 * necessary, or {@code fallback} if no such mapping exists. 578 */ optString(@ullable String name, @NonNull String fallback)579 @NonNull public String optString(@Nullable String name, @NonNull String fallback) { 580 Object object = opt(name); 581 String result = JSON.toString(object); 582 return result != null ? result : fallback; 583 } 584 585 /** 586 * Returns the value mapped by {@code name} if it exists and is a {@code 587 * JSONArray}, or throws otherwise. 588 * 589 * @throws JSONException if the mapping doesn't exist or is not a {@code 590 * JSONArray}. 591 */ getJSONArray(@onNull String name)592 @NonNull public JSONArray getJSONArray(@NonNull String name) throws JSONException { 593 Object object = get(name); 594 if (object instanceof JSONArray) { 595 return (JSONArray) object; 596 } else { 597 throw JSON.typeMismatch(name, object, "JSONArray"); 598 } 599 } 600 601 /** 602 * Returns the value mapped by {@code name} if it exists and is a {@code 603 * JSONArray}, or null otherwise. 604 */ optJSONArray(@ullable String name)605 @Nullable public JSONArray optJSONArray(@Nullable String name) { 606 Object object = opt(name); 607 return object instanceof JSONArray ? (JSONArray) object : null; 608 } 609 610 /** 611 * Returns the value mapped by {@code name} if it exists and is a {@code 612 * JSONObject}, or throws otherwise. 613 * 614 * @throws JSONException if the mapping doesn't exist or is not a {@code 615 * JSONObject}. 616 */ getJSONObject(@onNull String name)617 @NonNull public JSONObject getJSONObject(@NonNull String name) throws JSONException { 618 Object object = get(name); 619 if (object instanceof JSONObject) { 620 return (JSONObject) object; 621 } else { 622 throw JSON.typeMismatch(name, object, "JSONObject"); 623 } 624 } 625 626 /** 627 * Returns the value mapped by {@code name} if it exists and is a {@code 628 * JSONObject}, or null otherwise. 629 */ optJSONObject(@ullable String name)630 @Nullable public JSONObject optJSONObject(@Nullable String name) { 631 Object object = opt(name); 632 return object instanceof JSONObject ? (JSONObject) object : null; 633 } 634 635 /** 636 * Returns an array with the values corresponding to {@code names}. The 637 * array contains null for names that aren't mapped. This method returns 638 * null if {@code names} is either null or empty. 639 */ toJSONArray(@ullable JSONArray names)640 @Nullable public JSONArray toJSONArray(@Nullable JSONArray names) throws JSONException { 641 JSONArray result = new JSONArray(); 642 if (names == null) { 643 return null; 644 } 645 int length = names.length(); 646 if (length == 0) { 647 return null; 648 } 649 for (int i = 0; i < length; i++) { 650 String name = JSON.toString(names.opt(i)); 651 result.put(opt(name)); 652 } 653 return result; 654 } 655 656 /** 657 * Returns an iterator of the {@code String} names in this object. The 658 * returned iterator supports {@link Iterator#remove() remove}, which will 659 * remove the corresponding mapping from this object. If this object is 660 * modified after the iterator is returned, the iterator's behavior is 661 * undefined. The order of the keys is undefined. 662 */ keys()663 @NonNull public Iterator<@NonNull String> keys() { 664 return nameValuePairs.keySet().iterator(); 665 } 666 667 /** 668 * Returns the set of {@code String} names in this object. The returned set 669 * is a view of the keys in this object. {@link Set#remove(Object)} will remove 670 * the corresponding mapping from this object and set iterator behaviour 671 * is undefined if this object is modified after it is returned. 672 * 673 * See {@link #keys()}. 674 * 675 * @return set of keys in this object 676 * 677 * @hide 678 */ 679 @UnsupportedAppUsage 680 @SystemApi(client = MODULE_LIBRARIES) keySet()681 @NonNull public Set<@NonNull 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