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