1 /*******************************************************************************
\r
2 * ============LICENSE_START==================================================
\r
4 * * ===========================================================================
\r
5 * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
\r
6 * * ===========================================================================
\r
7 * * Licensed under the Apache License, Version 2.0 (the "License");
\r
8 * * you may not use this file except in compliance with the License.
\r
9 * * You may obtain a copy of the License at
\r
11 * * http://www.apache.org/licenses/LICENSE-2.0
\r
13 * * Unless required by applicable law or agreed to in writing, software
\r
14 * * distributed under the License is distributed on an "AS IS" BASIS,
\r
15 * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
16 * * See the License for the specific language governing permissions and
\r
17 * * limitations under the License.
\r
18 * * ============LICENSE_END====================================================
\r
20 * * ECOMP is a trademark and service mark of AT&T Intellectual Property.
\r
22 ******************************************************************************/
\r
27 import java.io.IOException;
\r
28 import java.io.StringWriter;
\r
29 import java.io.Writer;
\r
30 import java.lang.reflect.Field;
\r
31 import java.lang.reflect.Method;
\r
32 import java.lang.reflect.Modifier;
\r
33 import java.util.Collection;
\r
34 import java.util.Enumeration;
\r
35 import java.util.HashMap;
\r
36 import java.util.Iterator;
\r
37 import java.util.Locale;
\r
38 import java.util.Map;
\r
39 import java.util.ResourceBundle;
\r
40 import java.util.Set;
\r
43 public class JSONObject {
\r
45 * The maximum number of keys in the key pool.
\r
47 private static final int keyPoolSize = 100;
\r
50 * Key pooling is like string interning, but without permanently tying up
\r
51 * memory. To help conserve memory, storage of duplicated key strings in
\r
52 * JSONObjects will be avoided by using a key pool to manage unique key
\r
53 * string objects. This is used by JSONObject.put(string, object).
\r
55 private static Map<String,Object> keyPool = new HashMap<String,Object>(keyPoolSize);
\r
58 * JSONObject.NULL is equivalent to the value that JavaScript calls null,
\r
59 * whilst Java's null is equivalent to the value that JavaScript calls
\r
62 private static final class Null {
\r
65 * There is only intended to be a single instance of the NULL object,
\r
66 * so the clone method returns itself.
\r
69 protected final Object clone() {
\r
74 * A Null object is equal to the null value and to itself.
\r
75 * @param object An object to test for nullness.
\r
76 * @return true if the object parameter is the JSONObject.NULL object
\r
79 public boolean equals(Object object) {
\r
80 return object == null || object == this;
\r
84 * Get the "null" string value.
\r
85 * @return The string "null".
\r
87 public String toString() {
\r
94 * The map where the JSONObject's properties are kept.
\r
96 private final Map<String,Object> map;
\r
100 * It is sometimes more convenient and less ambiguous to have a
\r
101 * <code>NULL</code> object than to use Java's <code>null</code> value.
\r
102 * <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>.
\r
103 * <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>.
\r
105 public static final Object NULL = new Null();
\r
109 * Construct an empty JSONObject.
\r
111 public JSONObject() {
\r
112 this.map = new HashMap<String,Object>();
\r
117 * Construct a JSONObject from a subset of another JSONObject.
\r
118 * An array of strings is used to identify the keys that should be copied.
\r
119 * Missing keys are ignored.
\r
120 * @param jo A JSONObject.
\r
121 * @param names An array of strings.
\r
122 * @throws JSONException
\r
123 * @exception JSONException If a value is a non-finite number or if a name is duplicated.
\r
125 public JSONObject(JSONObject jo, String[] names) {
\r
127 for (int i = 0; i < names.length; i += 1) {
\r
129 this.putOnce(names[i], jo.opt(names[i]));
\r
130 } catch (Exception ignore) {
\r
137 * Construct a JSONObject from a JSONTokener.
\r
138 * @param x A JSONTokener object containing the source string.
\r
139 * @throws JSONException If there is a syntax error in the source string
\r
140 * or a duplicated key.
\r
142 public JSONObject(JSONTokener x) throws JSONException {
\r
147 if (x.nextClean() != '{') {
\r
148 throw x.syntaxError("A JSONObject text must begin with '{'");
\r
154 throw x.syntaxError("A JSONObject text must end with '}'");
\r
159 key = x.nextValue().toString();
\r
162 // The key is followed by ':'. We will also tolerate '=' or '=>'.
\r
166 if (x.next() != '>') {
\r
169 } else if (c != ':') {
\r
170 throw x.syntaxError("Expected a ':' after a key");
\r
172 this.putOnce(key, x.nextValue());
\r
174 // Pairs are separated by ','. We will also tolerate ';'.
\r
176 switch (x.nextClean()) {
\r
179 if (x.nextClean() == '}') {
\r
187 throw x.syntaxError("Expected a ',' or '}'");
\r
194 * Construct a JSONObject from a Map.
\r
196 * @param map A map object that can be used to initialize the contents of
\r
198 * @throws JSONException
\r
200 public JSONObject(Map<String,Object> map) {
\r
201 this.map = new HashMap<String,Object>();
\r
203 Iterator<Map.Entry<String,Object>> i = map.entrySet().iterator();
\r
204 while (i.hasNext()) {
\r
205 Map.Entry<String,Object> e = i.next();
\r
206 Object value = e.getValue();
\r
207 if (value != null) {
\r
208 this.map.put(e.getKey(), wrap(value));
\r
216 * Construct a JSONObject from an Object using bean getters.
\r
217 * It reflects on all of the public methods of the object.
\r
218 * For each of the methods with no parameters and a name starting
\r
219 * with <code>"get"</code> or <code>"is"</code> followed by an uppercase letter,
\r
220 * the method is invoked, and a key and the value returned from the getter method
\r
221 * are put into the new JSONObject.
\r
223 * The key is formed by removing the <code>"get"</code> or <code>"is"</code> prefix.
\r
224 * If the second remaining character is not upper case, then the first
\r
225 * character is converted to lower case.
\r
227 * For example, if an object has a method named <code>"getName"</code>, and
\r
228 * if the result of calling <code>object.getName()</code> is <code>"Larry Fine"</code>,
\r
229 * then the JSONObject will contain <code>"name": "Larry Fine"</code>.
\r
231 * @param bean An object that has getter methods that should be used
\r
232 * to make a JSONObject.
\r
234 public JSONObject(Object bean) {
\r
236 this.populateMap(bean);
\r
241 * Construct a JSONObject from an Object, using reflection to find the
\r
242 * public members. The resulting JSONObject's keys will be the strings
\r
243 * from the names array, and the values will be the field values associated
\r
244 * with those keys in the object. If a key is not found or not visible,
\r
245 * then it will not be copied into the new JSONObject.
\r
246 * @param object An object that has fields that should be used to make a
\r
248 * @param names An array of strings, the names of the fields to be obtained
\r
251 public JSONObject(Object object, String names[]) {
\r
253 Class<? extends Object> c = object.getClass();
\r
254 for (int i = 0; i < names.length; i += 1) {
\r
255 String name = names[i];
\r
257 this.putOpt(name, c.getField(name).get(object));
\r
258 } catch (Exception ignore) {
\r
265 * Construct a JSONObject from a source JSON text string.
\r
266 * This is the most commonly used JSONObject constructor.
\r
267 * @param source A string beginning
\r
268 * with <code>{</code> <small>(left brace)</small> and ending
\r
269 * with <code>}</code> <small>(right brace)</small>.
\r
270 * @exception JSONException If there is a syntax error in the source
\r
271 * string or a duplicated key.
\r
273 public JSONObject(String source) throws JSONException {
\r
274 this(new JSONTokener(source));
\r
279 * Construct a JSONObject from a ResourceBundle.
\r
280 * @param baseName The ResourceBundle base name.
\r
281 * @param locale The Locale to load the ResourceBundle for.
\r
282 * @throws JSONException If any JSONExceptions are detected.
\r
284 public JSONObject(String baseName, Locale locale) throws JSONException {
\r
286 ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale,
\r
287 Thread.currentThread().getContextClassLoader());
\r
289 // Iterate through the keys in the bundle.
\r
291 Enumeration<?> keys = bundle.getKeys();
\r
292 while (keys.hasMoreElements()) {
\r
293 Object key = keys.nextElement();
\r
294 if (key instanceof String) {
\r
296 // Go through the path, ensuring that there is a nested JSONObject for each
\r
297 // segment except the last. Add the value using the last segment's name into
\r
298 // the deepest nested JSONObject.
\r
300 String[] path = ((String)key).split("\\.");
\r
301 int last = path.length - 1;
\r
302 JSONObject target = this;
\r
303 for (int i = 0; i < last; i += 1) {
\r
304 String segment = path[i];
\r
305 JSONObject nextTarget = target.optJSONObject(segment);
\r
306 if (nextTarget == null) {
\r
307 nextTarget = new JSONObject();
\r
308 target.put(segment, nextTarget);
\r
310 target = nextTarget;
\r
312 target.put(path[last], bundle.getString((String)key));
\r
319 * Accumulate values under a key. It is similar to the put method except
\r
320 * that if there is already an object stored under the key then a
\r
321 * JSONArray is stored under the key to hold all of the accumulated values.
\r
322 * If there is already a JSONArray, then the new value is appended to it.
\r
323 * In contrast, the put method replaces the previous value.
\r
325 * If only one value is accumulated that is not a JSONArray, then the
\r
326 * result will be the same as using put. But if multiple values are
\r
327 * accumulated, then the result will be like append.
\r
328 * @param key A key string.
\r
329 * @param value An object to be accumulated under the key.
\r
331 * @throws JSONException If the value is an invalid number
\r
332 * or if the key is null.
\r
334 public JSONObject accumulate(
\r
337 ) throws JSONException {
\r
338 testValidity(value);
\r
339 Object object = this.opt(key);
\r
340 if (object == null) {
\r
341 this.put(key, value instanceof JSONArray
\r
342 ? new JSONArray().put(value)
\r
344 } else if (object instanceof JSONArray) {
\r
345 ((JSONArray)object).put(value);
\r
347 this.put(key, new JSONArray().put(object).put(value));
\r
354 * Append values to the array under a key. If the key does not exist in the
\r
355 * JSONObject, then the key is put in the JSONObject with its value being a
\r
356 * JSONArray containing the value parameter. If the key was already
\r
357 * associated with a JSONArray, then the value parameter is appended to it.
\r
358 * @param key A key string.
\r
359 * @param value An object to be accumulated under the key.
\r
361 * @throws JSONException If the key is null or if the current value
\r
362 * associated with the key is not a JSONArray.
\r
364 public JSONObject append(String key, Object value) throws JSONException {
\r
365 testValidity(value);
\r
366 Object object = this.opt(key);
\r
367 if (object == null) {
\r
368 this.put(key, new JSONArray().put(value));
\r
369 } else if (object instanceof JSONArray) {
\r
370 this.put(key, ((JSONArray)object).put(value));
\r
372 throw new JSONException("JSONObject[" + key +
\r
373 "] is not a JSONArray.");
\r
380 * Produce a string from a double. The string "null" will be returned if
\r
381 * the number is not finite.
\r
382 * @param d A double.
\r
383 * @return A String.
\r
385 public static String doubleToString(double d) {
\r
386 if (Double.isInfinite(d) || Double.isNaN(d)) {
\r
390 // Shave off trailing zeros and decimal point, if possible.
\r
392 String string = Double.toString(d);
\r
393 if (string.indexOf('.') > 0 && string.indexOf('e') < 0 &&
\r
394 string.indexOf('E') < 0) {
\r
395 while (string.endsWith("0")) {
\r
396 string = string.substring(0, string.length() - 1);
\r
398 if (string.endsWith(".")) {
\r
399 string = string.substring(0, string.length() - 1);
\r
407 * Get the value object associated with a key.
\r
409 * @param key A key string.
\r
410 * @return The object associated with the key.
\r
411 * @throws JSONException if the key is not found.
\r
413 public Object get(String key) throws JSONException {
\r
415 throw new JSONException("Null key.");
\r
417 Object object = this.opt(key);
\r
418 if (object == null) {
\r
419 throw new JSONException("JSONObject[" + quote(key) +
\r
427 * Get the boolean value associated with a key.
\r
429 * @param key A key string.
\r
430 * @return The truth.
\r
431 * @throws JSONException
\r
432 * if the value is not a Boolean or the String "true" or "false".
\r
434 public boolean getBoolean(String key) throws JSONException {
\r
435 Object object = this.get(key);
\r
436 if (object.equals(Boolean.FALSE) ||
\r
437 (object instanceof String &&
\r
438 ((String)object).equalsIgnoreCase("false"))) {
\r
440 } else if (object.equals(Boolean.TRUE) ||
\r
441 (object instanceof String &&
\r
442 ((String)object).equalsIgnoreCase("true"))) {
\r
445 throw new JSONException("JSONObject[" + quote(key) +
\r
446 "] is not a Boolean.");
\r
451 * Get the double value associated with a key.
\r
452 * @param key A key string.
\r
453 * @return The numeric value.
\r
454 * @throws JSONException if the key is not found or
\r
455 * if the value is not a Number object and cannot be converted to a number.
\r
457 public double getDouble(String key) throws JSONException {
\r
458 Object object = this.get(key);
\r
460 return object instanceof Number
\r
461 ? ((Number)object).doubleValue()
\r
462 : Double.parseDouble((String)object);
\r
463 } catch (Exception e) {
\r
464 throw new JSONException("JSONObject[" + quote(key) +
\r
465 "] is not a number.");
\r
471 * Get the int value associated with a key.
\r
473 * @param key A key string.
\r
474 * @return The integer value.
\r
475 * @throws JSONException if the key is not found or if the value cannot
\r
476 * be converted to an integer.
\r
478 public int getInt(String key) throws JSONException {
\r
479 Object object = this.get(key);
\r
481 return object instanceof Number
\r
482 ? ((Number)object).intValue()
\r
483 : Integer.parseInt((String)object);
\r
484 } catch (Exception e) {
\r
485 throw new JSONException("JSONObject[" + quote(key) +
\r
486 "] is not an int.");
\r
492 * Get the JSONArray value associated with a key.
\r
494 * @param key A key string.
\r
495 * @return A JSONArray which is the value.
\r
496 * @throws JSONException if the key is not found or
\r
497 * if the value is not a JSONArray.
\r
499 public JSONArray getJSONArray(String key) throws JSONException {
\r
500 Object object = this.get(key);
\r
501 if (object instanceof JSONArray) {
\r
502 return (JSONArray)object;
\r
504 throw new JSONException("JSONObject[" + quote(key) +
\r
505 "] is not a JSONArray.");
\r
510 * Get the JSONObject value associated with a key.
\r
512 * @param key A key string.
\r
513 * @return A JSONObject which is the value.
\r
514 * @throws JSONException if the key is not found or
\r
515 * if the value is not a JSONObject.
\r
517 public JSONObject getJSONObject(String key) throws JSONException {
\r
518 Object object = this.get(key);
\r
519 if (object instanceof JSONObject) {
\r
520 return (JSONObject)object;
\r
522 throw new JSONException("JSONObject[" + quote(key) +
\r
523 "] is not a JSONObject.");
\r
528 * Get the long value associated with a key.
\r
530 * @param key A key string.
\r
531 * @return The long value.
\r
532 * @throws JSONException if the key is not found or if the value cannot
\r
533 * be converted to a long.
\r
535 public long getLong(String key) throws JSONException {
\r
536 Object object = this.get(key);
\r
538 return object instanceof Number
\r
539 ? ((Number)object).longValue()
\r
540 : Long.parseLong((String)object);
\r
541 } catch (Exception e) {
\r
542 throw new JSONException("JSONObject[" + quote(key) +
\r
543 "] is not a long.");
\r
549 * Get an array of field names from a JSONObject.
\r
551 * @return An array of field names, or null if there are no names.
\r
553 public static String[] getNames(JSONObject jo) {
\r
554 int length = jo.length();
\r
558 Iterator<String> iterator = jo.keys();
\r
559 String[] names = new String[length];
\r
561 while (iterator.hasNext()) {
\r
562 names[i] = iterator.next();
\r
570 * Get an array of field names from an Object.
\r
572 * @return An array of field names, or null if there are no names.
\r
574 public static String[] getNames(Object object) {
\r
575 if (object == null) {
\r
578 Class<? extends Object> klass = object.getClass();
\r
579 Field[] fields = klass.getFields();
\r
580 int length = fields.length;
\r
584 String[] names = new String[length];
\r
585 for (int i = 0; i < length; i += 1) {
\r
586 names[i] = fields[i].getName();
\r
593 * Get the string associated with a key.
\r
595 * @param key A key string.
\r
596 * @return A string which is the value.
\r
597 * @throws JSONException if there is no string value for the key.
\r
599 public String getString(String key) throws JSONException {
\r
600 Object object = this.get(key);
\r
601 if (object instanceof String) {
\r
602 return (String)object;
\r
604 throw new JSONException("JSONObject[" + quote(key) +
\r
605 "] not a string.");
\r
610 * Determine if the JSONObject contains a specific key.
\r
611 * @param key A key string.
\r
612 * @return true if the key exists in the JSONObject.
\r
614 public boolean has(String key) {
\r
615 return this.map.containsKey(key);
\r
620 * Increment a property of a JSONObject. If there is no such property,
\r
621 * create one with a value of 1. If there is such a property, and if
\r
622 * it is an Integer, Long, Double, or Float, then add one to it.
\r
623 * @param key A key string.
\r
625 * @throws JSONException If there is already a property with this name
\r
626 * that is not an Integer, Long, Double, or Float.
\r
628 public JSONObject increment(String key) throws JSONException {
\r
629 Object value = this.opt(key);
\r
630 if (value == null) {
\r
632 } else if (value instanceof Integer) {
\r
633 this.put(key, ((Integer)value).intValue() + 1);
\r
634 } else if (value instanceof Long) {
\r
635 this.put(key, ((Long)value).longValue() + 1);
\r
636 } else if (value instanceof Double) {
\r
637 this.put(key, ((Double)value).doubleValue() + 1);
\r
638 } else if (value instanceof Float) {
\r
639 this.put(key, ((Float)value).floatValue() + 1);
\r
641 throw new JSONException("Unable to increment [" + quote(key) + "].");
\r
648 * Determine if the value associated with the key is null or if there is
\r
650 * @param key A key string.
\r
651 * @return true if there is no value associated with the key or if
\r
652 * the value is the JSONObject.NULL object.
\r
654 public boolean isNull(String key) {
\r
655 return JSONObject.NULL.equals(this.opt(key));
\r
660 * Get an enumeration of the keys of the JSONObject.
\r
662 * @return An iterator of the keys.
\r
664 public Iterator<String> keys() {
\r
665 return this.keySet().iterator();
\r
670 * Get a set of keys of the JSONObject.
\r
672 * @return A keySet.
\r
674 public Set<String> keySet() {
\r
675 return this.map.keySet();
\r
680 * Get the number of keys stored in the JSONObject.
\r
682 * @return The number of keys in the JSONObject.
\r
684 public int length() {
\r
685 return this.map.size();
\r
690 * Produce a JSONArray containing the names of the elements of this
\r
692 * @return A JSONArray containing the key strings, or null if the JSONObject
\r
695 public JSONArray names() {
\r
696 JSONArray ja = new JSONArray();
\r
697 Iterator<String> keys = this.keys();
\r
698 while (keys.hasNext()) {
\r
699 ja.put(keys.next());
\r
701 return ja.length() == 0 ? null : ja;
\r
705 * Produce a string from a Number.
\r
706 * @param number A Number
\r
707 * @return A String.
\r
708 * @throws JSONException If n is a non-finite number.
\r
710 public static String numberToString(Number number)
\r
711 throws JSONException {
\r
712 if (number == null) {
\r
713 throw new JSONException("Null pointer");
\r
715 testValidity(number);
\r
717 // Shave off trailing zeros and decimal point, if possible.
\r
719 String string = number.toString();
\r
720 if (string.indexOf('.') > 0 && string.indexOf('e') < 0 &&
\r
721 string.indexOf('E') < 0) {
\r
722 while (string.endsWith("0")) {
\r
723 string = string.substring(0, string.length() - 1);
\r
725 if (string.endsWith(".")) {
\r
726 string = string.substring(0, string.length() - 1);
\r
734 * Get an optional value associated with a key.
\r
735 * @param key A key string.
\r
736 * @return An object which is the value, or null if there is no value.
\r
738 public Object opt(String key) {
\r
739 return key == null ? null : this.map.get(key);
\r
744 * Get an optional boolean associated with a key.
\r
745 * It returns false if there is no such key, or if the value is not
\r
746 * Boolean.TRUE or the String "true".
\r
748 * @param key A key string.
\r
749 * @return The truth.
\r
751 public boolean optBoolean(String key) {
\r
752 return this.optBoolean(key, false);
\r
757 * Get an optional boolean associated with a key.
\r
758 * It returns the defaultValue if there is no such key, or if it is not
\r
759 * a Boolean or the String "true" or "false" (case insensitive).
\r
761 * @param key A key string.
\r
762 * @param defaultValue The default.
\r
763 * @return The truth.
\r
765 public boolean optBoolean(String key, boolean defaultValue) {
\r
767 return this.getBoolean(key);
\r
768 } catch (Exception e) {
\r
769 return defaultValue;
\r
775 * Get an optional double associated with a key,
\r
776 * or NaN if there is no such key or if its value is not a number.
\r
777 * If the value is a string, an attempt will be made to evaluate it as
\r
780 * @param key A string which is the key.
\r
781 * @return An object which is the value.
\r
783 public double optDouble(String key) {
\r
784 return this.optDouble(key, Double.NaN);
\r
789 * Get an optional double associated with a key, or the
\r
790 * defaultValue if there is no such key or if its value is not a number.
\r
791 * If the value is a string, an attempt will be made to evaluate it as
\r
794 * @param key A key string.
\r
795 * @param defaultValue The default.
\r
796 * @return An object which is the value.
\r
798 public double optDouble(String key, double defaultValue) {
\r
800 return this.getDouble(key);
\r
801 } catch (Exception e) {
\r
802 return defaultValue;
\r
808 * Get an optional int value associated with a key,
\r
809 * or zero if there is no such key or if the value is not a number.
\r
810 * If the value is a string, an attempt will be made to evaluate it as
\r
813 * @param key A key string.
\r
814 * @return An object which is the value.
\r
816 public int optInt(String key) {
\r
817 return this.optInt(key, 0);
\r
822 * Get an optional int value associated with a key,
\r
823 * or the default if there is no such key or if the value is not a number.
\r
824 * If the value is a string, an attempt will be made to evaluate it as
\r
827 * @param key A key string.
\r
828 * @param defaultValue The default.
\r
829 * @return An object which is the value.
\r
831 public int optInt(String key, int defaultValue) {
\r
833 return this.getInt(key);
\r
834 } catch (Exception e) {
\r
835 return defaultValue;
\r
841 * Get an optional JSONArray associated with a key.
\r
842 * It returns null if there is no such key, or if its value is not a
\r
845 * @param key A key string.
\r
846 * @return A JSONArray which is the value.
\r
848 public JSONArray optJSONArray(String key) {
\r
849 Object o = this.opt(key);
\r
850 return o instanceof JSONArray ? (JSONArray)o : null;
\r
855 * Get an optional JSONObject associated with a key.
\r
856 * It returns null if there is no such key, or if its value is not a
\r
859 * @param key A key string.
\r
860 * @return A JSONObject which is the value.
\r
862 public JSONObject optJSONObject(String key) {
\r
863 Object object = this.opt(key);
\r
864 return object instanceof JSONObject ? (JSONObject)object : null;
\r
869 * Get an optional long value associated with a key,
\r
870 * or zero if there is no such key or if the value is not a number.
\r
871 * If the value is a string, an attempt will be made to evaluate it as
\r
874 * @param key A key string.
\r
875 * @return An object which is the value.
\r
877 public long optLong(String key) {
\r
878 return this.optLong(key, 0);
\r
883 * Get an optional long value associated with a key,
\r
884 * or the default if there is no such key or if the value is not a number.
\r
885 * If the value is a string, an attempt will be made to evaluate it as
\r
888 * @param key A key string.
\r
889 * @param defaultValue The default.
\r
890 * @return An object which is the value.
\r
892 public long optLong(String key, long defaultValue) {
\r
894 return this.getLong(key);
\r
895 } catch (Exception e) {
\r
896 return defaultValue;
\r
902 * Get an optional string associated with a key.
\r
903 * It returns an empty string if there is no such key. If the value is not
\r
904 * a string and is not null, then it is converted to a string.
\r
906 * @param key A key string.
\r
907 * @return A string which is the value.
\r
909 public String optString(String key) {
\r
910 return this.optString(key, "");
\r
915 * Get an optional string associated with a key.
\r
916 * It returns the defaultValue if there is no such key.
\r
918 * @param key A key string.
\r
919 * @param defaultValue The default.
\r
920 * @return A string which is the value.
\r
922 public String optString(String key, String defaultValue) {
\r
923 Object object = this.opt(key);
\r
924 return NULL.equals(object) ? defaultValue : object.toString();
\r
928 private void populateMap(Object bean) {
\r
929 Class<? extends Object> klass = bean.getClass();
\r
931 // If klass is a System class then set includeSuperClass to false.
\r
933 boolean includeSuperClass = klass.getClassLoader() != null;
\r
935 Method[] methods = includeSuperClass
\r
936 ? klass.getMethods()
\r
937 : klass.getDeclaredMethods();
\r
938 for (int i = 0; i < methods.length; i += 1) {
\r
940 Method method = methods[i];
\r
941 if (Modifier.isPublic(method.getModifiers())) {
\r
942 String name = method.getName();
\r
944 if (name.startsWith("get")) {
\r
945 if ("getClass".equals(name) ||
\r
946 "getDeclaringClass".equals(name)) {
\r
949 key = name.substring(3);
\r
951 } else if (name.startsWith("is")) {
\r
952 key = name.substring(2);
\r
954 if (key.length() > 0 &&
\r
955 Character.isUpperCase(key.charAt(0)) &&
\r
956 method.getParameterTypes().length == 0) {
\r
957 if (key.length() == 1) {
\r
958 key = key.toLowerCase();
\r
959 } else if (!Character.isUpperCase(key.charAt(1))) {
\r
960 key = key.substring(0, 1).toLowerCase() +
\r
964 Object result = method.invoke(bean, (Object[])null);
\r
965 if (result != null) {
\r
966 this.map.put(key, wrap(result));
\r
970 } catch (Exception ignore) {
\r
977 * Put a key/boolean pair in the JSONObject.
\r
979 * @param key A key string.
\r
980 * @param value A boolean which is the value.
\r
982 * @throws JSONException If the key is null.
\r
984 public JSONObject put(String key, boolean value) throws JSONException {
\r
985 this.put(key, value ? Boolean.TRUE : Boolean.FALSE);
\r
991 * Put a key/value pair in the JSONObject, where the value will be a
\r
992 * JSONArray which is produced from a Collection.
\r
993 * @param key A key string.
\r
994 * @param value A Collection value.
\r
996 * @throws JSONException
\r
998 public JSONObject put(String key, Collection<Object> value) throws JSONException {
\r
999 this.put(key, new JSONArray(value));
\r
1005 * Put a key/double pair in the JSONObject.
\r
1007 * @param key A key string.
\r
1008 * @param value A double which is the value.
\r
1010 * @throws JSONException If the key is null or if the number is invalid.
\r
1012 public JSONObject put(String key, double value) throws JSONException {
\r
1013 this.put(key, new Double(value));
\r
1019 * Put a key/int pair in the JSONObject.
\r
1021 * @param key A key string.
\r
1022 * @param value An int which is the value.
\r
1024 * @throws JSONException If the key is null.
\r
1026 public JSONObject put(String key, int value) throws JSONException {
\r
1027 this.put(key, new Integer(value));
\r
1033 * Put a key/long pair in the JSONObject.
\r
1035 * @param key A key string.
\r
1036 * @param value A long which is the value.
\r
1038 * @throws JSONException If the key is null.
\r
1040 public JSONObject put(String key, long value) throws JSONException {
\r
1041 this.put(key, new Long(value));
\r
1047 * Put a key/value pair in the JSONObject, where the value will be a
\r
1048 * JSONObject which is produced from a Map.
\r
1049 * @param key A key string.
\r
1050 * @param value A Map value.
\r
1052 * @throws JSONException
\r
1054 public JSONObject put(String key, Map<String, Object> value) throws JSONException {
\r
1055 this.put(key, new JSONObject(value));
\r
1061 * Put a key/value pair in the JSONObject. If the value is null,
\r
1062 * then the key will be removed from the JSONObject if it is present.
\r
1063 * @param key A key string.
\r
1064 * @param value An object which is the value. It should be of one of these
\r
1065 * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String,
\r
1066 * or the JSONObject.NULL object.
\r
1068 * @throws JSONException If the value is non-finite number
\r
1069 * or if the key is null.
\r
1071 public JSONObject put(String key, Object value) throws JSONException {
\r
1073 if (key == null) {
\r
1074 throw new JSONException("Null key.");
\r
1076 if (value != null) {
\r
1077 testValidity(value);
\r
1078 pooled = (String)keyPool.get(key);
\r
1079 if (pooled == null) {
\r
1080 if (keyPool.size() >= keyPoolSize) {
\r
1081 keyPool = new HashMap<String, Object>(keyPoolSize);
\r
1083 keyPool.put(key, key);
\r
1087 this.map.put(key, value);
\r
1096 * Put a key/value pair in the JSONObject, but only if the key and the
\r
1097 * value are both non-null, and only if there is not already a member
\r
1102 * @throws JSONException if the key is a duplicate
\r
1104 public JSONObject putOnce(String key, Object value) throws JSONException {
\r
1105 if (key != null && value != null) {
\r
1106 if (this.opt(key) != null) {
\r
1107 throw new JSONException("Duplicate key \"" + key + "\"");
\r
1109 this.put(key, value);
\r
1116 * Put a key/value pair in the JSONObject, but only if the
\r
1117 * key and the value are both non-null.
\r
1118 * @param key A key string.
\r
1119 * @param value An object which is the value. It should be of one of these
\r
1120 * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String,
\r
1121 * or the JSONObject.NULL object.
\r
1123 * @throws JSONException If the value is a non-finite number.
\r
1125 public JSONObject putOpt(String key, Object value) throws JSONException {
\r
1126 if (key != null && value != null) {
\r
1127 this.put(key, value);
\r
1134 * Produce a string in double quotes with backslash sequences in all the
\r
1135 * right places. A backslash will be inserted within </, producing <\/,
\r
1136 * allowing JSON text to be delivered in HTML. In JSON text, a string
\r
1137 * cannot contain a control character or an unescaped quote or backslash.
\r
1138 * @param string A String
\r
1139 * @return A String correctly formatted for insertion in a JSON text.
\r
1141 public static String quote(String string) {
\r
1142 StringWriter sw = new StringWriter();
\r
1143 synchronized (sw.getBuffer()) {
\r
1145 return quote(string, sw).toString();
\r
1146 } catch (IOException ignored) {
\r
1147 // will never happen - we are writing to a string writer
\r
1153 public static Writer quote(String string, Writer w) throws IOException {
\r
1154 if (string == null || string.length() == 0) {
\r
1163 int len = string.length();
\r
1166 for (i = 0; i < len; i += 1) {
\r
1168 c = string.charAt(i);
\r
1197 if (c < ' ' || (c >= '\u0080' && c < '\u00a0')
\r
1198 || (c >= '\u2000' && c < '\u2100')) {
\r
1200 hhhh = Integer.toHexString(c);
\r
1201 w.write("0000", 0, 4 - hhhh.length());
\r
1213 * Remove a name and its value, if present.
\r
1214 * @param key The name to be removed.
\r
1215 * @return The value that was associated with the name,
\r
1216 * or null if there was no value.
\r
1218 public Object remove(String key) {
\r
1219 return this.map.remove(key);
\r
1223 * Try to convert a string into a number, boolean, or null. If the string
\r
1224 * can't be converted, return the string.
\r
1225 * @param string A String.
\r
1226 * @return A simple JSON value.
\r
1228 public static Object stringToValue(String string) {
\r
1230 if (string.equals("")) {
\r
1233 if (string.equalsIgnoreCase("true")) {
\r
1234 return Boolean.TRUE;
\r
1236 if (string.equalsIgnoreCase("false")) {
\r
1237 return Boolean.FALSE;
\r
1239 if (string.equalsIgnoreCase("null")) {
\r
1240 return JSONObject.NULL;
\r
1244 * If it might be a number, try converting it.
\r
1245 * If a number cannot be produced, then the value will just
\r
1246 * be a string. Note that the plus and implied string
\r
1247 * conventions are non-standard. A JSON parser may accept
\r
1248 * non-JSON forms as long as it accepts all correct JSON forms.
\r
1251 char b = string.charAt(0);
\r
1252 if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') {
\r
1254 if (string.indexOf('.') > -1 ||
\r
1255 string.indexOf('e') > -1 || string.indexOf('E') > -1) {
\r
1256 d = Double.valueOf(string);
\r
1257 if (!d.isInfinite() && !d.isNaN()) {
\r
1261 Long myLong = new Long(string);
\r
1262 if (myLong.longValue() == myLong.intValue()) {
\r
1263 return new Integer(myLong.intValue());
\r
1268 } catch (Exception ignore) {
\r
1276 * Throw an exception if the object is a NaN or infinite number.
\r
1277 * @param o The object to test.
\r
1278 * @throws JSONException If o is a non-finite number.
\r
1280 public static void testValidity(Object o) throws JSONException {
\r
1282 if (o instanceof Double) {
\r
1283 if (((Double)o).isInfinite() || ((Double)o).isNaN()) {
\r
1284 throw new JSONException(
\r
1285 "JSON does not allow non-finite numbers.");
\r
1287 } else if (o instanceof Float) {
\r
1288 if (((Float)o).isInfinite() || ((Float)o).isNaN()) {
\r
1289 throw new JSONException(
\r
1290 "JSON does not allow non-finite numbers.");
\r
1298 * Produce a JSONArray containing the values of the members of this
\r
1300 * @param names A JSONArray containing a list of key strings. This
\r
1301 * determines the sequence of the values in the result.
\r
1302 * @return A JSONArray of values.
\r
1303 * @throws JSONException If any of the values are non-finite numbers.
\r
1305 public JSONArray toJSONArray(JSONArray names) throws JSONException {
\r
1306 if (names == null || names.length() == 0) {
\r
1309 JSONArray ja = new JSONArray();
\r
1310 for (int i = 0; i < names.length(); i += 1) {
\r
1311 ja.put(this.opt(names.getString(i)));
\r
1317 * Make a JSON text of this JSONObject. For compactness, no whitespace
\r
1318 * is added. If this would not result in a syntactically correct JSON text,
\r
1319 * then null will be returned instead.
\r
1321 * Warning: This method assumes that the data structure is acyclical.
\r
1323 * @return a printable, displayable, portable, transmittable
\r
1324 * representation of the object, beginning
\r
1325 * with <code>{</code> <small>(left brace)</small> and ending
\r
1326 * with <code>}</code> <small>(right brace)</small>.
\r
1328 public String toString() {
\r
1330 return this.toString(0);
\r
1331 } catch (Exception e) {
\r
1338 * Make a prettyprinted JSON text of this JSONObject.
\r
1340 * Warning: This method assumes that the data structure is acyclical.
\r
1341 * @param indentFactor The number of spaces to add to each level of
\r
1343 * @return a printable, displayable, portable, transmittable
\r
1344 * representation of the object, beginning
\r
1345 * with <code>{</code> <small>(left brace)</small> and ending
\r
1346 * with <code>}</code> <small>(right brace)</small>.
\r
1347 * @throws JSONException If the object contains an invalid number.
\r
1349 public String toString(int indentFactor) throws JSONException {
\r
1350 StringWriter w = new StringWriter();
\r
1351 synchronized (w.getBuffer()) {
\r
1352 return this.write(w, indentFactor, 0).toString();
\r
1357 * Make a JSON text of an Object value. If the object has an
\r
1358 * value.toJSONString() method, then that method will be used to produce
\r
1359 * the JSON text. The method is required to produce a strictly
\r
1360 * conforming text. If the object does not contain a toJSONString
\r
1361 * method (which is the most common case), then a text will be
\r
1362 * produced by other means. If the value is an array or Collection,
\r
1363 * then a JSONArray will be made from it and its toJSONString method
\r
1364 * will be called. If the value is a MAP, then a JSONObject will be made
\r
1365 * from it and its toJSONString method will be called. Otherwise, the
\r
1366 * value's toString method will be called, and the result will be quoted.
\r
1369 * Warning: This method assumes that the data structure is acyclical.
\r
1370 * @param value The value to be serialized.
\r
1371 * @return a printable, displayable, transmittable
\r
1372 * representation of the object, beginning
\r
1373 * with <code>{</code> <small>(left brace)</small> and ending
\r
1374 * with <code>}</code> <small>(right brace)</small>.
\r
1375 * @throws JSONException If the value is or contains an invalid number.
\r
1377 @SuppressWarnings("unchecked")
\r
1378 public static String valueToString(Object value) throws JSONException {
\r
1379 if (value == null || value.equals(null)) {
\r
1382 if (value instanceof JSONString) {
\r
1385 object = ((JSONString)value).toJSONString();
\r
1386 } catch (Exception e) {
\r
1387 throw new JSONException(e);
\r
1389 if (object instanceof String) {
\r
1390 return (String)object;
\r
1392 throw new JSONException("Bad value from toJSONString: " + object);
\r
1394 if (value instanceof Number) {
\r
1395 return numberToString((Number) value);
\r
1397 if (value instanceof Boolean || value instanceof JSONObject ||
\r
1398 value instanceof JSONArray) {
\r
1399 return value.toString();
\r
1401 if (value instanceof Map) {
\r
1402 return new JSONObject((Map<String, Object>)value).toString();
\r
1404 if (value instanceof Collection) {
\r
1405 return new JSONArray((Collection<Object>)value).toString();
\r
1407 if (value.getClass().isArray()) {
\r
1408 return new JSONArray(value).toString();
\r
1410 return quote(value.toString());
\r
1414 * Wrap an object, if necessary. If the object is null, return the NULL
\r
1415 * object. If it is an array or collection, wrap it in a JSONArray. If
\r
1416 * it is a map, wrap it in a JSONObject. If it is a standard property
\r
1417 * (Double, String, et al) then it is already wrapped. Otherwise, if it
\r
1418 * comes from one of the java packages, turn it into a string. And if
\r
1419 * it doesn't, try to wrap it in a JSONObject. If the wrapping fails,
\r
1420 * then null is returned.
\r
1422 * @param object The object to wrap
\r
1423 * @return The wrapped value
\r
1425 @SuppressWarnings("unchecked")
\r
1426 public static Object wrap(Object object) {
\r
1428 if (object == null) {
\r
1431 if (object instanceof JSONObject || object instanceof JSONArray ||
\r
1432 NULL.equals(object) || object instanceof JSONString ||
\r
1433 object instanceof Byte || object instanceof Character ||
\r
1434 object instanceof Short || object instanceof Integer ||
\r
1435 object instanceof Long || object instanceof Boolean ||
\r
1436 object instanceof Float || object instanceof Double ||
\r
1437 object instanceof String) {
\r
1441 if (object instanceof Collection) {
\r
1442 return new JSONArray((Collection<Object>)object);
\r
1444 if (object.getClass().isArray()) {
\r
1445 return new JSONArray(object);
\r
1447 if (object instanceof Map) {
\r
1448 return new JSONObject((Map<String, Object>)object);
\r
1450 Package objectPackage = object.getClass().getPackage();
\r
1451 String objectPackageName = objectPackage != null
\r
1452 ? objectPackage.getName()
\r
1455 objectPackageName.startsWith("java.") ||
\r
1456 objectPackageName.startsWith("javax.") ||
\r
1457 object.getClass().getClassLoader() == null
\r
1459 return object.toString();
\r
1461 return new JSONObject(object);
\r
1462 } catch(Exception exception) {
\r
1469 * Write the contents of the JSONObject as JSON text to a writer.
\r
1470 * For compactness, no whitespace is added.
\r
1472 * Warning: This method assumes that the data structure is acyclical.
\r
1474 * @return The writer.
\r
1475 * @throws JSONException
\r
1477 public Writer write(Writer writer) throws JSONException {
\r
1478 return this.write(writer, 0, 0);
\r
1482 @SuppressWarnings("unchecked")
\r
1483 static final Writer writeValue(Writer writer, Object value,
\r
1484 int indentFactor, int indent) throws JSONException, IOException {
\r
1485 if (value == null || value.equals(null)) {
\r
1486 writer.write("null");
\r
1487 } else if (value instanceof JSONObject) {
\r
1488 ((JSONObject) value).write(writer, indentFactor, indent);
\r
1489 } else if (value instanceof JSONArray) {
\r
1490 ((JSONArray) value).write(writer, indentFactor, indent);
\r
1491 } else if (value instanceof Map) {
\r
1492 new JSONObject((Map<String, Object>) value).write(writer, indentFactor, indent);
\r
1493 } else if (value instanceof Collection) {
\r
1494 new JSONArray((Collection<Object>) value).write(writer, indentFactor,
\r
1496 } else if (value.getClass().isArray()) {
\r
1497 new JSONArray(value).write(writer, indentFactor, indent);
\r
1498 } else if (value instanceof Number) {
\r
1499 writer.write(numberToString((Number) value));
\r
1500 } else if (value instanceof Boolean) {
\r
1501 writer.write(value.toString());
\r
1502 } else if (value instanceof JSONString) {
\r
1505 o = ((JSONString) value).toJSONString();
\r
1506 } catch (Exception e) {
\r
1507 throw new JSONException(e);
\r
1509 writer.write(o != null ? o.toString() : quote(value.toString()));
\r
1511 quote(value.toString(), writer);
\r
1516 static final void indent(Writer writer, int indent) throws IOException {
\r
1517 for (int i = 0; i < indent; i += 1) {
\r
1518 writer.write(' ');
\r
1523 * Write the contents of the JSONObject as JSON text to a writer. For
\r
1524 * compactness, no whitespace is added.
\r
1526 * Warning: This method assumes that the data structure is acyclical.
\r
1528 * @return The writer.
\r
1529 * @throws JSONException
\r
1531 Writer write(Writer writer, int indentFactor, int indent)
\r
1532 throws JSONException {
\r
1534 boolean commanate = false;
\r
1535 final int length = this.length();
\r
1536 Iterator<String> keys = this.keys();
\r
1537 writer.write('{');
\r
1539 if (length == 1) {
\r
1540 Object key = keys.next();
\r
1541 writer.write(quote(key.toString()));
\r
1542 writer.write(':');
\r
1543 if (indentFactor > 0) {
\r
1544 writer.write(' ');
\r
1546 writeValue(writer, this.map.get(key), indentFactor, indent);
\r
1547 } else if (length != 0) {
\r
1548 final int newindent = indent + indentFactor;
\r
1549 while (keys.hasNext()) {
\r
1550 Object key = keys.next();
\r
1552 writer.write(',');
\r
1554 if (indentFactor > 0) {
\r
1555 writer.write('\n');
\r
1557 indent(writer, newindent);
\r
1558 writer.write(quote(key.toString()));
\r
1559 writer.write(':');
\r
1560 if (indentFactor > 0) {
\r
1561 writer.write(' ');
\r
1563 writeValue(writer, this.map.get(key), indentFactor,
\r
1567 if (indentFactor > 0) {
\r
1568 writer.write('\n');
\r
1570 indent(writer, indent);
\r
1572 writer.write('}');
\r
1574 } catch (IOException exception) {
\r
1575 throw new JSONException(exception);
\r