1 package org.apache.struts.flow.json;
2
3 import java.util.HashMap;
4 import java.util.Iterator;
5 import java.util.Map;
6 import java.util.NoSuchElementException;
7 import java.text.ParseException;
8
9
10 /***
11 * A JSONObject is an unordered collection of name/value pairs. Its
12 * external form is a string wrapped in curly braces with colons between the
13 * names and values, and commas between the values and names. The internal form
14 * is an object having get() and opt() methods for accessing the values by name,
15 * and put() methods for adding or replacing values by name. The values can be
16 * any of these types: Boolean, JSONArray, JSONObject, Number, String, or the
17 * JSONObject.NULL object.
18 * <p>
19 * The constructor can convert an external form string into an internal form
20 * Java object. The toString() method creates an external form string.
21 * <p>
22 * A get() method returns a value if one can be found, and throws an exception
23 * if one cannot be found. An opt() method returns a default value instead of
24 * throwing an exception, and so is useful for obtaining optional values.
25 * <p>
26 * The generic get() and opt() methods return an object, which you can cast or
27 * query for type. There are also typed get() and opt() methods that do typing
28 * checking and type coersion for you.
29 * <p>
30 * The texts produced by the toString() methods are very strict.
31 * The constructors are more forgiving in the texts they will accept.
32 * <ul>
33 * <li>An extra comma may appear just before the closing brace.</li>
34 * <li>Strings may be quoted with single quotes.</li>
35 * <li>Strings do not need to be quoted at all if they do not contain leading
36 * or trailing spaces, and if they do not contain any of these characters:
37 * { } [ ] / \ : , </li>
38 * <li>Numbers may have the 0- (octal) or 0x- (hex) prefix.</li>
39 * </ul>
40 * <p>
41 * Public Domain 2002 JSON.org
42 * @author JSON.org
43 * @version 0.1
44 */
45 public class JSONObject {
46
47 /***
48 * JSONObject.NULL is equivalent to the value that JavaScript calls null,
49 * whilst Java's null is equivalent to the value that JavaScript calls
50 * undefined.
51 */
52 private static final class Null {
53
54 /***
55 * Make a Null object.
56 */
57 private Null() {
58 }
59
60
61 /***
62 * There is only intended to be a single instance of the NULL object,
63 * so the clone method returns itself.
64 * @return NULL.
65 */
66 protected final Object clone() {
67 return this;
68 }
69
70
71 /***
72 * A Null object is equal to the null value and to itself.
73 * @param object An object to test for nullness.
74 * @return true if the object parameter is the JSONObject.NULL object
75 * or null.
76 */
77 public boolean equals(Object object) {
78 return object == null || object == this;
79 }
80
81
82 /***
83 * Get the "null" string value.
84 * @return The string "null".
85 */
86 public String toString() {
87 return "null";
88 }
89 }
90
91
92 /***
93 * The hash map where the JSONObject's properties are kept.
94 */
95 private HashMap myHashMap;
96
97 /***
98 * It is sometimes more convenient and less ambiguous to have a NULL
99 * object than to use Java's null value.
100 * JSONObject.NULL.equals(null) returns true.
101 * JSONObject.NULL.toString() returns "null".
102 */
103 public static final Object NULL = new Null();
104
105 /***
106 * Construct an empty JSONObject.
107 */
108 public JSONObject() {
109 myHashMap = new HashMap();
110 }
111
112
113 /***
114 * Construct a JSONObject from a JSONTokener.
115 * @throws ParseException if there is a syntax error in the source string.
116 * @param x A JSONTokener object containing the source string.
117 */
118 public JSONObject(JSONTokener x) throws ParseException {
119 this();
120 char c;
121 String key;
122 if (x.next() == '%') {
123 x.unescape();
124 }
125 x.back();
126 if (x.nextClean() != '{') {
127 throw x.syntaxError("A JSONObject must begin with '{'");
128 }
129 while (true) {
130 c = x.nextClean();
131 switch (c) {
132 case 0:
133 throw x.syntaxError("A JSONObject must end with '}'");
134 case '}':
135 return;
136 default:
137 x.back();
138 key = x.nextValue().toString();
139 }
140 if (x.nextClean() != ':') {
141 throw x.syntaxError("Expected a ':' after a key");
142 }
143 myHashMap.put(key, x.nextValue());
144 switch (x.nextClean()) {
145 case ',':
146 if (x.nextClean() == '}') {
147 return;
148 }
149 x.back();
150 break;
151 case '}':
152 return;
153 default:
154 throw x.syntaxError("Expected a ',' or '}'");
155 }
156 }
157 }
158
159
160 /***
161 * Construct a JSONObject from a string.
162 * @exception ParseException The string must be properly formatted.
163 * @param string A string beginning with '{' and ending with '}'.
164 */
165 public JSONObject(String string) throws ParseException {
166 this(new JSONTokener(string));
167 }
168
169
170 /***
171 * Construct a JSONObject from a Map.
172 * @param map A map object that can be used to initialize the contents of
173 * the JSONObject.
174 */
175 public JSONObject(Map map) {
176 myHashMap = new HashMap(map);
177 }
178
179
180 /***
181 * Accumulate values under a key. It is similar to the put method except
182 * that if there is already an object stored under the key then a
183 * JSONArray is stored under the key to hold all of the accumulated values.
184 * If there is already a JSONArray, then the new value is appended to it.
185 * In contrast, the put method replaces the previous value.
186 * @throws NullPointerException if the key is null
187 * @param key A key string.
188 * @param value An object to be accumulated under the key.
189 * @return this.
190 */
191 public JSONObject accumulate(String key, Object value)
192 throws NullPointerException {
193 JSONArray a;
194 Object o = opt(key);
195 if (o == null) {
196 put(key, value);
197 } else if (o instanceof JSONArray) {
198 a = (JSONArray)o;
199 a.put(value);
200 } else {
201 a = new JSONArray();
202 a.put(o);
203 a.put(value);
204 put(key, a);
205 }
206 return this;
207 }
208
209
210 /***
211 * Get the value object associated with a key.
212 * @exception NoSuchElementException if the key is not found.
213 *
214 * @param key A key string.
215 * @return The object associated with the key.
216 */
217 public Object get(String key) throws NoSuchElementException {
218 Object o = opt(key);
219 if (o == null) {
220 throw new NoSuchElementException("JSONObject[" +
221 quote(key) + "] not found.");
222 }
223 return o;
224 }
225
226
227 /***
228 * Get the boolean value associated with a key.
229 * @exception NoSuchElementException if the key is not found.
230 * @exception ClassCastException
231 * if the value is not a Boolean or the String "true" or "false".
232 *
233 * @param key A key string.
234 * @return The truth.
235 */
236 public boolean getBoolean(String key)
237 throws ClassCastException, NoSuchElementException {
238 Object o = get(key);
239 if (o == Boolean.FALSE || o.equals("false")) {
240 return false;
241 } else if (o == Boolean.TRUE || o.equals("true")) {
242 return true;
243 }
244 throw new ClassCastException("JSONObject[" +
245 quote(key) + "] is not a Boolean.");
246 }
247
248
249 /***
250 * Get the double value associated with a key.
251 * @exception NoSuchElementException if the key is not found or
252 * if the value is a Number object.
253 * @exception NumberFormatException if the value cannot be converted to a
254 * number.
255 * @param key A key string.
256 * @return The numeric value.
257 */
258 public double getDouble(String key)
259 throws NoSuchElementException, NumberFormatException {
260 Object o = get(key);
261 if (o instanceof Number) {
262 return ((Number)o).doubleValue();
263 }
264 if (o instanceof String) {
265 return new Double((String)o).doubleValue();
266 }
267 throw new NumberFormatException("JSONObject[" +
268 quote(key) + "] is not a number.");
269 }
270
271
272 /***
273 * Get the HashMap the holds that contents of the JSONObject.
274 * @return The getHashMap.
275 */
276 HashMap getHashMap() {
277 return myHashMap;
278 }
279
280
281 /***
282 * Get the int value associated with a key.
283 * @exception NoSuchElementException if the key is not found
284 * @exception NumberFormatException
285 * if the value cannot be converted to a number.
286 *
287 * @param key A key string.
288 * @return The integer value.
289 */
290 public int getInt(String key)
291 throws NoSuchElementException, NumberFormatException {
292 Object o = get(key);
293 if (o instanceof Number) {
294 return ((Number)o).intValue();
295 }
296 return (int)getDouble(key);
297 }
298
299
300 /***
301 * Get the JSONArray value associated with a key.
302 * @exception NoSuchElementException if the key is not found or
303 * if the value is not a JSONArray.
304 *
305 * @param key A key string.
306 * @return A JSONArray which is the value.
307 */
308 public JSONArray getJSONArray(String key) throws NoSuchElementException {
309 Object o = get(key);
310 if (o instanceof JSONArray) {
311 return (JSONArray)o;
312 }
313 throw new NoSuchElementException("JSONObject[" +
314 quote(key) + "] is not a JSONArray.");
315 }
316
317
318 /***
319 * Get the JSONObject value associated with a key.
320 * @exception NoSuchElementException if the key is not found or
321 * if the value is not a JSONObject.
322 *
323 * @param key A key string.
324 * @return A JSONObject which is the value.
325 */
326 public JSONObject getJSONObject(String key) throws NoSuchElementException {
327 Object o = get(key);
328 if (o instanceof JSONObject) {
329 return (JSONObject)o;
330 }
331 throw new NoSuchElementException("JSONObject[" +
332 quote(key) + "] is not a JSONObject.");
333 }
334
335
336 /***
337 * Get the string associated with a key.
338 * @exception NoSuchElementException if the key is not found.
339 *
340 * @param key A key string.
341 * @return A string which is the value.
342 */
343 public String getString(String key) throws NoSuchElementException {
344 return get(key).toString();
345 }
346
347
348 /***
349 * Determine if the JSONObject contains a specific key.
350 * @param key A key string.
351 * @return true if the key exists in the JSONObject.
352 */
353 public boolean has(String key) {
354 return myHashMap.containsKey(key);
355 }
356
357
358 /***
359 * Determine if the value associated with the key is null or if there is
360 * no value.
361 * @param key A key string.
362 * @return true if there is no value associated with the key or if
363 * the value is the JSONObject.NULL object.
364 */
365 public boolean isNull(String key) {
366 return JSONObject.NULL.equals(opt(key));
367 }
368
369
370 /***
371 * Get an enumeration of the keys of the JSONObject.
372 *
373 * @return An iterator of the keys.
374 */
375 public Iterator keys() {
376 return myHashMap.keySet().iterator();
377 }
378
379
380 /***
381 * Get the number of keys stored in the JSONObject.
382 *
383 * @return The number of keys in the JSONObject.
384 */
385 public int length() {
386 return myHashMap.size();
387 }
388
389
390 /***
391 * Produce a JSONArray containing the names of the elements of this
392 * JSONObject.
393 * @return A JSONArray containing the key strings, or null if the JSONObject
394 * is empty.
395 */
396 public JSONArray names() {
397 JSONArray ja = new JSONArray();
398 Iterator keys = keys();
399 while (keys.hasNext()) {
400 ja.put(keys.next());
401 }
402 if (ja.length() == 0) {
403 return null;
404 }
405 return ja;
406 }
407
408
409 /***
410 * Produce a string from a number.
411 * @exception ArithmeticException JSON can only serialize finite numbers.
412 * @param n A Number
413 * @return A String.
414 */
415 static public String numberToString(Number n) throws ArithmeticException {
416 if (
417 (n instanceof Float &&
418 (((Float)n).isInfinite() || ((Float)n).isNaN())) ||
419 (n instanceof Double &&
420 (((Double)n).isInfinite() || ((Double)n).isNaN()))) {
421 throw new ArithmeticException(
422 "JSON can only serialize finite numbers.");
423 }
424
425
426
427 String s = n.toString().toLowerCase();
428 if (s.indexOf('e') < 0 && s.indexOf('.') > 0) {
429 while (s.endsWith("0")) {
430 s = s.substring(0, s.length() - 1);
431 }
432 if (s.endsWith(".")) {
433 s = s.substring(0, s.length() - 1);
434 }
435 }
436 return s;
437 }
438
439
440 /***
441 * Get an optional value associated with a key.
442 * @exception NullPointerException The key must not be null.
443 * @param key A key string.
444 * @return An object which is the value, or null if there is no value.
445 */
446 public Object opt(String key) throws NullPointerException {
447 if (key == null) {
448 throw new NullPointerException("Null key");
449 }
450 return myHashMap.get(key);
451 }
452
453
454 /***
455 * Get an optional boolean associated with a key.
456 * It returns false if there is no such key, or if the value is not
457 * Boolean.TRUE or the String "true".
458 *
459 * @param key A key string.
460 * @return The truth.
461 */
462 public boolean optBoolean(String key) {
463 return optBoolean(key, false);
464 }
465
466
467 /***
468 * Get an optional boolean associated with a key.
469 * It returns the defaultValue if there is no such key, or if it is not
470 * a Boolean or the String "true" or "false".
471 *
472 * @param key A key string.
473 * @param defaultValue The default.
474 * @return The truth.
475 */
476 public boolean optBoolean(String key, boolean defaultValue) {
477 Object o = opt(key);
478 if (o != null) {
479 if (o == Boolean.FALSE || o.equals("false")) {
480 return false;
481 } else if (o == Boolean.TRUE || o.equals("true")) {
482 return true;
483 }
484 }
485 return defaultValue;
486 }
487
488
489 /***
490 * Get an optional double associated with a key,
491 * or NaN if there is no such key or if its value is not a number.
492 * If the value is a string, an attempt will be made to evaluate it as
493 * a number.
494 *
495 * @param key A string which is the key.
496 * @return An object which is the value.
497 */
498 public double optDouble(String key) {
499 return optDouble(key, Double.NaN);
500 }
501
502
503 /***
504 * Get an optional double associated with a key, or the
505 * defaultValue if there is no such key or if its value is not a number.
506 * If the value is a string, an attempt will be made to evaluate it as
507 * a number.
508 *
509 * @param key A key string.
510 * @param defaultValue The default.
511 * @return An object which is the value.
512 */
513 public double optDouble(String key, double defaultValue) {
514 Object o = opt(key);
515 if (o != null) {
516 if (o instanceof Number) {
517 return ((Number)o).doubleValue();
518 }
519 try {
520 return new Double((String)o).doubleValue();
521 }
522 catch (Exception e) {
523 }
524 }
525 return defaultValue;
526 }
527
528
529 /***
530 * Get an optional int value associated with a key,
531 * or zero if there is no such key or if the value is not a number.
532 * If the value is a string, an attempt will be made to evaluate it as
533 * a number.
534 *
535 * @param key A key string.
536 * @return An object which is the value.
537 */
538 public int optInt(String key) {
539 return optInt(key, 0);
540 }
541
542
543 /***
544 * Get an optional int value associated with a key,
545 * or the default if there is no such key or if the value is not a number.
546 * If the value is a string, an attempt will be made to evaluate it as
547 * a number.
548 *
549 * @param key A key string.
550 * @param defaultValue The default.
551 * @return An object which is the value.
552 */
553 public int optInt(String key, int defaultValue) {
554 Object o = opt(key);
555 if (o != null) {
556 if (o instanceof Number) {
557 return ((Number)o).intValue();
558 }
559 try {
560 return Integer.parseInt((String)o);
561 } catch (Exception e) {
562 }
563 }
564 return defaultValue;
565 }
566
567
568 /***
569 * Get an optional JSONArray associated with a key.
570 * It returns null if there is no such key, or if its value is not a
571 * JSONArray.
572 *
573 * @param key A key string.
574 * @return A JSONArray which is the value.
575 */
576 public JSONArray optJSONArray(String key) {
577 Object o = opt(key);
578 if (o instanceof JSONArray) {
579 return (JSONArray) o;
580 }
581 return null;
582 }
583
584
585 /***
586 * Get an optional JSONObject associated with a key.
587 * It returns null if there is no such key, or if its value is not a
588 * JSONObject.
589 *
590 * @param key A key string.
591 * @return A JSONObject which is the value.
592 */
593 public JSONObject optJSONObject(String key) {
594 Object o = opt(key);
595 if (o instanceof JSONObject) {
596 return (JSONObject)o;
597 }
598 return null;
599 }
600
601
602 /***
603 * Get an optional string associated with a key.
604 * It returns an empty string if there is no such key. If the value is not
605 * a string and is not null, then it is coverted to a string.
606 *
607 * @param key A key string.
608 * @return A string which is the value.
609 */
610 public String optString(String key) {
611 return optString(key, "");
612 }
613
614
615 /***
616 * Get an optional string associated with a key.
617 * It returns the defaultValue if there is no such key.
618 *
619 * @param key A key string.
620 * @param defaultValue The default.
621 * @return A string which is the value.
622 */
623 public String optString(String key, String defaultValue) {
624 Object o = opt(key);
625 if (o != null) {
626 return o.toString();
627 }
628 return defaultValue;
629 }
630
631
632 /***
633 * Put a key/boolean pair in the JSONObject.
634 *
635 * @param key A key string.
636 * @param value A boolean which is the value.
637 * @return this.
638 */
639 public JSONObject put(String key, boolean value) {
640 put(key, new Boolean(value));
641 return this;
642 }
643
644
645 /***
646 * Put a key/double pair in the JSONObject.
647 *
648 * @param key A key string.
649 * @param value A double which is the value.
650 * @return this.
651 */
652 public JSONObject put(String key, double value) {
653 put(key, new Double(value));
654 return this;
655 }
656
657
658 /***
659 * Put a key/int pair in the JSONObject.
660 *
661 * @param key A key string.
662 * @param value An int which is the value.
663 * @return this.
664 */
665 public JSONObject put(String key, int value) {
666 put(key, new Integer(value));
667 return this;
668 }
669
670
671 /***
672 * Put a key/value pair in the JSONObject. If the value is null,
673 * then the key will be removed from the JSONObject if it is present.
674 * @exception NullPointerException The key must be non-null.
675 * @param key A key string.
676 * @param value An object which is the value. It should be of one of these
677 * types: Boolean, Double, Integer, JSONArray, JSONObject, String, or the
678 * JSONObject.NULL object.
679 * @return this.
680 */
681 public JSONObject put(String key, Object value) throws NullPointerException {
682 if (key == null) {
683 throw new NullPointerException("Null key.");
684 }
685 if (value != null) {
686 myHashMap.put(key, value);
687 } else {
688 remove(key);
689 }
690 return this;
691 }
692
693
694 /***
695 * Put a key/value pair in the JSONObject, but only if the
696 * value is non-null.
697 * @exception NullPointerException The key must be non-null.
698 * @param key A key string.
699 * @param value An object which is the value. It should be of one of these
700 * types: Boolean, Double, Integer, JSONArray, JSONObject, String, or the
701 * JSONObject.NULL object.
702 * @return this.
703 */
704 public JSONObject putOpt(String key, Object value) throws NullPointerException {
705 if (value != null) {
706 put(key, value);
707 }
708 return this;
709 }
710
711
712 /***
713 * Produce a string in double quotes with backslash sequences in all the
714 * right places.
715 * @param string A String
716 * @return A String correctly formatted for insertion in a JSON message.
717 */
718 public static String quote(String string) {
719 if (string == null || string.length() == 0) {
720 return "\"\"";
721 }
722
723 char c;
724 int i;
725 int len = string.length();
726 StringBuffer sb = new StringBuffer(len + 4);
727 String t;
728
729 sb.append('"');
730 for (i = 0; i < len; i += 1) {
731 c = string.charAt(i);
732 switch (c) {
733 case '//':
734 case '"':
735 case '/':
736 sb.append('//');
737 sb.append(c);
738 break;
739 case '\b':
740 sb.append("//b");
741 break;
742 case '\t':
743 sb.append("//t");
744 break;
745 case '\n':
746 sb.append("//n");
747 break;
748 case '\f':
749 sb.append("//f");
750 break;
751 case '\r':
752 sb.append("//r");
753 break;
754 default:
755 if (c < ' ') {
756 t = "000" + Integer.toHexString(c);
757 sb.append("//u" + t.substring(t.length() - 4));
758 } else {
759 sb.append(c);
760 }
761 }
762 }
763 sb.append('"');
764 return sb.toString();
765 }
766
767 /***
768 * Remove a name and its value, if present.
769 * @param key The name to be removed.
770 * @return The value that was associated with the name,
771 * or null if there was no value.
772 */
773 public Object remove(String key) {
774 return myHashMap.remove(key);
775 }
776
777 /***
778 * Produce a JSONArray containing the values of the members of this
779 * JSONObject.
780 * @param names A JSONArray containing a list of key strings. This
781 * determines the sequence of the values in the result.
782 * @return A JSONArray of values.
783 */
784 public JSONArray toJSONArray(JSONArray names) {
785 if (names == null || names.length() == 0) {
786 return null;
787 }
788 JSONArray ja = new JSONArray();
789 for (int i = 0; i < names.length(); i += 1) {
790 ja.put(this.opt(names.getString(i)));
791 }
792 return ja;
793 }
794
795 /***
796 * Make an JSON external form string of this JSONObject. For compactness, no
797 * unnecessary whitespace is added.
798 * <p>
799 * Warning: This method assumes that the data structure is acyclical.
800 *
801 * @return a printable, displayable, portable, transmittable
802 * representation of the object, beginning with '{' and ending with '}'.
803 */
804 public String toString() {
805 Iterator keys = keys();
806 Object o = null;
807 String s;
808 StringBuffer sb = new StringBuffer();
809
810 sb.append('{');
811 while (keys.hasNext()) {
812 if (o != null) {
813 sb.append(',');
814 }
815 s = keys.next().toString();
816 o = myHashMap.get(s);
817 if (o != null) {
818 sb.append(quote(s));
819 sb.append(':');
820 if (o instanceof String) {
821 sb.append(quote((String)o));
822 } else if (o instanceof Number) {
823 sb.append(numberToString((Number)o));
824 } else {
825 sb.append(o.toString());
826 }
827 }
828 }
829 sb.append('}');
830 return sb.toString();
831 }
832
833
834 /***
835 * Make a prettyprinted JSON external form string of this JSONObject.
836 * <p>
837 * Warning: This method assumes that the data structure is acyclical.
838 * @param indentFactor The number of spaces to add to each level of
839 * indentation.
840 * @return a printable, displayable, portable, transmittable
841 * representation of the object, beginning with '{' and ending with '}'.
842 */
843 public String toString(int indentFactor) {
844 return toString(indentFactor, 0);
845 }
846
847
848 /***
849 * Make a prettyprinted JSON string of this JSONObject.
850 * <p>
851 * Warning: This method assumes that the data structure is acyclical.
852 * @param indentFactor The number of spaces to add to each level of
853 * indentation.
854 * @param indent The indentation of the top level.
855 * @return a printable, displayable, transmittable
856 * representation of the object, beginning with '{' and ending with '}'.
857 */
858 String toString(int indentFactor, int indent) {
859 int i;
860 Iterator keys = keys();
861 String pad = "";
862 StringBuffer sb = new StringBuffer();
863 indent += indentFactor;
864 for (i = 0; i < indent; i += 1) {
865 pad += ' ';
866 }
867 sb.append("{\n");
868 while (keys.hasNext()) {
869 String s = keys.next().toString();
870 Object o = myHashMap.get(s);
871 if (o != null) {
872 if (sb.length() > 2) {
873 sb.append(",\n");
874 }
875 sb.append(pad);
876 sb.append(quote(s));
877 sb.append(": ");
878 if (o instanceof String) {
879 sb.append(quote((String)o));
880 } else if (o instanceof Number) {
881 sb.append(numberToString((Number) o));
882 } else if (o instanceof JSONObject) {
883 sb.append(((JSONObject)o).toString(indentFactor, indent));
884 } else if (o instanceof JSONArray) {
885 sb.append(((JSONArray)o).toString(indentFactor, indent));
886 } else {
887 sb.append(o.toString());
888 }
889 }
890 }
891 sb.append('}');
892 return sb.toString();
893 }
894 }