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        @Override
130        public String toJSONString()
131        {
132            return "null";
133        }
134
135        // Serialization magic: after de-serializing, it will be back to the singleton instance of NULL.
136        private Object readResolve() throws ObjectStreamException
137        {
138            return NULL;
139        }
140    }
141
142    /**
143     * The map where the JSONObject's properties are kept.
144     */
145    private final Map<String, Object> properties = new LinkedHashMap<String, Object>();
146
147    /**
148     * It is sometimes more convenient and less ambiguous to have a <code>NULL</code> object than to use Java's
149     * <code>null</code> value. <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>.
150     * <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>.
151     */
152    public static final Object NULL = new Null();
153
154    /**
155     * Construct an empty JSONObject.
156     */
157    public JSONObject()
158    {
159    }
160
161    /**
162     * Returns a new JSONObject that is a shallow copy of this JSONObject.
163     *
164     * @since 5.4
165     */
166    public JSONObject copy()
167    {
168        JSONObject dupe = new JSONObject();
169        dupe.properties.putAll(properties);
170
171        return dupe;
172    }
173
174    /**
175     * Constructs a new JSONObject using a series of String keys and object values.
176     * Object values sholuld be compatible with {@link #put(String, Object)}. Keys must be strings
177     * (toString() will be invoked on each key).
178     * <p/>
179     * Prior to release 5.4, keysAndValues was type String...; changing it to Object... makes
180     * it much easier to initialize a JSONObject in a single statement, which is more readable.
181     *
182     * @since 5.2.0
183     */
184    public JSONObject(Object... keysAndValues)
185    {
186        int i = 0;
187
188        while (i < keysAndValues.length)
189        {
190            put(keysAndValues[i++].toString(), keysAndValues[i++]);
191        }
192    }
193
194    /**
195     * Construct a JSONObject from a subset of another JSONObject. An array of strings is used to identify the keys that
196     * should be copied. Missing keys are ignored.
197     *
198     * @param source
199     *         A JSONObject.
200     * @param propertyNames
201     *         The strings to copy.
202     * @throws RuntimeException
203     *         If a value is a non-finite number.
204     */
205    public JSONObject(JSONObject source, String... propertyNames)
206    {
207        for (String name : propertyNames)
208        {
209            Object value = source.opt(name);
210
211            if (value != null)
212                put(name, value);
213        }
214    }
215
216    /**
217     * Construct a JSONObject from a JSONTokener.
218     *
219     * @param x
220     *         A JSONTokener object containing the source string. @ If there is a syntax error in the source string.
221     */
222    JSONObject(JSONTokener x)
223    {
224        String key;
225
226        if (x.nextClean() != '{')
227        {
228            throw x.syntaxError("A JSONObject text must begin with '{'");
229        }
230
231        while (true)
232        {
233            char c = x.nextClean();
234            switch (c)
235            {
236                case 0:
237                    throw x.syntaxError("A JSONObject text must end with '}'");
238                case '}':
239                    return;
240                default:
241                    x.back();
242                    key = x.nextValue().toString();
243            }
244
245            /*
246             * The key is followed by ':'. We will also tolerate '=' or '=>'.
247             */
248
249            c = x.nextClean();
250            if (c == '=')
251            {
252                if (x.next() != '>')
253                {
254                    x.back();
255                }
256            } else if (c != ':')
257            {
258                throw x.syntaxError("Expected a ':' after a key");
259            }
260            put(key, x.nextValue());
261
262            /*
263             * Pairs are separated by ','. We will also tolerate ';'.
264             */
265
266            switch (x.nextClean())
267            {
268                case ';':
269                case ',':
270                    if (x.nextClean() == '}')
271                    {
272                        return;
273                    }
274                    x.back();
275                    break;
276                case '}':
277                    return;
278                default:
279                    throw x.syntaxError("Expected a ',' or '}'");
280            }
281        }
282    }
283
284    /**
285     * Construct a JSONObject from a string. This is the most commonly used JSONObject constructor.
286     *
287     * @param string
288     *         A string beginning with <code>{</code>&nbsp;<small>(left brace)</small> and ending with <code>}</code>
289     *         &nbsp;<small>(right brace)</small>.
290     * @throws RuntimeException
291     *         If there is a syntax error in the source string.
292     */
293    public JSONObject(String string)
294    {
295        this(new JSONTokener(string));
296    }
297
298    /**
299     * Accumulate values under a key. It is similar to the put method except that if there is already an object stored
300     * under the key then a JSONArray is stored under the key to hold all of the accumulated values. If there is already
301     * a JSONArray, then the new value is appended to it. In contrast, the put method replaces the previous value.
302     *
303     * @param key
304     *         A key string.
305     * @param value
306     *         An object to be accumulated under the key.
307     * @return this.
308     * @throws {@link
309     *         RuntimeException} If the value is an invalid number or if the key is null.
310     */
311    public JSONObject accumulate(String key, Object value)
312    {
313        testValidity(value);
314
315        Object existing = opt(key);
316
317        if (existing == null)
318        {
319            // Note that the original implementation of this method contradicted the method
320            // documentation.
321            put(key, value);
322            return this;
323        }
324
325        if (existing instanceof JSONArray)
326        {
327            ((JSONArray) existing).put(value);
328            return this;
329        }
330
331        // Replace the existing value, of any type, with an array that includes both the
332        // existing and the new value.
333
334        put(key, new JSONArray().put(existing).put(value));
335
336        return this;
337    }
338
339    /**
340     * Append values to the array under a key. If the key does not exist in the JSONObject, then the key is put in the
341     * JSONObject with its value being a JSONArray containing the value parameter. If the key was already associated
342     * with a JSONArray, then the value parameter is appended to it.
343     *
344     * @param key
345     *         A key string.
346     * @param value
347     *         An object to be accumulated under the key.
348     * @return this. @ If the key is null or if the current value associated with the key is not a JSONArray.
349     */
350    public JSONObject append(String key, Object value)
351    {
352        testValidity(value);
353        Object o = opt(key);
354        if (o == null)
355        {
356            put(key, new JSONArray().put(value));
357        } else if (o instanceof JSONArray)
358        {
359            put(key, ((JSONArray) o).put(value));
360        } else
361        {
362            throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONArray.");
363        }
364
365        return this;
366    }
367
368    /**
369     * Produce a string from a double. The string "null" will be returned if the number is not finite.
370     *
371     * @param d
372     *         A double.
373     * @return A String.
374     */
375    static String doubleToString(double d)
376    {
377        if (Double.isInfinite(d) || Double.isNaN(d))
378        {
379            return "null";
380        }
381
382        // Shave off trailing zeros and decimal point, if possible.
383
384        String s = Double.toString(d);
385        if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0)
386        {
387            while (s.endsWith("0"))
388            {
389                s = s.substring(0, s.length() - 1);
390            }
391            if (s.endsWith("."))
392            {
393                s = s.substring(0, s.length() - 1);
394            }
395        }
396        return s;
397    }
398
399    /**
400     * Get the value object associated with a key.
401     *
402     * @param key
403     *         A key string.
404     * @return The object associated with the key.
405     * @throws RuntimeException
406     *         if the key is not found.
407     * @see #opt(String)
408     */
409    public Object get(String key)
410    {
411        Object o = opt(key);
412        if (o == null)
413        {
414            throw new RuntimeException("JSONObject[" + quote(key) + "] not found.");
415        }
416
417        return o;
418    }
419
420    /**
421     * Get the boolean value associated with a key.
422     *
423     * @param key
424     *         A key string.
425     * @return The truth.
426     * @throws RuntimeException
427     *         if the value does not exist, is not a Boolean or the String "true" or "false".
428     */
429    public boolean getBoolean(String key)
430    {
431        Object o = get(key);
432
433        if (o instanceof Boolean)
434            return o.equals(Boolean.TRUE);
435
436        if (o instanceof String)
437        {
438            String value = (String) o;
439
440            if (value.equalsIgnoreCase("true"))
441                return true;
442
443            if (value.equalsIgnoreCase("false"))
444                return false;
445        }
446
447        throw new RuntimeException("JSONObject[" + quote(key) + "] is not a Boolean.");
448    }
449
450    /**
451     * Get the double value associated with a key.
452     *
453     * @param key
454     *         A key string.
455     * @return The numeric value. @throws RuntimeException if the key is not found or if the value is not a Number object and cannot be
456     *         converted to a number.
457     */
458    public double getDouble(String key)
459    {
460        Object value = get(key);
461
462        try
463        {
464            if (value instanceof Number)
465                return ((Number) value).doubleValue();
466
467            // This is a bit sloppy for the case where value is not a string.
468
469            return Double.valueOf((String) value);
470        } catch (Exception e)
471        {
472            throw new RuntimeException("JSONObject[" + quote(key) + "] is not a number.");
473        }
474    }
475
476    /**
477     * Get the int value associated with a key. If the number value is too large for an int, it will be clipped.
478     *
479     * @param key
480     *         A key string.
481     * @return The integer value.
482     * @throws RuntimeException
483     *         if the key is not found or if the value cannot be converted to an integer.
484     */
485    public int getInt(String key)
486    {
487        Object value = get(key);
488
489        if (value instanceof Number)
490            return ((Number) value).intValue();
491
492        // Very inefficient way to do this!
493        return (int) getDouble(key);
494    }
495
496    /**
497     * Get the JSONArray value associated with a key.
498     *
499     * @param key
500     *         A key string.
501     * @return A JSONArray which is the value.
502     * @throws RuntimeException
503     *         if the key is not found or if the value is not a JSONArray.
504     */
505    public JSONArray getJSONArray(String key)
506    {
507        Object o = get(key);
508        if (o instanceof JSONArray)
509        {
510            return (JSONArray) o;
511        }
512
513        throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONArray.");
514    }
515
516    /**
517     * Get the JSONObject value associated with a key.
518     *
519     * @param key
520     *         A key string.
521     * @return A JSONObject which is the value.
522     * @throws RuntimeException
523     *         if the key is not found or if the value is not a JSONObject.
524     */
525    public JSONObject getJSONObject(String key)
526    {
527        Object o = get(key);
528        if (o instanceof JSONObject)
529        {
530            return (JSONObject) o;
531        }
532
533        throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONObject.");
534    }
535
536    /**
537     * Get the long value associated with a key. If the number value is too long for a long, it will be clipped.
538     *
539     * @param key
540     *         A key string.
541     * @return The long value.
542     * @throws RuntimeException
543     *         if the key is not found or if the value cannot be converted to a long.
544     */
545    public long getLong(String key)
546    {
547        Object o = get(key);
548        return o instanceof Number ? ((Number) o).longValue() : (long) getDouble(key);
549    }
550
551    /**
552     * Get the string associated with a key.
553     *
554     * @param key
555     *         A key string.
556     * @return A string which is the value.
557     * @throws RuntimeException
558     *         if the key is not found.
559     */
560    public String getString(String key)
561    {
562        return get(key).toString();
563    }
564
565    /**
566     * Determine if the JSONObject contains a specific key.
567     *
568     * @param key
569     *         A key string.
570     * @return true if the key exists in the JSONObject.
571     */
572    public boolean has(String key)
573    {
574        return properties.containsKey(key);
575    }
576
577    /**
578     * Determine if the value associated with the key is null or if there is no value.
579     *
580     * @param key
581     *         A key string.
582     * @return true if there is no value associated with the key or if the value is the JSONObject.NULL object.
583     */
584    public boolean isNull(String key)
585    {
586        return JSONObject.NULL.equals(opt(key));
587    }
588
589    /**
590     * Get an enumeration of the keys of the JSONObject. Caution: the set should not be modified.
591     *
592     * @return An iterator of the keys.
593     */
594    public Set<String> keys()
595    {
596        return properties.keySet();
597    }
598
599    /**
600     * Get the number of keys stored in the JSONObject.
601     *
602     * @return The number of keys in the JSONObject.
603     */
604    public int length()
605    {
606        return properties.size();
607    }
608
609    /**
610     * Produce a JSONArray containing the names of the elements of this JSONObject.
611     *
612     * @return A JSONArray containing the key strings, or null if the JSONObject is empty.
613     */
614    public JSONArray names()
615    {
616        JSONArray ja = new JSONArray();
617
618        for (String key : keys())
619        {
620            ja.put(key);
621        }
622
623        return ja.length() == 0 ? null : ja;
624    }
625
626    /**
627     * Produce a string from a Number.
628     *
629     * @param n
630     *         A Number
631     * @return A String. @ If n is a non-finite number.
632     */
633    static String numberToString(Number n)
634    {
635        assert n != null;
636
637        testValidity(n);
638
639        // Shave off trailing zeros and decimal point, if possible.
640
641        String s = n.toString();
642        if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0)
643        {
644            while (s.endsWith("0"))
645            {
646                s = s.substring(0, s.length() - 1);
647            }
648            if (s.endsWith("."))
649            {
650                s = s.substring(0, s.length() - 1);
651            }
652        }
653        return s;
654    }
655
656    /**
657     * Get an optional value associated with a key.
658     *
659     * @param key
660     *         A key string.
661     * @return An object which is the value, or null if there is no value.
662     * @see #get(String)
663     */
664    public Object opt(String key)
665    {
666        return properties.get(key);
667    }
668
669    /**
670     * Put a key/value pair in the JSONObject. If the value is null, then the key will be removed from the JSONObject if
671     * it is present.
672     *
673     * @param key
674     *         A key string.
675     * @param value
676     *         An object which is the value. It should be of one of these types: Boolean, Double, Integer,
677     *         JSONArray, JSONObject, JSONLiteral, Long, String, or the JSONObject.NULL object.
678     * @return this.
679     * @throws RuntimeException
680     *         If the value is non-finite number or if the key is null.
681     */
682    public JSONObject put(String key, Object value)
683    {
684        assert key != null;
685
686        if (value != null)
687        {
688            testValidity(value);
689            properties.put(key, value);
690        } else
691        {
692            remove(key);
693        }
694
695        return this;
696    }
697
698    /**
699     * Produce a string in double quotes with backslash sequences in all the right places. A backslash will be inserted
700     * within </, allowing JSON text to be delivered in HTML. In JSON text, a string cannot contain a control character
701     * or an unescaped quote or backslash.
702     *
703     * @param string
704     *         A String
705     * @return A String correctly formatted for insertion in a JSON text.
706     */
707    public static String quote(String string)
708    {
709        if (string == null || string.length() == 0)
710        {
711            return "\"\"";
712        }
713
714        char b;
715        char c = 0;
716        int i;
717        int len = string.length();
718        StringBuilder buffer = new StringBuilder(len + 4);
719        String t;
720
721        buffer.append('"');
722        for (i = 0; i < len; i += 1)
723        {
724            b = c;
725            c = string.charAt(i);
726            switch (c)
727            {
728                case '\\':
729                case '"':
730                    buffer.append('\\');
731                    buffer.append(c);
732                    break;
733                case '/':
734                    if (b == '<')
735                    {
736                        buffer.append('\\');
737                    }
738                    buffer.append(c);
739                    break;
740                case '\b':
741                    buffer.append("\\b");
742                    break;
743                case '\t':
744                    buffer.append("\\t");
745                    break;
746                case '\n':
747                    buffer.append("\\n");
748                    break;
749                case '\f':
750                    buffer.append("\\f");
751                    break;
752                case '\r':
753                    buffer.append("\\r");
754                    break;
755                default:
756                    if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100'))
757                    {
758                        t = "000" + Integer.toHexString(c);
759                        buffer.append("\\u").append(t.substring(t.length() - 4));
760                    } else
761                    {
762                        buffer.append(c);
763                    }
764            }
765        }
766        buffer.append('"');
767        return buffer.toString();
768    }
769
770    /**
771     * Remove a name and its value, if present.
772     *
773     * @param key
774     *         The name to be removed.
775     * @return The value that was associated with the name, or null if there was no value.
776     */
777    public Object remove(String key)
778    {
779        return properties.remove(key);
780    }
781
782    private static final Class[] ALLOWED = new Class[]
783            {String.class, Boolean.class, Number.class, JSONObject.class, JSONArray.class, JSONString.class,
784                    JSONLiteral.class, Null.class};
785
786    /**
787     * Throw an exception if the object is an NaN or infinite number, or not a type which may be stored.
788     *
789     * @param value
790     *         The object to test. @ If o is a non-finite number.
791     */
792    @SuppressWarnings("unchecked")
793    static void testValidity(Object value)
794    {
795        if (value == null)
796            throw new IllegalArgumentException("null isn't valid in JSONObject and JSONArray. Use JSONObject.NULL instead.");
797
798        boolean found = false;
799        Class actual = value.getClass();
800
801        for (Class allowed : ALLOWED)
802        {
803            if (allowed.isAssignableFrom(actual))
804            {
805                found = true;
806                break;
807            }
808        }
809
810        if (!found)
811        {
812            List<String> typeNames = new ArrayList<String>();
813
814            for (Class c : ALLOWED)
815            {
816                String name = c.getName();
817
818                if (name.startsWith("java.lang."))
819                    name = name.substring(10);
820
821                typeNames.add(name);
822            }
823
824            Collections.sort(typeNames);
825
826            StringBuilder joined = new StringBuilder();
827            String sep = "";
828
829            for (String name : typeNames)
830            {
831                joined.append(sep);
832                joined.append(name);
833
834                sep = ", ";
835            }
836
837            String message = String.format("JSONObject properties may be one of %s. Type %s is not allowed.",
838                    joined.toString(), actual.getName());
839
840            throw new RuntimeException(message);
841        }
842
843        if (value instanceof Double)
844        {
845            Double asDouble = (Double) value;
846
847            if (asDouble.isInfinite() || asDouble.isNaN())
848            {
849                throw new RuntimeException(
850                        "JSON does not allow non-finite numbers.");
851            }
852
853            return;
854        }
855
856        if (value instanceof Float)
857        {
858            Float asFloat = (Float) value;
859
860            if (asFloat.isInfinite() || asFloat.isNaN())
861            {
862                throw new RuntimeException(
863                        "JSON does not allow non-finite numbers.");
864            }
865
866        }
867
868    }
869
870    /**
871     * Prints the JSONObject using the session.
872     *
873     * @since 5.2.0
874     */
875    @Override
876    void print(JSONPrintSession session)
877    {
878        session.printSymbol('{');
879
880        session.indent();
881
882        boolean comma = false;
883
884        for (String key : keys())
885        {
886            if (comma)
887                session.printSymbol(',');
888
889            session.newline();
890
891            session.printQuoted(key);
892
893            session.printSymbol(':');
894
895            printValue(session, properties.get(key));
896
897            comma = true;
898        }
899
900        session.outdent();
901
902        if (comma)
903            session.newline();
904
905        session.printSymbol('}');
906    }
907
908    /**
909     * Prints a value (a JSONArray or JSONObject, or a value stored in an array or object) using
910     * the session.
911     *
912     * @since 5.2.0
913     */
914    static void printValue(JSONPrintSession session, Object value)
915    {
916        
917        if (value instanceof JSONObject)
918        {
919            ((JSONObject) value).print(session);
920            return;
921        }
922
923        if (value instanceof JSONArray)
924        {
925            ((JSONArray) value).print(session);
926            return;
927        }
928
929        if (value instanceof JSONString)
930        {
931            String printValue = ((JSONString) value).toJSONString();
932
933            session.print(printValue);
934
935            return;
936        }
937
938        if (value instanceof Number)
939        {
940            String printValue = numberToString((Number) value);
941            session.print(printValue);
942            return;
943        }
944
945        if (value instanceof Boolean)
946        {
947            session.print(value.toString());
948
949            return;
950        }
951
952        // Otherwise it really should just be a string. Nothing else can go in.
953        session.printQuoted(value.toString());
954    }
955
956    /**
957     * Returns true if the other object is a JSONObject and its set of properties matches this object's properties.
958     */
959    @Override
960    public boolean equals(Object obj)
961    {
962        if (obj == null)
963            return false;
964
965        if (!(obj instanceof JSONObject))
966            return false;
967
968        JSONObject other = (JSONObject) obj;
969
970        return properties.equals(other.properties);
971    }
972
973    /**
974     * Returns a Map of the keys and values of the JSONObject. The returned map is unmodifiable.
975     * Note that changes to the JSONObject will be reflected in the map. In addition, null values in the JSONObject
976     * are represented as {@link JSONObject#NULL} in the map.
977     *
978     * @return unmodifiable map of properties and values
979     * @since 5.4
980     */
981    public Map<String, Object> toMap()
982    {
983        return Collections.unmodifiableMap(properties);
984    }
985
986    /**
987     * Invokes {@link #put(String, Object)} for each value from the map.
988     *
989     * @param newProperties
990     *         to add to this JSONObject
991     * @return this JSONObject
992     * @since 5.4
993     */
994    public JSONObject putAll(Map<String, ?> newProperties)
995    {
996        assert newProperties != null;
997
998        for (Map.Entry<String, ?> e : newProperties.entrySet())
999        {
1000            put(e.getKey(), e.getValue());
1001        }
1002
1003        return this;
1004    }
1005
1006    /**
1007     * Navigates into a nested JSONObject, creating the JSONObject if necessary. They key must not exist,
1008     * or must be a JSONObject.
1009     *
1010     * @param key
1011     * @return the nested JSONObject
1012     * @throws IllegalStateException
1013     *         if the current value for the key is not null and not JSONObject
1014     */
1015    public JSONObject in(String key)
1016    {
1017        assert key != null;
1018
1019        Object nested = properties.get(key);
1020
1021        if (nested != null && !(nested instanceof JSONObject))
1022        {
1023            throw new IllegalStateException(String.format("JSONObject[%s] is not a JSONObject.", quote(key)));
1024        }
1025
1026        if (nested == null)
1027        {
1028            nested = new JSONObject();
1029            properties.put(key, nested);
1030        }
1031
1032        return (JSONObject) nested;
1033    }
1034}