You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
838 lines
30 KiB
838 lines
30 KiB
5 years ago
|
/*
|
||
|
* Copyright (C) 2010 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package org.json;
|
||
|
|
||
|
import android.compat.annotation.UnsupportedAppUsage;
|
||
|
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.Collection;
|
||
|
import java.util.Iterator;
|
||
|
import java.util.LinkedHashMap;
|
||
|
import java.util.Map;
|
||
|
import java.util.Objects;
|
||
|
import java.util.Set;
|
||
|
import libcore.util.NonNull;
|
||
|
import libcore.util.Nullable;
|
||
|
|
||
|
// 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 any mix of {@link JSONObject JSONObjects}, {@link JSONArray
|
||
|
* JSONArrays}, Strings, Booleans, Integers, Longs, Doubles or {@link #NULL}.
|
||
|
* Values may not be {@code null}, {@link Double#isNaN() NaNs}, {@link
|
||
|
* Double#isInfinite() infinities}, or of any type not listed here.
|
||
|
*
|
||
|
* <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 a
|
||
|
* case-insensitive comparison to "true" and "false".
|
||
|
* <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><a name="lossy">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.</a>
|
||
|
* <li>When the requested type is a String, other non-null values will be
|
||
|
* coerced using {@link String#valueOf(Object)}. Although null cannot be
|
||
|
* coerced, the sentinel value {@link JSONObject#NULL} is coerced to the
|
||
|
* string "null".
|
||
|
* </ul>
|
||
|
*
|
||
|
* <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} reference, and the sentinel value {@link
|
||
|
* 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 overrideable 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 {
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
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".
|
||
|
*/
|
||
|
@NonNull public static final Object NULL = new Object() {
|
||
|
@Override public boolean equals(Object o) {
|
||
|
return o == this || o == null; // API specifies this broken equals implementation
|
||
|
}
|
||
|
// at least make the broken equals(null) consistent with Objects.hashCode(null).
|
||
|
@Override public int hashCode() { return Objects.hashCode(null); }
|
||
|
@Override public String toString() {
|
||
|
return "null";
|
||
|
}
|
||
|
};
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
private final LinkedHashMap<String, Object> nameValuePairs;
|
||
|
|
||
|
/**
|
||
|
* Creates a {@code JSONObject} with no name/value mappings.
|
||
|
*/
|
||
|
public JSONObject() {
|
||
|
nameValuePairs = new LinkedHashMap<String, Object>();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new {@code 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.
|
||
|
* @throws NullPointerException if any of the map's keys are null.
|
||
|
*/
|
||
|
/* (accept a raw type for API compatibility) */
|
||
|
public JSONObject(@NonNull Map copyFrom) {
|
||
|
this();
|
||
|
Map<?, ?> contentsTyped = (Map<?, ?>) copyFrom;
|
||
|
for (Map.Entry<?, ?> entry : contentsTyped.entrySet()) {
|
||
|
/*
|
||
|
* Deviate from the original by checking that keys are non-null and
|
||
|
* of the proper type. (We still defer validating the values).
|
||
|
*/
|
||
|
String key = (String) entry.getKey();
|
||
|
if (key == null) {
|
||
|
throw new NullPointerException("key == null");
|
||
|
}
|
||
|
nameValuePairs.put(key, wrap(entry.getValue()));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new {@code JSONObject} with name/value mappings from the next
|
||
|
* object in the tokener.
|
||
|
*
|
||
|
* @param readFrom a tokener whose nextValue() method will yield a
|
||
|
* {@code JSONObject}.
|
||
|
* @throws JSONException if the parse fails or doesn't yield a
|
||
|
* {@code JSONObject}.
|
||
|
*/
|
||
|
public JSONObject(@NonNull JSONTokener readFrom) throws JSONException {
|
||
|
/*
|
||
|
* Getting the parser to populate this could get tricky. Instead, just
|
||
|
* parse to temporary JSONObject and then steal the data from that.
|
||
|
*/
|
||
|
Object object = readFrom.nextValue();
|
||
|
if (object instanceof JSONObject) {
|
||
|
this.nameValuePairs = ((JSONObject) object).nameValuePairs;
|
||
|
} else {
|
||
|
throw JSON.typeMismatch(object, "JSONObject");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new {@code 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 {@code
|
||
|
* JSONObject}.
|
||
|
*/
|
||
|
public JSONObject(@NonNull String json) throws JSONException {
|
||
|
this(new JSONTokener(json));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new {@code 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(@NonNull JSONObject copyFrom, @NonNull String @NonNull [] names) throws JSONException {
|
||
|
this();
|
||
|
for (String name : names) {
|
||
|
Object value = copyFrom.opt(name);
|
||
|
if (value != null) {
|
||
|
nameValuePairs.put(name, value);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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.
|
||
|
*/
|
||
|
@NonNull public JSONObject put(@NonNull 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.
|
||
|
*/
|
||
|
@NonNull public JSONObject put(@NonNull 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.
|
||
|
*/
|
||
|
@NonNull public JSONObject put(@NonNull 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.
|
||
|
*/
|
||
|
@NonNull public JSONObject put(@NonNull 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.
|
||
|
*/
|
||
|
@NonNull public JSONObject put(@NonNull String name, @Nullable Object value) throws JSONException {
|
||
|
if (value == null) {
|
||
|
nameValuePairs.remove(name);
|
||
|
return this;
|
||
|
}
|
||
|
if (value instanceof Number) {
|
||
|
// deviate from the original by checking all Numbers, not just floats & doubles
|
||
|
JSON.checkDouble(((Number) value).doubleValue());
|
||
|
}
|
||
|
nameValuePairs.put(checkName(name), value);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Equivalent to {@code put(name, value)} when both parameters are non-null;
|
||
|
* does nothing otherwise.
|
||
|
*/
|
||
|
@NonNull public JSONObject putOpt(@Nullable String name, @Nullable Object value) throws JSONException {
|
||
|
if (name == null || value == null) {
|
||
|
return this;
|
||
|
}
|
||
|
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.
|
||
|
*
|
||
|
* <p> Note that {@code append(String, Object)} provides better semantics.
|
||
|
* In particular, the mapping for {@code name} will <b>always</b> be a
|
||
|
* {@link JSONArray}. Using {@code accumulate} will result in either a
|
||
|
* {@link JSONArray} or a mapping whose type is the type of {@code value}
|
||
|
* depending on the number of calls to it.
|
||
|
*
|
||
|
* @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}.
|
||
|
*/
|
||
|
// TODO: Change {@code append) to {@link #append} when append is
|
||
|
// unhidden.
|
||
|
@NonNull public JSONObject accumulate(@NonNull String name, @Nullable Object value) throws JSONException {
|
||
|
Object current = nameValuePairs.get(checkName(name));
|
||
|
if (current == null) {
|
||
|
return put(name, value);
|
||
|
}
|
||
|
|
||
|
if (current instanceof JSONArray) {
|
||
|
JSONArray array = (JSONArray) current;
|
||
|
array.checkedPut(value);
|
||
|
} else {
|
||
|
JSONArray array = new JSONArray();
|
||
|
array.checkedPut(current);
|
||
|
array.checkedPut(value);
|
||
|
nameValuePairs.put(name, array);
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Appends values to the array mapped to {@code name}. A new {@link JSONArray}
|
||
|
* mapping for {@code name} will be inserted if no mapping exists. If the existing
|
||
|
* mapping for {@code name} is not a {@link JSONArray}, a {@link JSONException}
|
||
|
* will be thrown.
|
||
|
*
|
||
|
* @throws JSONException if {@code name} is {@code null} or if the mapping for
|
||
|
* {@code name} is non-null and is not a {@link JSONArray}.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
@UnsupportedAppUsage
|
||
|
public JSONObject append(String name, Object value) throws JSONException {
|
||
|
Object current = nameValuePairs.get(checkName(name));
|
||
|
|
||
|
final JSONArray array;
|
||
|
if (current instanceof JSONArray) {
|
||
|
array = (JSONArray) current;
|
||
|
} else if (current == null) {
|
||
|
JSONArray newArray = new JSONArray();
|
||
|
nameValuePairs.put(name, newArray);
|
||
|
array = newArray;
|
||
|
} else {
|
||
|
throw new JSONException("Key " + name + " is not a JSONArray");
|
||
|
}
|
||
|
|
||
|
array.checkedPut(value);
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
String checkName(String name) throws JSONException {
|
||
|
if (name == null) {
|
||
|
throw new JSONException("Names must be non-null");
|
||
|
}
|
||
|
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.
|
||
|
*/
|
||
|
@Nullable public Object remove(@Nullable 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(@Nullable 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(@Nullable String name) {
|
||
|
return nameValuePairs.containsKey(name);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the value mapped by {@code name}, or throws if no such mapping exists.
|
||
|
*
|
||
|
* @throws JSONException if no such mapping exists.
|
||
|
*/
|
||
|
@NonNull public Object get(@NonNull String name) throws JSONException {
|
||
|
Object result = nameValuePairs.get(name);
|
||
|
if (result == null) {
|
||
|
throw new JSONException("No value for " + name);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the value mapped by {@code name}, or null if no such mapping
|
||
|
* exists.
|
||
|
*/
|
||
|
@Nullable public Object opt(@Nullable 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, or throws otherwise.
|
||
|
*
|
||
|
* @throws JSONException if the mapping doesn't exist or cannot be coerced
|
||
|
* to a boolean.
|
||
|
*/
|
||
|
public boolean getBoolean(@NonNull String name) throws JSONException {
|
||
|
Object object = get(name);
|
||
|
Boolean result = JSON.toBoolean(object);
|
||
|
if (result == null) {
|
||
|
throw JSON.typeMismatch(name, object, "boolean");
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the value mapped by {@code name} if it exists and is a boolean or
|
||
|
* can be coerced to a boolean, or false otherwise.
|
||
|
*/
|
||
|
public boolean optBoolean(@Nullable 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, or {@code fallback} otherwise.
|
||
|
*/
|
||
|
public boolean optBoolean(@Nullable 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, or throws otherwise.
|
||
|
*
|
||
|
* @throws JSONException if the mapping doesn't exist or cannot be coerced
|
||
|
* to a double.
|
||
|
*/
|
||
|
public double getDouble(@NonNull String name) throws JSONException {
|
||
|
Object object = get(name);
|
||
|
Double result = JSON.toDouble(object);
|
||
|
if (result == null) {
|
||
|
throw JSON.typeMismatch(name, object, "double");
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the value mapped by {@code name} if it exists and is a double or
|
||
|
* can be coerced to a double, or {@code NaN} otherwise.
|
||
|
*/
|
||
|
public double optDouble(@Nullable 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, or {@code fallback} otherwise.
|
||
|
*/
|
||
|
public double optDouble(@Nullable 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, or throws otherwise.
|
||
|
*
|
||
|
* @throws JSONException if the mapping doesn't exist or cannot be coerced
|
||
|
* to an int.
|
||
|
*/
|
||
|
public int getInt(@NonNull String name) throws JSONException {
|
||
|
Object object = get(name);
|
||
|
Integer result = JSON.toInteger(object);
|
||
|
if (result == null) {
|
||
|
throw JSON.typeMismatch(name, object, "int");
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the value mapped by {@code name} if it exists and is an int or
|
||
|
* can be coerced to an int, or 0 otherwise.
|
||
|
*/
|
||
|
public int optInt(@Nullable 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, or {@code fallback} otherwise.
|
||
|
*/
|
||
|
public int optInt(@Nullable 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, or throws otherwise.
|
||
|
* Note that JSON represents numbers as doubles,
|
||
|
* so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via JSON.
|
||
|
*
|
||
|
* @throws JSONException if the mapping doesn't exist or cannot be coerced
|
||
|
* to a long.
|
||
|
*/
|
||
|
public long getLong(@NonNull String name) throws JSONException {
|
||
|
Object object = get(name);
|
||
|
Long result = JSON.toLong(object);
|
||
|
if (result == null) {
|
||
|
throw JSON.typeMismatch(name, object, "long");
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the value mapped by {@code name} if it exists and is a long or
|
||
|
* can be coerced to a long, or 0 otherwise. Note that JSON represents numbers as doubles,
|
||
|
* so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via JSON.
|
||
|
*/
|
||
|
public long optLong(@Nullable 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, or {@code fallback} otherwise. Note that JSON represents
|
||
|
* numbers as doubles, so this is <a href="#lossy">lossy</a>; use strings to transfer
|
||
|
* numbers via JSON.
|
||
|
*/
|
||
|
public long optLong(@Nullable 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, or throws if no such mapping exists.
|
||
|
*
|
||
|
* @throws JSONException if no such mapping exists.
|
||
|
*/
|
||
|
@NonNull public String getString(@NonNull String name) throws JSONException {
|
||
|
Object object = get(name);
|
||
|
String result = JSON.toString(object);
|
||
|
if (result == null) {
|
||
|
throw JSON.typeMismatch(name, object, "String");
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the value mapped by {@code name} if it exists, coercing it if
|
||
|
* necessary, or the empty string if no such mapping exists.
|
||
|
*/
|
||
|
@NonNull public String optString(@Nullable String name) {
|
||
|
return optString(name, "");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the value mapped by {@code name} if it exists, coercing it if
|
||
|
* necessary, or {@code fallback} if no such mapping exists.
|
||
|
*/
|
||
|
@NonNull public String optString(@Nullable String name, @NonNull 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}, or throws otherwise.
|
||
|
*
|
||
|
* @throws JSONException if the mapping doesn't exist or is not a {@code
|
||
|
* JSONArray}.
|
||
|
*/
|
||
|
@NonNull public JSONArray getJSONArray(@NonNull String name) throws JSONException {
|
||
|
Object object = get(name);
|
||
|
if (object instanceof JSONArray) {
|
||
|
return (JSONArray) object;
|
||
|
} else {
|
||
|
throw JSON.typeMismatch(name, object, "JSONArray");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the value mapped by {@code name} if it exists and is a {@code
|
||
|
* JSONArray}, or null otherwise.
|
||
|
*/
|
||
|
@Nullable public JSONArray optJSONArray(@Nullable 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}, or throws otherwise.
|
||
|
*
|
||
|
* @throws JSONException if the mapping doesn't exist or is not a {@code
|
||
|
* JSONObject}.
|
||
|
*/
|
||
|
@NonNull public JSONObject getJSONObject(@NonNull String name) throws JSONException {
|
||
|
Object object = get(name);
|
||
|
if (object instanceof JSONObject) {
|
||
|
return (JSONObject) object;
|
||
|
} else {
|
||
|
throw JSON.typeMismatch(name, object, "JSONObject");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the value mapped by {@code name} if it exists and is a {@code
|
||
|
* JSONObject}, or null otherwise.
|
||
|
*/
|
||
|
@Nullable public JSONObject optJSONObject(@Nullable 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.
|
||
|
*/
|
||
|
@Nullable public JSONArray toJSONArray(@Nullable JSONArray names) throws JSONException {
|
||
|
JSONArray result = new JSONArray();
|
||
|
if (names == null) {
|
||
|
return null;
|
||
|
}
|
||
|
int length = names.length();
|
||
|
if (length == 0) {
|
||
|
return null;
|
||
|
}
|
||
|
for (int i = 0; i < length; i++) {
|
||
|
String name = JSON.toString(names.opt(i));
|
||
|
result.put(opt(name));
|
||
|
}
|
||
|
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.
|
||
|
*/
|
||
|
@NonNull public Iterator<@NonNull String> keys() {
|
||
|
return nameValuePairs.keySet().iterator();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the set of {@code String} names in this object. The returned set
|
||
|
* is a view of the keys in this object. {@link Set#remove(Object)} will remove
|
||
|
* the corresponding mapping from this object and set iterator behaviour
|
||
|
* is undefined if this object is modified after it is returned.
|
||
|
*
|
||
|
* See {@link #keys()}.
|
||
|
*
|
||
|
* @hide.
|
||
|
*/
|
||
|
@UnsupportedAppUsage
|
||
|
@libcore.api.CorePlatformApi
|
||
|
public Set<String> keySet() {
|
||
|
return nameValuePairs.keySet();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an array containing the string names in this object. This method
|
||
|
* returns null if this object contains no mappings.
|
||
|
*/
|
||
|
@Nullable 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 @NonNull public String toString() {
|
||
|
try {
|
||
|
JSONStringer stringer = new JSONStringer();
|
||
|
writeTo(stringer);
|
||
|
return stringer.toString();
|
||
|
} catch (JSONException e) {
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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.
|
||
|
*/
|
||
|
@NonNull public String toString(int indentSpaces) throws JSONException {
|
||
|
JSONStringer stringer = new JSONStringer(indentSpaces);
|
||
|
writeTo(stringer);
|
||
|
return stringer.toString();
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
void writeTo(JSONStringer stringer) throws JSONException {
|
||
|
stringer.object();
|
||
|
for (Map.Entry<String, Object> entry : nameValuePairs.entrySet()) {
|
||
|
stringer.key(entry.getKey()).value(entry.getValue());
|
||
|
}
|
||
|
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}.
|
||
|
*/
|
||
|
@NonNull public static String numberToString(@NonNull Number number) throws JSONException {
|
||
|
if (number == null) {
|
||
|
throw new JSONException("Number must be non-null");
|
||
|
}
|
||
|
|
||
|
double doubleValue = number.doubleValue();
|
||
|
JSON.checkDouble(doubleValue);
|
||
|
|
||
|
// the original returns "-0" instead of "-0.0" for negative zero
|
||
|
if (number.equals(NEGATIVE_ZERO)) {
|
||
|
return "-0";
|
||
|
}
|
||
|
|
||
|
long longValue = number.longValue();
|
||
|
if (doubleValue == (double) longValue) {
|
||
|
return Long.toString(longValue);
|
||
|
}
|
||
|
|
||
|
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.
|
||
|
*/
|
||
|
@NonNull public static String quote(@Nullable String data) {
|
||
|
if (data == null) {
|
||
|
return "\"\"";
|
||
|
}
|
||
|
try {
|
||
|
JSONStringer stringer = new JSONStringer();
|
||
|
stringer.open(JSONStringer.Scope.NULL, "");
|
||
|
stringer.value(data);
|
||
|
stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, "");
|
||
|
return stringer.toString();
|
||
|
} catch (JSONException e) {
|
||
|
throw new AssertionError();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Wraps the given object if necessary.
|
||
|
*
|
||
|
* <p>If the object is null or , returns {@link #NULL}.
|
||
|
* If the object is a {@code JSONArray} or {@code JSONObject}, no wrapping is necessary.
|
||
|
* If the object is {@code NULL}, no wrapping is necessary.
|
||
|
* If the object is an array or {@code Collection}, returns an equivalent {@code JSONArray}.
|
||
|
* If the object is a {@code Map}, returns an equivalent {@code JSONObject}.
|
||
|
* If the object is a primitive wrapper type or {@code String}, returns the object.
|
||
|
* Otherwise if the object is from a {@code java} package, returns the result of {@code toString}.
|
||
|
* If wrapping fails, returns null.
|
||
|
*/
|
||
|
@Nullable public static Object wrap(@Nullable Object o) {
|
||
|
if (o == null) {
|
||
|
return NULL;
|
||
|
}
|
||
|
if (o instanceof JSONArray || o instanceof JSONObject) {
|
||
|
return o;
|
||
|
}
|
||
|
if (o.equals(NULL)) {
|
||
|
return o;
|
||
|
}
|
||
|
try {
|
||
|
if (o instanceof Collection) {
|
||
|
return new JSONArray((Collection) o);
|
||
|
} else if (o.getClass().isArray()) {
|
||
|
return new JSONArray(o);
|
||
|
}
|
||
|
if (o instanceof Map) {
|
||
|
return new JSONObject((Map) o);
|
||
|
}
|
||
|
if (o instanceof Boolean ||
|
||
|
o instanceof Byte ||
|
||
|
o instanceof Character ||
|
||
|
o instanceof Double ||
|
||
|
o instanceof Float ||
|
||
|
o instanceof Integer ||
|
||
|
o instanceof Long ||
|
||
|
o instanceof Short ||
|
||
|
o instanceof String) {
|
||
|
return o;
|
||
|
}
|
||
|
if (o.getClass().getPackage().getName().startsWith("java.")) {
|
||
|
return o.toString();
|
||
|
}
|
||
|
} catch (Exception ignored) {
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
}
|