/* * 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. */ /* * This class was taken from * https://android.googlesource.com/platform/libcore/+/refs/heads/master/json/src/main/java/org/json * and slightly modified (by mc@yacy.net): * - removed dependency from other libraries (i.e. android.compat.annotation) * - fixed "statement unnecessary nested" warnings * - fixed raw type declarations * - added keepOrder option to initializer to reduce memory footprint if order is not required * - added deprecated flag to has() an get() methods (see comment in code) * - inlined opt() where appropriate */ package org.json; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import java.util.Set; // 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. * *
This class can coerce values to another type when requested. *
This class can look up both mandatory and optional values: *
getType()
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.
* optType()
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.
* Warning: 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}. * *
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 * Effective Java 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: *
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
}
// 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";
}
};
private final Map Note that {@code append(String, Object)} provides better semantics.
* In particular, the mapping for {@code name} will always 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.
public JSONObject accumulate(String name, 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(2);
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
*/
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;
}
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.
*/
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}.
*
* This method has the deprecated flag because usage of this method leads to poor
* code performance if used in combination with get(). Its much better to opt() and
* check the result instead of doing two look-ups (first has(), then get()).
*/
@Deprecated
public boolean has(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.
*
* This method has the deprecated flag because usage of this method leads to poor
* code performance if used in combination with has(). Its much better to opt() and
* check the result instead of doing two look-ups (first has(), then get()).
*/
@Deprecated
public Object get(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.
*/
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, or throws otherwise.
*
* @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);
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(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(String name, boolean fallback) {
Object object = nameValuePairs.get(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(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(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(String name, double fallback) {
Object object = nameValuePairs.get(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(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(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(String name, int fallback) {
Object object = nameValuePairs.get(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 lossy; 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(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 lossy; use strings to transfer numbers via JSON.
*/
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, or {@code fallback} otherwise. Note that JSON represents
* numbers as doubles, so this is lossy; use strings to transfer
* numbers via JSON.
*/
public long optLong(String name, long fallback) {
Object object = nameValuePairs.get(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.
*/
public String getString(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.
*/
public String optString(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.
*/
public String optString(String name, String fallback) {
Object object = nameValuePairs.get(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}.
*/
public JSONArray getJSONArray(String name) throws JSONException {
Object object = get(name);
if (object instanceof JSONArray) {
return (JSONArray) object;
}
throw JSON.typeMismatch(name, object, "JSONArray");
}
/**
* Returns the value mapped by {@code name} if it exists and is a {@code
* JSONArray}, or null otherwise.
*/
public JSONArray optJSONArray(String name) {
Object object = nameValuePairs.get(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}.
*/
public JSONObject getJSONObject(String name) throws JSONException {
Object object = get(name);
if (object instanceof JSONObject) {
return (JSONObject) object;
}
throw JSON.typeMismatch(name, object, "JSONObject");
}
/**
* Returns the value mapped by {@code name} if it exists and is a {@code
* JSONObject}, or null otherwise.
*/
public JSONObject optJSONObject(String name) {
Object object = nameValuePairs.get(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) {
if (names == null) {
return null;
}
int length = names.length();
if (length == 0) {
return null;
}
JSONArray result = new JSONArray(length);
for (int i = 0; i < length; i++) {
String name = JSON.toString(names.opt(i));
result.put(nameValuePairs.get(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.
*/
public Iterator 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.
*/
public static Object wrap(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;
}
}
{"query":"Pizza","locations":[94043,90210]}
*/
@Override 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:
*
* {
* "query": "Pizza",
* "locations": [
* 94043,
* 90210
* ]
* }
*
* @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);
return stringer.toString();
}
void writeTo(JSONStringer stringer) throws JSONException {
stringer.object();
for (Map.Entry