View Javadoc

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 // Shave off trailing zeros and decimal point, if possible.
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 }