001    package net.sourceforge.javajson;
002    
003    import java.io.InputStream;
004    import java.io.Reader;
005    import java.io.StringReader;
006    import java.util.HashMap;
007    import java.util.Iterator;
008    import java.util.Map;
009    
010    import net.sourceforge.javajson.parser.ASTparse;
011    import net.sourceforge.javajson.parser.JsonParser;
012    import net.sourceforge.javajson.parser.ParseException;
013    import net.sourceforge.javajson.parser.TokenMgrError;
014    
015    /**
016     * Simpler implementation of Json that throws less exceptions. For all the
017     * getXXX method, if the item is not found, it returns null, false, or 0. If
018     * they key is null or otherwise invalid, it throws an exception.
019     * 
020     * @author mdeanda
021     */
022    public class JsonObject implements Iterable<String> {
023            private Map<String, JsonValue> map;
024    
025            /** Parses a string to a json object. */
026            public static JsonObject parse(String input) throws JsonException {
027                    return parse(new StringReader(input));
028            }
029    
030            /** Parses a string to a json object. */
031            public static JsonObject parse(Reader reader) throws JsonException {
032                    try {
033                            JsonParser parser = new JsonParser(reader);
034                            ASTparse root = (ASTparse) parser.parse();
035                            return root.getJsonObject();
036                    } catch (ParseException pe) {
037                            throw new JsonException(pe);
038                    } catch (TokenMgrError error) {
039                            throw new JsonException(error);
040                    }
041            }
042    
043            /** Parses a string to a json object. */
044            public static JsonObject parse(InputStream is) throws JsonException {
045                    try {
046                            JsonParser parser = new JsonParser(is);
047                            ASTparse root = (ASTparse) parser.parse();
048                            return root.getJsonObject();
049                    } catch (ParseException pe) {
050                            throw new JsonException(pe);
051                    } catch (TokenMgrError error) {
052                            throw new JsonException(error);
053                    }
054            }
055    
056            public JsonObject() {
057                    map = new HashMap<String, JsonValue>();
058            }
059    
060            /**
061             * Accumlates multiple values into an array. This is a convenience function
062             * for creating an array, adding it to the object, then adding to the array.
063             * If there is already a value for this key, it will be the first item in
064             * the array
065             * 
066             * @param key
067             * @param val
068             */
069            public JsonObject accumulate(String key, boolean val) {
070                    if (!map.containsKey(key)) {
071                            put(key, new JsonArray());
072                    } else if (!map.get(key).isJsonArray()) {
073                            JsonValue old = map.get(key);
074                            put(key, new JsonArray());
075                            getJsonArray(key).add(old);
076                    }
077    
078                    getJsonArray(key).add(val);
079                    return this;
080            }
081    
082            /**
083             * Accumlates multiple values into an array. This is a convenience function
084             * for creating an array, adding it to the object, then adding to the array.
085             * If there is already a value for this key, it will be the first item in
086             * the array
087             * 
088             * @param key
089             * @param val
090             */
091            public JsonObject accumulate(String key, double val) {
092                    if (!map.containsKey(key)) {
093                            put(key, new JsonArray());
094                    } else if (!map.get(key).isJsonArray()) {
095                            JsonValue old = map.get(key);
096                            put(key, new JsonArray());
097                            getJsonArray(key).add(old);
098                    }
099    
100                    getJsonArray(key).add(val);
101                    return this;
102            }
103    
104            /**
105             * Accumlates multiple values into an array. This is a convenience function
106             * for creating an array, adding it to the object, then adding to the array.
107             * If there is already a value for this key, it will be the first item in
108             * the array
109             * 
110             * @param key
111             * @param val
112             */
113            public JsonObject accumulate(String key, float val) {
114                    if (!map.containsKey(key)) {
115                            put(key, new JsonArray());
116                    } else if (!map.get(key).isJsonArray()) {
117                            JsonValue old = map.get(key);
118                            put(key, new JsonArray());
119                            getJsonArray(key).add(old);
120                    }
121    
122                    getJsonArray(key).add(val);
123                    return this;
124            }
125    
126            /**
127             * Accumlates multiple values into an array. This is a convenience function
128             * for creating an array, adding it to the object, then adding to the array.
129             * If there is already a value for this key, it will be the first item in
130             * the array
131             * 
132             * @param key
133             * @param val
134             */
135            public JsonObject accumulate(String key, int val) {
136                    if (!map.containsKey(key)) {
137                            put(key, new JsonArray());
138                    } else if (!map.get(key).isJsonArray()) {
139                            JsonValue old = map.get(key);
140                            put(key, new JsonArray());
141                            getJsonArray(key).add(old);
142                    }
143    
144                    getJsonArray(key).add(val);
145                    return this;
146            }
147    
148            /**
149             * Accumlates multiple values into an array. This is a convenience function
150             * for creating an array, adding it to the object, then adding to the array.
151             * If there is already a value for this key, it will be the first item in
152             * the array
153             * 
154             * @param key
155             * @param val
156             */
157            public JsonObject accumulate(String key, JsonArray val) {
158                    if (!map.containsKey(key)) {
159                            put(key, new JsonArray());
160                    } else if (!map.get(key).isJsonArray()) {
161                            JsonValue old = map.get(key);
162                            put(key, new JsonArray());
163                            getJsonArray(key).add(old);
164                    }
165    
166                    getJsonArray(key).add(val);
167                    return this;
168            }
169    
170            /**
171             * Accumlates multiple values into an array. This is a convenience function
172             * for creating an array, adding it to the object, then adding to the array.
173             * If there is already a value for this key, it will be the first item in
174             * the array
175             * 
176             * @param key
177             * @param val
178             */
179            public JsonObject accumulate(String key, JsonObject val) {
180                    if (!map.containsKey(key)) {
181                            put(key, new JsonArray());
182                    } else if (!map.get(key).isJsonArray()) {
183                            JsonValue old = map.get(key);
184                            put(key, new JsonArray());
185                            getJsonArray(key).add(old);
186                    }
187    
188                    getJsonArray(key).add(val);
189                    return this;
190            }
191    
192            /**
193             * Accumlates multiple values into an array. This is a convenience function
194             * for creating an array, adding it to the object, then adding to the array.
195             * If there is already a value for this key, it will be the first item in
196             * the array
197             * 
198             * @param key
199             * @param val
200             */
201            public JsonObject accumulate(String key, String val) {
202                    if (!map.containsKey(key)) {
203                            put(key, new JsonArray());
204                    } else if (!map.get(key).isJsonArray()) {
205                            JsonValue old = map.get(key);
206                            put(key, new JsonArray());
207                            getJsonArray(key).add(old);
208                    }
209    
210                    getJsonArray(key).add(val);
211                    return this;
212            }
213    
214            public boolean getBoolean(String key) {
215                    if (map.containsKey(key))
216                            return map.get(key).getBoolean();
217                    return false;
218            }
219    
220            public double getDouble(String key) {
221                    if (map.containsKey(key))
222                            return map.get(key).getDouble();
223                    return 0.0;
224            }
225    
226            public float getFloat(String key) {
227                    if (map.containsKey(key))
228                            return map.get(key).getFloat();
229                    return 0.0f;
230            }
231    
232            public int getInt(String key) {
233                    if (map.containsKey(key))
234                            return map.get(key).getInt();
235                    return 0;
236            }
237    
238            public JsonArray getJsonArray(String key) {
239                    if (map.containsKey(key))
240                            return map.get(key).getJsonArray();
241                    return null;
242            }
243    
244            public JsonObject getJsonObject(String key) {
245                    if (map.containsKey(key))
246                            return map.get(key).getJsonObject();
247                    return null;
248            }
249    
250            public long getLong(String key) {
251                    if (map.containsKey(key))
252                            return map.get(key).getLong();
253                    return 0l;
254            }
255    
256            public String getString(String key) {
257                    if (map.containsKey(key))
258                            return map.get(key).getString();
259                    return null;
260            }
261    
262            public boolean hasKey(String key) {
263                    return map.containsKey(key);
264            }
265    
266            /**
267             * Checks if the item at a key is a boolean. See
268             * {@link JsonValue#isBoolean()} for more information
269             * 
270             * @param key
271             * @return True if the object contains the item and is a boolean
272             */
273            public boolean isBoolean(String key) {
274                    if (map.containsKey(key))
275                            return map.get(key).isBoolean();
276                    else
277                            return false;
278            }
279    
280            /**
281             * Checks if the item at a key is a double. See {@link JsonValue#isDouble()}
282             * for more information
283             * 
284             * @param key
285             */
286            public boolean isDouble(String key) {
287                    if (map.containsKey(key))
288                            return map.get(key).isDouble();
289                    else
290                            return false;
291            }
292    
293            /**
294             * Checks if the item at a key is a float. See {@link JsonValue#isFloat()}
295             * for more information
296             * 
297             * @param key
298             */
299            public boolean isFloat(String key) {
300                    if (map.containsKey(key))
301                            return map.get(key).isFloat();
302                    else
303                            return false;
304            }
305    
306            /**
307             * Checks if the item at a key is a int. See {@link JsonValue#isInt()} for
308             * more information
309             * 
310             * @param key
311             */
312            public boolean isInt(String key) {
313                    if (map.containsKey(key))
314                            return map.get(key).isInt();
315                    else
316                            return false;
317            }
318    
319            /**
320             * Checks if the item at a key is a long. See {@link JsonValue#isLong()} for
321             * more information
322             * 
323             * @param key
324             */
325            public boolean isLong(String key) {
326                    if (map.containsKey(key))
327                            return map.get(key).isLong();
328                    else
329                            return false;
330            }
331    
332            /**
333             * Checks if the item at a key is an array. See
334             * {@link JsonValue#isJsonArray()} for more information
335             * 
336             * @param key
337             * @return True if the object contains the item and is a json array
338             */
339            public boolean isJsonArray(String key) {
340                    if (map.containsKey(key))
341                            return map.get(key).isJsonArray();
342                    else
343                            return false;
344            }
345    
346            /**
347             * Checks if the item at a key is an object. See
348             * {@link JsonValue#isJsonObject()} for more information
349             * 
350             * @param key
351             * @return True if the object contains the item and is a json object
352             */
353            public boolean isJsonObject(String key) {
354                    if (map.containsKey(key))
355                            return map.get(key).isJsonObject();
356                    else
357                            return false;
358            }
359    
360            /**
361             * Checks if the field contains a null value (different than hasKey because
362             * it can have the key but be null)
363             */
364            public boolean isNull(String key) {
365                    if (map.containsKey(key)) {
366                            JsonValue jv = map.get(key);
367                            return jv.isNull();
368                    }
369    
370                    return false;
371            }
372    
373            /**
374             * checks that this object contains all fields as passed object. also checks
375             * that types match. if this object contains more fields then they can still
376             * be similar, but calling in reverse order may return false
377             * 
378             * @param obj
379             * @return
380             */
381            public boolean isSimilar(JsonObject obj) {
382                    if (obj == null)
383                            return false;
384    
385                    for (String key : this) {
386                            if (!obj.hasKey(key))
387                                    return false;
388                            else if (!map.get(key).isSimilar(obj.map.get(key)))
389                                    return false;
390                    }
391    
392                    return true;
393            }
394    
395            /**
396             * Checks if the item at a key is an string. See
397             * {@link JsonValue#isString()} for more information
398             * 
399             * @param key
400             * @return True if the object contains the item and is a string
401             */
402            public boolean isString(String key) {
403                    if (map.containsKey(key))
404                            return map.get(key).isString();
405                    else
406                            return false;
407            }
408    
409            public Iterator<String> iterator() {
410                    return map.keySet().iterator();
411            }
412    
413            public void put(String key, Object value) {
414                    if (value instanceof Boolean)
415                            put(key, ((Boolean) value).booleanValue());
416                    else if (value instanceof Double)
417                            put(key, ((Double) value).doubleValue());
418                    else if (value instanceof Float)
419                            put(key, ((Float) value).floatValue());
420                    else if (value instanceof Integer)
421                            put(key, ((Integer) value).intValue());
422                    else if (value instanceof Long)
423                            put(key, ((Long) value).longValue());
424                    else if (value instanceof JsonObject)
425                            put(key, (JsonObject) value);
426                    else if (value instanceof JsonArray)
427                            put(key, (JsonArray) value);
428                    else if (value instanceof String)
429                            put(key, (String) value);
430                    else if (value == null)
431                            put(key, (String) null);
432                    else
433                            throw new ClassCastException("Unrecognized class");
434            }
435    
436            public JsonObject put(String key, boolean value) {
437                    map.put(key, new JsonValue(value));
438                    return this;
439            }
440    
441            public JsonObject put(String key, double value) {
442                    map.put(key, new JsonValue(value));
443                    return this;
444            }
445    
446            public JsonObject put(String key, float value) {
447                    map.put(key, new JsonValue(value));
448                    return this;
449            }
450    
451            public JsonObject put(String key, int value) {
452                    map.put(key, new JsonValue(value));
453                    return this;
454            }
455    
456            public JsonObject put(String key, JsonArray value) {
457                    map.put(key, new JsonValue(value));
458                    return this;
459            }
460    
461            public JsonObject put(String key, JsonObject value) {
462                    map.put(key, new JsonValue(value));
463                    return this;
464            }
465    
466            public JsonObject put(String key, long value) {
467                    map.put(key, new JsonValue(value));
468                    return this;
469            }
470    
471            public JsonObject put(String key, String value) {
472                    map.put(key, new JsonValue(value));
473                    return this;
474            }
475    
476            public int size() {
477                    return map.size();
478            }
479    
480            @Override
481            public String toString() {
482                    StringBuffer sb = new StringBuffer();
483                    boolean hadSome = false;
484                    sb.append("{");
485                    for (String key : map.keySet()) {
486                            JsonValue val = map.get(key);
487                            if (hadSome)
488                                    sb.append(",");
489                            // TODO: escape keys
490                            sb.append("\"" + JsonValue.escape(key) + "\":");
491                            sb.append(val);
492                            hadSome = true;
493                    }
494                    sb.append("}");
495                    return sb.toString();
496            }
497    
498            /**
499             * Returns a nicely formatted string
500             * 
501             * @param spacing
502             * @return
503             */
504            public String toString(int spacing) {
505                    return toString(spacing, 0);
506            }
507    
508            /**
509             * Called by toString(int) to keep track of the spacing
510             * 
511             * @param spacing
512             * @param margin
513             * @return
514             */
515            protected String toString(int spacing, int margin) {
516                    if (map.isEmpty())
517                            return "{}";
518                    else {
519                            StringBuffer sb = new StringBuffer();
520                            boolean hadSome = false;
521                            // sb.append(getSpaces(margin));
522                            sb.append("{\n");
523                            for (String key : map.keySet()) {
524                                    JsonValue val = map.get(key);
525                                    if (hadSome)
526                                            sb.append(",\n");
527    
528                                    sb.append(getSpaces(spacing + margin));
529                                    sb.append("\"" + JsonValue.escape(key) + "\":");
530                                    sb.append(val.toString(spacing, margin + spacing));
531                                    hadSome = true;
532                            }
533                            sb.append("\n");
534                            sb.append(getSpaces(margin));
535                            sb.append("}");
536    
537                            return sb.toString();
538                    }
539            }
540    
541            private String getSpaces(int spaces) {
542                    StringBuffer sb = new StringBuffer();
543                    for (int i = 0; i < spaces; i++)
544                            sb.append(" ");
545                    return sb.toString();
546            }
547    
548    }