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