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