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

net.pwall.json.JSONObject Maven / Gradle / Ivy

There is a newer version: 1.6
Show newest version
/*
 * @(#) JSONObject.java
 *
 * jsonutil JSON Utility Library
 * Copyright (c) 2014, 2015 Peter Wall
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.pwall.json;

import java.io.IOException;
import java.io.Serializable;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import net.pwall.util.Strings;

/**
 * A JSON object.
 *
 * @author Peter Wall
 */
public class JSONObject implements JSONComposite, Map, Iterable {

    private static final long serialVersionUID = 4424892153019501302L;

    private List list;

    /**
     * Construct an empty {@code JSONObject}.
     */
    public JSONObject() {
        list = new ArrayList();
    }

    /**
     * Construct a {@code JSONObject}, copying the contents of another object.
     *
     * @param   other   the other {@code JSONObject}
     */
    public JSONObject(JSONObject other) {
        list = new ArrayList(other.list);
    }

    public JSONObject putValue(String key, CharSequence cs) {
        put(key, new JSONString(cs));
        return this;
    }

    /**
     * Add a {@link JSONInteger} to the {@code JSONObject} representing the supplied
     * {@code int}.
     *
     * @param   key     the key to use when storing the value
     * @param   value   the value
     * @return          {@code this} (for chaining)
     */
    public JSONObject putValue(String key, int value) {
        put(key, JSONInteger.valueOf(value));
        return this;
    }

    /**
     * Add a {@link JSONLong} to the {@code JSONObject} representing the supplied {@code long}.
     *
     * @param   key     the key to use when storing the value
     * @param   value   the value
     * @return          {@code this} (for chaining)
     */
    public JSONObject putValue(String key, long value) {
        put(key, JSONLong.valueOf(value));
        return this;
    }

    /**
     * Add a {@link JSONFloat} to the {@code JSONObject} representing the supplied
     * {@code float}.
     *
     * @param   key     the key to use when storing the value
     * @param   value   the value
     * @return          {@code this} (for chaining)
     */
    public JSONObject putValue(String key, float value) {
        put(key, JSONFloat.valueOf(value));
        return this;
    }

    /**
     * Add a {@link JSONDouble} to the {@code JSONObject} representing the supplied
     * {@code double}.
     *
     * @param   key     the key to use when storing the value
     * @param   value   the value
     * @return          {@code this} (for chaining)
     */
    public JSONObject putValue(String key, double value) {
        put(key, JSONDouble.valueOf(value));
        return this;
    }

    /**
     * Add a {@link JSONBoolean} to the {@code JSONObject} representing the supplied
     * {@code boolean}.
     *
     * @param   key     the key to use when storing the value
     * @param   value   the value
     * @return          {@code this} (for chaining)
     */
    public JSONObject putValue(String key, boolean value) {
        put(key, JSONBoolean.valueOf(value));
        return this;
    }

    /**
     * Add a {@link JSONBoolean} to the {@code JSONObject}.
     *
     * @param   key     the key to use when storing the value
     * @param   value   the value
     * @return          {@code this} (for chaining)
     */
    public JSONObject putValue(String key, Boolean value) {
        put(key, JSONBoolean.valueOf(requireNonNull(value).booleanValue()));
        return this;
    }

    /**
     * Add a {@code null} to the {@code JSONObject}.
     *
     * @param   key     the key to use when storing the value
     * @return          {@code this} (for chaining)
     */
    public JSONObject putNull(String key) {
        put(key, null);
        return this;
    }

    /**
     * Add a {@link JSONValue} to the {@code JSONObject}.  This method duplicates the
     * {@link #put(String, JSONValue)} method specified by the {@link Map} interface, but it
     * returns {@code this} to allow for chaining.
     *
     * @param   key     the key to use when storing the value
     * @param   json    the {@link JSONValue}
     * @return          {@code this} (for chaining)
     */
    public JSONObject putJSON(String key, JSONValue json) {
        put(key, json);
        return this;
    }

    /**
     * Get a value from the {@code JSONObject} as a {@link String}.
     *
     * @param   key     the key of the value
     * @return          the value, or {@code null} if not found
     * @throws  JSONException if the value is found but is not a string
     */
    public String getString(String key) {
        return JSON.getString(get(key));
    }

    /**
     * Get a value from the {@code JSONObject} as an {@code int}.
     *
     * @param   key     the key of the value
     * @return          the value, or {@code 0} if not found
     * @throws  JSONException if the value is found but is not a number
     */
    public int getInt(String key) {
        return JSON.getInt(get(key));
    }

    /**
     * Get a value from the {@code JSONObject} as a {@code long}.
     *
     * @param   key     the key of the value
     * @return          the value, or {@code 0} if not found
     * @throws  JSONException if the value is found but is not a number
     */
    public long getLong(String key) {
        return JSON.getLong(get(key));
    }

    /**
     * Get a value from the {@code JSONObject} as a {@code float}.
     *
     * @param   key     the key of the value
     * @return          the value, or {@code 0} if not found
     * @throws  JSONException if the value is found but is not a number
     */
    public float getFloat(String key) {
        return JSON.getFloat(get(key));
    }

    /**
     * Get a value from the {@code JSONObject} as a {@code double}.
     *
     * @param   key     the key of the value
     * @return          the value, or {@code 0} if not found
     * @throws  JSONException if the value is found but is not a number
     */
    public double getDouble(String key) {
        return JSON.getDouble(get(key));
    }

    /**
     * Get a value from the {@code JSONObject} as a {@code boolean}.
     *
     * @param   key     the key of the value
     * @return          the value, or {@code false} if not found
     * @throws  JSONException if the value is found but is not a number
     */
    public boolean getBoolean(String key) {
        return JSON.getBoolean(get(key));
    }

    /**
     * Get a value from the {@code JSONObject} as a {@code JSONArray}.
     *
     * @param   key     the key of the value
     * @return  the value
     * @throws  JSONException if the array entry is not an array
     */
    public JSONArray getArray(String key) {
        return JSON.getArray(get(key));
    }

    /**
     * Get a value from the {@code JSONObject} as a {@code JSONObject}.
     *
     * @param   key     the key of the value
     * @return          the value, or {@code null} if not found
     * @throws  JSONException if the array entry is not an object
     */
    public JSONObject getObject(String key) {
        return JSON.getObject(get(key));
    }

    /**
     * Get a value from the {@code JSONObject}.
     *
     * @param   key     the key of the value
     * @return          the value, or {@code null} if not found
     * @see     Map#get(Object)
     */
    @Override
    public JSONValue get(Object key) {
        int index = findIndex(requireNonNull(key));
        return index < 0 ? null : list.get(index).getValue();
    }

    /**
     * Test whether the {@code JSONObject} contains a specified key.
     *
     * @param   key     the key to test for
     * @return          {@code true} if the key is found
     * @see     Map#containsKey(Object)
     */
    @Override
    public boolean containsKey(Object key) {
        return findIndex(requireNonNull(key)) >= 0;
    }

    /**
     * Store a value in the {@code JSONObject} with the specified key.
     *
     * @param   key     the key
     * @param   value   the value
     * @return          the previous value stored with that key, or {@code null} if no previous
     *                  value
     * @see     Map#put(Object, Object)
     */
    @Override
    public JSONValue put(String key, JSONValue value) {
        int index = findIndex(requireNonNull(key));
        if (index >= 0) {
            JSONValue oldValue = list.get(index).getValue();
            list.get(index).setValue(value);
            return oldValue;
        }
        list.add(new Entry(key, value));
        return null;
    }

    /**
     * Remove the specified key-value mapping from the {@code JSONObject}.
     *
     * @param   key     the key
     * @return          the value stored with that key, or {@code null} if key not used
     * @see     Map#remove(Object)
     */
    @Override
    public JSONValue remove(Object key) {
        int index = findIndex(requireNonNull(key));
        if (index >= 0) {
            Entry entry = list.remove(index);
            return entry.getValue();
        }
        return null;
    }

    /**
     * Get the number of values in the {@code JSONObject}.
     *
     * @return  the number of values
     * @see     Map#size()
     */
    @Override
    public int size() {
        return list.size();
    }

    /**
     * Test whether the {@code JSONObject}is empty.
     *
     * @return  {@code true} if the {@code JSONObject} is empty
     * @see     Map#isEmpty()
     */
    @Override
    public boolean isEmpty() {
        return size() == 0;
    }

    /**
     * Test whether the {@code JSONObject} contains the specified value.
     *
     * @param   value   the value
     * @return          {@code true} if the {@code JSONObject} contains the value
     * @see     Map#containsValue(Object)
     */
    @Override
    public boolean containsValue(Object value) {
        for (int i = 0, n = list.size(); i < n; i++)
            if (equals(list.get(i).getValue(), value))
                return true;
        return false;
    }

    /**
     * Add all the members of another {@link Map} to this {@code JSONObject}.
     *
     * @param   m       the other {@link Map}
     * @see     Map#putAll(Map)
     */
    @Override
    public void putAll(Map m) {
        for (String s : m.keySet())
            put(s, m.get(s));
    }

    /**
     * Remove all members from this {@code JSONObject}.
     *
     * @see     Map#clear()
     */
    @Override
    public void clear() {
        list.clear();
    }

    /**
     * Get a {@link Set} representing the keys in use in the {@code JSONObject}.
     *
     * @return  the {@link Set} of keys
     * @see     Map#keySet()
     */
    @Override
    public Set keySet() {
        return new KeySet();
    }

    /**
     * Get a {@link Collection} of the values in the {@code JSONObject}.
     *
     * @return  the {@link Collection} of values
     * @see     Map#values()
     */
    @Override
    public Collection values() {
        return new ValueCollection();
    }

    /**
     * Get a {@link Set} of the key-value pairs in use in the {@code JSONObject}.
     *
     * @return  the {@link Set} of key-value pairs
     * @see     Map#entrySet()
     */
    @Override
    public Set> entrySet() {
        return new EntrySet();
    }

    /**
     * Get an {@link Iterator} over the names (keys) of the members of the object.
     *
     * @return  an {@link Iterator}
     * @see     Iterable#iterator()
     */
    @Override
    public Iterator iterator() {
        return new KeyIterator();
    }

    /**
     * Create the external representation for this {@code JSONObject}.
     *
     * @return  the JSON representation for this object
     * @see     JSONValue#toJSON()
     */
    @Override
    public String toJSON() {
        int estimate = size() * 40;
        StringBuilder sb = new StringBuilder(estimate);
        try {
            appendJSON(sb);
        }
        catch (IOException e) {
            // can't happen - StringBuilder does not throw IOException
        }
        return sb.toString();
    }

    /**
     * Append the external representation for this JSON object to a given {@link Appendable}.
     *
     * @param   a   the {@link Appendable}
     * @throws  IOException     if thrown by the {@link Appendable}
     * @see     JSONValue#appendJSON(Appendable)
     */
    @Override
    public void appendJSON(Appendable a) throws IOException {
        a.append('{');
        int n = list.size();
        if (n > 0) {
            int i = 0;
            for (;;) {
                Entry entry = list.get(i++);
                a.append('"');
                Strings.appendEscaped(a, entry.getKey(), JSON.charMapper);
                a.append('"').append(':');
                JSON.appendJSON(a, entry.getValue());
                if (i >= n)
                    break;
                a.append(',');
            }
        }
        a.append('}');
    }

    /**
     * Test whether the composite is "simple", i.e. it contains only non-composite values
     * (to assist with formatting).
     *
     * @return  {@code true} if the composite is simple
     * @see     JSONComposite#isSimple()
     */
    @Override
    public boolean isSimple() {
        for (int i = 0; i < list.size(); i++)
            if (list.get(i).getValue() instanceof JSONComposite)
                return false;
        return true;
    }

    /**
     * Get the {@link String} representation of this {@code JSONObject}.
     *
     * @return  the string representation for this object
     * @see     Object#toString()
     */
    @Override
    public String toString() {
        return toJSON();
    }

    /**
     * Get the hash code for this {@code JSONObject}.
     *
     * @return  the hash code
     * @see     Object#hashCode()
     */
    @Override
    public int hashCode() {
        int result = 0;
        for (int i = 0, n = list.size(); i < n; i++)
            result ^= list.get(i).hashCode();
        return result;
    }

    /**
     * Compare this {@code JSONObject} with another object for equality.
     *
     * @param   other   the other object
     * @return  {@code true} if the other object is a {@code JSONObject} and is identical to
     *          this object
     * @see     Object#equals(Object)
     */
    @Override
    public boolean equals(Object other) {
        if (other == this)
            return true;
        if (!(other instanceof JSONObject))
            return false;
        JSONObject otherObj = (JSONObject)other;
        if (list.size() != otherObj.list.size())
            return false;
        for (Entry entry : list)
            if (!equals(entry.getValue(), otherObj.get(entry.getKey())))
                return false;
        return true;
    }

    /**
     * Get an {@link Entry} by index.
     *
     * @param   index   the index
     * @return  the list entry
     */
    public Entry getEntry(int index) {
        return list.get(index);
    }

    /**
     * Find the index for the specified key.
     *
     * @param   key     the key
     * @return          the index for this key, or -1 if not found
     */
    private int findIndex(Object key) {
        for (int i = 0, n = list.size(); i < n; i++)
            if (list.get(i).getKey().equals(key))
                return i;
        return -1;
    }

    /**
     * Convenience method to create a {@code JSONObject}.  Supports the idiom:
     * 
     *     JSONObject obj = JSONObject.create().putValue("zero", 0).putValue("one", 1);
     * 
* * @return the new {@code JSONObject} */ public static JSONObject create() { return new JSONObject(); } private static T requireNonNull(T obj) { if (obj == null) throw new NullPointerException(); return obj; } private static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); } private static int hash(Object... values) { return Arrays.hashCode(values); } /** * Inner class to represent a key-value pair in the {@code JSONObject}. */ public static class Entry implements Map.Entry, Serializable { private static final long serialVersionUID = 7415672545416600849L; private String key; private JSONValue value; public Entry(String key, JSONValue value) { this.key = key; this.value = value; } @Override public String getKey() { return key; } @Override public JSONValue getValue() { return value; } @Override public JSONValue setValue(JSONValue value) { JSONValue oldValue = this.value; this.value = value; return oldValue; } @Override public boolean equals(Object other) { if (!(other instanceof Entry)) return false; Entry otherEntry = (Entry)other; return JSONObject.equals(key, otherEntry.key) && JSONObject.equals(value, otherEntry.value); } @Override public int hashCode() { return hash(key, value); } } /** * An {@link Iterator} over the {@link Set} of key-value pairs in the {@code JSONObject}. */ private class EntryIterator extends BaseIterator> { @Override public Entry next() { return nextEntry(); } } /** * An {@link Iterator} over the {@link Set} of keys in the {@code JSONObject}. */ private class KeyIterator extends BaseIterator { @Override public String next() { return nextEntry().getKey(); } } /** * An {@link Iterator} over the {@link Collection} of values in the {@code JSONObject}. */ private class ValueIterator extends BaseIterator { @Override public JSONValue next() { return nextEntry().getValue(); } } /** * Abstract base class for various iterators. * * @param the returned type */ private abstract class BaseIterator implements Iterator { private int index; public BaseIterator() { index = 0; } public Entry nextEntry() { if (!hasNext()) throw new NoSuchElementException(); return list.get(index++); } @Override public boolean hasNext() { return index < list.size(); } @Override public void remove() { throw new UnsupportedOperationException(); } } /** * A collection of the key-value pairs in the {@code JSONObject}. * * @see #entrySet() */ private class EntrySet extends CollectionBase> { @Override public boolean contains(Object o) { return list.contains(o); } @Override public Iterator> iterator() { return new EntryIterator(); } @Override public boolean containsAll(Collection c) { for (Object o : c) if (!list.contains(o)) return false; return true; } } /** * A collection of the keys in the {@code JSONObject}. * * @see #keySet() */ private class KeySet extends CollectionBase { @Override public boolean contains(Object o) { return containsKey(o); } @Override public Iterator iterator() { return new KeyIterator(); } @Override public boolean containsAll(Collection c) { for (Object o : c) if (!containsKey(o)) return false; return true; } } /** * A collection of the values in the {@code JSONObject}. * * @see #values() */ private class ValueCollection extends CollectionBase { @Override public boolean contains(Object o) { return containsValue(o); } @Override public Iterator iterator() { return new ValueIterator(); } @Override public boolean containsAll(Collection c) { for (Object o : c) if (!containsValue(o)) return false; return true; } } /** * Abstract base class for various returned collections. All modifying operations throw an * {@link UnsupportedOperationException}. * * @param the returned type */ private abstract class CollectionBase extends AbstractSet { /** * Return the number of elements in the set. All returned collections are the same size * as the underlying collection. * * @return the number of elements in the collection */ @Override public int size() { return list.size(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy