    001/*
    002 * Copyright (C) 2010 The Android Open Source Project
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *      http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    013 * See the License for the specific language governing permissions and
    014 * limitations under the License.
    015 */
    016
    017package org.apache.tapestry5.json;
    018
    019import java.io.ObjectStreamException;
    020import java.io.Serializable;
    021import java.util.Collection;
    022import java.util.Collections;
    023import java.util.LinkedHashMap;
    024import java.util.Map;
    025import java.util.Set;
    026
    027import org.apache.tapestry5.json.exceptions.JSONTypeMismatchException;
    028import org.apache.tapestry5.json.exceptions.JSONValueNotFoundException;
    029
    030// Note: this class was written without inspecting the non-free org.json sourcecode.
    031
    032/**
    033 * A modifiable set of name/value mappings. Names are unique, non-null strings.
    034 * Values may be any mix of {@link JSONObject JSONObjects}, {@link JSONArray
    035 * JSONArrays}, Strings, Booleans, Integers, Longs, Doubles or {@link #NULL}.
    036 * Values may not be {@code null}, {@link Double#isNaN() NaNs}, {@link
    037 * Double#isInfinite() infinities}, or of any type not listed here.
    038 *
    039 * <p>This class can coerce values to another type when requested.
    040 * <ul>
    041 * <li>When the requested type is a boolean, strings will be coerced using a
    042 * case-insensitive comparison to "true" and "false".
    043 * <li>When the requested type is a double, other {@link Number} types will
    044 * be coerced using {@link Number#doubleValue() doubleValue}. Strings
    045 * that can be coerced using {@link Double#valueOf(String)} will be.
    046 * <li>When the requested type is an int, other {@link Number} types will
    047 * be coerced using {@link Number#intValue() intValue}. Strings
    048 * that can be coerced using {@link Double#valueOf(String)} will be,
    049 * and then cast to int.
    050 * <li><a name="lossy">When the requested type is a long, other {@link Number} types will
    051 * be coerced using {@link Number#longValue() longValue}. Strings
    052 * that can be coerced using {@link Double#valueOf(String)} will be,
    053 * and then cast to long. This two-step conversion is lossy for very
    054 * large values. For example, the string "9223372036854775806" yields the
    055 * long 9223372036854775807.</a>
    056 * <li>When the requested type is a String, other non-null values will be
    057 * coerced using {@link String#valueOf(Object)}. Although null cannot be
    058 * coerced, the sentinel value {@link JSONObject#NULL} is coerced to the
    059 * string "null".
    060 * </ul>
    061 *
    062 * <p>This class can look up both mandatory and optional values:
    063 * <ul>
    064 * <li>Use <code>get<i>Type</i>()</code> to retrieve a mandatory value. This
    065 * fails with a {@code RuntimeException} if the requested name has no value
    066 * or if the value cannot be coerced to the requested type.
    067 * <li>Use <code>opt()</code> to retrieve an optional value.
    068 * </ul>
    069 *
    070 * <p><strong>Warning:</strong> this class represents null in two incompatible
    071 * ways: the standard Java {@code null} reference, and the sentinel value {@link
    072 * JSONObject#NULL}. In particular, calling {@code put(name, null)} removes the
    073 * named entry from the object but {@code put(name, JSONObject.NULL)} stores an
    074 * entry whose value is {@code JSONObject.NULL}.
    075 *
    076 * <p>Instances of this class are not thread safe.
    077 */
    078public final class JSONObject extends JSONCollection implements Map<String, Object> {
    079
    080    private static final long serialVersionUID = 1L;
    081
    082    private static final Double NEGATIVE_ZERO = -0d;
    083
    084    /**
    085     * A sentinel value used to explicitly define a name with no value. Unlike
    086     * {@code null}, names with this value:
    087     * <ul>
    088     * <li>show up in the {@link #names} array
    089     * <li>show up in the {@link #keys} iterator
    090     * <li>return {@code true} for {@link #has(String)}
    091     * <li>do not throw on {@link #get}
    092     * <li>are included in the encoded JSON string.
    093     * </ul>
    094     *
    095     * <p>This value violates the general contract of {@link Object#equals} by
    096     * returning true when compared to {@code null}. Its {@link #toString}
    097     * method returns "null".
    098     */
    099    public static final Object NULL = new Serializable() {
    100
    101      private static final long serialVersionUID = 1L;
    102
    103        @Override
    104        public boolean equals(Object o) {
    105            return o == this || o == null; // API specifies this broken equals implementation
    106        }
    107
    108        // at least make the broken equals(null) consistent with Objects.hashCode(null).
    109        @Override
    110        public int hashCode() {
    111            return 0;
    112        }
    113
    114        @Override
    115        public String toString() {
    116            return "null";
    117        }
    118
    119        // Serialization magic: after de-serializing, it will be back to the singleton instance of NULL.
    120        private Object readResolve() throws ObjectStreamException
    121        {
    122            return NULL;
    123        }
    124
    125    };
    126
    127    private final LinkedHashMap<String, Object> nameValuePairs;
    128
    129    /**
    130     * Creates a {@code JSONObject} with no name/value mappings.
    131     */
    132    public JSONObject() {
    133        nameValuePairs = new LinkedHashMap<String, Object>();
    134    }
    135
    136    /**
    137     * Creates a new {@code JSONObject} with name/value mappings from the next
    138     * object in the tokener.
    139     *
    140     * @param readFrom a tokener whose nextValue() method will yield a
    141     *                 {@code JSONObject}.
    142     * @throws RuntimeException if the parse fails or doesn't yield a
    143     *                       {@code JSONObject}.
    144     */
    145    JSONObject(JSONTokener readFrom) {
    146        /*
    147         * Getting the parser to populate this could get tricky. Instead, just
    148         * parse to temporary JSONObject and then steal the data from that.
    149         */
    150        Object object = readFrom.nextValue(JSONObject.class);
    151        if (object instanceof JSONObject) {
    152            this.nameValuePairs = ((JSONObject) object).nameValuePairs;
    153        } else {
    154            throw JSONExceptionBuilder.tokenerTypeMismatch(object, JSONType.OBJECT);
    155        }
    156    }
    157
    158    /**
    159     * Creates a new {@code JSONObject} with name/value mappings from the JSON
    160     * string.
    161     *
    162     * @param json a JSON-encoded string containing an object.
    163     * @throws RuntimeException if the parse fails or doesn't yield a {@code
    164     *                       JSONObject}.
    165     */
    166    public JSONObject(String json) {
    167        this(new JSONTokener(json));
    168    }
    169
    170    /**
    171     * Creates a new {@code JSONObject} by copying mappings for the listed names
    172     * from the given object. Names that aren't present in {@code copyFrom} will
    173     * be skipped.
    174     *
    175     * @param copyFrom The source object.
    176     * @param names    The names of the fields to copy.
    177     * @throws RuntimeException On internal errors. Shouldn't happen.
    178     */
    179    public JSONObject(JSONObject copyFrom, String... names) {
    180        this();
    181        for (String name : names) {
    182            Object value = copyFrom.opt(name);
    183            if (value != null) {
    184                nameValuePairs.put(name, value);
    185            }
    186        }
    187    }
    188
    189
    190    /**
    191     * Returns a new JSONObject that is a shallow copy of this JSONObject.
    192     *
    193     * @since 5.4
    194     */
    195    public JSONObject copy()
    196    {
    197        JSONObject dupe = new JSONObject();
    198        dupe.nameValuePairs.putAll(nameValuePairs);
    199
    200        return dupe;
    201    }
    202
    203    /**
    204     * Constructs a new JSONObject using a series of String keys and object values.
    205     * Object values should be compatible with {@link #put(String, Object)}. Keys must be strings
    206     * (toString() will be invoked on each key).
    207     *
    208     * Prior to release 5.4, keysAndValues was type String...; changing it to Object... makes
    209     * it much easier to initialize a JSONObject in a single statement, which is more readable.
    210     *
    211     * @since 5.2.0
    212     */
    213    public JSONObject(Object... keysAndValues)
    214    {
    215        this();
    216
    217        int i = 0;
    218
    219        while (i < keysAndValues.length)
    220        {
    221            put(keysAndValues[i++].toString(), keysAndValues[i++]);
    222        }
    223    }
    224
    225    /**
    226     * Returns the number of name/value mappings in this object.
    227     *
    228     * @deprecated Use {@link #size()} instead.
    229     * @return the length of this.
    230     */
    231    @Deprecated
    232    public int length() {
    233        return nameValuePairs.size();
    234    }
    235
    236    /**
    237     * Maps {@code name} to {@code value}, clobbering any existing name/value
    238     * mapping with the same name. If the value is {@code null}, any existing
    239     * mapping for {@code name} is removed.
    240     *
    241     * @param name  The name of the new value.
    242     * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
    243     *              Integer, Long, Double, {@link #NULL}, or {@code null}. May not be
    244     *              {@link Double#isNaN() NaNs} or {@link Double#isInfinite()
    245     *              infinities}.
    246     * @return this object.
    247     * @throws IllegalArgumentException if the value is an invalid double (infinite or NaN).
    248     */
    249    @Override
    250    public JSONObject put(String name, Object value) {
    251        if (value == null) {
    252            nameValuePairs.remove(name);
    253            return this;
    254        }
    255        JSON.testValidity(value);
    256        if (value instanceof Number) {
    257            // deviate from the original by checking all Numbers, not just floats & doubles
    258            JSON.checkDouble(((Number) value).doubleValue());
    259        }
    260        nameValuePairs.put(checkName(name), value);
    261        return this;
    262    }
    263
    264    /**
    265     * Appends {@code value} to the array already mapped to {@code name}. If
    266     * this object has no mapping for {@code name}, this inserts a new mapping.
    267     * If the mapping exists but its value is not an array, the existing
    268     * and new values are inserted in order into a new array which is itself
    269     * mapped to {@code name}. In aggregate, this allows values to be added to a
    270     * mapping one at a time.
    271     *
    272     * Note that {@code append(String, Object)} provides better semantics.
    273     * In particular, the mapping for {@code name} will <b>always</b> be a
    274     * {@link JSONArray}. Using {@code accumulate} will result in either a
    275     * {@link JSONArray} or a mapping whose type is the type of {@code value}
    276     * depending on the number of calls to it.
    277     *
    278     * @param name  The name of the field to change.
    279     * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
    280     *              Integer, Long, Double, {@link #NULL} or null. May not be {@link
    281     *              Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}.
    282     * @return this object after mutation.
    283     * @throws RuntimeException If the object being added is an invalid number.
    284     */
    285    // TODO: Change {@code append) to {@link #append} when append is
    286    // unhidden.
    287    public JSONObject accumulate(String name, Object value) {
    288        Object current = nameValuePairs.get(checkName(name));
    289        if (current == null) {
    290            return put(name, value);
    291        }
    292
    293        if (current instanceof JSONArray) {
    294            JSONArray array = (JSONArray) current;
    295            array.checkedPut(value);
    296        } else {
    297            JSONArray array = new JSONArray();
    298            array.checkedPut(current);
    299            array.checkedPut(value);
    300            nameValuePairs.put(name, array);
    301        }
    302        return this;
    303    }
    304
    305    /**
    306     * Appends values to the array mapped to {@code name}. A new {@link JSONArray}
    307     * mapping for {@code name} will be inserted if no mapping exists. If the existing
    308     * mapping for {@code name} is not a {@link JSONArray}, a {@link RuntimeException}
    309     * will be thrown.
    310     *
    311     * @param name  The name of the array to which the value should be appended.
    312     * @param value The value to append.
    313     * @return this object.
    314     * @throws JSONTypeMismatchException if {@code name} is {@code null} or if the mapping for
    315     *                       {@code name} is non-null and is not a {@link JSONArray}.
    316     */
    317    public JSONObject append(String name, Object value) {
    318        JSON.testValidity(value);
    319        Object current = nameValuePairs.get(checkName(name));
    320
    321        final JSONArray array;
    322        if (current instanceof JSONArray) {
    323            array = (JSONArray) current;
    324        } else if (current == null) {
    325            JSONArray newArray = new JSONArray();
    326            nameValuePairs.put(name, newArray);
    327            array = newArray;
    328        } else {
    329            throw new JSONTypeMismatchException("JSONObject[\"" + name + "\"]", JSONType.ARRAY, current.getClass());
    330        }
    331
    332        array.checkedPut(value);
    333
    334        return this;
    335    }
    336
    337    String checkName(String name) {
    338        if (name == null) {
    339            throw new RuntimeException("Names must be non-null");
    340        }
    341        return name;
    342    }
    343
    344    /**
    345     * Returns true if this object has no mapping for {@code name} or if it has
    346     * a mapping whose value is {@link #NULL}.
    347     *
    348     * @param name The name of the value to check on.
    349     * @return true if the field doesn't exist or is null.
    350     */
    351    public boolean isNull(String name) {
    352        Object value = nameValuePairs.get(name);
    353        return value == null || value == NULL;
    354    }
    355
    356    /**
    357     * Returns true if this object has a mapping for {@code name}. The mapping
    358     * may be {@link #NULL}.
    359     *
    360     * @deprecated use {@link #containsKey(Object)} instead
    361     * @param name
    362     *            The name of the value to check on.
    363     * @return true if this object has a field named {@code name}
    364     */
    365    @Deprecated
    366    public boolean has(String name) {
    367        return containsKey(name);
    368    }
    369
    370    /**
    371     * Returns the value mapped by {@code name}, or null if no such mapping
    372     * exists.
    373     *
    374     * @param name The name of the value to get.
    375     * @return The value.
    376     */
    377    public Object opt(Object name) {
    378        return nameValuePairs.get(name);
    379    }
    380
    381    /**
    382     * Returns the value mapped by {@code name} if it exists and is a boolean or
    383     * can be coerced to a boolean, or throws otherwise.
    384     *
    385     * @param name The name of the field we want.
    386     * @return The selected value if it exists.
    387     * @throws JSONValueNotFoundException if the mapping doesn't exist
    388     * @throws JSONTypeMismatchException if the mapping cannot be coerced
    389     *                       to a boolean.
    390     */
    391    public boolean getBoolean(String name) {
    392        Object object = opt(name);
    393        if (object == null) {
    394            throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.BOOLEAN);
    395        }
    396        Boolean result = JSON.toBoolean(object);
    397        if (result == null) {
    398            throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.BOOLEAN);
    399        }
    400        return result;
    401    }
    402
    403    /**
    404     * Returns the value to which the specified key is mapped and a boolean, or
    405     * {@code defaultValue} if this JSONObject contains no mapping for the key.
    406     *
    407     * @param name the key whose associated value is to be returned
    408     * @param defaultValue the default mapping of the key
    409     * @return the value to which the specified key is mapped, or
    410     * {@code defaultValue} if this JSONObject contains no mapping for the key
    411     * @throws JSONTypeMismatchException if the mapping cannot be coerced
    412     *                       to a boolean.
    413     * @since 5.7
    414     */
    415    public boolean getBooleanOrDefault(String name, boolean defaultValue)
    416    {
    417        Object object = opt(name);
    418        if (object == null)
    419        {
    420            return defaultValue;
    421        }
    422        Boolean result = JSON.toBoolean(object);
    423        if (result == null)
    424        {
    425            throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.BOOLEAN);
    426        }
    427        return result;
    428    }
    429
    430    /**
    431     * Returns the value mapped by {@code name} if it exists and is a double or
    432     * can be coerced to a double, or throws otherwise.
    433     *
    434     * @param name The name of the field we want.
    435     * @return The selected value if it exists.
    436     * @throws JSONValueNotFoundException if the mapping doesn't exist
    437     * @throws JSONTypeMismatchException if the mapping cannot be coerced
    438     *                       to a double.
    439     */
    440    public double getDouble(String name) {
    441        Object object = opt(name);
    442        if (object == null) {
    443            throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.NUMBER);
    444        }
    445        Double result = JSON.toDouble(object);
    446        if (result == null) {
    447            throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.NUMBER);
    448        }
    449        return result;
    450    }
    451
    452    /**
    453     * Returns the value mapped by {@code name} if it exists and is an int or
    454     * can be coerced to an int, or throws otherwise.
    455     *
    456     * @param name The name of the field we want.
    457     * @return The selected value if it exists.
    458     * @throws JSONValueNotFoundException if the mapping doesn't exist
    459     * @throws JSONTypeMismatchException if the mapping cannot be coerced
    460     *                       to an int.
    461     */
    462    public int getInt(String name) {
    463        Object object = opt(name);
    464        if (object == null) {
    465            throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.NUMBER);
    466        }
    467        Integer result = JSON.toInteger(object);
    468        if (result == null) {
    469            throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.NUMBER);
    470        }
    471        return result;
    472    }
    473
    474    /**
    475     * Returns the value to which the specified key is mapped and an int, or
    476     * {@code defaultValue} if this JSONObject contains no mapping for the key.
    477     *
    478     * @param name the key whose associated value is to be returned
    479     * @param defaultValue the default mapping of the key
    480     * @return the value to which the specified key is mapped, or
    481     * {@code defaultValue} if this JSONObject contains no mapping for the key
    482     * @throws JSONTypeMismatchException if the mapping cannot be coerced
    483     *                       to an int.
    484     * @since 5.7
    485     */
    486    public int getIntOrDefault(String name, int defaultValue)
    487    {
    488        Object object = opt(name);
    489        if (object == null)
    490        {
    491            return defaultValue;
    492        }
    493        Integer result = JSON.toInteger(object);
    494        if (result == null)
    495        {
    496            throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.NUMBER);
    497        }
    498        return result;
    499    }
    500
    501    /**
    502     * Returns the value mapped by {@code name} if it exists and is a long or
    503     * can be coerced to a long, or throws otherwise.
    504     * Note that JSON represents numbers as doubles,
    505     *
    506     * so this is <a href="#lossy">lossy</a>; use strings to transfer numbers
    507     * via JSON without loss.
    508     *
    509     * @param name The name of the field that we want.
    510     * @return The value of the field.
    511     * @throws JSONValueNotFoundException if the mapping doesn't exist
    512     * @throws JSONTypeMismatchException if the mapping cannot be coerced
    513     *                       to a long.
    514     */
    515    public long getLong(String name) {
    516        Object object = opt(name);
    517        if (object == null) {
    518            throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.NUMBER);
    519        }
    520        Long result = JSON.toLong(object);
    521        if (result == null) {
    522            throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.NUMBER);
    523        }
    524        return result;
    525    }
    526
    527    /**
    528     * Returns the value to which the specified key is mapped and a long, or
    529     * {@code defaultValue} if this JSONObject contains no mapping for the key.
    530     *
    531     * @param name the key whose associated value is to be returned
    532     * @param defaultValue the default mapping of the key
    533     * @return the value to which the specified key is mapped, or
    534     * {@code defaultValue} if this JSONObject contains no mapping for the key
    535     * @throws JSONTypeMismatchException if the mapping cannot be coerced
    536     *                       to a long.
    537     * @since 5.7
    538     */
    539    public long getLongOrDefault(String name, int defaultValue)
    540    {
    541        Object object = opt(name);
    542        if (object == null)
    543        {
    544            return defaultValue;
    545        }
    546        Long result = JSON.toLong(object);
    547        if (result == null)
    548        {
    549            throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.NUMBER);
    550        }
    551        return result;
    552    }
    553
    554    /**
    555     * Returns the value mapped by {@code name} if it exists, coercing it if
    556     * necessary, or throws if no such mapping exists.
    557     *
    558     * @param name The name of the field we want.
    559     * @return The value of the field.
    560     * @throws JSONValueNotFoundException if the mapping doesn't exist
    561     * @throws JSONTypeMismatchException if the mapping cannot be coerced
    562     *                               to String
    563     */
    564    public String getString(String name) {
    565        Object object = opt(name);
    566        if (object == null) {
    567            throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.STRING);
    568        }
    569        String result = JSON.toString(object);
    570        if (result == null) {
    571            throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.STRING);
    572        }
    573        return result;
    574    }
    575
    576    /**
    577     * Returns the value to which the specified key is mapped and a string, or
    578     * {@code defaultValue} if this JSONObject contains no mapping for the key.
    579     *
    580     * @param name the key whose associated value is to be returned
    581     * @param defaultValue the default mapping of the key
    582     * @return the value to which the specified key is mapped, or
    583     * {@code defaultValue} if this JSONObject contains no mapping for the key
    584     * @throws JSONTypeMismatchException if the mapping cannot be coerced
    585     *                       to a string.
    586     * @since 5.7
    587     */
    588    public String getStringOrDefault(String name, String defaultValue)
    589    {
    590        Object object = opt(name);
    591        if (object == null)
    592        {
    593            return defaultValue;
    594        }
    595        String result = JSON.toString(object);
    596        if (result == null)
    597        {
    598            throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.STRING);
    599        }
    600        return result;
    601    }
    602
    603    /**
    604     * Returns the value mapped by {@code name} if it exists and is a {@code
    605     * JSONArray}, or throws otherwise.
    606     *
    607     * @param name The field we want to get.
    608     * @return The value of the field (if it is a JSONArray.
    609     * @throws JSONValueNotFoundException if the mapping doesn't exist
    610     * @throws JSONTypeMismatchException if the mapping is not a {@code
    611     *                       JSONArray}.
    612     */
    613    public JSONArray getJSONArray(String name) {
    614        Object object = opt(name);
    615        if (object == null) {
    616            throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.ARRAY);
    617        }
    618        if (object instanceof JSONArray) {
    619            return (JSONArray) object;
    620        } else {
    621            throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.ARRAY);
    622        }
    623    }
    624
    625    /**
    626     * Returns the value to which the specified key is mapped and a JSONArray, or
    627     * {@code defaultValue} if this JSONObject contains no mapping for the key.
    628     *
    629     * @param name the key whose associated value is to be returned
    630     * @param defaultValue the default mapping of the key
    631     * @return the value to which the specified key is mapped, or
    632     * {@code defaultValue} if this JSONObject contains no mapping for the key
    633     * @throws JSONTypeMismatchException if the mapping cannot be coerced
    634     *                       to a JSONArray.
    635     * @since 5.7
    636     */
    637    public JSONArray getJSONArrayOrDefault(String name, JSONArray defaultValue)
    638    {
    639        Object object = opt(name);
    640        if (object == null)
    641        {
    642            return defaultValue;
    643        }
    644        if (object instanceof JSONArray) {
    645            return (JSONArray) object;
    646        } else {
    647            throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.ARRAY);
    648        }
    649    }
    650
    651    /**
    652     * Returns the value mapped by {@code name} if it exists and is a {@code
    653     * JSONObject}, or throws otherwise.
    654     *
    655     * @param name The name of the field that we want.
    656     * @return a specified field value (if it is a JSONObject)
    657     * @throws JSONValueNotFoundException if the mapping doesn't exist
    658     * @throws JSONTypeMismatchException if the mapping is not a {@code
    659     *                       JSONObject}.
    660     */
    661    public JSONObject getJSONObject(String name) {
    662        Object object = opt(name);
    663        if (object == null) {
    664            throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.OBJECT);
    665        }
    666        if (object instanceof JSONObject) {
    667            return (JSONObject) object;
    668        } else {
    669            throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.OBJECT);
    670        }
    671    }
    672
    673
    674    /**
    675     * Returns the value to which the specified key is mapped and a JSONObject, or
    676     * {@code defaultValue} if this map contains no mapping for the key.
    677     *
    678     * @param name the key whose associated value is to be returned
    679     * @param defaultValue the default mapping of the key
    680     * @return the value to which the specified key is mapped, or
    681     * {@code defaultValue} if this map contains no mapping for the key
    682     * @throws JSONTypeMismatchException if the mapping cannot be coerced
    683     *                       to a JSONObject.
    684     * @since 5.7
    685     */
    686    public JSONObject getJSONObjectOrDefault(String name, JSONObject defaultValue)
    687    {
    688        Object object = opt(name);
    689        if (object == null)
    690        {
    691            return defaultValue;
    692        }
    693        if (object instanceof JSONObject) {
    694            return (JSONObject) object;
    695        } else {
    696            throw JSONExceptionBuilder.typeMismatch(false, name, object, JSONType.OBJECT);
    697        }
    698    }
    699    /**
    700     * Returns the set of {@code String} names in this object. The returned set
    701     * is a view of the keys in this object. {@link Set#remove(Object)} will remove
    702     * the corresponding mapping from this object and set iterator behaviour
    703     * is undefined if this object is modified after it is returned.
    704     *
    705     * See {@link #keys()}.
    706     *
    707     * @return The names in this object.
    708     */
    709    public Set<String> keys() {
    710        return nameValuePairs.keySet();
    711    }
    712
    713    /**
    714     * Returns an array containing the string names in this object. This method
    715     * returns null if this object contains no mappings.
    716     *
    717     * @return the names.
    718     */
    719    public JSONArray names() {
    720        return nameValuePairs.isEmpty()
    721                ? null
    722                : JSONArray.from(nameValuePairs.keySet());
    723    }
    724
    725    /**
    726     * Encodes the number as a JSON string.
    727     *
    728     * @param number a finite value. May not be {@link Double#isNaN() NaNs} or
    729     *               {@link Double#isInfinite() infinities}.
    730     * @return The encoded number in string form.
    731     * @throws RuntimeException On internal errors. Shouldn't happen.
    732     */
    733    public static String numberToString(Number number) {
    734        if (number == null) {
    735            throw new RuntimeException("Number must be non-null");
    736        }
    737
    738        double doubleValue = number.doubleValue();
    739        JSON.checkDouble(doubleValue);
    740
    741        // the original returns "-0" instead of "-0.0" for negative zero
    742        if (number.equals(NEGATIVE_ZERO)) {
    743            return "-0";
    744        }
    745
    746        long longValue = number.longValue();
    747        if (doubleValue == (double) longValue) {
    748            return Long.toString(longValue);
    749        }
    750
    751        return number.toString();
    752    }
    753
    754    static String doubleToString(double d)
    755    {
    756        if (Double.isInfinite(d) || Double.isNaN(d))
    757        {
    758            return "null";
    759        }
    760
    761        return numberToString(d);
    762    }
    763
    764    /**
    765     * Encodes {@code data} as a JSON string. This applies quotes and any
    766     * necessary character escaping.
    767     *
    768     * @param data the string to encode. Null will be interpreted as an empty
    769     *             string.
    770     * @return the quoted string.
    771     */
    772    public static String quote(String data) {
    773        if (data == null) {
    774            return "\"\"";
    775        }
    776        try {
    777            JSONStringer stringer = new JSONStringer();
    778            stringer.open(JSONStringer.Scope.NULL, "");
    779            stringer.string(data);
    780            stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, "");
    781            return stringer.toString();
    782        } catch (RuntimeException e) {
    783            throw new AssertionError();
    784        }
    785    }
    786
    787
    788
    789    /**
    790     * Prints the JSONObject using the session.
    791     *
    792     * @since 5.2.0
    793     */
    794    @Override
    795    void print(JSONPrintSession session)
    796    {
    797        session.printSymbol('{');
    798
    799        session.indent();
    800
    801        boolean comma = false;
    802
    803        for (String key : keys())
    804        {
    805            if (comma)
    806                session.printSymbol(',');
    807
    808            session.newline();
    809
    810            session.printQuoted(key);
    811
    812            session.printSymbol(':');
    813
    814            printValue(session, nameValuePairs.get(key));
    815
    816            comma = true;
    817        }
    818
    819        session.outdent();
    820
    821        if (comma)
    822            session.newline();
    823
    824        session.printSymbol('}');
    825    }
    826
    827
    828    /**
    829     * Prints a value (a JSONArray or JSONObject, or a value stored in an array or object) using
    830     * the session.
    831     *
    832     * @since 5.2.0
    833     */
    834    static void printValue(JSONPrintSession session, Object value)
    835    {
    836
    837        if (value == null || value == NULL)
    838        {
    839            session.print("null");
    840            return;
    841        }
    842        if (value instanceof JSONObject)
    843        {
    844            ((JSONObject) value).print(session);
    845            return;
    846        }
    847
    848        if (value instanceof JSONArray)
    849        {
    850            ((JSONArray) value).print(session);
    851            return;
    852        }
    853
    854        if (value instanceof JSONString)
    855        {
    856            String printValue = ((JSONString) value).toJSONString();
    857
    858            session.print(printValue);
    859
    860            return;
    861        }
    862
    863        if (value instanceof Number)
    864        {
    865            String printValue = numberToString((Number) value);
    866            session.print(printValue);
    867            return;
    868        }
    869
    870        if (value instanceof Boolean)
    871        {
    872            session.print(value.toString());
    873
    874            return;
    875        }
    876
    877        // Otherwise it really should just be a string. Nothing else can go in.
    878        session.printQuoted(value.toString());
    879    }
    880
    881    public boolean equals(Object obj)
    882    {
    883        if (obj == null)
    884            return false;
    885
    886        if (!(obj instanceof JSONObject))
    887            return false;
    888
    889        JSONObject other = (JSONObject) obj;
    890
    891        return nameValuePairs.equals(other.nameValuePairs);
    892    }
    893
    894    /**
    895     * Returns a Map of the keys and values of the JSONObject. The returned map is unmodifiable.
    896     * Note that changes to the JSONObject will be reflected in the map. In addition, null values in the JSONObject
    897     * are represented as {@link JSONObject#NULL} in the map.
    898     *
    899     * @return unmodifiable map of properties and values
    900     * @since 5.4
    901     */
    902    public Map<String, Object> toMap()
    903    {
    904        return Collections.unmodifiableMap(nameValuePairs);
    905    }
    906
    907    /**
    908     * Navigates into a nested JSONObject, creating the JSONObject if necessary. They key must not exist,
    909     * or must be a JSONObject.
    910     *
    911     * @param key
    912     * @return the nested JSONObject
    913     * @throws IllegalStateException
    914     *         if the current value for the key is not null and not JSONObject
    915     */
    916    public JSONObject in(String key)
    917    {
    918        assert key != null;
    919
    920        Object nested = nameValuePairs.get(key);
    921
    922        if (nested != null && !(nested instanceof JSONObject))
    923        {
    924            throw new IllegalStateException(String.format("JSONObject[%s] is not a JSONObject.", quote(key)));
    925        }
    926
    927        if (nested == null)
    928        {
    929            nested = new JSONObject();
    930            nameValuePairs.put(key, nested);
    931        }
    932
    933        return (JSONObject) nested;
    934    }
    935
    936    /**
    937     * Returns the number of key-value mappings in this JSONObject.
    938     * If it contains more than {@code Integer.MAX_VALUE} elements, returns
    939     * {@code Integer.MAX_VALUE}.
    940     *
    941     * @return the number of key-value mappings in this JSONObject
    942     * @since 5.7
    943     */
    944    @Override
    945    public int size()
    946    {
    947        return nameValuePairs.size();
    948    }
    949
    950    /**
    951     * Returns {@code true} if this JSONObject contains no key-value mappings.
    952     *
    953     * @return {@code true} if this JSONObject contains no key-value mappings
    954     * @since 5.7
    955     */
    956    @Override
    957    public boolean isEmpty()
    958    {
    959        return nameValuePairs.isEmpty();
    960    }
    961
    962    /**
    963     * Returns {@code true} if this JSONObject contains a mapping for the specified
    964     * key.
    965     *
    966     * @param key
    967     *            key whose presence in this map is to be tested
    968     * @return {@code true} if this map contains a mapping for the specified
    969     *         key
    970     * @since 5.7
    971     */
    972    @Override
    973    public boolean containsKey(Object key)
    974    {
    975        return nameValuePairs.containsKey(key);
    976    }
    977
    978    /**
    979     * Returns {@code true} if this JSONObject maps one or more keys to the
    980     * specified value.
    981     *
    982     * @param value value whose presence in this map is to be tested
    983     * @return {@code true} if this JSONObject maps one or more keys to the
    984     *         specified value
    985     * @since 5.7
    986     */
    987    @Override
    988    public boolean containsValue(Object value)
    989    {
    990        return nameValuePairs.containsValue(value);
    991    }
    992
    993    /**
    994     * Returns the value mapped by {@code name}, or throws if no such mapping exists.
    995     *
    996     * @param name The name of the value to get.
    997     * @return The value.
    998     * @throws JSONValueNotFoundException if no such mapping exists.
    999     */ @Override
    1000    public Object get(Object name)
    1001    {
    1002        Object result = nameValuePairs.get(name);
    1003         if (result == null) {
    1004            throw JSONExceptionBuilder.valueNotFound(false, name, JSONType.ANY);
    1005         }
    1006         return result;
    1007    }
    1008
    1009    /**
    1010      * Returns the value to which the specified key is mapped, or
    1011      * {@code defaultValue} if this JSONObject contains no mapping for the key.
    1012      *
    1013      * @param key the key whose associated value is to be returned
    1014      * @param defaultValue the default mapping of the key
    1015      * @return the value to which the specified key is mapped, or
    1016      *         {@code defaultValue} if this JSONObject contains no mapping for the key
    1017      * @since 5.7
    1018      */
    1019    @Override
    1020    public Object getOrDefault(Object key, Object defaultValue)
    1021    {
    1022        Object value = opt(key);
    1023        return value == null ? defaultValue : value;
    1024    }
    1025
    1026    /**
    1027     * Removes the named mapping if it exists; does nothing otherwise.
    1028     *
    1029     * @param name The name of the mapping to remove.
    1030     * @return the value previously mapped by {@code name}, or null if there was
    1031     *         no such mapping.
    1032     */
    1033    @Override
    1034    public Object remove(Object name)
    1035    {
    1036        return nameValuePairs.remove(name);
    1037    }
    1038
    1039    /**
    1040     * Invokes {@link #put(String, Object)} for each value from the map.
    1041     *
    1042     * @param newProperties
    1043     *            to add to this JSONObject
    1044     * @since 5.7
    1045     */
    1046    @Override
    1047    public void putAll(Map<? extends String, ? extends Object> newProperties)
    1048    {
    1049        assert newProperties != null;
    1050
    1051        for (Map.Entry<? extends String, ? extends Object> e : newProperties.entrySet())
    1052        {
    1053            put(e.getKey(), e.getValue());
    1054        }
    1055    }
    1056
    1057    /**
    1058     * Removes all of the mappings from this JSONObject.
    1059     *
    1060     * @since 5.7
    1061     */
    1062    @Override
    1063    public void clear()
    1064    {
    1065        nameValuePairs.clear();
    1066    }
    1067
    1068    /**
    1069     * Returns a {@link Set} view of the keys contained in this JSONObject.
    1070     * The set is backed by the JSONObject, so changes to the map are
    1071     * reflected in the set, and vice-versa.
    1072     *
    1073     * @return a set view of the keys contained in this JSONObject
    1074     * @since 5.7
    1075     */
    1076    @Override
    1077    public Set<String> keySet()
    1078    {
    1079        return nameValuePairs.keySet();
    1080    }
    1081
    1082    /**
    1083     * Returns a {@link Collection} view of the values contained in this JSONObject.
    1084     * The collection is backed by the JSONObject, so changes to the map are
    1085     * reflected in the collection, and vice-versa.
    1086     *
    1087     * @return a collection view of the values contained in this JSONObject
    1088     * @since 5.7
    1089     */
    1090    @Override
    1091    public Collection<Object> values()
    1092    {
    1093        return nameValuePairs.values();
    1094    }
    1095
    1096    /**
    1097     * Returns a {@link Set} view of the mappings contained in this JSONObject.
    1098     * The set is backed by the JSONObject, so changes to the map are
    1099     * reflected in the set, and vice-versa.
    1100     *
    1101     * @return a set view of the mappings contained in this JSONObject
    1102     * @since 5.7
    1103     */
    1104    @Override
    1105    public Set<Entry<String, Object>> entrySet()
    1106    {
    1107        return nameValuePairs.entrySet();
    1108    }
    1109
    1110
    1111}