diff options
author | Jesse Wilson <jessewilson@google.com> | 2010-03-16 23:41:33 -0700 |
---|---|---|
committer | Jesse Wilson <jessewilson@google.com> | 2010-03-17 11:17:47 -0700 |
commit | 1483c41ab440a713e2b935801792da9039ec5d61 (patch) | |
tree | dc95a9904622c2f6bde83a608db5b1649b8b1383 /json | |
parent | a10509b68308e2bff501ece4862434108900c249 (diff) | |
download | libcore-1483c41ab440a713e2b935801792da9039ec5d61.zip libcore-1483c41ab440a713e2b935801792da9039ec5d61.tar.gz libcore-1483c41ab440a713e2b935801792da9039ec5d61.tar.bz2 |
Javadocs for JSONObject.
Change-Id: I5ec9df6a3a9baac8f4f498890cd35feff774737a
Diffstat (limited to 'json')
-rw-r--r-- | json/src/main/java/org/json/JSON.java | 4 | ||||
-rw-r--r-- | json/src/main/java/org/json/JSONObject.java | 330 | ||||
-rw-r--r-- | json/src/test/java/org/json/JSONObjectTest.java | 47 |
3 files changed, 374 insertions, 7 deletions
diff --git a/json/src/main/java/org/json/JSON.java b/json/src/main/java/org/json/JSON.java index 029884b..b32124d 100644 --- a/json/src/main/java/org/json/JSON.java +++ b/json/src/main/java/org/json/JSON.java @@ -58,7 +58,7 @@ class JSON { return ((Number) value).intValue(); } else if (value instanceof String) { try { - return Double.valueOf((String) value).intValue(); + return (int) Double.parseDouble((String) value); } catch (NumberFormatException e) { } } @@ -72,7 +72,7 @@ class JSON { return ((Number) value).longValue(); } else if (value instanceof String) { try { - return Double.valueOf((String) value).longValue(); + return (long) Double.parseDouble((String) value); } catch (NumberFormatException e) { } } diff --git a/json/src/main/java/org/json/JSONObject.java b/json/src/main/java/org/json/JSONObject.java index c92d549..fcc5d6a 100644 --- a/json/src/main/java/org/json/JSONObject.java +++ b/json/src/main/java/org/json/JSONObject.java @@ -24,14 +24,75 @@ import java.util.Map; // Note: this class was written without inspecting the non-free org.json sourcecode. /** + * A modifiable set of name/value mappings. Names are unique, non-null strings. + * Values may be other {@link JSONObject JSONObjects}, {@link JSONArray + * JSONArrays}, Strings, Booleans, Integers, Longs, Doubles or {@link #NULL}. + * Values may not be {@code null}, {@link Double#isNaN() NaNs} or {@link + * Double#isInfinite() infinities}. * + * <p>This class can coerce values to another type when requested. + * <ul> + * <li>When the requested type is a boolean, strings will be coerced + * using {@link Boolean#valueOf(String)}. + * <li>When the requested type is a double, other {@link Number} types will + * be coerced using {@link Number#doubleValue() doubleValue()}. Strings + * that can be coerced using {@link Double#valueOf(String)} will be. + * <li>When the requested type is an int, other {@link Number} types will + * be coerced using {@link Number#intValue() intValue()}. Strings + * that can be coerced using {@link Double#valueOf(String)} will be, + * and then cast to int. + * <li>When the requested type is a long, other {@link Number} types will + * be coerced using {@link Number#longValue() longValue()}. Strings + * that can be coerced using {@link Double#valueOf(String)} will be, + * and then cast to long. This two-step conversion is lossy for very + * large values. For example, the string "9223372036854775806" yields the + * long 9223372036854775807. + * <li>When the requested type is a String, other non-null values will be + * coerced using {@link String#valueOf(Object)}. + * </ul> * - * <p>TODO: Note about self-use + * <p>This class can look up both mandatory and optional values: + * <ul> + * <li>Use <code>get<i>Type</i>()</code> to retrieve a mandatory value. This + * fails with a {@code JSONException} if the requested name has no value + * or if the value cannot be coerced to the requested type. + * <li>Use <code>opt<i>Type</i>()</code> to retrieve an optional value. This + * returns a system- or user-supplied default if the requested name has no + * value or if the value cannot be coerced to the requested type. + * </ul> + * + * <p><strong>Warning:</strong> this class represents null in two incompatible + * ways: the standard Java {@code null} literal, and the sentinel value {@link + * JSONObject#NULL JSONObject.NULL}. In particular, calling {@code + * put(name, null)} removes the named entry from the object but {@code + * put(name, JSONObject.NULL)} stores an entry whose value is {@code + * JSONObject.NULL}. + * + * <p>Instances of this class are not thread safe. Although this class is + * nonfinal, it was not designed for inheritance and should not be subclassed. + * In particular, self-use by overridable methods is not specified. See + * <i>Effective Java</i> Item 17, "Design and Document or inheritance or else + * prohibit it" for further information. */ public class JSONObject { private static final Double NEGATIVE_ZERO = -0d; + /** + * A sentinel value used to explicitly define a name with no value. Unlike + * {@code null}, names with this value: + * <ul> + * <li>show up in the {@link #names()} array + * <li>show up in the {@link #keys()} iterator + * <li>return {@code true} for {@link #has(String)} + * <li>do not throw on {@link #get(String)} + * <li>are included in the encoded JSON string. + * </ul> + * + * <p>This value violates the general contract of {@link Object#equals} by + * returning true when compared to {@code null}. Its {@link #toString} + * method returns "null". + */ public static final Object NULL = new Object() { @Override public boolean equals(Object o) { return o == this || o == null; // API specifies this broken equals implementation @@ -43,11 +104,22 @@ public class JSONObject { private final Map<String, Object> nameValuePairs; + /** + * Creates a JSONObject with no name/value mappings. + */ public JSONObject() { nameValuePairs = new HashMap<String, Object>(); } - /* Accept a raw type for API compatibility */ + /** + * Creates a new JSONObject by copying all name/value mappings from the + * given map. + * + * @param copyFrom a map whose keys are of type {@link String} and whose + * values are of supported types. Values do not need to be homogeneous. + * @throws NullPointerException if any of the map's keys are null. + */ + /* (accept a raw type for API compatibility) */ public JSONObject(Map copyFrom) { this(); Map<?, ?> contentsTyped = (Map<?, ?>) copyFrom; @@ -64,6 +136,13 @@ public class JSONObject { } } + /** + * Creates a new JSONObject with name/value mappings from the next value in + * the tokenizer. + * + * @param readFrom a tokenizer whose nextValue() method will yield a JSONObject. + * @throws JSONException if the parse fails or doesn't yield a JSONObject. + */ public JSONObject(JSONTokener readFrom) throws JSONException { /* * Getting the parser to populate this could get tricky. Instead, just @@ -77,10 +156,21 @@ public class JSONObject { } } + /** + * Creates a new JSONObject with name/value mappings from the JSON string. + * + * @param json a JSON-encoded string containing an object. + * @throws JSONException if the parse fails or doesn't yield a JSONObject. + */ public JSONObject(String json) throws JSONException { this(new JSONTokener(json)); } + /** + * Creates a new JSONObject by copying mappings for the listed names from + * the given object. Names that aren't present in {@code copyFrom} will be + * skipped. + */ public JSONObject(JSONObject copyFrom, String[] names) throws JSONException { this(); for (String name : names) { @@ -91,30 +181,70 @@ public class JSONObject { } } + /** + * Returns the number of name/value mappings in this object. + */ public int length() { return nameValuePairs.size(); } + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value + * mapping with the same name. + * + * @return this object. + */ public JSONObject put(String name, boolean value) throws JSONException { nameValuePairs.put(checkName(name), value); return this; } + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value + * mapping with the same name. + * + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this object. + */ public JSONObject put(String name, double value) throws JSONException { nameValuePairs.put(checkName(name), JSON.checkDouble(value)); return this; } + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value + * mapping with the same name. + * + * @return this object. + */ public JSONObject put(String name, int value) throws JSONException { nameValuePairs.put(checkName(name), value); return this; } + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value + * mapping with the same name. + * + * @return this object. + */ public JSONObject put(String name, long value) throws JSONException { nameValuePairs.put(checkName(name), value); return this; } + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value + * mapping with the same name. If the value is {@code null}, any existing + * mapping for {@code name} is removed. + * + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, + * Integer, Long, Double, {@link #NULL}, or {@code null}. May not be + * {@link Double#isNaN() NaNs} or {@link Double#isInfinite() + * infinities}. + * @return this object. + */ public JSONObject put(String name, Object value) throws JSONException { if (value == null) { nameValuePairs.remove(name); @@ -128,6 +258,10 @@ public class JSONObject { return this; } + /** + * Equivalent to {@code put(name, value)} when both parameters are non-null; + * does nothing otherwise. + */ public JSONObject putOpt(String name, Object value) throws JSONException { if (name == null || value == null) { return this; @@ -135,17 +269,36 @@ public class JSONObject { return put(name, value); } + /** + * Appends {@code value} to the array already mapped to {@code name}. If + * this object has no mapping for {@code name}, this inserts a new mapping. + * If the mapping exists but its value is not an array, the existing + * and new values are inserted in order into a new array which is itself + * mapped to {@code name}. In aggregate, this allows values to be added to a + * mapping one at a time. + * + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, + * Integer, Long, Double, {@link #NULL} or null. May not be {@link + * Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}. + */ public JSONObject accumulate(String name, Object value) throws JSONException { Object current = nameValuePairs.get(checkName(name)); if (current == null) { - put(name, value); - } else if (current instanceof JSONArray) { + return put(name, value); + } + + // check in accumulate, since array.put(Object) doesn't do any checking + if (value instanceof Number) { + JSON.checkDouble(((Number) value).doubleValue()); + } + + if (current instanceof JSONArray) { JSONArray array = (JSONArray) current; array.put(value); } else { JSONArray array = new JSONArray(); array.put(current); - array.put(value); // fails on bogus values + array.put(value); nameValuePairs.put(name, array); } return this; @@ -158,19 +311,38 @@ public class JSONObject { return name; } + /** + * Removes the named mapping if it exists; does nothing otherwise. + * + * @return the value previously mapped by {@code name}, or null if there was + * no such mapping. + */ public Object remove(String name) { return nameValuePairs.remove(name); } + /** + * Returns true if this object has no mapping for {@code name} or if it has + * a mapping whose value is {@link #NULL}. + */ public boolean isNull(String name) { Object value = nameValuePairs.get(name); return value == null || value == NULL; } + /** + * Returns true if this object has a mapping for {@code name}. The mapping + * may be {@link #NULL}. + */ public boolean has(String name) { return nameValuePairs.containsKey(name); } + /** + * Returns the value mapped by {@code name}. + * + * @throws JSONException if no such mapping exists. + */ public Object get(String name) throws JSONException { Object result = nameValuePairs.get(name); if (result == null) { @@ -179,10 +351,21 @@ public class JSONObject { return result; } + /** + * Returns the value mapped by {@code name}, or null if no such mapping + * exists. + */ public Object opt(String name) { return nameValuePairs.get(name); } + /** + * Returns the value mapped by {@code name} if it exists and is a boolean or + * can be coerced to a boolean. + * + * @throws JSONException if the mapping doesn't exist or cannot be coerced + * to a boolean. + */ public boolean getBoolean(String name) throws JSONException { Object object = get(name); Boolean result = JSON.toBoolean(object); @@ -192,16 +375,31 @@ public class JSONObject { return result; } + /** + * Returns the value mapped by {@code name} if it exists and is a boolean or + * can be coerced to a boolean. Returns false otherwise. + */ public boolean optBoolean(String name) { return optBoolean(name, false); } + /** + * Returns the value mapped by {@code name} if it exists and is a boolean or + * can be coerced to a boolean. Returns {@code fallback} otherwise. + */ public boolean optBoolean(String name, boolean fallback) { Object object = opt(name); Boolean result = JSON.toBoolean(object); return result != null ? result : fallback; } + /** + * Returns the value mapped by {@code name} if it exists and is a double or + * can be coerced to a double. + * + * @throws JSONException if the mapping doesn't exist or cannot be coerced + * to a double. + */ public double getDouble(String name) throws JSONException { Object object = get(name); Double result = JSON.toDouble(object); @@ -211,16 +409,31 @@ public class JSONObject { return result; } + /** + * Returns the value mapped by {@code name} if it exists and is a double or + * can be coerced to a double. Returns {@code NaN} otherwise. + */ public double optDouble(String name) { return optDouble(name, Double.NaN); } + /** + * Returns the value mapped by {@code name} if it exists and is a double or + * can be coerced to a double. Returns {@code fallback} otherwise. + */ public double optDouble(String name, double fallback) { Object object = opt(name); Double result = JSON.toDouble(object); return result != null ? result : fallback; } + /** + * Returns the value mapped by {@code name} if it exists and is an int or + * can be coerced to an int. + * + * @throws JSONException if the mapping doesn't exist or cannot be coerced + * to an int. + */ public int getInt(String name) throws JSONException { Object object = get(name); Integer result = JSON.toInteger(object); @@ -230,16 +443,31 @@ public class JSONObject { return result; } + /** + * Returns the value mapped by {@code name} if it exists and is an int or + * can be coerced to an int. Returns 0 otherwise. + */ public int optInt(String name) { return optInt(name, 0); } + /** + * Returns the value mapped by {@code name} if it exists and is an int or + * can be coerced to an int. Returns {@code fallback} otherwise. + */ public int optInt(String name, int fallback) { Object object = opt(name); Integer result = JSON.toInteger(object); return result != null ? result : fallback; } + /** + * Returns the value mapped by {@code name} if it exists and is a long or + * can be coerced to a long. + * + * @throws JSONException if the mapping doesn't exist or cannot be coerced + * to a long. + */ public long getLong(String name) throws JSONException { Object object = get(name); Long result = JSON.toLong(object); @@ -249,16 +477,30 @@ public class JSONObject { return result; } + /** + * Returns the value mapped by {@code name} if it exists and is a long or + * can be coerced to a long. Returns 0 otherwise. + */ public long optLong(String name) { return optLong(name, 0L); } + /** + * Returns the value mapped by {@code name} if it exists and is a long or + * can be coerced to a long. Returns {@code fallback} otherwise. + */ public long optLong(String name, long fallback) { Object object = opt(name); Long result = JSON.toLong(object); return result != null ? result : fallback; } + /** + * Returns the value mapped by {@code name} if it exists, coercing it if + * necessary. + * + * @throws JSONException if no such mapping exists. + */ public String getString(String name) throws JSONException { Object object = get(name); String result = JSON.toString(object); @@ -268,16 +510,31 @@ public class JSONObject { return result; } + /** + * Returns the value mapped by {@code name} if it exists, coercing it if + * necessary. Returns the empty string if no such mapping exists. + */ public String optString(String name) { return optString(name, ""); } + /** + * Returns the value mapped by {@code name} if it exists, coercing it if + * necessary. Returns {@code fallback} if no such mapping exists. + */ public String optString(String name, String fallback) { Object object = opt(name); String result = JSON.toString(object); return result != null ? result : fallback; } + /** + * Returns the value mapped by {@code name} if it exists and is a {@code + * JSONArray}. + * + * @throws JSONException if the mapping doesn't exist or is not a {@code + * JSONArray}. + */ public JSONArray getJSONArray(String name) throws JSONException { Object object = get(name); if (object instanceof JSONArray) { @@ -287,11 +544,22 @@ public class JSONObject { } } + /** + * Returns the value mapped by {@code name} if it exists and is a {@code + * JSONArray}. Returns null otherwise. + */ public JSONArray optJSONArray(String name) { Object object = opt(name); return object instanceof JSONArray ? (JSONArray) object : null; } + /** + * Returns the value mapped by {@code name} if it exists and is a {@code + * JSONObject}. + * + * @throws JSONException if the mapping doesn't exist or is not a {@code + * JSONObject}. + */ public JSONObject getJSONObject(String name) throws JSONException { Object object = get(name); if (object instanceof JSONObject) { @@ -301,11 +569,20 @@ public class JSONObject { } } + /** + * Returns the value mapped by {@code name} if it exists and is a {@code + * JSONObject}. Returns null otherwise. + */ public JSONObject optJSONObject(String name) { Object object = opt(name); return object instanceof JSONObject ? (JSONObject) object : null; } + /** + * Returns an array with the values corresponding to {@code names}. The + * array contains null for names that aren't mapped. This method returns + * null if {@code names} is either null or empty. + */ public JSONArray toJSONArray(JSONArray names) throws JSONException { JSONArray result = new JSONArray(); if (names == null) { @@ -322,17 +599,32 @@ public class JSONObject { return result; } + /** + * Returns an iterator of the {@code String} names in this object. The + * returned iterator supports {@link Iterator#remove() remove()}, which will + * remove the corresponding mapping from this object. If this object is + * modified after the iterator is returned, the iterator's behavior is + * undefined. The order of the keys is undefined. + */ /* Return a raw type for API compatibility */ public Iterator keys() { return nameValuePairs.keySet().iterator(); } + /** + * Returns an array containing the string names in this object. This method + * returns null if this object contains no mappings. + */ public JSONArray names() { return nameValuePairs.isEmpty() ? null : new JSONArray(new ArrayList<String>(nameValuePairs.keySet())); } + /** + * Encodes this object as a compact JSON string, such as + * <pre>{"query":"Pizza","locations":[94043,90210]}</pre> + */ @Override public String toString() { try { JSONStringer stringer = new JSONStringer(); @@ -343,6 +635,21 @@ public class JSONObject { } } + /** + * Encodes this object as a human readable JSON string for debugging, such + * as + * <pre> + * { + * "query": "Pizza", + * "locations": [ + * 94043, + * 90210 + * ] + * }</pre> + * + * @param indentSpaces the number of spaces to indent for each level of + * nesting. + */ public String toString(int indentSpaces) throws JSONException { JSONStringer stringer = new JSONStringer(indentSpaces); writeTo(stringer); @@ -357,6 +664,12 @@ public class JSONObject { stringer.endObject(); } + /** + * Encodes the number as a JSON string. + * + * @param number a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + */ public static String numberToString(Number number) throws JSONException { if (number == null) { throw new JSONException("Number must be non-null"); @@ -378,6 +691,13 @@ public class JSONObject { return number.toString(); } + /** + * Encodes {@code data} as a JSON string. This applies quotes and any + * necessary character escaping. + * + * @param data the string to encode. Null will be interpreted as an empty + * string. + */ public static String quote(String data) { if (data == null) { return "\"\""; diff --git a/json/src/test/java/org/json/JSONObjectTest.java b/json/src/test/java/org/json/JSONObjectTest.java index e431096..b896a7f 100644 --- a/json/src/test/java/org/json/JSONObjectTest.java +++ b/json/src/test/java/org/json/JSONObjectTest.java @@ -453,6 +453,53 @@ public class JSONObjectTest extends TestCase { assertEquals(null, object.optJSONObject("foo")); } + public void testNullCoercionToString() throws JSONException { + JSONObject object = new JSONObject(); + object.put("foo", JSONObject.NULL); + assertEquals("null", object.getString("foo")); + } + + public void testArrayCoercion() throws JSONException { + JSONObject object = new JSONObject(); + object.put("foo", "[true]"); + try { + object.getJSONArray("foo"); + fail(); + } catch (JSONException e) { + } + } + + public void testObjectCoercion() throws JSONException { + JSONObject object = new JSONObject(); + object.put("foo", "{}"); + try { + object.getJSONObject("foo"); + fail(); + } catch (JSONException e) { + } + } + + public void testAccumulateValueChecking() throws JSONException { + JSONObject object = new JSONObject(); + try { + object.accumulate("foo", Double.NaN); + fail(); + } catch (JSONException e) { + } + object.accumulate("foo", 1); + try { + object.accumulate("foo", Double.NaN); + fail(); + } catch (JSONException e) { + } + object.accumulate("foo", 2); + try { + object.accumulate("foo", Double.NaN); + fail(); + } catch (JSONException e) { + } + } + public void testToJSONArray() throws JSONException { JSONObject object = new JSONObject(); Object value = new Object(); |