001// Copyright 2007-2013 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.json;
016
017/*
018 * Copyright (c) 2002 JSON.org
019 * Permission is hereby granted, free of charge, to any person obtaining a copy
020 * of this software and associated documentation files (the "Software"), to deal
021 * in the Software without restriction, including without limitation the rights
022 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
023 * copies of the Software, and to permit persons to whom the Software is
024 * furnished to do so, subject to the following conditions:
025 * The above copyright notice and this permission notice shall be included in all
026 * copies or substantial portions of the Software.
027 * The Software shall be used for Good, not Evil.
028 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
029 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
030 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
031 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
032 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
033 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
034 * SOFTWARE.
035 */
036
037import java.io.ObjectStreamException;
038import java.io.Serializable;
039import java.util.*;
040
041/**
042 * A JSONObject is an unordered collection of name/value pairs. Its external form is a string wrapped in curly braces
043 * with colons between the names and values, and commas between the values and names. The internal form is an object
044 * having <code>get</code> and <code>opt</code> methods for accessing the values by name, and <code>put</code> methods
045 * for adding or replacing values by name. The values can be any of these types: <code>Boolean</code>,
046 * {@link org.apache.tapestry5.json.JSONArray}, {@link org.apache.tapestry5.json.JSONLiteral}, <code>JSONObject</code>,
047 * <code>Number</code>, <code>String</code>, or the <code>JSONObject.NULL</code> object. A JSONObject constructor can be
048 * used to convert an external form JSON text into
049 * an internal form whose values can be retrieved with the <code>get</code> and <code>opt</code> methods, or to convert
050 * values into a JSON text using the <code>put</code> and <code>toString</code> methods. A <code>get</code> method
051 * returns a value if one can be found, and throws an exception if one cannot be found. An <code>opt</code> method
052 * returns a default value instead of throwing an exception, and so is useful for obtaining optional values.
053 * <p/>
054 * The generic <code>get()</code> and <code>opt()</code> methods return an object, which you can cast or query for type.
055 * There are also typed <code>get</code> and <code>opt</code> methods that do type checking and type coersion for you.
056 * <p/>
057 * The <code>put</code> methods adds values to an object. For example,
058 * <p/>
059 * <pre>
060 * myString = new JSONObject().put(&quot;JSON&quot;, &quot;Hello, World!&quot;).toString();
061 * </pre>
062 * <p/>
063 * produces the string <code>{"JSON": "Hello, World"}</code>.
064 * <p/>
065 * The texts produced by the <code>toString</code> methods strictly conform to the JSON syntax rules. The constructors
066 * are more forgiving in the texts they will accept:
067 * <ul>
068 * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just before the closing brace.</li>
069 * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single quote)</small>.</li>
070 * <li>Strings do not need to be quoted at all if they do not begin with a quote or single quote, and if they do not
071 * contain leading or trailing spaces, and if they do not contain any of these characters: <code>{ }
072 * [ ] / \ : , = ; #</code> and if they do not look like numbers and if they are not the reserved words
073 * <code>true</code>, <code>false</code>, or <code>null</code>.</li>
074 * <li>Keys can be followed by <code>=</code> or <code>=></code> as well as by <code>:</code>.</li>
075 * <li>Values can be followed by <code>;</code> <small>(semicolon)</small> as well as by <code>,</code>
076 * <small>(comma)</small>.</li>
077 * <li>Numbers may have the <code>0-</code> <small>(octal)</small> or <code>0x-</code> <small>(hex)</small> prefix.</li>
078 * <li>Comments written in the slashshlash, slashstar, and hash conventions will be ignored.</li>
079 * </ul>
080 * <hr/>
081 * <p/>
082 * This class, and the other related classes, have been heavily modified from the original source, to fit Tapestry
083 * standards and to make use of JDK 1.5 features such as generics. Further, since the interest of Tapestry is primarily
084 * constructing JSON (and not parsing it), many of the non-essential methods have been removed (since the original code
085 * came with no tests).
086 * <p/>
087 * Finally, support for the {@link org.apache.tapestry5.json.JSONLiteral} type has been added, which allows the exact
088 * output to be controlled; useful when a JSONObject is being used as a configuration object, and must contain values
089 * that are not simple data, such as an inline function (making the result not JSON).
090 *
091 * @author JSON.org
092 * @version 2
093 */
094@SuppressWarnings(
095        {"CloneDoesntCallSuperClone"})
096public final class JSONObject extends JSONCollection
097{
098
099    /**
100     * JSONObject.NULL is equivalent to the value that JavaScript calls null, whilst Java's null is equivalent to the
101     * value that JavaScript calls undefined.
102     */
103    private static final class Null implements JSONString, Serializable
104    {
105        /**
106         * A Null object is equal to the null value and to itself.
107         *
108         * @param object
109         *         An object to test for nullness.
110         * @return true if the object parameter is the JSONObject.NULL object or null.
111         */
112        @Override
113        public boolean equals(Object object)
114        {
115            return object == null || object == this;
116        }
117
118        /**
119         * Get the "null" string value.
120         *
121         * @return The string "null".
122         */
123        @Override
124        public String toString()
125        {
126            return "null";
127        }
128
129        public String toJSONString()
130        {
131            return "null";
132        }
133
134        // Serialization magic: after de-serializing, it will be back to the singleton instance of NULL.
135        private Object readResolve() throws ObjectStreamException
136        {
137            return NULL;
138        }
139    }
140
141    /**
142     * The map where the JSONObject's properties are kept.
143     */
144    private final Map<String, Object> properties = new LinkedHashMap<String, Object>();
145
146    /**
147     * It is sometimes more convenient and less ambiguous to have a <code>NULL</code> object than to use Java's
148     * <code>null</code> value. <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>.
149     * <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>.
150     */
151    public static final Object NULL = new Null();
152
153    /**
154     * Construct an empty JSONObject.
155     */
156    public JSONObject()
157    {
158    }
159
160    /**
161     * Returns a new JSONObject that is a shallow copy of this JSONObject.
162     *
163     * @since 5.4
164     */
165    public JSONObject copy()
166    {
167        JSONObject dupe = new JSONObject();
168        dupe.properties.putAll(properties);
169
170        return dupe;
171    }
172
173    /**
174     * Constructs a new JSONObject using a series of String keys and object values.
175     * Object values sholuld be compatible with {@link #put(String, Object)}. Keys must be strings
176     * (toString() will be invoked on each key).
177     * <p/>
178     * Prior to release 5.4, keysAndValues was type String...; changing it to Object... makes
179     * it much easier to initialize a JSONObject in a single statement, which is more readable.
180     *
181     * @since 5.2.0
182     */
183    public JSONObject(Object... keysAndValues)
184    {
185        int i = 0;
186
187        while (i < keysAndValues.length)
188        {
189            put(keysAndValues[i++].toString(), keysAndValues[i++]);
190        }
191    }
192
193    /**
194     * Construct a JSONObject from a subset of another JSONObject. An array of strings is used to identify the keys that
195     * should be copied. Missing keys are ignored.
196     *
197     * @param source
198     *         A JSONObject.
199     * @param propertyNames
200     *         The strings to copy.
201     * @throws RuntimeException
202     *         If a value is a non-finite number.
203     */
204    public JSONObject(JSONObject source, String... propertyNames)
205    {
206        for (String name : propertyNames)
207        {
208            Object value = source.opt(name);
209
210            if (value != null)
211                put(name, value);
212        }
213    }
214
215    /**
216     * Construct a JSONObject from a JSONTokener.
217     *
218     * @param x
219     *         A JSONTokener object containing the source string. @ If there is a syntax error in the source string.
220     */
221    JSONObject(JSONTokener x)
222    {
223        String key;
224
225        if (x.nextClean() != '{')
226        {
227            throw x.syntaxError("A JSONObject text must begin with '{'");
228        }
229
230        while (true)
231        {
232            char c = x.nextClean();
233            switch (c)
234            {
235                case 0:
236                    throw x.syntaxError("A JSONObject text must end with '}'");
237                case '}':
238                    return;
239                default:
240                    x.back();
241                    key = x.nextValue().toString();
242            }
243
244            /*
245             * The key is followed by ':'. We will also tolerate '=' or '=>'.
246             */
247
248            c = x.nextClean();
249            if (c == '=')
250            {
251                if (x.next() != '>')
252                {
253                    x.back();
254                }
255            } else if (c != ':')
256            {
257                throw x.syntaxError("Expected a ':' after a key");
258            }
259            put(key, x.nextValue());
260
261            /*
262             * Pairs are separated by ','. We will also tolerate ';'.
263             */
264
265            switch (x.nextClean())
266            {
267                case ';':
268                case ',':
269                    if (x.nextClean() == '}')
270                    {
271                        return;
272                    }
273                    x.back();
274                    break;
275                case '}':
276                    return;
277                default:
278                    throw x.syntaxError("Expected a ',' or '}'");
279            }
280        }
281    }
282
283    /**
284     * Construct a JSONObject from a string. This is the most commonly used JSONObject constructor.
285     *
286     * @param string
287     *         A string beginning with <code>{</code>&nbsp;<small>(left brace)</small> and ending with <code>}</code>
288     *         &nbsp;<small>(right brace)</small>.
289     * @throws RuntimeException
290     *         If there is a syntax error in the source string.
291     */
292    public JSONObject(String string)
293    {
294        this(new JSONTokener(string));
295    }
296
297    /**
298     * Accumulate values under a key. It is similar to the put method except that if there is already an object stored
299     * under the key then a JSONArray is stored under the key to hold all of the accumulated values. If there is already
300     * a JSONArray, then the new value is appended to it. In contrast, the put method replaces the previous value.
301     *
302     * @param key
303     *         A key string.
304     * @param value
305     *         An object to be accumulated under the key.
306     * @return this.
307     * @throws {@link
308     *         RuntimeException} If the value is an invalid number or if the key is null.
309     */
310    public JSONObject accumulate(String key, Object value)
311    {
312        testValidity(value);
313
314        Object existing = opt(key);
315
316        if (existing == null)
317        {
318            // Note that the original implementation of this method contradicted the method
319            // documentation.
320            put(key, value);
321            return this;
322        }
323
324        if (existing instanceof JSONArray)
325        {
326            ((JSONArray) existing).put(value);
327            return this;
328        }
329
330        // Replace the existing value, of any type, with an array that includes both the
331        // existing and the new value.
332
333        put(key, new JSONArray().put(existing).put(value));
334
335        return this;
336    }
337
338    /**
339     * Append values to the array under a key. If the key does not exist in the JSONObject, then the key is put in the
340     * JSONObject with its value being a JSONArray containing the value parameter. If the key was already associated
341     * with a JSONArray, then the value parameter is appended to it.
342     *
343     * @param key
344     *         A key string.
345     * @param value
346     *         An object to be accumulated under the key.
347     * @return this. @ If the key is null or if the current value associated with the key is not a JSONArray.
348     */
349    public JSONObject append(String key, Object value)
350    {
351        testValidity(value);
352        Object o = opt(key);
353        if (o == null)
354        {
355            put(key, new JSONArray().put(value));
356        } else if (o instanceof JSONArray)
357        {
358            put(key, ((JSONArray) o).put(value));
359        } else
360        {
361            throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONArray.");
362        }
363
364        return this;
365    }
366
367    /**
368     * Produce a string from a double. The string "null" will be returned if the number is not finite.
369     *
370     * @param d
371     *         A double.
372     * @return A String.
373     */
374    static String doubleToString(double d)
375    {
376        if (Double.isInfinite(d) || Double.isNaN(d))
377        {
378            return "null";
379        }
380
381        // Shave off trailing zeros and decimal point, if possible.
382
383        String s = Double.toString(d);
384        if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0)
385        {
386            while (s.endsWith("0"))
387            {
388                s = s.substring(0, s.length() - 1);
389            }
390            if (s.endsWith("."))
391            {
392                s = s.substring(0, s.length() - 1);
393            }
394        }
395        return s;
396    }
397
398    /**
399     * Get the value object associated with a key.
400     *
401     * @param key
402     *         A key string.
403     * @return The object associated with the key.
404     * @throws RuntimeException
405     *         if the key is not found.
406     * @see #opt(String)
407     */
408    public Object get(String key)
409    {
410        Object o = opt(key);
411        if (o == null)
412        {
413            throw new RuntimeException("JSONObject[" + quote(key) + "] not found.");
414        }
415
416        return o;
417    }
418
419    /**
420     * Get the boolean value associated with a key.
421     *
422     * @param key
423     *         A key string.
424     * @return The truth.
425     * @throws RuntimeException
426     *         if the value does not exist, is not a Boolean or the String "true" or "false".
427     */
428    public boolean getBoolean(String key)
429    {
430        Object o = get(key);
431
432        if (o instanceof Boolean)
433            return o.equals(Boolean.TRUE);
434
435        if (o instanceof String)
436        {
437            String value = (String) o;
438
439            if (value.equalsIgnoreCase("true"))
440                return true;
441
442            if (value.equalsIgnoreCase("false"))
443                return false;
444        }
445
446        throw new RuntimeException("JSONObject[" + quote(key) + "] is not a Boolean.");
447    }
448
449    /**
450     * Get the double value associated with a key.
451     *
452     * @param key
453     *         A key string.
454     * @return The numeric value. @throws RuntimeException if the key is not found or if the value is not a Number object and cannot be
455     *         converted to a number.
456     */
457    public double getDouble(String key)
458    {
459        Object value = get(key);
460
461        try
462        {
463            if (value instanceof Number)
464                return ((Number) value).doubleValue();
465
466            // This is a bit sloppy for the case where value is not a string.
467
468            return Double.valueOf((String) value);
469        } catch (Exception e)
470        {
471            throw new RuntimeException("JSONObject[" + quote(key) + "] is not a number.");
472        }
473    }
474
475    /**
476     * Get the int value associated with a key. If the number value is too large for an int, it will be clipped.
477     *
478     * @param key
479     *         A key string.
480     * @return The integer value.
481     * @throws RuntimeException
482     *         if the key is not found or if the value cannot be converted to an integer.
483     */
484    public int getInt(String key)
485    {
486        Object value = get(key);
487
488        if (value instanceof Number)
489            return ((Number) value).intValue();
490
491        // Very inefficient way to do this!
492        return (int) getDouble(key);
493    }
494
495    /**
496     * Get the JSONArray value associated with a key.
497     *
498     * @param key
499     *         A key string.
500     * @return A JSONArray which is the value.
501     * @throws RuntimeException
502     *         if the key is not found or if the value is not a JSONArray.
503     */
504    public JSONArray getJSONArray(String key)
505    {
506        Object o = get(key);
507        if (o instanceof JSONArray)
508        {
509            return (JSONArray) o;
510        }
511
512        throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONArray.");
513    }
514
515    /**
516     * Get the JSONObject value associated with a key.
517     *
518     * @param key
519     *         A key string.
520     * @return A JSONObject which is the value.
521     * @throws RuntimeException
522     *         if the key is not found or if the value is not a JSONObject.
523     */
524    public JSONObject getJSONObject(String key)
525    {
526        Object o = get(key);
527        if (o instanceof JSONObject)
528        {
529            return (JSONObject) o;
530        }
531
532        throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONObject.");
533    }
534
535    /**
536     * Get the long value associated with a key. If the number value is too long for a long, it will be clipped.
537     *
538     * @param key
539     *         A key string.
540     * @return The long value.
541     * @throws RuntimeException
542     *         if the key is not found or if the value cannot be converted to a long.
543     */
544    public long getLong(String key)
545    {
546        Object o = get(key);
547        return o instanceof Number ? ((Number) o).longValue() : (long) getDouble(key);
548    }
549
550    /**
551     * Get the string associated with a key.
552     *
553     * @param key
554     *         A key string.
555     * @return A string which is the value.
556     * @throws RuntimeException
557     *         if the key is not found.
558     */
559    public String getString(String key)
560    {
561        return get(key).toString();
562    }
563
564    /**
565     * Determine if the JSONObject contains a specific key.
566     *
567     * @param key
568     *         A key string.
569     * @return true if the key exists in the JSONObject.
570     */
571    public boolean has(String key)
572    {
573        return properties.containsKey(key);
574    }
575
576    /**
577     * Determine if the value associated with the key is null or if there is no value.
578     *
579     * @param key
580     *         A key string.
581     * @return true if there is no value associated with the key or if the value is the JSONObject.NULL object.
582     */
583    public boolean isNull(String key)
584    {
585        return JSONObject.NULL.equals(opt(key));
586    }
587
588    /**
589     * Get an enumeration of the keys of the JSONObject. Caution: the set should not be modified.
590     *
591     * @return An iterator of the keys.
592     */
593    public Set<String> keys()
594    {
595        return properties.keySet();
596    }
597
598    /**
599     * Get the number of keys stored in the JSONObject.
600     *
601     * @return The number of keys in the JSONObject.
602     */
603    public int length()
604    {
605        return properties.size();
606    }
607
608    /**
609     * Produce a JSONArray containing the names of the elements of this JSONObject.
610     *
611     * @return A JSONArray containing the key strings, or null if the JSONObject is empty.
612     */
613    public JSONArray names()
614    {
615        JSONArray ja = new JSONArray();
616
617        for (String key : keys())
618        {
619            ja.put(key);
620        }
621
622        return ja.length() == 0 ? null : ja;
623    }
624
625    /**
626     * Produce a string from a Number.
627     *
628     * @param n
629     *         A Number
630     * @return A String. @ If n is a non-finite number.
631     */
632    static String numberToString(Number n)
633    {
634        assert n != null;
635
636        testValidity(n);
637
638        // Shave off trailing zeros and decimal point, if possible.
639
640        String s = n.toString();
641        if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0)
642        {
643            while (s.endsWith("0"))
644            {
645                s = s.substring(0, s.length() - 1);
646            }
647            if (s.endsWith("."))
648            {
649                s = s.substring(0, s.length() - 1);
650            }
651        }
652        return s;
653    }
654
655    /**
656     * Get an optional value associated with a key.
657     *
658     * @param key
659     *         A key string.
660     * @return An object which is the value, or null if there is no value.
661     * @see #get(String)
662     */
663    public Object opt(String key)
664    {
665        return properties.get(key);
666    }
667
668    /**
669     * Put a key/value pair in the JSONObject. If the value is null, then the key will be removed from the JSONObject if
670     * it is present.
671     *
672     * @param key
673     *         A key string.
674     * @param value
675     *         An object which is the value. It should be of one of these types: Boolean, Double, Integer,
676     *         JSONArray, JSONObject, JSONLiteral, Long, String, or the JSONObject.NULL object.
677     * @return this.
678     * @throws RuntimeException
679     *         If the value is non-finite number or if the key is null.
680     */
681    public JSONObject put(String key, Object value)
682    {
683        assert key != null;
684
685        if (value != null)
686        {
687            testValidity(value);
688            properties.put(key, value);
689        } else
690        {
691            remove(key);
692        }
693
694        return this;
695    }
696
697    /**
698     * Produce a string in double quotes with backslash sequences in all the right places. A backslash will be inserted
699     * within </, allowing JSON text to be delivered in HTML. In JSON text, a string cannot contain a control character
700     * or an unescaped quote or backslash.
701     *
702     * @param string
703     *         A String
704     * @return A String correctly formatted for insertion in a JSON text.
705     */
706    public static String quote(String string)
707    {
708        if (string == null || string.length() == 0)
709        {
710            return "\"\"";
711        }
712
713        char b;
714        char c = 0;
715        int i;
716        int len = string.length();
717        StringBuilder buffer = new StringBuilder(len + 4);
718        String t;
719
720        buffer.append('"');
721        for (i = 0; i < len; i += 1)
722        {
723            b = c;
724            c = string.charAt(i);
725            switch (c)
726            {
727                case '\\':
728                case '"':
729                    buffer.append('\\');
730                    buffer.append(c);
731                    break;
732                case '/':
733                    if (b == '<')
734                    {
735                        buffer.append('\\');
736                    }
737                    buffer.append(c);
738                    break;
739                case '\b':
740                    buffer.append("\\b");
741                    break;
742                case '\t':
743                    buffer.append("\\t");
744                    break;
745                case '\n':
746                    buffer.append("\\n");
747                    break;
748                case '\f':
749                    buffer.append("\\f");
750                    break;
751                case '\r':
752                    buffer.append("\\r");
753                    break;
754                default:
755                    if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100'))
756                    {
757                        t = "000" + Integer.toHexString(c);
758                        buffer.append("\\u").append(t.substring(t.length() - 4));
759                    } else
760                    {
761                        buffer.append(c);
762                    }
763            }
764        }
765        buffer.append('"');
766        return buffer.toString();
767    }
768
769    /**
770     * Remove a name and its value, if present.
771     *
772     * @param key
773     *         The name to be removed.
774     * @return The value that was associated with the name, or null if there was no value.
775     */
776    public Object remove(String key)
777    {
778        return properties.remove(key);
779    }
780
781    private static final Class[] ALLOWED = new Class[]
782            {String.class, Boolean.class, Number.class, JSONObject.class, JSONArray.class, JSONString.class,
783                    JSONLiteral.class, Null.class};
784
785    /**
786     * Throw an exception if the object is an NaN or infinite number, or not a type which may be stored.
787     *
788     * @param value
789     *         The object to test. @ If o is a non-finite number.
790     */
791    @SuppressWarnings("unchecked")
792    static void testValidity(Object value)
793    {
794        if (value == null)
795            return;
796
797        boolean found = false;
798        Class actual = value.getClass();
799
800        for (Class allowed : ALLOWED)
801        {
802            if (allowed.isAssignableFrom(actual))
803            {
804                found = true;
805                break;
806            }
807        }
808
809        if (!found)
810        {
811            List<String> typeNames = new ArrayList<String>();
812
813            for (Class c : ALLOWED)
814            {
815                String name = c.getName();
816
817                if (name.startsWith("java.lang."))
818                    name = name.substring(10);
819
820                typeNames.add(name);
821            }
822
823            Collections.sort(typeNames);
824
825            StringBuilder joined = new StringBuilder();
826            String sep = "";
827
828            for (String name : typeNames)
829            {
830                joined.append(sep);
831                joined.append(name);
832
833                sep = ", ";
834            }
835
836            String message = String.format("JSONObject properties may be one of %s. Type %s is not allowed.",
837                    joined.toString(), actual.getName());
838
839            throw new RuntimeException(message);
840        }
841
842        if (value instanceof Double)
843        {
844            Double asDouble = (Double) value;
845
846            if (asDouble.isInfinite() || asDouble.isNaN())
847            {
848                throw new RuntimeException(
849                        "JSON does not allow non-finite numbers.");
850            }
851
852            return;
853        }
854
855        if (value instanceof Float)
856        {
857            Float asFloat = (Float) value;
858
859            if (asFloat.isInfinite() || asFloat.isNaN())
860            {
861                throw new RuntimeException(
862                        "JSON does not allow non-finite numbers.");
863            }
864
865        }
866
867    }
868
869    /**
870     * Prints the JSONObject using the session.
871     *
872     * @since 5.2.0
873     */
874    void print(JSONPrintSession session)
875    {
876        session.printSymbol('{');
877
878        session.indent();
879
880        boolean comma = false;
881
882        for (String key : keys())
883        {
884            if (comma)
885                session.printSymbol(',');
886
887            session.newline();
888
889            session.printQuoted(key);
890
891            session.printSymbol(':');
892
893            printValue(session, properties.get(key));
894
895            comma = true;
896        }
897
898        session.outdent();
899
900        if (comma)
901            session.newline();
902
903        session.printSymbol('}');
904    }
905
906    /**
907     * Prints a value (a JSONArray or JSONObject, or a value stored in an array or object) using
908     * the session.
909     *
910     * @since 5.2.0
911     */
912    static void printValue(JSONPrintSession session, Object value)
913    {
914        if (value instanceof JSONObject)
915        {
916            ((JSONObject) value).print(session);
917            return;
918        }
919
920        if (value instanceof JSONArray)
921        {
922            ((JSONArray) value).print(session);
923            return;
924        }
925
926        if (value instanceof JSONString)
927        {
928            String printValue = ((JSONString) value).toJSONString();
929
930            session.print(printValue);
931
932            return;
933        }
934
935        if (value instanceof Number)
936        {
937            String printValue = numberToString((Number) value);
938            session.print(printValue);
939            return;
940        }
941
942        if (value instanceof Boolean)
943        {
944            session.print(value.toString());
945
946            return;
947        }
948
949        // Otherwise it really should just be a string. Nothing else can go in.
950        session.printQuoted(value.toString());
951    }
952
953    /**
954     * Returns true if the other object is a JSONObject and its set of properties matches this object's properties.
955     */
956    @Override
957    public boolean equals(Object obj)
958    {
959        if (obj == null)
960            return false;
961
962        if (!(obj instanceof JSONObject))
963            return false;
964
965        JSONObject other = (JSONObject) obj;
966
967        return properties.equals(other.properties);
968    }
969
970    /**
971     * Returns a Map of the keys and values of the JSONObject. The returned map is unmodifiable.
972     * Note that changes to the JSONObject will be reflected in the map. In addition, null values in the JSONObject
973     * are represented as {@link JSONObject#NULL} in the map.
974     *
975     * @return unmodifiable map of properties and values
976     * @since 5.4
977     */
978    public Map<String, Object> toMap()
979    {
980        return Collections.unmodifiableMap(properties);
981    }
982
983    /**
984     * Invokes {@link #put(String, Object)} for each value from the map.
985     *
986     * @param newProperties
987     *         to add to this JSONObject
988     * @return this JSONObject
989     * @since 5.4
990     */
991    public JSONObject putAll(Map<String, ?> newProperties)
992    {
993        assert newProperties != null;
994
995        for (Map.Entry<String, ?> e : newProperties.entrySet())
996        {
997            put(e.getKey(), e.getValue());
998        }
999
1000        return this;
1001    }
1002
1003    /**
1004     * Navigates into a nested JSONObject, creating the JSONObject if necessary. They key must not exist,
1005     * or must be a JSONObject.
1006     *
1007     * @param key
1008     * @return the nested JSONObject
1009     * @throws IllegalStateException
1010     *         if the current value for the key is not null and not JSONObject
1011     */
1012    public JSONObject in(String key)
1013    {
1014        assert key != null;
1015
1016        Object nested = properties.get(key);
1017
1018        if (nested != null && !(nested instanceof JSONObject))
1019        {
1020            throw new IllegalStateException(String.format("JSONObject[%s] is not a JSONObject.", quote(key)));
1021        }
1022
1023        if (nested == null)
1024        {
1025            nested = new JSONObject();
1026            properties.put(key, nested);
1027        }
1028
1029        return (JSONObject) nested;
1030    }
1031}