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