001/* 002 * Copyright (C) 2010 The Android Open Source Project 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.apache.tapestry5.json; 018 019import java.io.ObjectStreamException; 020import java.io.Serializable; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.LinkedHashMap; 024import java.util.Map; 025import java.util.Objects; 026import java.util.Set; 027import java.util.function.BiFunction; 028 029import org.apache.tapestry5.json.exceptions.JSONTypeMismatchException; 030import org.apache.tapestry5.json.exceptions.JSONValueNotFoundException; 031 032// Note: this class was written without inspecting the non-free org.json sourcecode. 033 034/** 035 * A modifiable set of name/value mappings. Names are unique, non-null strings. 036 * Values may be any mix of {@link JSONObject JSONObjects}, {@link JSONArray 037 * JSONArrays}, Strings, Booleans, Integers, Longs, Doubles or {@link #NULL}. 038 * Values may not be {@code null}, {@link Double#isNaN() NaNs}, {@link 039 * Double#isInfinite() infinities}, or of any type not listed here. 040 * 041 * <p>This class can coerce values to another type when requested. 042 * <ul> 043 * <li>When the requested type is a boolean, strings will be coerced using a 044 * case-insensitive comparison to "true" and "false". 045 * <li>When the requested type is a double, other {@link Number} types will 046 * be coerced using {@link Number#doubleValue() doubleValue}. Strings 047 * that can be coerced using {@link Double#valueOf(String)} will be. 048 * <li>When the requested type is an int, other {@link Number} types will 049 * be coerced using {@link Number#intValue() intValue}. Strings 050 * that can be coerced using {@link Double#valueOf(String)} will be, 051 * and then cast to int. 052 * <li><a href="#lossy">When the requested type is a long, other {@link Number} types will 053 * be coerced using {@link Number#longValue() longValue}. Strings 054 * that can be coerced using {@link Double#valueOf(String)} will be, 055 * and then cast to long. This two-step conversion is lossy for very 056 * large values. For example, the string "9223372036854775806" yields the 057 * long 9223372036854775807.</a> 058 * <li>When the requested type is a String, other non-null values will be 059 * coerced using {@link String#valueOf(Object)}. Although null cannot be 060 * coerced, the sentinel value {@link JSONObject#NULL} is coerced to the 061 * string "null". 062 * </ul> 063 * 064 * <p>This class can look up both mandatory and optional values: 065 * <ul> 066 * <li>Use <code>get<i>Type</i>()</code> to retrieve a mandatory value. This 067 * fails with a {@code RuntimeException} if the requested name has no value 068 * or if the value cannot be coerced to the requested type. 069 * <li>Use <code>opt()</code> to retrieve an optional value. 070 * </ul> 071 * 072 * <p><strong>Warning:</strong> this class represents null in two incompatible 073 * ways: the standard Java {@code null} reference, and the sentinel value {@link 074 * JSONObject#NULL}. In particular, calling {@code put(name, null)} removes the 075 * named entry from the object but {@code put(name, JSONObject.NULL)} stores an 076 * entry whose value is {@code JSONObject.NULL}. 077 * 078 * <p>Instances of this class are not thread safe. 079 */ 080public final class JSONObject extends JSONCollection implements Map<String, Object> { 081 082 private static final long serialVersionUID = 1L; 083 084 private static final Double NEGATIVE_ZERO = -0d; 085 086 /** 087 * A sentinel value used to explicitly define a name with no value. Unlike 088 * {@code null}, names with this value: 089 * <ul> 090 * <li>show up in the {@link #names} array 091 * <li>show up in the {@link #keys} iterator 092 * <li>return {@code true} for {@link #has(String)} 093 * <li>do not throw on {@link #get} 094 * <li>are included in the encoded JSON string. 095 * </ul> 096 * 097 * <p>This value violates the general contract of {@link Object#equals} by 098 * returning true when compared to {@code null}. Its {@link #toString} 099 * method returns "null". 100 */ 101 public static final Object NULL = new Serializable() { 102 103 private static final long serialVersionUID = 1L; 104 105 @Override 106 public boolean equals(Object o) { 107 return o == this || o == null; // API specifies this broken equals implementation 108 } 109 110 // at least make the broken equals(null) consistent with Objects.hashCode(null). 111 @Override 112 public int hashCode() { 113 return 0; 114 } 115 116 @Override 117 public String toString() { 118 return "null"; 119 } 120 121 // Serialization magic: after de-serializing, it will be back to the singleton instance of NULL. 122 private Object readResolve() throws ObjectStreamException 123 { 124 return NULL; 125 } 126 127 }; 128 129 private final LinkedHashMap<String, Object> nameValuePairs; 130 131 /** 132 * Creates a {@code JSONObject} with no name/value mappings. 133 */ 134 public JSONObject() { 135 nameValuePairs = new LinkedHashMap<String, Object>(); 136 } 137 138 /** 139 * Creates a new {@code JSONObject} with name/value mappings from the next 140 * object in the tokener. 141 * 142 * @param readFrom a tokener whose nextValue() method will yield a 143 * {@code JSONObject}. 144 * @throws RuntimeException if the parse fails or doesn't yield a 145 * {@code JSONObject}. 146 */ 147 JSONObject(JSONTokener readFrom) { 148 /* 149 * Getting the parser to populate this could get tricky. Instead, just 150 * parse to temporary JSONObject and then steal the data from that. 151 */ 152 Object object = readFrom.nextValue(JSONObject.class); 153 if (object instanceof JSONObject) { 154 this.nameValuePairs = ((JSONObject) object).nameValuePairs; 155 } else { 156 throw JSONExceptionBuilder.tokenerTypeMismatch(object, JSONType.OBJECT); 157 } 158 } 159 160 /** 161 * Creates a new {@code JSONObject} with name/value mappings from the JSON 162 * string. 163 * 164 * @param json a JSON-encoded string containing an object. 165 * @throws RuntimeException if the parse fails or doesn't yield a {@code 166 * JSONObject}. 167 */ 168 public JSONObject(String json) { 169 this(new JSONTokener(json)); 170 } 171 172 /** 173 * Creates a new {@code JSONObject} by copying mappings for the listed names 174 * from the given object. Names that aren't present in {@code copyFrom} will 175 * be skipped. 176 * 177 * @param copyFrom The source object. 178 * @param names The names of the fields to copy. 179 * @throws RuntimeException On internal errors. Shouldn't happen. 180 */ 181 public JSONObject(JSONObject copyFrom, String... names) { 182 this(); 183 for (String name : names) { 184 Object value = copyFrom.opt(name); 185 if (value != null) { 186 nameValuePairs.put(name, value); 187 } 188 } 189 } 190 191 192 /** 193 * Returns a new JSONObject that is a shallow copy of this JSONObject. 194 * 195 * @since 5.4 196 */ 197 public JSONObject copy() 198 { 199 JSONObject dupe = new JSONObject(); 200 dupe.nameValuePairs.putAll(nameValuePairs); 201 202 return dupe; 203 } 204 205 /** 206 * Constructs a new JSONObject using a series of String keys and object values. 207 * Object values should be compatible with {@link #put(String, Object)}. Keys must be strings 208 * (toString() will be invoked on each key). 209 * 210 * Prior to release 5.4, keysAndValues was type String...; changing it to Object... makes 211 * it much easier to initialize a JSONObject in a single statement, which is more readable. 212 * 213 * @since 5.2.0 214 */ 215 public JSONObject(Object... keysAndValues) 216 { 217 this(); 218 219 int i = 0; 220 221 while (i < keysAndValues.length) 222 { 223 put(keysAndValues[i++].toString(), keysAndValues[i++]); 224 } 225 } 226 227 /** 228 * Returns the number of name/value mappings in this object. 229 * 230 * @deprecated Use {@link #size()} instead. 231 * @return the length of this. 232 */ 233 @Deprecated 234 public int length() { 235 return nameValuePairs.size(); 236 } 237 238 /** 239 * Maps {@code name} to {@code value}, clobbering any existing name/value 240 * mapping with the same name. If the value is {@code null}, any existing 241 * mapping for {@code name} is removed. 242 * 243 * @param name The name of the new value. 244 * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, 245 * Integer, Long, Double, {@link #NULL}, or {@code null}. May not be 246 * {@link Double#isNaN() NaNs} or {@link Double#isInfinite() 247 * infinities}. 248 * @return this object. 249 * @throws IllegalArgumentException if the value is an invalid double (infinite or NaN). 250 */ 251 @Override 252 public JSONObject put(String name, Object value) { 253 if (value == null) { 254 nameValuePairs.remove(name); 255 return this; 256 } 257 JSON.testValidity(value); 258 if (value instanceof Number) { 259 // deviate from the original by checking all Numbers, not just floats & doubles 260 JSON.checkDouble(((Number) value).doubleValue()); 261 } 262 nameValuePairs.put(checkName(name), value); 263 return this; 264 } 265 266 /** 267 * Appends {@code value} to the array already mapped to {@code name}. If 268 * this object has no mapping for {@code name}, this inserts a new mapping. 269 * If the mapping exists but its value is not an array, the existing 270 * and new values are inserted in order into a new array which is itself 271 * mapped to {@code name}. In aggregate, this allows values to be added to a 272 * mapping one at a time. 273 * 274 * Note that {@code append(String, Object)} provides better semantics. 275 * In particular, the mapping for {@code name} will <b>always</b> be a 276 * {@link JSONArray}. Using {@code accumulate} will result in either a 277 * {@link JSONArray} or a mapping whose type is the type of {@code value} 278 * depending on the number of calls to it. 279 * 280 * @param name The name of the field to change. 281 * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, 282 * Integer, Long, Double, {@link #NULL} or null. May not be {@link 283 * Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}. 284 * @return this object after mutation. 285 * @throws RuntimeException If the object being added is an invalid number. 286 */ 287 // TODO: Change {@code append) to {@link #append} when append is 288 // unhidden. 289 public JSONObject accumulate(String name, Object value) { 290 Object current = nameValuePairs.get(checkName(name)); 291 if (current == null) { 292 return put(name, value); 293 } 294 295 if (current instanceof JSONArray) { 296 JSONArray array = (JSONArray) current; 297 array.checkedPut(value); 298 } else { 299 JSONArray array = new JSONArray(); 300 array.checkedPut(current); 301 array.checkedPut(value); 302 nameValuePairs.put(name, array); 303 } 304 return this; 305 } 306 307 /** 308 * Appends values to the array mapped to {@code name}. A new {@link JSONArray} 309 * mapping for {@code name} will be inserted if no mapping exists. If the existing 310 * mapping for {@code name} is not a {@link JSONArray}, a {@link RuntimeException} 311 * will be thrown. 312 * 313 * @param name The name of the array to which the value should be appended. 314 * @param value The value to append. 315 * @return this object. 316 * @throws JSONTypeMismatchException if {@code name} is {@code null} or if the mapping for 317 * {@code name} is non-null and is not a {@link JSONArray}. 318 */ 319 public JSONObject append(String name, Object value) { 320 JSON.testValidity(value); 321 Object current = nameValuePairs.get(checkName(name)); 322 323 final JSONArray array; 324 if (current instanceof JSONArray) { 325 array = (JSONArray) current; 326 } else if (current == null) { 327 JSONArray newArray = new JSONArray(); 328 nameValuePairs.put(name, newArray); 329 array = newArray; 330 } else { 331 throw new JSONTypeMismatchException("JSONObject[\"" + name + "\"]", JSONType.ARRAY, current.getClass()); 332 } 333 334 array.checkedPut(value); 335 336 return this; 337 } 338 339 String checkName(String name) { 340 if (name == null) { 341 throw new RuntimeException("Names must be non-null"); 342 } 343 return name; 344 } 345 346 /** 347 * Returns true if this object has no mapping for {@code name} or if it has 348 * a mapping whose value is {@link #NULL}. 349 * 350 * @param name The name of the value to check on. 351 * @return true if the field doesn't exist or is null. 352 */ 353 public boolean isNull(String name) { 354 Object value = nameValuePairs.get(name); 355 return value == null || value == NULL; 356 } 357 358 /** 359 * Returns true if this object has a mapping for {@code name}. The mapping 360 * may be {@link #NULL}. 361 * 362 * @deprecated use {@link #containsKey(Object)} instead 363 * @param name 364 * The name of the value to check on. 365 * @return true if this object has a field named {@code name} 366 */ 367 @Deprecated 368 public boolean has(String name) { 369 return containsKey(name); 370 } 371 372 /** 373 * Returns the value mapped by {@code name}, or null if no such mapping 374 * exists. 375 * 376 * @param name The name of the value to get. 377 * @return The value. 378 */ 379 public Object opt(Object name) { 380 return nameValuePairs.get(name); 381 } 382 383 /** 384 * Returns the value mapped by {@code name} if it exists and is a boolean or 385 * can be coerced to a boolean, or throws otherwise. 386 * 387 * @param name The name of the field we want. 388 * @return The selected value if it exists. 389 * @throws JSONValueNotFoundException if the mapping doesn't exist 390 * @throws JSONTypeMismatchException if the mapping cannot be coerced 391 * to a boolean. 392 */ 393 public boolean getBoolean(String name) { 394 Object object = opt(name); 395 if (object == null) { 396 throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.BOOLEAN); 397 } 398 Boolean result = JSON.toBoolean(object); 399 if (result == null) { 400 throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.BOOLEAN); 401 } 402 return result; 403 } 404 405 /** 406 * Returns the value to which the specified key is mapped and a boolean, or 407 * {@code defaultValue} if this JSONObject contains no mapping for the key. 408 * 409 * @param name the key whose associated value is to be returned 410 * @param defaultValue the default mapping of the key 411 * @return the value to which the specified key is mapped, or 412 * {@code defaultValue} if this JSONObject contains no mapping for the key 413 * @throws JSONTypeMismatchException if the mapping cannot be coerced 414 * to a boolean. 415 * @since 5.7 416 */ 417 public boolean getBooleanOrDefault(String name, boolean defaultValue) 418 { 419 Object object = opt(name); 420 if (object == null) 421 { 422 return defaultValue; 423 } 424 Boolean result = JSON.toBoolean(object); 425 if (result == null) 426 { 427 throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.BOOLEAN); 428 } 429 return result; 430 } 431 432 /** 433 * Returns the value mapped by {@code name} if it exists and is a double or 434 * can be coerced to a double, or throws otherwise. 435 * 436 * @param name The name of the field we want. 437 * @return The selected value if it exists. 438 * @throws JSONValueNotFoundException if the mapping doesn't exist 439 * @throws JSONTypeMismatchException if the mapping cannot be coerced 440 * to a double. 441 */ 442 public double getDouble(String name) { 443 Object object = opt(name); 444 if (object == null) { 445 throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.NUMBER); 446 } 447 Double result = JSON.toDouble(object); 448 if (result == null) { 449 throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.NUMBER); 450 } 451 return result; 452 } 453 454 /** 455 * Returns the value mapped by {@code name} if it exists and is an int or 456 * can be coerced to an int, or throws otherwise. 457 * 458 * @param name The name of the field we want. 459 * @return The selected value if it exists. 460 * @throws JSONValueNotFoundException if the mapping doesn't exist 461 * @throws JSONTypeMismatchException if the mapping cannot be coerced 462 * to an int. 463 */ 464 public int getInt(String name) { 465 Object object = opt(name); 466 if (object == null) { 467 throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.NUMBER); 468 } 469 Integer result = JSON.toInteger(object); 470 if (result == null) { 471 throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.NUMBER); 472 } 473 return result; 474 } 475 476 /** 477 * Returns the value to which the specified key is mapped and an int, or 478 * {@code defaultValue} if this JSONObject contains no mapping for the key. 479 * 480 * @param name the key whose associated value is to be returned 481 * @param defaultValue the default mapping of the key 482 * @return the value to which the specified key is mapped, or 483 * {@code defaultValue} if this JSONObject contains no mapping for the key 484 * @throws JSONTypeMismatchException if the mapping cannot be coerced 485 * to an int. 486 * @since 5.7 487 */ 488 public int getIntOrDefault(String name, int defaultValue) 489 { 490 Object object = opt(name); 491 if (object == null) 492 { 493 return defaultValue; 494 } 495 Integer result = JSON.toInteger(object); 496 if (result == null) 497 { 498 throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.NUMBER); 499 } 500 return result; 501 } 502 503 /** 504 * Returns the value mapped by {@code name} if it exists and is a long or 505 * can be coerced to a long, or throws otherwise. 506 * Note that JSON represents numbers as doubles, 507 * 508 * so this is <a href="#lossy">lossy</a>; use strings to transfer numbers 509 * via JSON without loss. 510 * 511 * @param name The name of the field that we want. 512 * @return The value of the field. 513 * @throws JSONValueNotFoundException if the mapping doesn't exist 514 * @throws JSONTypeMismatchException if the mapping cannot be coerced 515 * to a long. 516 */ 517 public long getLong(String name) { 518 Object object = opt(name); 519 if (object == null) { 520 throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.NUMBER); 521 } 522 Long result = JSON.toLong(object); 523 if (result == null) { 524 throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.NUMBER); 525 } 526 return result; 527 } 528 529 /** 530 * Returns the value to which the specified key is mapped and a long, or 531 * {@code defaultValue} if this JSONObject contains no mapping for the key. 532 * 533 * @param name the key whose associated value is to be returned 534 * @param defaultValue the default mapping of the key 535 * @return the value to which the specified key is mapped, or 536 * {@code defaultValue} if this JSONObject contains no mapping for the key 537 * @throws JSONTypeMismatchException if the mapping cannot be coerced 538 * to a long. 539 * @since 5.7 540 */ 541 public long getLongOrDefault(String name, int defaultValue) 542 { 543 Object object = opt(name); 544 if (object == null) 545 { 546 return defaultValue; 547 } 548 Long result = JSON.toLong(object); 549 if (result == null) 550 { 551 throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.NUMBER); 552 } 553 return result; 554 } 555 556 /** 557 * Returns the value mapped by {@code name} if it exists, coercing it if 558 * necessary, or throws if no such mapping exists. 559 * 560 * @param name The name of the field we want. 561 * @return The value of the field. 562 * @throws JSONValueNotFoundException if the mapping doesn't exist 563 * @throws JSONTypeMismatchException if the mapping cannot be coerced 564 * to String 565 */ 566 public String getString(String name) { 567 Object object = opt(name); 568 if (object == null) { 569 throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.STRING); 570 } 571 String result = JSON.toString(object); 572 if (result == null) { 573 throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.STRING); 574 } 575 return result; 576 } 577 578 /** 579 * Returns the value to which the specified key is mapped and a string, or 580 * {@code defaultValue} if this JSONObject contains no mapping for the key. 581 * 582 * @param name the key whose associated value is to be returned 583 * @param defaultValue the default mapping of the key 584 * @return the value to which the specified key is mapped, or 585 * {@code defaultValue} if this JSONObject contains no mapping for the key 586 * @throws JSONTypeMismatchException if the mapping cannot be coerced 587 * to a string. 588 * @since 5.7 589 */ 590 public String getStringOrDefault(String name, String defaultValue) 591 { 592 Object object = opt(name); 593 if (object == null) 594 { 595 return defaultValue; 596 } 597 String result = JSON.toString(object); 598 if (result == null) 599 { 600 throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.STRING); 601 } 602 return result; 603 } 604 605 /** 606 * Returns the value mapped by {@code name} if it exists and is a {@code 607 * JSONArray}, or throws otherwise. 608 * 609 * @param name The field we want to get. 610 * @return The value of the field (if it is a JSONArray. 611 * @throws JSONValueNotFoundException if the mapping doesn't exist 612 * @throws JSONTypeMismatchException if the mapping is not a {@code 613 * JSONArray}. 614 */ 615 public JSONArray getJSONArray(String name) { 616 Object object = opt(name); 617 if (object == null) { 618 throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.ARRAY); 619 } 620 if (object instanceof JSONArray) { 621 return (JSONArray) object; 622 } else { 623 throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.ARRAY); 624 } 625 } 626 627 /** 628 * Returns the value to which the specified key is mapped and a JSONArray, or 629 * {@code defaultValue} if this JSONObject contains no mapping for the key. 630 * 631 * @param name the key whose associated value is to be returned 632 * @param defaultValue the default mapping of the key 633 * @return the value to which the specified key is mapped, or 634 * {@code defaultValue} if this JSONObject contains no mapping for the key 635 * @throws JSONTypeMismatchException if the mapping cannot be coerced 636 * to a JSONArray. 637 * @since 5.7 638 */ 639 public JSONArray getJSONArrayOrDefault(String name, JSONArray defaultValue) 640 { 641 Object object = opt(name); 642 if (object == null) 643 { 644 return defaultValue; 645 } 646 if (object instanceof JSONArray) { 647 return (JSONArray) object; 648 } else { 649 throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.ARRAY); 650 } 651 } 652 653 /** 654 * Returns the value mapped by {@code name} if it exists and is a {@code 655 * JSONObject}, or throws otherwise. 656 * 657 * @param name The name of the field that we want. 658 * @return a specified field value (if it is a JSONObject) 659 * @throws JSONValueNotFoundException if the mapping doesn't exist 660 * @throws JSONTypeMismatchException if the mapping is not a {@code 661 * JSONObject}. 662 */ 663 public JSONObject getJSONObject(String name) { 664 Object object = opt(name); 665 if (object == null) { 666 throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.OBJECT); 667 } 668 if (object instanceof JSONObject) { 669 return (JSONObject) object; 670 } else { 671 throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.OBJECT); 672 } 673 } 674 675 676 /** 677 * Returns the value to which the specified key is mapped and a JSONObject, or 678 * {@code defaultValue} if this map contains no mapping for the key. 679 * 680 * @param name the key whose associated value is to be returned 681 * @param defaultValue the default mapping of the key 682 * @return the value to which the specified key is mapped, or 683 * {@code defaultValue} if this map contains no mapping for the key 684 * @throws JSONTypeMismatchException if the mapping cannot be coerced 685 * to a JSONObject. 686 * @since 5.7 687 */ 688 public JSONObject getJSONObjectOrDefault(String name, JSONObject defaultValue) 689 { 690 Object object = opt(name); 691 if (object == null) 692 { 693 return defaultValue; 694 } 695 if (object instanceof JSONObject) { 696 return (JSONObject) object; 697 } else { 698 throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.OBJECT); 699 } 700 } 701 /** 702 * Returns the set of {@code String} names in this object. The returned set 703 * is a view of the keys in this object. {@link Set#remove(Object)} will remove 704 * the corresponding mapping from this object and set iterator behaviour 705 * is undefined if this object is modified after it is returned. 706 * 707 * See {@link #keys()}. 708 * 709 * @return The names in this object. 710 */ 711 public Set<String> keys() { 712 return nameValuePairs.keySet(); 713 } 714 715 /** 716 * Returns an array containing the string names in this object. This method 717 * returns null if this object contains no mappings. 718 * 719 * @return the names. 720 */ 721 public JSONArray names() { 722 return nameValuePairs.isEmpty() 723 ? null 724 : JSONArray.from(nameValuePairs.keySet()); 725 } 726 727 /** 728 * Encodes the number as a JSON string. 729 * 730 * @param number a finite value. May not be {@link Double#isNaN() NaNs} or 731 * {@link Double#isInfinite() infinities}. 732 * @return The encoded number in string form. 733 * @throws RuntimeException On internal errors. Shouldn't happen. 734 */ 735 public static String numberToString(Number number) { 736 if (number == null) { 737 throw new RuntimeException("Number must be non-null"); 738 } 739 740 double doubleValue = number.doubleValue(); 741 JSON.checkDouble(doubleValue); 742 743 // the original returns "-0" instead of "-0.0" for negative zero 744 if (number.equals(NEGATIVE_ZERO)) { 745 return "-0"; 746 } 747 748 long longValue = number.longValue(); 749 if (doubleValue == (double) longValue) { 750 return Long.toString(longValue); 751 } 752 753 return number.toString(); 754 } 755 756 static String doubleToString(double d) 757 { 758 if (Double.isInfinite(d) || Double.isNaN(d)) 759 { 760 return "null"; 761 } 762 763 return numberToString(d); 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 * @return the quoted string. 773 */ 774 public static String quote(String data) { 775 if (data == null) { 776 return "\"\""; 777 } 778 try { 779 JSONStringer stringer = new JSONStringer(); 780 stringer.open(JSONStringer.Scope.NULL, ""); 781 stringer.string(data); 782 stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, ""); 783 return stringer.toString(); 784 } catch (RuntimeException e) { 785 throw new AssertionError(); 786 } 787 } 788 789 790 791 /** 792 * Prints the JSONObject using the session. 793 * 794 * @since 5.2.0 795 */ 796 @Override 797 void print(JSONPrintSession session) 798 { 799 session.printSymbol('{'); 800 801 session.indent(); 802 803 boolean comma = false; 804 805 for (String key : keys()) 806 { 807 if (comma) 808 session.printSymbol(','); 809 810 session.newline(); 811 812 session.printQuoted(key); 813 814 session.printSymbol(':'); 815 816 printValue(session, nameValuePairs.get(key)); 817 818 comma = true; 819 } 820 821 session.outdent(); 822 823 if (comma) 824 session.newline(); 825 826 session.printSymbol('}'); 827 } 828 829 830 /** 831 * Prints a value (a JSONArray or JSONObject, or a value stored in an array or object) using 832 * the session. 833 * 834 * @since 5.2.0 835 */ 836 static void printValue(JSONPrintSession session, Object value) 837 { 838 839 if (value == null || value == NULL) 840 { 841 session.print("null"); 842 return; 843 } 844 if (value instanceof JSONObject) 845 { 846 ((JSONObject) value).print(session); 847 return; 848 } 849 850 if (value instanceof JSONArray) 851 { 852 ((JSONArray) value).print(session); 853 return; 854 } 855 856 if (value instanceof JSONString) 857 { 858 String printValue = ((JSONString) value).toJSONString(); 859 860 session.print(printValue); 861 862 return; 863 } 864 865 if (value instanceof Number) 866 { 867 String printValue = numberToString((Number) value); 868 session.print(printValue); 869 return; 870 } 871 872 if (value instanceof Boolean) 873 { 874 session.print(value.toString()); 875 876 return; 877 } 878 879 // Otherwise it really should just be a string. Nothing else can go in. 880 session.printQuoted(value.toString()); 881 } 882 883 public boolean equals(Object obj) 884 { 885 if (obj == null) 886 return false; 887 888 if (!(obj instanceof JSONObject)) 889 return false; 890 891 JSONObject other = (JSONObject) obj; 892 893 return nameValuePairs.equals(other.nameValuePairs); 894 } 895 896 /** 897 * Returns a Map of the keys and values of the JSONObject. The returned map is unmodifiable. 898 * Note that changes to the JSONObject will be reflected in the map. In addition, null values in the JSONObject 899 * are represented as {@link JSONObject#NULL} in the map. 900 * 901 * @return unmodifiable map of properties and values 902 * @since 5.4 903 */ 904 public Map<String, Object> toMap() 905 { 906 return Collections.unmodifiableMap(nameValuePairs); 907 } 908 909 /** 910 * Navigates into a nested JSONObject, creating the JSONObject if necessary. They key must not exist, 911 * or must be a JSONObject. 912 * 913 * @param key 914 * @return the nested JSONObject 915 * @throws IllegalStateException 916 * if the current value for the key is not null and not JSONObject 917 */ 918 public JSONObject in(String key) 919 { 920 assert key != null; 921 922 Object nested = nameValuePairs.get(key); 923 924 if (nested != null && !(nested instanceof JSONObject)) 925 { 926 throw new IllegalStateException(String.format("JSONObject[%s] is not a JSONObject.", quote(key))); 927 } 928 929 if (nested == null) 930 { 931 nested = new JSONObject(); 932 nameValuePairs.put(key, nested); 933 } 934 935 return (JSONObject) nested; 936 } 937 938 /** 939 * Returns the number of key-value mappings in this JSONObject. 940 * If it contains more than {@code Integer.MAX_VALUE} elements, returns 941 * {@code Integer.MAX_VALUE}. 942 * 943 * @return the number of key-value mappings in this JSONObject 944 * @since 5.7 945 */ 946 @Override 947 public int size() 948 { 949 return nameValuePairs.size(); 950 } 951 952 /** 953 * Returns {@code true} if this JSONObject contains no key-value mappings. 954 * 955 * @return {@code true} if this JSONObject contains no key-value mappings 956 * @since 5.7 957 */ 958 @Override 959 public boolean isEmpty() 960 { 961 return nameValuePairs.isEmpty(); 962 } 963 964 /** 965 * Returns {@code true} if this JSONObject contains a mapping for the specified 966 * key. 967 * 968 * @param key 969 * key whose presence in this map is to be tested 970 * @return {@code true} if this map contains a mapping for the specified 971 * key 972 * @since 5.7 973 */ 974 @Override 975 public boolean containsKey(Object key) 976 { 977 return nameValuePairs.containsKey(key); 978 } 979 980 /** 981 * Returns {@code true} if this JSONObject maps one or more keys to the 982 * specified value. 983 * 984 * @param value value whose presence in this map is to be tested 985 * @return {@code true} if this JSONObject maps one or more keys to the 986 * specified value 987 * @since 5.7 988 */ 989 @Override 990 public boolean containsValue(Object value) 991 { 992 return nameValuePairs.containsValue(value); 993 } 994 995 /** 996 * Returns the value mapped by {@code name}, or throws if no such mapping exists. 997 * 998 * @param name The name of the value to get. 999 * @return The value. 1000 * @throws JSONValueNotFoundException if no such mapping exists. 1001 */ @Override 1002 public Object get(Object name) 1003 { 1004 Object result = nameValuePairs.get(name); 1005 if (result == null) { 1006 throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.ANY); 1007 } 1008 return result; 1009 } 1010 1011 /** 1012 * Returns the value to which the specified key is mapped, or 1013 * {@code defaultValue} if this JSONObject contains no mapping for the key. 1014 * 1015 * @param key the key whose associated value is to be returned 1016 * @param defaultValue the default mapping of the key 1017 * @return the value to which the specified key is mapped, or 1018 * {@code defaultValue} if this JSONObject contains no mapping for the key 1019 * @since 5.7 1020 */ 1021 @Override 1022 public Object getOrDefault(Object key, Object defaultValue) 1023 { 1024 Object value = opt(key); 1025 return value == null ? defaultValue : value; 1026 } 1027 1028 /** 1029 * Removes the named mapping if it exists; does nothing otherwise. 1030 * 1031 * @param name The name of the mapping to remove. 1032 * @return the value previously mapped by {@code name}, or null if there was 1033 * no such mapping. 1034 */ 1035 @Override 1036 public Object remove(Object name) 1037 { 1038 return nameValuePairs.remove(name); 1039 } 1040 1041 /** 1042 * Invokes {@link #put(String, Object)} for each value from the map. 1043 * 1044 * @param newProperties 1045 * to add to this JSONObject 1046 * @since 5.7 1047 */ 1048 @Override 1049 public void putAll(Map<? extends String, ? extends Object> newProperties) 1050 { 1051 assert newProperties != null; 1052 1053 for (Map.Entry<? extends String, ? extends Object> e : newProperties.entrySet()) 1054 { 1055 put(e.getKey(), e.getValue()); 1056 } 1057 } 1058 1059 /** 1060 * Removes all of the mappings from this JSONObject. 1061 * 1062 * @since 5.7 1063 */ 1064 @Override 1065 public void clear() 1066 { 1067 nameValuePairs.clear(); 1068 } 1069 1070 /** 1071 * Returns a {@link Set} view of the keys contained in this JSONObject. 1072 * The set is backed by the JSONObject, so changes to the map are 1073 * reflected in the set, and vice-versa. 1074 * 1075 * @return a set view of the keys contained in this JSONObject 1076 * @since 5.7 1077 */ 1078 @Override 1079 public Set<String> keySet() 1080 { 1081 return nameValuePairs.keySet(); 1082 } 1083 1084 /** 1085 * Returns a {@link Collection} view of the values contained in this JSONObject. 1086 * The collection is backed by the JSONObject, so changes to the map are 1087 * reflected in the collection, and vice-versa. 1088 * 1089 * @return a collection view of the values contained in this JSONObject 1090 * @since 5.7 1091 */ 1092 @Override 1093 public Collection<Object> values() 1094 { 1095 return nameValuePairs.values(); 1096 } 1097 1098 /** 1099 * Returns a {@link Set} view of the mappings contained in this JSONObject. 1100 * The set is backed by the JSONObject, so changes to the map are 1101 * reflected in the set, and vice-versa. 1102 * 1103 * @return a set view of the mappings contained in this JSONObject 1104 * @since 5.7 1105 */ 1106 @Override 1107 public Set<Entry<String, Object>> entrySet() 1108 { 1109 return nameValuePairs.entrySet(); 1110 } 1111 1112 /** 1113 * If the specified key is not already associated with a value or is 1114 * associated with null, associates it with the given non-null value. 1115 * 1116 * Otherwise, replaces the associated value with the results of the given 1117 * remapping function, or removes if the result is {@code null}. 1118 * 1119 * @param key 1120 * key with which the resulting value is to be associated 1121 * @param value 1122 * the non-null value to be merged with the existing value 1123 * associated with the key or, if no existing value or a null value 1124 * is associated with the key, to be associated with the key 1125 * @param remappingFunction 1126 * the function to recompute a value if present 1127 * @return the new value associated with the specified key, or null if no 1128 * value is associated with the key 1129 * @throws NullPointerException 1130 * if the specified key is null or the value or remappingFunction 1131 * is null 1132 * @since 5.7 1133 */ 1134 @Override 1135 public Object merge(String key, Object value, 1136 BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) 1137 { 1138 // We need to override the merge method due to the default implementation using 1139 // #get(String) to check for the key, which will throw a {@code JSONValueNotFoundException} 1140 // if not found. 1141 1142 Objects.requireNonNull(remappingFunction); 1143 Objects.requireNonNull(value); 1144 1145 Object oldValue = opt(key); 1146 Object newValue = (oldValue == null) ? value : remappingFunction.apply(oldValue, value); 1147 1148 put(key, newValue); 1149 1150 return newValue; 1151 } 1152 1153}