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, long 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 779 StringBuilder out = new StringBuilder(data.length() + 8); 780 out.append('"'); 781 escapeInto(out, data); 782 out.append('"'); 783 return out.toString(); 784 } 785 786 private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); 787 788 /** 789 * Appends the escaped (but not quoted) form of {@code value} to {@code out}: the same 790 * character escaping rules as {@link #quote(String)}, without allocating an intermediate 791 * String or wrapping quotes. 792 * 793 * <p>This is the direct-to-{@link StringBuilder} fast path used by {@code quote()} itself, 794 * which always targets a StringBuilder and is called often enough (once per printed string/key) 795 * that the {@link JSONPrintSink} indirection used by {@link #escapeInto(JSONPrintSink, String)} 796 * measurably costs more than it's worth here. See {@link #escapeInto(JSONPrintSink, String)} 797 * for the print-session variant. 798 */ 799 static void escapeInto(StringBuilder out, String value) { 800 char currentChar = 0; 801 802 for (int i = 0, length = value.length(); i < length; i++) { 803 char previousChar = currentChar; 804 currentChar = value.charAt(i); 805 806 /* 807 * From RFC 4627, "All Unicode characters may be placed within the 808 * quotation marks except for the characters that must be escaped: 809 * quotation mark, reverse solidus, and the control characters 810 * (U+0000 through U+001F)." 811 */ 812 switch (currentChar) { 813 case '"': 814 case '\\': 815 out.append('\\').append(currentChar); 816 break; 817 818 case '/': 819 // it makes life easier for HTML embedding of javascript if we escape </ sequences 820 if (previousChar == '<') { 821 out.append('\\'); 822 } 823 out.append(currentChar); 824 break; 825 826 case '\t': 827 out.append("\\t"); 828 break; 829 830 case '\b': 831 out.append("\\b"); 832 break; 833 834 case '\n': 835 out.append("\\n"); 836 break; 837 838 case '\r': 839 out.append("\\r"); 840 break; 841 842 case '\f': 843 out.append("\\f"); 844 break; 845 846 default: 847 if (currentChar <= 0x1F || (currentChar >= 0x0080 && currentChar < 0x00a0) || (currentChar >= 0x2000 && currentChar < 0x2100)) { 848 out.append('\\').append('u') 849 .append(HEX_DIGITS[(currentChar >> 12) & 0xF]) 850 .append(HEX_DIGITS[(currentChar >> 8) & 0xF]) 851 .append(HEX_DIGITS[(currentChar >> 4) & 0xF]) 852 .append(HEX_DIGITS[currentChar & 0xF]); 853 } else { 854 out.append(currentChar); 855 } 856 break; 857 } 858 } 859 } 860 861 /** 862 * Same escaping rules as {@link #escapeInto(StringBuilder, String)}, targeting a 863 * {@link JSONPrintSink} instead. 864 * 865 * <p>Used by the print sessions, whose destination varies between an in-memory 866 * {@link StringBuilder} ({@code toString()}/{@code toCompactString()}) and a 867 * caller-supplied {@link java.io.PrintWriter} ({@code print(PrintWriter, boolean)}), 868 * so they can escape directly into whichever one they were constructed with. 869 * 870 * <p>Note: Having dedicated methods for the types instead of a classifying mechanism 871 * keeps the call site monomorphic for the JVM, allowing for greater optimization 872 * potential. 873 */ 874 static void escapeInto(JSONPrintSink out, String value) { 875 char currentChar = 0; 876 877 for (int i = 0, length = value.length(); i < length; i++) { 878 char previousChar = currentChar; 879 currentChar = value.charAt(i); 880 881 switch (currentChar) { 882 case '"': 883 case '\\': 884 out.append('\\').append(currentChar); 885 break; 886 887 case '/': 888 if (previousChar == '<') { 889 out.append('\\'); 890 } 891 out.append(currentChar); 892 break; 893 894 case '\t': 895 out.append("\\t"); 896 break; 897 898 case '\b': 899 out.append("\\b"); 900 break; 901 902 case '\n': 903 out.append("\\n"); 904 break; 905 906 case '\r': 907 out.append("\\r"); 908 break; 909 910 case '\f': 911 out.append("\\f"); 912 break; 913 914 default: 915 if (currentChar <= 0x1F || (currentChar >= 0x0080 && currentChar < 0x00a0) || (currentChar >= 0x2000 && currentChar < 0x2100)) { 916 out.append('\\').append('u') 917 .append(HEX_DIGITS[(currentChar >> 12) & 0xF]) 918 .append(HEX_DIGITS[(currentChar >> 8) & 0xF]) 919 .append(HEX_DIGITS[(currentChar >> 4) & 0xF]) 920 .append(HEX_DIGITS[currentChar & 0xF]); 921 } else { 922 out.append(currentChar); 923 } 924 break; 925 } 926 } 927 } 928 929 930 931 /** 932 * Prints the JSONObject using the session. 933 * 934 * @since 5.2.0 935 */ 936 @Override 937 void print(JSONPrintSession session) 938 { 939 session.printSymbol('{'); 940 941 session.indent(); 942 943 boolean comma = false; 944 945 for (String key : keys()) 946 { 947 if (comma) 948 session.printSymbol(','); 949 950 session.newline(); 951 952 session.printQuoted(key); 953 954 session.printSymbol(':'); 955 956 printValue(session, nameValuePairs.get(key)); 957 958 comma = true; 959 } 960 961 session.outdent(); 962 963 if (comma) 964 session.newline(); 965 966 session.printSymbol('}'); 967 } 968 969 /** 970 * Prints a value (a JSONArray or JSONObject, or a value stored in an array or object) using 971 * the session. 972 * 973 * @since 5.2.0 974 */ 975 static void printValue(JSONPrintSession session, Object value) 976 { 977 // Orderd by (assumed) most common frequency in JSON 978 979 // Null Check: Must be first. 980 if (value == null || value == NULL) 981 { 982 session.print("null"); 983 return; 984 } 985 986 // String: Explicitly checking here prevents the most common 987 // JSON type from falling through all other checks. 988 if (value instanceof String) 989 { 990 session.printQuoted((String) value); 991 return; 992 } 993 994 // Number: Highly common in JSON payloads. 995 if (value instanceof Number) 996 { 997 String printValue = numberToString((Number) value); 998 session.print(printValue); 999 return; 1000 } 1001 1002 // JSONObject: The primary structural container. 1003 if (value instanceof JSONObject) 1004 { 1005 ((JSONObject) value).print(session); 1006 return; 1007 } 1008 1009 // Boolean: Common, but usually fewer instances per payload than numbers. 1010 if (value instanceof Boolean) 1011 { 1012 session.print(value.toString()); 1013 return; 1014 } 1015 1016 // JSONArray: Generally fewer arrays than objects in nested structures. 1017 if (value instanceof JSONArray) 1018 { 1019 ((JSONArray) value).print(session); 1020 return; 1021 } 1022 1023 // JSONString: Kept near the bottom assuming it is a custom wrapper. 1024 if (value instanceof JSONString) 1025 { 1026 String printValue = ((JSONString) value).toJSONString(); 1027 session.print(printValue); 1028 return; 1029 } 1030 1031 // Fallback for any other unexpected objects 1032 session.printQuoted(value.toString()); 1033 } 1034 1035 public boolean equals(Object obj) 1036 { 1037 if (obj == null) 1038 return false; 1039 1040 if (!(obj instanceof JSONObject)) 1041 return false; 1042 1043 JSONObject other = (JSONObject) obj; 1044 1045 return nameValuePairs.equals(other.nameValuePairs); 1046 } 1047 1048 @Override 1049 public int hashCode() { 1050 return nameValuePairs.hashCode(); 1051 } 1052 1053 /** 1054 * Returns a Map of the keys and values of the JSONObject. The returned map is unmodifiable. 1055 * Note that changes to the JSONObject will be reflected in the map. In addition, null values in the JSONObject 1056 * are represented as {@link JSONObject#NULL} in the map. 1057 * 1058 * @return unmodifiable map of properties and values 1059 * @since 5.4 1060 */ 1061 public Map<String, Object> toMap() 1062 { 1063 return Collections.unmodifiableMap(nameValuePairs); 1064 } 1065 1066 /** 1067 * Navigates into a nested JSONObject, creating the JSONObject if necessary. They key must not exist, 1068 * or must be a JSONObject. 1069 * 1070 * @param key 1071 * @return the nested JSONObject 1072 * @throws IllegalStateException 1073 * if the current value for the key is not null and not JSONObject 1074 */ 1075 public JSONObject in(String key) 1076 { 1077 assert key != null; 1078 1079 Object nested = nameValuePairs.get(key); 1080 1081 if (nested != null && !(nested instanceof JSONObject)) 1082 { 1083 throw new IllegalStateException(String.format("JSONObject[%s] is not a JSONObject.", quote(key))); 1084 } 1085 1086 if (nested == null) 1087 { 1088 nested = new JSONObject(); 1089 nameValuePairs.put(key, nested); 1090 } 1091 1092 return (JSONObject) nested; 1093 } 1094 1095 /** 1096 * Returns the number of key-value mappings in this JSONObject. 1097 * If it contains more than {@code Integer.MAX_VALUE} elements, returns 1098 * {@code Integer.MAX_VALUE}. 1099 * 1100 * @return the number of key-value mappings in this JSONObject 1101 * @since 5.7 1102 */ 1103 @Override 1104 public int size() 1105 { 1106 return nameValuePairs.size(); 1107 } 1108 1109 /** 1110 * Returns {@code true} if this JSONObject contains no key-value mappings. 1111 * 1112 * @return {@code true} if this JSONObject contains no key-value mappings 1113 * @since 5.7 1114 */ 1115 @Override 1116 public boolean isEmpty() 1117 { 1118 return nameValuePairs.isEmpty(); 1119 } 1120 1121 /** 1122 * Returns {@code true} if this JSONObject contains a mapping for the specified 1123 * key. 1124 * 1125 * @param key 1126 * key whose presence in this map is to be tested 1127 * @return {@code true} if this map contains a mapping for the specified 1128 * key 1129 * @since 5.7 1130 */ 1131 @Override 1132 public boolean containsKey(Object key) 1133 { 1134 return nameValuePairs.containsKey(key); 1135 } 1136 1137 /** 1138 * Returns {@code true} if this JSONObject maps one or more keys to the 1139 * specified value. 1140 * 1141 * @param value value whose presence in this map is to be tested 1142 * @return {@code true} if this JSONObject maps one or more keys to the 1143 * specified value 1144 * @since 5.7 1145 */ 1146 @Override 1147 public boolean containsValue(Object value) 1148 { 1149 return nameValuePairs.containsValue(value); 1150 } 1151 1152 /** 1153 * Returns the value mapped by {@code name}, or throws if no such mapping exists. 1154 * 1155 * @param name The name of the value to get. 1156 * @return The value. 1157 * @throws JSONValueNotFoundException if no such mapping exists. 1158 */ @Override 1159 public Object get(Object name) 1160 { 1161 Object result = nameValuePairs.get(name); 1162 if (result == null) { 1163 throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.ANY); 1164 } 1165 return result; 1166 } 1167 1168 /** 1169 * Returns the value to which the specified key is mapped, or 1170 * {@code defaultValue} if this JSONObject contains no mapping for the key. 1171 * 1172 * @param key the key whose associated value is to be returned 1173 * @param defaultValue the default mapping of the key 1174 * @return the value to which the specified key is mapped, or 1175 * {@code defaultValue} if this JSONObject contains no mapping for the key 1176 * @since 5.7 1177 */ 1178 @Override 1179 public Object getOrDefault(Object key, Object defaultValue) 1180 { 1181 Object value = opt(key); 1182 return value == null ? defaultValue : value; 1183 } 1184 1185 /** 1186 * Removes the named mapping if it exists; does nothing otherwise. 1187 * 1188 * @param name The name of the mapping to remove. 1189 * @return the value previously mapped by {@code name}, or null if there was 1190 * no such mapping. 1191 */ 1192 @Override 1193 public Object remove(Object name) 1194 { 1195 return nameValuePairs.remove(name); 1196 } 1197 1198 /** 1199 * Invokes {@link #put(String, Object)} for each value from the map. 1200 * 1201 * @param newProperties 1202 * to add to this JSONObject 1203 * @since 5.7 1204 */ 1205 @Override 1206 public void putAll(Map<? extends String, ? extends Object> newProperties) 1207 { 1208 assert newProperties != null; 1209 1210 for (Map.Entry<? extends String, ? extends Object> e : newProperties.entrySet()) 1211 { 1212 put(e.getKey(), e.getValue()); 1213 } 1214 } 1215 1216 /** 1217 * Removes all of the mappings from this JSONObject. 1218 * 1219 * @since 5.7 1220 */ 1221 @Override 1222 public void clear() 1223 { 1224 nameValuePairs.clear(); 1225 } 1226 1227 /** 1228 * Returns a {@link Set} view of the keys contained in this JSONObject. 1229 * The set is backed by the JSONObject, so changes to the map are 1230 * reflected in the set, and vice-versa. 1231 * 1232 * @return a set view of the keys contained in this JSONObject 1233 * @since 5.7 1234 */ 1235 @Override 1236 public Set<String> keySet() 1237 { 1238 return nameValuePairs.keySet(); 1239 } 1240 1241 /** 1242 * Returns a {@link Collection} view of the values contained in this JSONObject. 1243 * The collection is backed by the JSONObject, so changes to the map are 1244 * reflected in the collection, and vice-versa. 1245 * 1246 * @return a collection view of the values contained in this JSONObject 1247 * @since 5.7 1248 */ 1249 @Override 1250 public Collection<Object> values() 1251 { 1252 return nameValuePairs.values(); 1253 } 1254 1255 /** 1256 * Returns a {@link Set} view of the mappings contained in this JSONObject. 1257 * The set is backed by the JSONObject, so changes to the map are 1258 * reflected in the set, and vice-versa. 1259 * 1260 * @return a set view of the mappings contained in this JSONObject 1261 * @since 5.7 1262 */ 1263 @Override 1264 public Set<Entry<String, Object>> entrySet() 1265 { 1266 return nameValuePairs.entrySet(); 1267 } 1268 1269 /** 1270 * If the specified key is not already associated with a value or is 1271 * associated with null, associates it with the given non-null value. 1272 * 1273 * Otherwise, replaces the associated value with the results of the given 1274 * remapping function, or removes if the result is {@code null}. 1275 * 1276 * @param key 1277 * key with which the resulting value is to be associated 1278 * @param value 1279 * the non-null value to be merged with the existing value 1280 * associated with the key or, if no existing value or a null value 1281 * is associated with the key, to be associated with the key 1282 * @param remappingFunction 1283 * the function to recompute a value if present 1284 * @return the new value associated with the specified key, or null if no 1285 * value is associated with the key 1286 * @throws NullPointerException 1287 * if the specified key is null or the value or remappingFunction 1288 * is null 1289 * @since 5.7 1290 */ 1291 @Override 1292 public Object merge(String key, Object value, 1293 BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) 1294 { 1295 // We need to override the merge method due to the default implementation using 1296 // #get(String) to check for the key, which will throw a {@code JSONValueNotFoundException} 1297 // if not found. 1298 1299 Objects.requireNonNull(remappingFunction); 1300 Objects.requireNonNull(value); 1301 1302 Object oldValue = opt(key); 1303 Object newValue = (oldValue == null) ? value : remappingFunction.apply(oldValue, value); 1304 1305 put(key, newValue); 1306 1307 return newValue; 1308 } 1309 1310}