All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.org.json.JsonObject.kt Maven / Gradle / Ivy

The newest version!
/*
 * 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 kotlin.jvm.JvmOverloads

// 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 [JsonObjects][JsonObject], [ JsonArrays][JsonArray], Strings, Booleans, Integers, Longs, Doubles or [.NULL].
 * Values may not be `null`, [NaNs][Double.isNaN], [ ][Double.isInfinite], or of any type not listed here.
 *
 *
 * This class can coerce values to another type when requested.
 *
 *  * When the requested type is a boolean, strings will be coerced using a
 * case-insensitive comparison to "true" and "false".
 *  * When the requested type is a double, other [Number] types will
 * be coerced using [doubleValue][Number.doubleValue]. Strings
 * that can be coerced using [Double.valueOf] will be.
 *  * When the requested type is an int, other [Number] types will
 * be coerced using [intValue][Number.intValue]. Strings
 * that can be coerced using [Double.valueOf] will be,
 * and then cast to int.
 *  * When the requested type is a long, other [Number] types will
 * be coerced using [longValue][Number.longValue]. Strings
 * that can be coerced using [Double.valueOf] 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.
 *  * When the requested type is a String, other non-null values will be
 * coerced using [String.valueOf]. Although null cannot be
 * coerced, the sentinel value [JsonObject.NULL] is coerced to the
 * string "null".
 *
 *
 *
 * This class can look up both mandatory and optional values:
 *
 *  * Use `get*Type*()` to retrieve a mandatory value. This
 * fails with a `JsonException` if the requested name has no value
 * or if the value cannot be coerced to the requested type.
 *  * Use `opt*Type*()` 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 `null` reference, and the sentinel value [ ][JsonObject.NULL]. In particular, calling `put(name, null)` removes the
 * named entry from the object but `put(name, JsonObject.NULL)` stores an
 * entry whose value is `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.
 */
class JsonObject {
    private val nameValuePairs: LinkedHashMap

    /**
     * Creates a `JsonObject` with no name/value mappings.
     */
    constructor() {
        nameValuePairs = LinkedHashMap()
    }

    /**
     * Creates a new `JsonObject` by copying all name/value mappings from
     * the given map.
     *
     * @param copyFrom a map whose keys are of type [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) */
    constructor(copyFrom: Map<*, *>) : this() {
        for ((key1, value) in copyFrom) {
            /*
             * Deviate from the original by checking that keys are non-null and
             * of the proper type. (We still defer validating the values).
             */
            val key = key1 as String? ?: throw NullPointerException("key == null")
            nameValuePairs[key] = wrap(value) ?: ""
        }
    }

    /**
     * Creates a new `JsonObject` with name/value mappings from the next
     * object in the tokener.
     *
     * @param readFrom a tokener whose nextValue() method will yield a
     * `JsonObject`.
     * @throws JsonException if the parse fails or doesn't yield a
     * `JsonObject`.
     */
    constructor(readFrom: JsonTokener) {
        /*
         * Getting the parser to populate this could get tricky. Instead, just
         * parse to temporary JsonObject and then steal the data from that.
         */
        val `object` = readFrom.nextValue()
        if (`object` is JsonObject) {
            nameValuePairs = `object`.nameValuePairs
        } else {
            throw Json.typeMismatch(`object`, "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`.
     */
    constructor(Json: String?) : this(JsonTokener(Json)) {}

    /**
     * Creates a new `JsonObject` by copying mappings for the listed names
     * from the given object. Names that aren't present in `copyFrom` will
     * be skipped.
     */
    constructor(copyFrom: JsonObject, names: Array?) : this() {
        for (name in names!!) {
            val value = copyFrom.opt(name)
            if (value != null) {
                nameValuePairs[name] = value
            }
        }
    }

    /**
     * Returns the number of name/value mappings in this object.
     */
    fun length(): Int {
        return nameValuePairs.size
    }

    /**
     * Maps `name` to `value`, clobbering any existing name/value
     * mapping with the same name.
     *
     * @return this object.
     */
    
    @Throws(JsonException::class)
    fun put(name: String?, value: Boolean): JsonObject {
        nameValuePairs[checkName(name)] = value
        return this
    }

    /**
     * Maps `name` to `value`, clobbering any existing name/value
     * mapping with the same name.
     *
     * @param value a finite value. May not be [NaNs][Double.isNaN] or
     * [infinities][Double.isInfinite].
     * @return this object.
     */
    
    @Throws(JsonException::class)
    fun put(name: String?, value: Double): JsonObject {
        nameValuePairs[checkName(name)] = Json.checkDouble(value)
        return this
    }

    /**
     * Maps `name` to `value`, clobbering any existing name/value
     * mapping with the same name.
     *
     * @return this object.
     */
    
    @Throws(JsonException::class)
    fun put(name: String?, value: Int): JsonObject {
        nameValuePairs[checkName(name)] = value
        return this
    }

    /**
     * Maps `name` to `value`, clobbering any existing name/value
     * mapping with the same name.
     *
     * @return this object.
     */
    
    @Throws(JsonException::class)
    fun put(name: String?, value: Long): JsonObject {
        nameValuePairs[checkName(name)] = value
        return this
    }

    /**
     * Maps `name` to `value`, clobbering any existing name/value
     * mapping with the same name. If the value is `null`, any existing
     * mapping for `name` is removed.
     *
     * @param value a [JsonObject], [JsonArray], String, Boolean,
     * Integer, Long, Double, [.NULL], or `null`. May not be
     * [NaNs][Double.isNaN] or [     infinities][Double.isInfinite].
     * @return this object.
     */
    
    @Throws(JsonException::class)
    fun put(name: String?, value: Any?): JsonObject {
        if (value == null) {
            nameValuePairs.remove(name)
            return this
        }
        if (value is Number) {
            // deviate from the original by checking all Numbers, not just floats & doubles
            Json.checkDouble(value.toDouble())
        }
        nameValuePairs[checkName(name)] = value
        return this
    }

    /**
     * Equivalent to `put(name, value)` when both parameters are non-null;
     * does nothing otherwise.
     */
    
    @Throws(JsonException::class)
    fun putOpt( name: String?,  value: Any?): JsonObject {
        return if (name == null || value == null) {
            this
        } else put(name, value)
    }

    /**
     * Appends `value` to the array already mapped to `name`. If
     * this object has no mapping for `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 `name`. In aggregate, this allows values to be added to a
     * mapping one at a time.
     *
     *
     *  Note that `append(String, Object)` provides better semantics.
     * In particular, the mapping for `name` will **always** be a
     * [JsonArray]. Using `accumulate` will result in either a
     * [JsonArray] or a mapping whose type is the type of `value`
     * depending on the number of calls to it.
     *
     * @param value a [JsonObject], [JsonArray], String, Boolean,
     * Integer, Long, Double, [.NULL] or null. May not be [     ][Double.isNaN] or [infinities][Double.isInfinite].
     */
    // TODO: Change {@code append) to {@link #append} when append is
    // unhidden.
    
    @Throws(JsonException::class)
    fun accumulate(name: String?,  value: Any?): JsonObject {
        val current = nameValuePairs[checkName(name)] ?: return put(name, value)
        if (current is JsonArray) {
            current.checkedPut(value)
        } else {
            val array = JsonArray()
            array.checkedPut(current)
            array.checkedPut(value)
            nameValuePairs[name] = array
        }
        return this
    }

    /**
     * Appends values to the array mapped to `name`. A new [JsonArray]
     * mapping for `name` will be inserted if no mapping exists. If the existing
     * mapping for `name` is not a [JsonArray], a [JsonException]
     * will be thrown.
     *
     * @throws JsonException if `name` is `null` or if the mapping for
     * `name` is non-null and is not a [JsonArray].
     *
     * @hide
     */
    @Throws(JsonException::class)
    fun append(name: String, value: Any?): JsonObject {
        val current = nameValuePairs[checkName(name)]
        val array: JsonArray
        if (current is JsonArray) {
            array = current
        } else if (current == null) {
            val newArray = JsonArray()
            nameValuePairs[name] = newArray
            array = newArray
        } else {
            throw JsonException("Key $name is not a JsonArray")
        }
        array.checkedPut(value)
        return this
    }

    @Throws(JsonException::class)
    fun checkName(name: String?): String {
        if (name == null) {
            throw JsonException("Names must be non-null")
        }
        return name
    }

    /**
     * Removes the named mapping if it exists; does nothing otherwise.
     *
     * @return the value previously mapped by `name`, or null if there was
     * no such mapping.
     */
    fun remove( name: String?): Any {
        return nameValuePairs.remove(name)!!
    }

    /**
     * Returns true if this object has no mapping for `name` or if it has
     * a mapping whose value is [.NULL].
     */
    fun isNull( name: String?): Boolean {
        val value = nameValuePairs[name]
        return value == null || value === JsonObject.Companion.NULL
    }

    /**
     * Returns true if this object has a mapping for `name`. The mapping
     * may be [.NULL].
     */
    fun has( name: String?): Boolean {
        return nameValuePairs.containsKey(name)
    }

    /**
     * Returns the value mapped by `name`, or throws if no such mapping exists.
     *
     * @throws JsonException if no such mapping exists.
     */
    
    @Throws(JsonException::class)
    operator fun get(name: String): Any {
        return nameValuePairs[name] ?: throw JsonException("No value for $name")
    }

    /**
     * Returns the value mapped by `name`, or null if no such mapping
     * exists.
     */
    
    fun opt( name: String?): Any? {
        return nameValuePairs[name]
    }

    /**
     * Returns the value mapped by `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.
     */
    @Throws(JsonException::class)
    fun getBoolean(name: String): Boolean {
        val `object` = get(name)
        return Json.toBoolean(`object`) ?: throw Json.typeMismatch(name, `object`, "boolean")
    }
    /**
     * Returns the value mapped by `name` if it exists and is a boolean or
     * can be coerced to a boolean, or `fallback` otherwise.
     */
    /**
     * Returns the value mapped by `name` if it exists and is a boolean or
     * can be coerced to a boolean, or false otherwise.
     */
    @JvmOverloads
    fun optBoolean(name: String?, fallback: Boolean = false): Boolean {
        val `object` = opt(name)
        return Json.toBoolean(`object`) ?: fallback
    }

    /**
     * Returns the value mapped by `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.
     */
    @Throws(JsonException::class)
    fun getDouble(name: String): Double {
        val `object` = get(name)
        return Json.toDouble(`object`) ?: throw Json.typeMismatch(name, `object`, "double")
    }
    /**
     * Returns the value mapped by `name` if it exists and is a double or
     * can be coerced to a double, or `fallback` otherwise.
     */
    /**
     * Returns the value mapped by `name` if it exists and is a double or
     * can be coerced to a double, or `NaN` otherwise.
     */
    @JvmOverloads
    fun optDouble(name: String?, fallback: Double = Double.NaN): Double {
        val `object` = opt(name)
        return Json.toDouble(`object`) ?: fallback
    }

    /**
     * Returns the value mapped by `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.
     */
    @Throws(JsonException::class)
    fun getInt(name: String): Int {
        val `object` = get(name)
        return Json.toInteger(`object`) ?: throw Json.typeMismatch(name, `object`, "int")
    }
    /**
     * Returns the value mapped by `name` if it exists and is an int or
     * can be coerced to an int, or `fallback` otherwise.
     */
    /**
     * Returns the value mapped by `name` if it exists and is an int or
     * can be coerced to an int, or 0 otherwise.
     */
    @JvmOverloads
    fun optInt(name: String?, fallback: Int = 0): Int {
        val `object` = opt(name)
        return Json.toInteger(`object`) ?: fallback
    }

    /**
     * Returns the value mapped by `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](#lossy); use strings to transfer numbers via Json.
     *
     * @throws JsonException if the mapping doesn't exist or cannot be coerced
     * to a long.
     */
    @Throws(JsonException::class)
    fun getLong(name: String): Long {
        val `object` = get(name)
        return Json.toLong(`object`) ?: throw Json.typeMismatch(name, `object`, "long")
    }
    /**
     * Returns the value mapped by `name` if it exists and is a long or
     * can be coerced to a long, or `fallback` otherwise. Note that Json represents
     * numbers as doubles, so this is [lossy](#lossy); use strings to transfer
     * numbers via Json.
     */
    /**
     * Returns the value mapped by `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](#lossy); use strings to transfer numbers via Json.
     */
    @JvmOverloads
    fun optLong(name: String?, fallback: Long = 0L): Long {
        val `object` = opt(name)
        return Json.toLong(`object`) ?: fallback
    }

    /**
     * Returns the value mapped by `name` if it exists, coercing it if
     * necessary, or throws if no such mapping exists.
     *
     * @throws JsonException if no such mapping exists.
     */
    
    @Throws(JsonException::class)
    fun getString(name: String): String {
        val `object` = get(name)
        return Json.toString(`object`) ?: throw Json.typeMismatch(name, `object`, "String")
    }

    /**
     * Returns the value mapped by `name` if it exists, coercing it if
     * necessary, or the empty string if no such mapping exists.
     */
    
    fun optString( name: String?): String {
        return optString(name, "")
    }

    /**
     * Returns the value mapped by `name` if it exists, coercing it if
     * necessary, or `fallback` if no such mapping exists.
     */

    fun optString(name: String?, fallback: String?): String {
        val `object` = opt(name)
        return Json.toString(`object`) ?: fallback ?: ""
    }

    /**
     * Returns the value mapped by `name` if it exists and is a `JsonArray`, or throws otherwise.
     *
     * @throws JsonException if the mapping doesn't exist or is not a `JsonArray`.
     */
    
    @Throws(JsonException::class)
    fun getJsonArray(name: String): JsonArray {
        val `object` = get(name)
        return if (`object` is JsonArray) {
            `object`
        } else {
            throw Json.typeMismatch(name, `object`, "JsonArray")
        }
    }

    /**
     * Returns the value mapped by `name` if it exists and is a `JsonArray`, or null otherwise.
     */
    
    fun optJsonArray( name: String?): JsonArray? {
        val `object` = opt(name)
        return if (`object` is JsonArray) `object` else null
    }

    /**
     * Returns the value mapped by `name` if it exists and is a `JsonObject`, or throws otherwise.
     *
     * @throws JsonException if the mapping doesn't exist or is not a `JsonObject`.
     */
    
    @Throws(JsonException::class)
    fun getJsonObject(name: String): JsonObject {
        val `object` = get(name)
        return if (`object` is JsonObject) {
            `object`
        } else {
            throw Json.typeMismatch(name, `object`, "JsonObject")
        }
    }

    /**
     * Returns the value mapped by `name` if it exists and is a `JsonObject`, or null otherwise.
     */
    
    fun optJsonObject( name: String?): JsonObject? {
        val `object` = opt(name)
        return if (`object` is JsonObject) `object` else null
    }

    /**
     * Returns an array with the values corresponding to `names`. The
     * array contains null for names that aren't mapped. This method returns
     * null if `names` is either null or empty.
     */
    
    @Throws(JsonException::class)
    fun toJsonArray( names: JsonArray?): JsonArray? {
        val result = JsonArray()
        if (names == null) {
            return null
        }
        val length = names.length()
        if (length == 0) {
            return null
        }
        for (i in 0 until length) {
            val name: String = Json.toString(names.opt(i)) ?: ""
            result.put(opt(name))
        }
        return result
    }

    /**
     * Returns an iterator of the `String` names in this object. The
     * returned iterator supports [remove][Iterator.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.
     */
    
    fun keys(): Iterator {
        return nameValuePairs.keys.iterator()
    }

    /**
     * Returns the set of `String` names in this object. The returned set
     * is a view of the keys in this object. [Set.remove] will remove
     * the corresponding mapping from this object and set iterator behaviour
     * is undefined if this object is modified after it is returned.
     *
     * See [.keys].
     *
     * @return set of keys in this object
     *
     * @hide
     */
    fun keySet(): Set {
        return nameValuePairs.keys
    }

    /**
     * Returns an array containing the string names in this object. This method
     * returns null if this object contains no mappings.
     */
    
    fun names(): JsonArray? {
        return if (nameValuePairs.isEmpty()) null else JsonArray(nameValuePairs.keys.toTypedArray())
    }

    /**
     * Encodes this object as a compact Json string, such as:
     * 
{"query":"Pizza","locations":[94043,90210]}
*/ override fun toString(): String { return try { val stringer = JsonStringer() writeTo(stringer) stringer.toString() } catch (e: JsonException) { "" } } /** * 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. */ @Throws(JsonException::class) fun toString(indentSpaces: Int): String { val stringer = JsonStringer(indentSpaces) writeTo(stringer) return stringer.toString() } @Throws(JsonException::class) fun writeTo(stringer: JsonStringer) { stringer.`object`() for ((key, value) in nameValuePairs) { stringer.key(key).value(value) } stringer.endObject() } companion object { private val NEGATIVE_ZERO = -0.0 /** * A sentinel value used to explicitly define a name with no value. Unlike * `null`, names with this value: * * * show up in the [.names] array * * show up in the [.keys] iterator * * return `true` for [.has] * * do not throw on [.get] * * are included in the encoded Json string. * * * * This value violates the general contract of [Object.equals] by * returning true when compared to `null`. Its [.toString] * method returns "null". */ val NULL: Any = object : Any() { override fun equals(o: Any?): Boolean { return o === this || o == null // API specifies this broken equals implementation } // at least make the broken equals(null) consistent with Objects.hashCode(null). override fun hashCode(): Int { return 0 } override fun toString(): String { return "null" } } /** * Encodes the number as a Json string. * * @param number a finite value. May not be [NaNs][Double.isNaN] or * [infinities][Double.isInfinite]. */ @Throws(JsonException::class) fun numberToString(number: Number?): String { if (number == null) { throw JsonException("Number must be non-null") } val doubleValue = number.toDouble() Json.checkDouble(doubleValue) // the original returns "-0" instead of "-0.0" for negative zero if (number == JsonObject.Companion.NEGATIVE_ZERO) { return "-0" } val longValue = number.toLong() return if (doubleValue == longValue.toDouble()) { longValue.toString() } else number.toString() } /** * Encodes `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. */ fun quote( data: String?): String { return if (data == null) { "\"\"" } else try { val stringer = JsonStringer() stringer.open(Scope.NULL, "") stringer.value(data) stringer.close(Scope.NULL, Scope.NULL, "") stringer.toString() } catch (e: JsonException) { throw AssertionError() } } /** * Wraps the given object if necessary. * * * If the object is null or , returns [.NULL]. * If the object is a `JsonArray` or `JsonObject`, no wrapping is necessary. * If the object is `NULL`, no wrapping is necessary. * If the object is an array or `Collection`, returns an equivalent `JsonArray`. * If the object is a `Map`, returns an equivalent `JsonObject`. * If the object is a primitive wrapper type or `String`, returns the object. * Otherwise if the object is from a `java` package, returns the result of `toString`. * If wrapping fails, returns null. */ fun wrap( o: Any?): Any? { if (o == null) { return JsonObject.Companion.NULL } if (o is JsonArray || o is JsonObject) { return o } if (o == JsonObject.Companion.NULL) { return o } try { if (o is Collection<*>) { return JsonArray(o as Collection<*>?) } else if (o is Array<*>) { return JsonArray(o) } if (o is Map<*, *>) { return (o as Map<*, *>?)?.let { JsonObject(it) } } if (o is Boolean || o is Byte || o is Char || o is Double || o is Float || o is Int || o is Long || o is Short || o is String ) { return o } /* if (o.javaClass.getPackage().getName().startsWith("java.")) { return o.toString() }*/ } catch (ignored: Exception) { } return null } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy