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 }