001// Copyright 2007-2013 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry5.json; 016 017/* 018 * Copyright (c) 2002 JSON.org 019 * Permission is hereby granted, free of charge, to any person obtaining a copy 020 * of this software and associated documentation files (the "Software"), to deal 021 * in the Software without restriction, including without limitation the rights 022 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 023 * copies of the Software, and to permit persons to whom the Software is 024 * furnished to do so, subject to the following conditions: 025 * The above copyright notice and this permission notice shall be included in all 026 * copies or substantial portions of the Software. 027 * The Software shall be used for Good, not Evil. 028 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 029 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 030 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 031 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 032 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 033 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 034 * SOFTWARE. 035 */ 036 037import java.io.ObjectStreamException; 038import java.io.Serializable; 039import java.util.*; 040 041/** 042 * A JSONObject is an unordered collection of name/value pairs. Its external form is a string wrapped in curly braces 043 * with colons between the names and values, and commas between the values and names. The internal form is an object 044 * having <code>get</code> and <code>opt</code> methods for accessing the values by name, and <code>put</code> methods 045 * for adding or replacing values by name. The values can be any of these types: <code>Boolean</code>, 046 * {@link org.apache.tapestry5.json.JSONArray}, {@link org.apache.tapestry5.json.JSONLiteral}, <code>JSONObject</code>, 047 * <code>Number</code>, <code>String</code>, or the <code>JSONObject.NULL</code> object. A JSONObject constructor can be 048 * used to convert an external form JSON text into 049 * an internal form whose values can be retrieved with the <code>get</code> and <code>opt</code> methods, or to convert 050 * values into a JSON text using the <code>put</code> and <code>toString</code> methods. A <code>get</code> method 051 * returns a value if one can be found, and throws an exception if one cannot be found. An <code>opt</code> method 052 * returns a default value instead of throwing an exception, and so is useful for obtaining optional values. 053 * <p/> 054 * The generic <code>get()</code> and <code>opt()</code> methods return an object, which you can cast or query for type. 055 * There are also typed <code>get</code> and <code>opt</code> methods that do type checking and type coersion for you. 056 * <p/> 057 * The <code>put</code> methods adds values to an object. For example, 058 * <p/> 059 * <pre> 060 * myString = new JSONObject().put("JSON", "Hello, World!").toString(); 061 * </pre> 062 * <p/> 063 * produces the string <code>{"JSON": "Hello, World"}</code>. 064 * <p/> 065 * The texts produced by the <code>toString</code> methods strictly conform to the JSON syntax rules. The constructors 066 * are more forgiving in the texts they will accept: 067 * <ul> 068 * <li>An extra <code>,</code> <small>(comma)</small> may appear just before the closing brace.</li> 069 * <li>Strings may be quoted with <code>'</code> <small>(single quote)</small>.</li> 070 * <li>Strings do not need to be quoted at all if they do not begin with a quote or single quote, and if they do not 071 * contain leading or trailing spaces, and if they do not contain any of these characters: <code>{ } 072 * [ ] / \ : , = ; #</code> and if they do not look like numbers and if they are not the reserved words 073 * <code>true</code>, <code>false</code>, or <code>null</code>.</li> 074 * <li>Keys can be followed by <code>=</code> or <code>=></code> as well as by <code>:</code>.</li> 075 * <li>Values can be followed by <code>;</code> <small>(semicolon)</small> as well as by <code>,</code> 076 * <small>(comma)</small>.</li> 077 * <li>Numbers may have the <code>0-</code> <small>(octal)</small> or <code>0x-</code> <small>(hex)</small> prefix.</li> 078 * <li>Comments written in the slashshlash, slashstar, and hash conventions will be ignored.</li> 079 * </ul> 080 * <hr/> 081 * <p/> 082 * This class, and the other related classes, have been heavily modified from the original source, to fit Tapestry 083 * standards and to make use of JDK 1.5 features such as generics. Further, since the interest of Tapestry is primarily 084 * constructing JSON (and not parsing it), many of the non-essential methods have been removed (since the original code 085 * came with no tests). 086 * <p/> 087 * Finally, support for the {@link org.apache.tapestry5.json.JSONLiteral} type has been added, which allows the exact 088 * output to be controlled; useful when a JSONObject is being used as a configuration object, and must contain values 089 * that are not simple data, such as an inline function (making the result not JSON). 090 * 091 * @author JSON.org 092 * @version 2 093 */ 094@SuppressWarnings( 095 {"CloneDoesntCallSuperClone"}) 096public final class JSONObject extends JSONCollection 097{ 098 099 /** 100 * JSONObject.NULL is equivalent to the value that JavaScript calls null, whilst Java's null is equivalent to the 101 * value that JavaScript calls undefined. 102 */ 103 private static final class Null implements JSONString, Serializable 104 { 105 /** 106 * A Null object is equal to the null value and to itself. 107 * 108 * @param object 109 * An object to test for nullness. 110 * @return true if the object parameter is the JSONObject.NULL object or null. 111 */ 112 @Override 113 public boolean equals(Object object) 114 { 115 return object == null || object == this; 116 } 117 118 /** 119 * Get the "null" string value. 120 * 121 * @return The string "null". 122 */ 123 @Override 124 public String toString() 125 { 126 return "null"; 127 } 128 129 @Override 130 public String toJSONString() 131 { 132 return "null"; 133 } 134 135 // Serialization magic: after de-serializing, it will be back to the singleton instance of NULL. 136 private Object readResolve() throws ObjectStreamException 137 { 138 return NULL; 139 } 140 } 141 142 /** 143 * The map where the JSONObject's properties are kept. 144 */ 145 private final Map<String, Object> properties = new LinkedHashMap<String, Object>(); 146 147 /** 148 * It is sometimes more convenient and less ambiguous to have a <code>NULL</code> object than to use Java's 149 * <code>null</code> value. <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>. 150 * <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>. 151 */ 152 public static final Object NULL = new Null(); 153 154 /** 155 * Construct an empty JSONObject. 156 */ 157 public JSONObject() 158 { 159 } 160 161 /** 162 * Returns a new JSONObject that is a shallow copy of this JSONObject. 163 * 164 * @since 5.4 165 */ 166 public JSONObject copy() 167 { 168 JSONObject dupe = new JSONObject(); 169 dupe.properties.putAll(properties); 170 171 return dupe; 172 } 173 174 /** 175 * Constructs a new JSONObject using a series of String keys and object values. 176 * Object values sholuld be compatible with {@link #put(String, Object)}. Keys must be strings 177 * (toString() will be invoked on each key). 178 * <p/> 179 * Prior to release 5.4, keysAndValues was type String...; changing it to Object... makes 180 * it much easier to initialize a JSONObject in a single statement, which is more readable. 181 * 182 * @since 5.2.0 183 */ 184 public JSONObject(Object... keysAndValues) 185 { 186 int i = 0; 187 188 while (i < keysAndValues.length) 189 { 190 put(keysAndValues[i++].toString(), keysAndValues[i++]); 191 } 192 } 193 194 /** 195 * Construct a JSONObject from a subset of another JSONObject. An array of strings is used to identify the keys that 196 * should be copied. Missing keys are ignored. 197 * 198 * @param source 199 * A JSONObject. 200 * @param propertyNames 201 * The strings to copy. 202 * @throws RuntimeException 203 * If a value is a non-finite number. 204 */ 205 public JSONObject(JSONObject source, String... propertyNames) 206 { 207 for (String name : propertyNames) 208 { 209 Object value = source.opt(name); 210 211 if (value != null) 212 put(name, value); 213 } 214 } 215 216 /** 217 * Construct a JSONObject from a JSONTokener. 218 * 219 * @param x 220 * A JSONTokener object containing the source string. @ If there is a syntax error in the source string. 221 */ 222 JSONObject(JSONTokener x) 223 { 224 String key; 225 226 if (x.nextClean() != '{') 227 { 228 throw x.syntaxError("A JSONObject text must begin with '{'"); 229 } 230 231 while (true) 232 { 233 char c = x.nextClean(); 234 switch (c) 235 { 236 case 0: 237 throw x.syntaxError("A JSONObject text must end with '}'"); 238 case '}': 239 return; 240 default: 241 x.back(); 242 key = x.nextValue().toString(); 243 } 244 245 /* 246 * The key is followed by ':'. We will also tolerate '=' or '=>'. 247 */ 248 249 c = x.nextClean(); 250 if (c == '=') 251 { 252 if (x.next() != '>') 253 { 254 x.back(); 255 } 256 } else if (c != ':') 257 { 258 throw x.syntaxError("Expected a ':' after a key"); 259 } 260 put(key, x.nextValue()); 261 262 /* 263 * Pairs are separated by ','. We will also tolerate ';'. 264 */ 265 266 switch (x.nextClean()) 267 { 268 case ';': 269 case ',': 270 if (x.nextClean() == '}') 271 { 272 return; 273 } 274 x.back(); 275 break; 276 case '}': 277 return; 278 default: 279 throw x.syntaxError("Expected a ',' or '}'"); 280 } 281 } 282 } 283 284 /** 285 * Construct a JSONObject from a string. This is the most commonly used JSONObject constructor. 286 * 287 * @param string 288 * A string beginning with <code>{</code> <small>(left brace)</small> and ending with <code>}</code> 289 * <small>(right brace)</small>. 290 * @throws RuntimeException 291 * If there is a syntax error in the source string. 292 */ 293 public JSONObject(String string) 294 { 295 this(new JSONTokener(string)); 296 } 297 298 /** 299 * Accumulate values under a key. It is similar to the put method except that if there is already an object stored 300 * under the key then a JSONArray is stored under the key to hold all of the accumulated values. If there is already 301 * a JSONArray, then the new value is appended to it. In contrast, the put method replaces the previous value. 302 * 303 * @param key 304 * A key string. 305 * @param value 306 * An object to be accumulated under the key. 307 * @return this. 308 * @throws {@link 309 * RuntimeException} If the value is an invalid number or if the key is null. 310 */ 311 public JSONObject accumulate(String key, Object value) 312 { 313 testValidity(value); 314 315 Object existing = opt(key); 316 317 if (existing == null) 318 { 319 // Note that the original implementation of this method contradicted the method 320 // documentation. 321 put(key, value); 322 return this; 323 } 324 325 if (existing instanceof JSONArray) 326 { 327 ((JSONArray) existing).put(value); 328 return this; 329 } 330 331 // Replace the existing value, of any type, with an array that includes both the 332 // existing and the new value. 333 334 put(key, new JSONArray().put(existing).put(value)); 335 336 return this; 337 } 338 339 /** 340 * Append values to the array under a key. If the key does not exist in the JSONObject, then the key is put in the 341 * JSONObject with its value being a JSONArray containing the value parameter. If the key was already associated 342 * with a JSONArray, then the value parameter is appended to it. 343 * 344 * @param key 345 * A key string. 346 * @param value 347 * An object to be accumulated under the key. 348 * @return this. @ If the key is null or if the current value associated with the key is not a JSONArray. 349 */ 350 public JSONObject append(String key, Object value) 351 { 352 testValidity(value); 353 Object o = opt(key); 354 if (o == null) 355 { 356 put(key, new JSONArray().put(value)); 357 } else if (o instanceof JSONArray) 358 { 359 put(key, ((JSONArray) o).put(value)); 360 } else 361 { 362 throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONArray."); 363 } 364 365 return this; 366 } 367 368 /** 369 * Produce a string from a double. The string "null" will be returned if the number is not finite. 370 * 371 * @param d 372 * A double. 373 * @return A String. 374 */ 375 static String doubleToString(double d) 376 { 377 if (Double.isInfinite(d) || Double.isNaN(d)) 378 { 379 return "null"; 380 } 381 382 // Shave off trailing zeros and decimal point, if possible. 383 384 String s = Double.toString(d); 385 if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0) 386 { 387 while (s.endsWith("0")) 388 { 389 s = s.substring(0, s.length() - 1); 390 } 391 if (s.endsWith(".")) 392 { 393 s = s.substring(0, s.length() - 1); 394 } 395 } 396 return s; 397 } 398 399 /** 400 * Get the value object associated with a key. 401 * 402 * @param key 403 * A key string. 404 * @return The object associated with the key. 405 * @throws RuntimeException 406 * if the key is not found. 407 * @see #opt(String) 408 */ 409 public Object get(String key) 410 { 411 Object o = opt(key); 412 if (o == null) 413 { 414 throw new RuntimeException("JSONObject[" + quote(key) + "] not found."); 415 } 416 417 return o; 418 } 419 420 /** 421 * Get the boolean value associated with a key. 422 * 423 * @param key 424 * A key string. 425 * @return The truth. 426 * @throws RuntimeException 427 * if the value does not exist, is not a Boolean or the String "true" or "false". 428 */ 429 public boolean getBoolean(String key) 430 { 431 Object o = get(key); 432 433 if (o instanceof Boolean) 434 return o.equals(Boolean.TRUE); 435 436 if (o instanceof String) 437 { 438 String value = (String) o; 439 440 if (value.equalsIgnoreCase("true")) 441 return true; 442 443 if (value.equalsIgnoreCase("false")) 444 return false; 445 } 446 447 throw new RuntimeException("JSONObject[" + quote(key) + "] is not a Boolean."); 448 } 449 450 /** 451 * Get the double value associated with a key. 452 * 453 * @param key 454 * A key string. 455 * @return The numeric value. @throws RuntimeException if the key is not found or if the value is not a Number object and cannot be 456 * converted to a number. 457 */ 458 public double getDouble(String key) 459 { 460 Object value = get(key); 461 462 try 463 { 464 if (value instanceof Number) 465 return ((Number) value).doubleValue(); 466 467 // This is a bit sloppy for the case where value is not a string. 468 469 return Double.valueOf((String) value); 470 } catch (Exception e) 471 { 472 throw new RuntimeException("JSONObject[" + quote(key) + "] is not a number."); 473 } 474 } 475 476 /** 477 * Get the int value associated with a key. If the number value is too large for an int, it will be clipped. 478 * 479 * @param key 480 * A key string. 481 * @return The integer value. 482 * @throws RuntimeException 483 * if the key is not found or if the value cannot be converted to an integer. 484 */ 485 public int getInt(String key) 486 { 487 Object value = get(key); 488 489 if (value instanceof Number) 490 return ((Number) value).intValue(); 491 492 // Very inefficient way to do this! 493 return (int) getDouble(key); 494 } 495 496 /** 497 * Get the JSONArray value associated with a key. 498 * 499 * @param key 500 * A key string. 501 * @return A JSONArray which is the value. 502 * @throws RuntimeException 503 * if the key is not found or if the value is not a JSONArray. 504 */ 505 public JSONArray getJSONArray(String key) 506 { 507 Object o = get(key); 508 if (o instanceof JSONArray) 509 { 510 return (JSONArray) o; 511 } 512 513 throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONArray."); 514 } 515 516 /** 517 * Get the JSONObject value associated with a key. 518 * 519 * @param key 520 * A key string. 521 * @return A JSONObject which is the value. 522 * @throws RuntimeException 523 * if the key is not found or if the value is not a JSONObject. 524 */ 525 public JSONObject getJSONObject(String key) 526 { 527 Object o = get(key); 528 if (o instanceof JSONObject) 529 { 530 return (JSONObject) o; 531 } 532 533 throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONObject."); 534 } 535 536 /** 537 * Get the long value associated with a key. If the number value is too long for a long, it will be clipped. 538 * 539 * @param key 540 * A key string. 541 * @return The long value. 542 * @throws RuntimeException 543 * if the key is not found or if the value cannot be converted to a long. 544 */ 545 public long getLong(String key) 546 { 547 Object o = get(key); 548 return o instanceof Number ? ((Number) o).longValue() : (long) getDouble(key); 549 } 550 551 /** 552 * Get the string associated with a key. 553 * 554 * @param key 555 * A key string. 556 * @return A string which is the value. 557 * @throws RuntimeException 558 * if the key is not found. 559 */ 560 public String getString(String key) 561 { 562 return get(key).toString(); 563 } 564 565 /** 566 * Determine if the JSONObject contains a specific key. 567 * 568 * @param key 569 * A key string. 570 * @return true if the key exists in the JSONObject. 571 */ 572 public boolean has(String key) 573 { 574 return properties.containsKey(key); 575 } 576 577 /** 578 * Determine if the value associated with the key is null or if there is no value. 579 * 580 * @param key 581 * A key string. 582 * @return true if there is no value associated with the key or if the value is the JSONObject.NULL object. 583 */ 584 public boolean isNull(String key) 585 { 586 return JSONObject.NULL.equals(opt(key)); 587 } 588 589 /** 590 * Get an enumeration of the keys of the JSONObject. Caution: the set should not be modified. 591 * 592 * @return An iterator of the keys. 593 */ 594 public Set<String> keys() 595 { 596 return properties.keySet(); 597 } 598 599 /** 600 * Get the number of keys stored in the JSONObject. 601 * 602 * @return The number of keys in the JSONObject. 603 */ 604 public int length() 605 { 606 return properties.size(); 607 } 608 609 /** 610 * Produce a JSONArray containing the names of the elements of this JSONObject. 611 * 612 * @return A JSONArray containing the key strings, or null if the JSONObject is empty. 613 */ 614 public JSONArray names() 615 { 616 JSONArray ja = new JSONArray(); 617 618 for (String key : keys()) 619 { 620 ja.put(key); 621 } 622 623 return ja.length() == 0 ? null : ja; 624 } 625 626 /** 627 * Produce a string from a Number. 628 * 629 * @param n 630 * A Number 631 * @return A String. @ If n is a non-finite number. 632 */ 633 static String numberToString(Number n) 634 { 635 assert n != null; 636 637 testValidity(n); 638 639 // Shave off trailing zeros and decimal point, if possible. 640 641 String s = n.toString(); 642 if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0) 643 { 644 while (s.endsWith("0")) 645 { 646 s = s.substring(0, s.length() - 1); 647 } 648 if (s.endsWith(".")) 649 { 650 s = s.substring(0, s.length() - 1); 651 } 652 } 653 return s; 654 } 655 656 /** 657 * Get an optional value associated with a key. 658 * 659 * @param key 660 * A key string. 661 * @return An object which is the value, or null if there is no value. 662 * @see #get(String) 663 */ 664 public Object opt(String key) 665 { 666 return properties.get(key); 667 } 668 669 /** 670 * Put a key/value pair in the JSONObject. If the value is null, then the key will be removed from the JSONObject if 671 * it is present. 672 * 673 * @param key 674 * A key string. 675 * @param value 676 * An object which is the value. It should be of one of these types: Boolean, Double, Integer, 677 * JSONArray, JSONObject, JSONLiteral, Long, String, or the JSONObject.NULL object. 678 * @return this. 679 * @throws RuntimeException 680 * If the value is non-finite number or if the key is null. 681 */ 682 public JSONObject put(String key, Object value) 683 { 684 assert key != null; 685 686 if (value != null) 687 { 688 testValidity(value); 689 properties.put(key, value); 690 } else 691 { 692 remove(key); 693 } 694 695 return this; 696 } 697 698 /** 699 * Produce a string in double quotes with backslash sequences in all the right places. A backslash will be inserted 700 * within </, allowing JSON text to be delivered in HTML. In JSON text, a string cannot contain a control character 701 * or an unescaped quote or backslash. 702 * 703 * @param string 704 * A String 705 * @return A String correctly formatted for insertion in a JSON text. 706 */ 707 public static String quote(String string) 708 { 709 if (string == null || string.length() == 0) 710 { 711 return "\"\""; 712 } 713 714 char b; 715 char c = 0; 716 int i; 717 int len = string.length(); 718 StringBuilder buffer = new StringBuilder(len + 4); 719 String t; 720 721 buffer.append('"'); 722 for (i = 0; i < len; i += 1) 723 { 724 b = c; 725 c = string.charAt(i); 726 switch (c) 727 { 728 case '\\': 729 case '"': 730 buffer.append('\\'); 731 buffer.append(c); 732 break; 733 case '/': 734 if (b == '<') 735 { 736 buffer.append('\\'); 737 } 738 buffer.append(c); 739 break; 740 case '\b': 741 buffer.append("\\b"); 742 break; 743 case '\t': 744 buffer.append("\\t"); 745 break; 746 case '\n': 747 buffer.append("\\n"); 748 break; 749 case '\f': 750 buffer.append("\\f"); 751 break; 752 case '\r': 753 buffer.append("\\r"); 754 break; 755 default: 756 if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) 757 { 758 t = "000" + Integer.toHexString(c); 759 buffer.append("\\u").append(t.substring(t.length() - 4)); 760 } else 761 { 762 buffer.append(c); 763 } 764 } 765 } 766 buffer.append('"'); 767 return buffer.toString(); 768 } 769 770 /** 771 * Remove a name and its value, if present. 772 * 773 * @param key 774 * The name to be removed. 775 * @return The value that was associated with the name, or null if there was no value. 776 */ 777 public Object remove(String key) 778 { 779 return properties.remove(key); 780 } 781 782 private static final Class[] ALLOWED = new Class[] 783 {String.class, Boolean.class, Number.class, JSONObject.class, JSONArray.class, JSONString.class, 784 JSONLiteral.class, Null.class}; 785 786 /** 787 * Throw an exception if the object is an NaN or infinite number, or not a type which may be stored. 788 * 789 * @param value 790 * The object to test. @ If o is a non-finite number. 791 */ 792 @SuppressWarnings("unchecked") 793 static void testValidity(Object value) 794 { 795 if (value == null) 796 throw new IllegalArgumentException("null isn't valid in JSONObject and JSONArray. Use JSONObject.NULL instead."); 797 798 boolean found = false; 799 Class actual = value.getClass(); 800 801 for (Class allowed : ALLOWED) 802 { 803 if (allowed.isAssignableFrom(actual)) 804 { 805 found = true; 806 break; 807 } 808 } 809 810 if (!found) 811 { 812 List<String> typeNames = new ArrayList<String>(); 813 814 for (Class c : ALLOWED) 815 { 816 String name = c.getName(); 817 818 if (name.startsWith("java.lang.")) 819 name = name.substring(10); 820 821 typeNames.add(name); 822 } 823 824 Collections.sort(typeNames); 825 826 StringBuilder joined = new StringBuilder(); 827 String sep = ""; 828 829 for (String name : typeNames) 830 { 831 joined.append(sep); 832 joined.append(name); 833 834 sep = ", "; 835 } 836 837 String message = String.format("JSONObject properties may be one of %s. Type %s is not allowed.", 838 joined.toString(), actual.getName()); 839 840 throw new RuntimeException(message); 841 } 842 843 if (value instanceof Double) 844 { 845 Double asDouble = (Double) value; 846 847 if (asDouble.isInfinite() || asDouble.isNaN()) 848 { 849 throw new RuntimeException( 850 "JSON does not allow non-finite numbers."); 851 } 852 853 return; 854 } 855 856 if (value instanceof Float) 857 { 858 Float asFloat = (Float) value; 859 860 if (asFloat.isInfinite() || asFloat.isNaN()) 861 { 862 throw new RuntimeException( 863 "JSON does not allow non-finite numbers."); 864 } 865 866 } 867 868 } 869 870 /** 871 * Prints the JSONObject using the session. 872 * 873 * @since 5.2.0 874 */ 875 @Override 876 void print(JSONPrintSession session) 877 { 878 session.printSymbol('{'); 879 880 session.indent(); 881 882 boolean comma = false; 883 884 for (String key : keys()) 885 { 886 if (comma) 887 session.printSymbol(','); 888 889 session.newline(); 890 891 session.printQuoted(key); 892 893 session.printSymbol(':'); 894 895 printValue(session, properties.get(key)); 896 897 comma = true; 898 } 899 900 session.outdent(); 901 902 if (comma) 903 session.newline(); 904 905 session.printSymbol('}'); 906 } 907 908 /** 909 * Prints a value (a JSONArray or JSONObject, or a value stored in an array or object) using 910 * the session. 911 * 912 * @since 5.2.0 913 */ 914 static void printValue(JSONPrintSession session, Object value) 915 { 916 917 if (value instanceof JSONObject) 918 { 919 ((JSONObject) value).print(session); 920 return; 921 } 922 923 if (value instanceof JSONArray) 924 { 925 ((JSONArray) value).print(session); 926 return; 927 } 928 929 if (value instanceof JSONString) 930 { 931 String printValue = ((JSONString) value).toJSONString(); 932 933 session.print(printValue); 934 935 return; 936 } 937 938 if (value instanceof Number) 939 { 940 String printValue = numberToString((Number) value); 941 session.print(printValue); 942 return; 943 } 944 945 if (value instanceof Boolean) 946 { 947 session.print(value.toString()); 948 949 return; 950 } 951 952 // Otherwise it really should just be a string. Nothing else can go in. 953 session.printQuoted(value.toString()); 954 } 955 956 /** 957 * Returns true if the other object is a JSONObject and its set of properties matches this object's properties. 958 */ 959 @Override 960 public boolean equals(Object obj) 961 { 962 if (obj == null) 963 return false; 964 965 if (!(obj instanceof JSONObject)) 966 return false; 967 968 JSONObject other = (JSONObject) obj; 969 970 return properties.equals(other.properties); 971 } 972 973 /** 974 * Returns a Map of the keys and values of the JSONObject. The returned map is unmodifiable. 975 * Note that changes to the JSONObject will be reflected in the map. In addition, null values in the JSONObject 976 * are represented as {@link JSONObject#NULL} in the map. 977 * 978 * @return unmodifiable map of properties and values 979 * @since 5.4 980 */ 981 public Map<String, Object> toMap() 982 { 983 return Collections.unmodifiableMap(properties); 984 } 985 986 /** 987 * Invokes {@link #put(String, Object)} for each value from the map. 988 * 989 * @param newProperties 990 * to add to this JSONObject 991 * @return this JSONObject 992 * @since 5.4 993 */ 994 public JSONObject putAll(Map<String, ?> newProperties) 995 { 996 assert newProperties != null; 997 998 for (Map.Entry<String, ?> e : newProperties.entrySet()) 999 { 1000 put(e.getKey(), e.getValue()); 1001 } 1002 1003 return this; 1004 } 1005 1006 /** 1007 * Navigates into a nested JSONObject, creating the JSONObject if necessary. They key must not exist, 1008 * or must be a JSONObject. 1009 * 1010 * @param key 1011 * @return the nested JSONObject 1012 * @throws IllegalStateException 1013 * if the current value for the key is not null and not JSONObject 1014 */ 1015 public JSONObject in(String key) 1016 { 1017 assert key != null; 1018 1019 Object nested = properties.get(key); 1020 1021 if (nested != null && !(nested instanceof JSONObject)) 1022 { 1023 throw new IllegalStateException(String.format("JSONObject[%s] is not a JSONObject.", quote(key))); 1024 } 1025 1026 if (nested == null) 1027 { 1028 nested = new JSONObject(); 1029 properties.put(key, nested); 1030 } 1031 1032 return (JSONObject) nested; 1033 } 1034}