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