| // Written in the D programming language. | 
 |  | 
 | /** | 
 | JavaScript Object Notation | 
 |  | 
 | Copyright: Copyright Jeremie Pelletier 2008 - 2009. | 
 | License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). | 
 | Authors:   Jeremie Pelletier, David Herberth | 
 | References: $(LINK http://json.org/) | 
 | Source:    $(PHOBOSSRC std/_json.d) | 
 | */ | 
 | /* | 
 |          Copyright Jeremie Pelletier 2008 - 2009. | 
 | Distributed under the Boost Software License, Version 1.0. | 
 |    (See accompanying file LICENSE_1_0.txt or copy at | 
 |          http://www.boost.org/LICENSE_1_0.txt) | 
 | */ | 
 | module std.json; | 
 |  | 
 | import std.array; | 
 | import std.conv; | 
 | import std.range.primitives; | 
 | import std.traits; | 
 |  | 
 | /// | 
 | @system unittest | 
 | { | 
 |     import std.conv : to; | 
 |  | 
 |     // parse a file or string of json into a usable structure | 
 |     string s = `{ "language": "D", "rating": 3.5, "code": "42" }`; | 
 |     JSONValue j = parseJSON(s); | 
 |     // j and j["language"] return JSONValue, | 
 |     // j["language"].str returns a string | 
 |     assert(j["language"].str == "D"); | 
 |     assert(j["rating"].floating == 3.5); | 
 |  | 
 |     // check a type | 
 |     long x; | 
 |     if (const(JSONValue)* code = "code" in j) | 
 |     { | 
 |         if (code.type() == JSON_TYPE.INTEGER) | 
 |             x = code.integer; | 
 |         else | 
 |             x = to!int(code.str); | 
 |     } | 
 |  | 
 |     // create a json struct | 
 |     JSONValue jj = [ "language": "D" ]; | 
 |     // rating doesnt exist yet, so use .object to assign | 
 |     jj.object["rating"] = JSONValue(3.5); | 
 |     // create an array to assign to list | 
 |     jj.object["list"] = JSONValue( ["a", "b", "c"] ); | 
 |     // list already exists, so .object optional | 
 |     jj["list"].array ~= JSONValue("D"); | 
 |  | 
 |     string jjStr = `{"language":"D","list":["a","b","c","D"],"rating":3.5}`; | 
 |     assert(jj.toString == jjStr); | 
 | } | 
 |  | 
 | /** | 
 | String literals used to represent special float values within JSON strings. | 
 | */ | 
 | enum JSONFloatLiteral : string | 
 | { | 
 |     nan         = "NaN",       /// string representation of floating-point NaN | 
 |     inf         = "Infinite",  /// string representation of floating-point Infinity | 
 |     negativeInf = "-Infinite", /// string representation of floating-point negative Infinity | 
 | } | 
 |  | 
 | /** | 
 | Flags that control how json is encoded and parsed. | 
 | */ | 
 | enum JSONOptions | 
 | { | 
 |     none,                       /// standard parsing | 
 |     specialFloatLiterals = 0x1, /// encode NaN and Inf float values as strings | 
 |     escapeNonAsciiChars = 0x2,  /// encode non ascii characters with an unicode escape sequence | 
 |     doNotEscapeSlashes = 0x4,   /// do not escape slashes ('/') | 
 | } | 
 |  | 
 | /** | 
 | JSON type enumeration | 
 | */ | 
 | enum JSON_TYPE : byte | 
 | { | 
 |     /// Indicates the type of a $(D JSONValue). | 
 |     NULL, | 
 |     STRING,  /// ditto | 
 |     INTEGER, /// ditto | 
 |     UINTEGER,/// ditto | 
 |     FLOAT,   /// ditto | 
 |     OBJECT,  /// ditto | 
 |     ARRAY,   /// ditto | 
 |     TRUE,    /// ditto | 
 |     FALSE    /// ditto | 
 | } | 
 |  | 
 | /** | 
 | JSON value node | 
 | */ | 
 | struct JSONValue | 
 | { | 
 |     import std.exception : enforceEx, enforce; | 
 |  | 
 |     union Store | 
 |     { | 
 |         string                          str; | 
 |         long                            integer; | 
 |         ulong                           uinteger; | 
 |         double                          floating; | 
 |         JSONValue[string]               object; | 
 |         JSONValue[]                     array; | 
 |     } | 
 |     private Store store; | 
 |     private JSON_TYPE type_tag; | 
 |  | 
 |     /** | 
 |       Returns the JSON_TYPE of the value stored in this structure. | 
 |     */ | 
 |     @property JSON_TYPE type() const pure nothrow @safe @nogc | 
 |     { | 
 |         return type_tag; | 
 |     } | 
 |     /// | 
 |     @safe unittest | 
 |     { | 
 |           string s = "{ \"language\": \"D\" }"; | 
 |           JSONValue j = parseJSON(s); | 
 |           assert(j.type == JSON_TYPE.OBJECT); | 
 |           assert(j["language"].type == JSON_TYPE.STRING); | 
 |     } | 
 |  | 
 |     /*** | 
 |      * Value getter/setter for $(D JSON_TYPE.STRING). | 
 |      * Throws: $(D JSONException) for read access if $(D type) is not | 
 |      * $(D JSON_TYPE.STRING). | 
 |      */ | 
 |     @property string str() const pure @trusted | 
 |     { | 
 |         enforce!JSONException(type == JSON_TYPE.STRING, | 
 |                                 "JSONValue is not a string"); | 
 |         return store.str; | 
 |     } | 
 |     /// ditto | 
 |     @property string str(string v) pure nothrow @nogc @safe | 
 |     { | 
 |         assign(v); | 
 |         return v; | 
 |     } | 
 |     /// | 
 |     @safe unittest | 
 |     { | 
 |         JSONValue j = [ "language": "D" ]; | 
 |  | 
 |         // get value | 
 |         assert(j["language"].str == "D"); | 
 |  | 
 |         // change existing key to new string | 
 |         j["language"].str = "Perl"; | 
 |         assert(j["language"].str == "Perl"); | 
 |     } | 
 |  | 
 |     /*** | 
 |      * Value getter/setter for $(D JSON_TYPE.INTEGER). | 
 |      * Throws: $(D JSONException) for read access if $(D type) is not | 
 |      * $(D JSON_TYPE.INTEGER). | 
 |      */ | 
 |     @property inout(long) integer() inout pure @safe | 
 |     { | 
 |         enforce!JSONException(type == JSON_TYPE.INTEGER, | 
 |                                 "JSONValue is not an integer"); | 
 |         return store.integer; | 
 |     } | 
 |     /// ditto | 
 |     @property long integer(long v) pure nothrow @safe @nogc | 
 |     { | 
 |         assign(v); | 
 |         return store.integer; | 
 |     } | 
 |  | 
 |     /*** | 
 |      * Value getter/setter for $(D JSON_TYPE.UINTEGER). | 
 |      * Throws: $(D JSONException) for read access if $(D type) is not | 
 |      * $(D JSON_TYPE.UINTEGER). | 
 |      */ | 
 |     @property inout(ulong) uinteger() inout pure @safe | 
 |     { | 
 |         enforce!JSONException(type == JSON_TYPE.UINTEGER, | 
 |                                 "JSONValue is not an unsigned integer"); | 
 |         return store.uinteger; | 
 |     } | 
 |     /// ditto | 
 |     @property ulong uinteger(ulong v) pure nothrow @safe @nogc | 
 |     { | 
 |         assign(v); | 
 |         return store.uinteger; | 
 |     } | 
 |  | 
 |     /*** | 
 |      * Value getter/setter for $(D JSON_TYPE.FLOAT). Note that despite | 
 |      * the name, this is a $(B 64)-bit `double`, not a 32-bit `float`. | 
 |      * Throws: $(D JSONException) for read access if $(D type) is not | 
 |      * $(D JSON_TYPE.FLOAT). | 
 |      */ | 
 |     @property inout(double) floating() inout pure @safe | 
 |     { | 
 |         enforce!JSONException(type == JSON_TYPE.FLOAT, | 
 |                                 "JSONValue is not a floating type"); | 
 |         return store.floating; | 
 |     } | 
 |     /// ditto | 
 |     @property double floating(double v) pure nothrow @safe @nogc | 
 |     { | 
 |         assign(v); | 
 |         return store.floating; | 
 |     } | 
 |  | 
 |     /*** | 
 |      * Value getter/setter for $(D JSON_TYPE.OBJECT). | 
 |      * Throws: $(D JSONException) for read access if $(D type) is not | 
 |      * $(D JSON_TYPE.OBJECT). | 
 |      * Note: this is @system because of the following pattern: | 
 |        --- | 
 |        auto a = &(json.object()); | 
 |        json.uinteger = 0;        // overwrite AA pointer | 
 |        (*a)["hello"] = "world";  // segmentation fault | 
 |        --- | 
 |      */ | 
 |     @property ref inout(JSONValue[string]) object() inout pure @system | 
 |     { | 
 |         enforce!JSONException(type == JSON_TYPE.OBJECT, | 
 |                                 "JSONValue is not an object"); | 
 |         return store.object; | 
 |     } | 
 |     /// ditto | 
 |     @property JSONValue[string] object(JSONValue[string] v) pure nothrow @nogc @safe | 
 |     { | 
 |         assign(v); | 
 |         return v; | 
 |     } | 
 |  | 
 |     /*** | 
 |      * Value getter for $(D JSON_TYPE.OBJECT). | 
 |      * Unlike $(D object), this retrieves the object by value and can be used in @safe code. | 
 |      * | 
 |      * A caveat is that, if the returned value is null, modifications will not be visible: | 
 |      * --- | 
 |      * JSONValue json; | 
 |      * json.object = null; | 
 |      * json.objectNoRef["hello"] = JSONValue("world"); | 
 |      * assert("hello" !in json.object); | 
 |      * --- | 
 |      * | 
 |      * Throws: $(D JSONException) for read access if $(D type) is not | 
 |      * $(D JSON_TYPE.OBJECT). | 
 |      */ | 
 |     @property inout(JSONValue[string]) objectNoRef() inout pure @trusted | 
 |     { | 
 |         enforce!JSONException(type == JSON_TYPE.OBJECT, | 
 |                                 "JSONValue is not an object"); | 
 |         return store.object; | 
 |     } | 
 |  | 
 |     /*** | 
 |      * Value getter/setter for $(D JSON_TYPE.ARRAY). | 
 |      * Throws: $(D JSONException) for read access if $(D type) is not | 
 |      * $(D JSON_TYPE.ARRAY). | 
 |      * Note: this is @system because of the following pattern: | 
 |        --- | 
 |        auto a = &(json.array()); | 
 |        json.uinteger = 0;  // overwrite array pointer | 
 |        (*a)[0] = "world";  // segmentation fault | 
 |        --- | 
 |      */ | 
 |     @property ref inout(JSONValue[]) array() inout pure @system | 
 |     { | 
 |         enforce!JSONException(type == JSON_TYPE.ARRAY, | 
 |                                 "JSONValue is not an array"); | 
 |         return store.array; | 
 |     } | 
 |     /// ditto | 
 |     @property JSONValue[] array(JSONValue[] v) pure nothrow @nogc @safe | 
 |     { | 
 |         assign(v); | 
 |         return v; | 
 |     } | 
 |  | 
 |     /*** | 
 |      * Value getter for $(D JSON_TYPE.ARRAY). | 
 |      * Unlike $(D array), this retrieves the array by value and can be used in @safe code. | 
 |      * | 
 |      * A caveat is that, if you append to the returned array, the new values aren't visible in the | 
 |      * JSONValue: | 
 |      * --- | 
 |      * JSONValue json; | 
 |      * json.array = [JSONValue("hello")]; | 
 |      * json.arrayNoRef ~= JSONValue("world"); | 
 |      * assert(json.array.length == 1); | 
 |      * --- | 
 |      * | 
 |      * Throws: $(D JSONException) for read access if $(D type) is not | 
 |      * $(D JSON_TYPE.ARRAY). | 
 |      */ | 
 |     @property inout(JSONValue[]) arrayNoRef() inout pure @trusted | 
 |     { | 
 |         enforce!JSONException(type == JSON_TYPE.ARRAY, | 
 |                                 "JSONValue is not an array"); | 
 |         return store.array; | 
 |     } | 
 |  | 
 |     /// Test whether the type is $(D JSON_TYPE.NULL) | 
 |     @property bool isNull() const pure nothrow @safe @nogc | 
 |     { | 
 |         return type == JSON_TYPE.NULL; | 
 |     } | 
 |  | 
 |     private void assign(T)(T arg) @safe | 
 |     { | 
 |         static if (is(T : typeof(null))) | 
 |         { | 
 |             type_tag = JSON_TYPE.NULL; | 
 |         } | 
 |         else static if (is(T : string)) | 
 |         { | 
 |             type_tag = JSON_TYPE.STRING; | 
 |             string t = arg; | 
 |             () @trusted { store.str = t; }(); | 
 |         } | 
 |         else static if (isSomeString!T) // issue 15884 | 
 |         { | 
 |             type_tag = JSON_TYPE.STRING; | 
 |             // FIXME: std.array.array(Range) is not deduced as 'pure' | 
 |             () @trusted { | 
 |                 import std.utf : byUTF; | 
 |                 store.str = cast(immutable)(arg.byUTF!char.array); | 
 |             }(); | 
 |         } | 
 |         else static if (is(T : bool)) | 
 |         { | 
 |             type_tag = arg ? JSON_TYPE.TRUE : JSON_TYPE.FALSE; | 
 |         } | 
 |         else static if (is(T : ulong) && isUnsigned!T) | 
 |         { | 
 |             type_tag = JSON_TYPE.UINTEGER; | 
 |             store.uinteger = arg; | 
 |         } | 
 |         else static if (is(T : long)) | 
 |         { | 
 |             type_tag = JSON_TYPE.INTEGER; | 
 |             store.integer = arg; | 
 |         } | 
 |         else static if (isFloatingPoint!T) | 
 |         { | 
 |             type_tag = JSON_TYPE.FLOAT; | 
 |             store.floating = arg; | 
 |         } | 
 |         else static if (is(T : Value[Key], Key, Value)) | 
 |         { | 
 |             static assert(is(Key : string), "AA key must be string"); | 
 |             type_tag = JSON_TYPE.OBJECT; | 
 |             static if (is(Value : JSONValue)) | 
 |             { | 
 |                 JSONValue[string] t = arg; | 
 |                 () @trusted { store.object = t; }(); | 
 |             } | 
 |             else | 
 |             { | 
 |                 JSONValue[string] aa; | 
 |                 foreach (key, value; arg) | 
 |                     aa[key] = JSONValue(value); | 
 |                 () @trusted { store.object = aa; }(); | 
 |             } | 
 |         } | 
 |         else static if (isArray!T) | 
 |         { | 
 |             type_tag = JSON_TYPE.ARRAY; | 
 |             static if (is(ElementEncodingType!T : JSONValue)) | 
 |             { | 
 |                 JSONValue[] t = arg; | 
 |                 () @trusted { store.array = t; }(); | 
 |             } | 
 |             else | 
 |             { | 
 |                 JSONValue[] new_arg = new JSONValue[arg.length]; | 
 |                 foreach (i, e; arg) | 
 |                     new_arg[i] = JSONValue(e); | 
 |                 () @trusted { store.array = new_arg; }(); | 
 |             } | 
 |         } | 
 |         else static if (is(T : JSONValue)) | 
 |         { | 
 |             type_tag = arg.type; | 
 |             store = arg.store; | 
 |         } | 
 |         else | 
 |         { | 
 |             static assert(false, text(`unable to convert type "`, T.stringof, `" to json`)); | 
 |         } | 
 |     } | 
 |  | 
 |     private void assignRef(T)(ref T arg) if (isStaticArray!T) | 
 |     { | 
 |         type_tag = JSON_TYPE.ARRAY; | 
 |         static if (is(ElementEncodingType!T : JSONValue)) | 
 |         { | 
 |             store.array = arg; | 
 |         } | 
 |         else | 
 |         { | 
 |             JSONValue[] new_arg = new JSONValue[arg.length]; | 
 |             foreach (i, e; arg) | 
 |                 new_arg[i] = JSONValue(e); | 
 |             store.array = new_arg; | 
 |         } | 
 |     } | 
 |  | 
 |     /** | 
 |      * Constructor for $(D JSONValue). If $(D arg) is a $(D JSONValue) | 
 |      * its value and type will be copied to the new $(D JSONValue). | 
 |      * Note that this is a shallow copy: if type is $(D JSON_TYPE.OBJECT) | 
 |      * or $(D JSON_TYPE.ARRAY) then only the reference to the data will | 
 |      * be copied. | 
 |      * Otherwise, $(D arg) must be implicitly convertible to one of the | 
 |      * following types: $(D typeof(null)), $(D string), $(D ulong), | 
 |      * $(D long), $(D double), an associative array $(D V[K]) for any $(D V) | 
 |      * and $(D K) i.e. a JSON object, any array or $(D bool). The type will | 
 |      * be set accordingly. | 
 |      */ | 
 |     this(T)(T arg) if (!isStaticArray!T) | 
 |     { | 
 |         assign(arg); | 
 |     } | 
 |     /// Ditto | 
 |     this(T)(ref T arg) if (isStaticArray!T) | 
 |     { | 
 |         assignRef(arg); | 
 |     } | 
 |     /// Ditto | 
 |     this(T : JSONValue)(inout T arg) inout | 
 |     { | 
 |         store = arg.store; | 
 |         type_tag = arg.type; | 
 |     } | 
 |     /// | 
 |     @safe unittest | 
 |     { | 
 |         JSONValue j = JSONValue( "a string" ); | 
 |         j = JSONValue(42); | 
 |  | 
 |         j = JSONValue( [1, 2, 3] ); | 
 |         assert(j.type == JSON_TYPE.ARRAY); | 
 |  | 
 |         j = JSONValue( ["language": "D"] ); | 
 |         assert(j.type == JSON_TYPE.OBJECT); | 
 |     } | 
 |  | 
 |     void opAssign(T)(T arg) if (!isStaticArray!T && !is(T : JSONValue)) | 
 |     { | 
 |         assign(arg); | 
 |     } | 
 |  | 
 |     void opAssign(T)(ref T arg) if (isStaticArray!T) | 
 |     { | 
 |         assignRef(arg); | 
 |     } | 
 |  | 
 |     /*** | 
 |      * Array syntax for json arrays. | 
 |      * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.ARRAY). | 
 |      */ | 
 |     ref inout(JSONValue) opIndex(size_t i) inout pure @safe | 
 |     { | 
 |         auto a = this.arrayNoRef; | 
 |         enforceEx!JSONException(i < a.length, | 
 |                                 "JSONValue array index is out of range"); | 
 |         return a[i]; | 
 |     } | 
 |     /// | 
 |     @safe unittest | 
 |     { | 
 |         JSONValue j = JSONValue( [42, 43, 44] ); | 
 |         assert( j[0].integer == 42 ); | 
 |         assert( j[1].integer == 43 ); | 
 |     } | 
 |  | 
 |     /*** | 
 |      * Hash syntax for json objects. | 
 |      * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.OBJECT). | 
 |      */ | 
 |     ref inout(JSONValue) opIndex(string k) inout pure @safe | 
 |     { | 
 |         auto o = this.objectNoRef; | 
 |         return *enforce!JSONException(k in o, | 
 |                                         "Key not found: " ~ k); | 
 |     } | 
 |     /// | 
 |     @safe unittest | 
 |     { | 
 |         JSONValue j = JSONValue( ["language": "D"] ); | 
 |         assert( j["language"].str == "D" ); | 
 |     } | 
 |  | 
 |     /*** | 
 |      * Operator sets $(D value) for element of JSON object by $(D key). | 
 |      * | 
 |      * If JSON value is null, then operator initializes it with object and then | 
 |      * sets $(D value) for it. | 
 |      * | 
 |      * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.OBJECT) | 
 |      * or $(D JSON_TYPE.NULL). | 
 |      */ | 
 |     void opIndexAssign(T)(auto ref T value, string key) pure | 
 |     { | 
 |         enforceEx!JSONException(type == JSON_TYPE.OBJECT || type == JSON_TYPE.NULL, | 
 |                                 "JSONValue must be object or null"); | 
 |         JSONValue[string] aa = null; | 
 |         if (type == JSON_TYPE.OBJECT) | 
 |         { | 
 |             aa = this.objectNoRef; | 
 |         } | 
 |  | 
 |         aa[key] = value; | 
 |         this.object = aa; | 
 |     } | 
 |     /// | 
 |     @safe unittest | 
 |     { | 
 |             JSONValue j = JSONValue( ["language": "D"] ); | 
 |             j["language"].str = "Perl"; | 
 |             assert( j["language"].str == "Perl" ); | 
 |     } | 
 |  | 
 |     void opIndexAssign(T)(T arg, size_t i) pure | 
 |     { | 
 |         auto a = this.arrayNoRef; | 
 |         enforceEx!JSONException(i < a.length, | 
 |                                 "JSONValue array index is out of range"); | 
 |         a[i] = arg; | 
 |         this.array = a; | 
 |     } | 
 |     /// | 
 |     @safe unittest | 
 |     { | 
 |             JSONValue j = JSONValue( ["Perl", "C"] ); | 
 |             j[1].str = "D"; | 
 |             assert( j[1].str == "D" ); | 
 |     } | 
 |  | 
 |     JSONValue opBinary(string op : "~", T)(T arg) @safe | 
 |     { | 
 |         auto a = this.arrayNoRef; | 
 |         static if (isArray!T) | 
 |         { | 
 |             return JSONValue(a ~ JSONValue(arg).arrayNoRef); | 
 |         } | 
 |         else static if (is(T : JSONValue)) | 
 |         { | 
 |             return JSONValue(a ~ arg.arrayNoRef); | 
 |         } | 
 |         else | 
 |         { | 
 |             static assert(false, "argument is not an array or a JSONValue array"); | 
 |         } | 
 |     } | 
 |  | 
 |     void opOpAssign(string op : "~", T)(T arg) @safe | 
 |     { | 
 |         auto a = this.arrayNoRef; | 
 |         static if (isArray!T) | 
 |         { | 
 |             a ~= JSONValue(arg).arrayNoRef; | 
 |         } | 
 |         else static if (is(T : JSONValue)) | 
 |         { | 
 |             a ~= arg.arrayNoRef; | 
 |         } | 
 |         else | 
 |         { | 
 |             static assert(false, "argument is not an array or a JSONValue array"); | 
 |         } | 
 |         this.array = a; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Support for the $(D in) operator. | 
 |      * | 
 |      * Tests wether a key can be found in an object. | 
 |      * | 
 |      * Returns: | 
 |      *      when found, the $(D const(JSONValue)*) that matches to the key, | 
 |      *      otherwise $(D null). | 
 |      * | 
 |      * Throws: $(D JSONException) if the right hand side argument $(D JSON_TYPE) | 
 |      * is not $(D OBJECT). | 
 |      */ | 
 |     auto opBinaryRight(string op : "in")(string k) const @safe | 
 |     { | 
 |         return k in this.objectNoRef; | 
 |     } | 
 |     /// | 
 |     @safe unittest | 
 |     { | 
 |         JSONValue j = [ "language": "D", "author": "walter" ]; | 
 |         string a = ("author" in j).str; | 
 |     } | 
 |  | 
 |     bool opEquals(const JSONValue rhs) const @nogc nothrow pure @safe | 
 |     { | 
 |         return opEquals(rhs); | 
 |     } | 
 |  | 
 |     bool opEquals(ref const JSONValue rhs) const @nogc nothrow pure @trusted | 
 |     { | 
 |         // Default doesn't work well since store is a union.  Compare only | 
 |         // what should be in store. | 
 |         // This is @trusted to remain nogc, nothrow, fast, and usable from @safe code. | 
 |         if (type_tag != rhs.type_tag) return false; | 
 |  | 
 |         final switch (type_tag) | 
 |         { | 
 |         case JSON_TYPE.STRING: | 
 |             return store.str == rhs.store.str; | 
 |         case JSON_TYPE.INTEGER: | 
 |             return store.integer == rhs.store.integer; | 
 |         case JSON_TYPE.UINTEGER: | 
 |             return store.uinteger == rhs.store.uinteger; | 
 |         case JSON_TYPE.FLOAT: | 
 |             return store.floating == rhs.store.floating; | 
 |         case JSON_TYPE.OBJECT: | 
 |             return store.object == rhs.store.object; | 
 |         case JSON_TYPE.ARRAY: | 
 |             return store.array == rhs.store.array; | 
 |         case JSON_TYPE.TRUE: | 
 |         case JSON_TYPE.FALSE: | 
 |         case JSON_TYPE.NULL: | 
 |             return true; | 
 |         } | 
 |     } | 
 |  | 
 |     /// Implements the foreach $(D opApply) interface for json arrays. | 
 |     int opApply(scope int delegate(size_t index, ref JSONValue) dg) @system | 
 |     { | 
 |         int result; | 
 |  | 
 |         foreach (size_t index, ref value; array) | 
 |         { | 
 |             result = dg(index, value); | 
 |             if (result) | 
 |                 break; | 
 |         } | 
 |  | 
 |         return result; | 
 |     } | 
 |  | 
 |     /// Implements the foreach $(D opApply) interface for json objects. | 
 |     int opApply(scope int delegate(string key, ref JSONValue) dg) @system | 
 |     { | 
 |         enforce!JSONException(type == JSON_TYPE.OBJECT, | 
 |                                 "JSONValue is not an object"); | 
 |         int result; | 
 |  | 
 |         foreach (string key, ref value; object) | 
 |         { | 
 |             result = dg(key, value); | 
 |             if (result) | 
 |                 break; | 
 |         } | 
 |  | 
 |         return result; | 
 |     } | 
 |  | 
 |     /*** | 
 |      * Implicitly calls $(D toJSON) on this JSONValue. | 
 |      * | 
 |      * $(I options) can be used to tweak the conversion behavior. | 
 |      */ | 
 |     string toString(in JSONOptions options = JSONOptions.none) const @safe | 
 |     { | 
 |         return toJSON(this, false, options); | 
 |     } | 
 |  | 
 |     /*** | 
 |      * Implicitly calls $(D toJSON) on this JSONValue, like $(D toString), but | 
 |      * also passes $(I true) as $(I pretty) argument. | 
 |      * | 
 |      * $(I options) can be used to tweak the conversion behavior | 
 |      */ | 
 |     string toPrettyString(in JSONOptions options = JSONOptions.none) const @safe | 
 |     { | 
 |         return toJSON(this, true, options); | 
 |     } | 
 | } | 
 |  | 
 | /** | 
 | Parses a serialized string and returns a tree of JSON values. | 
 | Throws: $(LREF JSONException) if the depth exceeds the max depth. | 
 | Params: | 
 |     json = json-formatted string to parse | 
 |     maxDepth = maximum depth of nesting allowed, -1 disables depth checking | 
 |     options = enable decoding string representations of NaN/Inf as float values | 
 | */ | 
 | JSONValue parseJSON(T)(T json, int maxDepth = -1, JSONOptions options = JSONOptions.none) | 
 | if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) | 
 | { | 
 |     import std.ascii : isWhite, isDigit, isHexDigit, toUpper, toLower; | 
 |     import std.typecons : Yes; | 
 |     JSONValue root; | 
 |     root.type_tag = JSON_TYPE.NULL; | 
 |  | 
 |     // Avoid UTF decoding when possible, as it is unnecessary when | 
 |     // processing JSON. | 
 |     static if (is(T : const(char)[])) | 
 |         alias Char = char; | 
 |     else | 
 |         alias Char = Unqual!(ElementType!T); | 
 |  | 
 |     if (json.empty) return root; | 
 |  | 
 |     int depth = -1; | 
 |     Char next = 0; | 
 |     int line = 1, pos = 0; | 
 |  | 
 |     void error(string msg) | 
 |     { | 
 |         throw new JSONException(msg, line, pos); | 
 |     } | 
 |  | 
 |     Char popChar() | 
 |     { | 
 |         if (json.empty) error("Unexpected end of data."); | 
 |         static if (is(T : const(char)[])) | 
 |         { | 
 |             Char c = json[0]; | 
 |             json = json[1..$]; | 
 |         } | 
 |         else | 
 |         { | 
 |             Char c = json.front; | 
 |             json.popFront(); | 
 |         } | 
 |  | 
 |         if (c == '\n') | 
 |         { | 
 |             line++; | 
 |             pos = 0; | 
 |         } | 
 |         else | 
 |         { | 
 |             pos++; | 
 |         } | 
 |  | 
 |         return c; | 
 |     } | 
 |  | 
 |     Char peekChar() | 
 |     { | 
 |         if (!next) | 
 |         { | 
 |             if (json.empty) return '\0'; | 
 |             next = popChar(); | 
 |         } | 
 |         return next; | 
 |     } | 
 |  | 
 |     void skipWhitespace() | 
 |     { | 
 |         while (isWhite(peekChar())) next = 0; | 
 |     } | 
 |  | 
 |     Char getChar(bool SkipWhitespace = false)() | 
 |     { | 
 |         static if (SkipWhitespace) skipWhitespace(); | 
 |  | 
 |         Char c; | 
 |         if (next) | 
 |         { | 
 |             c = next; | 
 |             next = 0; | 
 |         } | 
 |         else | 
 |             c = popChar(); | 
 |  | 
 |         return c; | 
 |     } | 
 |  | 
 |     void checkChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c) | 
 |     { | 
 |         static if (SkipWhitespace) skipWhitespace(); | 
 |         auto c2 = getChar(); | 
 |         static if (!CaseSensitive) c2 = toLower(c2); | 
 |  | 
 |         if (c2 != c) error(text("Found '", c2, "' when expecting '", c, "'.")); | 
 |     } | 
 |  | 
 |     bool testChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c) | 
 |     { | 
 |         static if (SkipWhitespace) skipWhitespace(); | 
 |         auto c2 = peekChar(); | 
 |         static if (!CaseSensitive) c2 = toLower(c2); | 
 |  | 
 |         if (c2 != c) return false; | 
 |  | 
 |         getChar(); | 
 |         return true; | 
 |     } | 
 |  | 
 |     wchar parseWChar() | 
 |     { | 
 |         wchar val = 0; | 
 |         foreach_reverse (i; 0 .. 4) | 
 |         { | 
 |             auto hex = toUpper(getChar()); | 
 |             if (!isHexDigit(hex)) error("Expecting hex character"); | 
 |             val += (isDigit(hex) ? hex - '0' : hex - ('A' - 10)) << (4 * i); | 
 |         } | 
 |         return val; | 
 |     } | 
 |  | 
 |     string parseString() | 
 |     { | 
 |         import std.ascii : isControl; | 
 |         import std.uni : isSurrogateHi, isSurrogateLo; | 
 |         import std.utf : encode, decode; | 
 |  | 
 |         auto str = appender!string(); | 
 |  | 
 |     Next: | 
 |         switch (peekChar()) | 
 |         { | 
 |             case '"': | 
 |                 getChar(); | 
 |                 break; | 
 |  | 
 |             case '\\': | 
 |                 getChar(); | 
 |                 auto c = getChar(); | 
 |                 switch (c) | 
 |                 { | 
 |                     case '"':       str.put('"');   break; | 
 |                     case '\\':      str.put('\\');  break; | 
 |                     case '/':       str.put('/');   break; | 
 |                     case 'b':       str.put('\b');  break; | 
 |                     case 'f':       str.put('\f');  break; | 
 |                     case 'n':       str.put('\n');  break; | 
 |                     case 'r':       str.put('\r');  break; | 
 |                     case 't':       str.put('\t');  break; | 
 |                     case 'u': | 
 |                         wchar wc = parseWChar(); | 
 |                         dchar val; | 
 |                         // Non-BMP characters are escaped as a pair of | 
 |                         // UTF-16 surrogate characters (see RFC 4627). | 
 |                         if (isSurrogateHi(wc)) | 
 |                         { | 
 |                             wchar[2] pair; | 
 |                             pair[0] = wc; | 
 |                             if (getChar() != '\\') error("Expected escaped low surrogate after escaped high surrogate"); | 
 |                             if (getChar() != 'u') error("Expected escaped low surrogate after escaped high surrogate"); | 
 |                             pair[1] = parseWChar(); | 
 |                             size_t index = 0; | 
 |                             val = decode(pair[], index); | 
 |                             if (index != 2) error("Invalid escaped surrogate pair"); | 
 |                         } | 
 |                         else | 
 |                         if (isSurrogateLo(wc)) | 
 |                             error(text("Unexpected low surrogate")); | 
 |                         else | 
 |                             val = wc; | 
 |  | 
 |                         char[4] buf; | 
 |                         immutable len = encode!(Yes.useReplacementDchar)(buf, val); | 
 |                         str.put(buf[0 .. len]); | 
 |                         break; | 
 |  | 
 |                     default: | 
 |                         error(text("Invalid escape sequence '\\", c, "'.")); | 
 |                 } | 
 |                 goto Next; | 
 |  | 
 |             default: | 
 |                 // RFC 7159 states that control characters U+0000 through | 
 |                 // U+001F must not appear unescaped in a JSON string. | 
 |                 auto c = getChar(); | 
 |                 if (isControl(c)) | 
 |                     error("Illegal control character."); | 
 |                 str.put(c); | 
 |                 goto Next; | 
 |         } | 
 |  | 
 |         return str.data.length ? str.data : ""; | 
 |     } | 
 |  | 
 |     bool tryGetSpecialFloat(string str, out double val) { | 
 |         switch (str) | 
 |         { | 
 |             case JSONFloatLiteral.nan: | 
 |                 val = double.nan; | 
 |                 return true; | 
 |             case JSONFloatLiteral.inf: | 
 |                 val = double.infinity; | 
 |                 return true; | 
 |             case JSONFloatLiteral.negativeInf: | 
 |                 val = -double.infinity; | 
 |                 return true; | 
 |             default: | 
 |                 return false; | 
 |         } | 
 |     } | 
 |  | 
 |     void parseValue(ref JSONValue value) | 
 |     { | 
 |         depth++; | 
 |  | 
 |         if (maxDepth != -1 && depth > maxDepth) error("Nesting too deep."); | 
 |  | 
 |         auto c = getChar!true(); | 
 |  | 
 |         switch (c) | 
 |         { | 
 |             case '{': | 
 |                 if (testChar('}')) | 
 |                 { | 
 |                     value.object = null; | 
 |                     break; | 
 |                 } | 
 |  | 
 |                 JSONValue[string] obj; | 
 |                 do | 
 |                 { | 
 |                     checkChar('"'); | 
 |                     string name = parseString(); | 
 |                     checkChar(':'); | 
 |                     JSONValue member; | 
 |                     parseValue(member); | 
 |                     obj[name] = member; | 
 |                 } | 
 |                 while (testChar(',')); | 
 |                 value.object = obj; | 
 |  | 
 |                 checkChar('}'); | 
 |                 break; | 
 |  | 
 |             case '[': | 
 |                 if (testChar(']')) | 
 |                 { | 
 |                     value.type_tag = JSON_TYPE.ARRAY; | 
 |                     break; | 
 |                 } | 
 |  | 
 |                 JSONValue[] arr; | 
 |                 do | 
 |                 { | 
 |                     JSONValue element; | 
 |                     parseValue(element); | 
 |                     arr ~= element; | 
 |                 } | 
 |                 while (testChar(',')); | 
 |  | 
 |                 checkChar(']'); | 
 |                 value.array = arr; | 
 |                 break; | 
 |  | 
 |             case '"': | 
 |                 auto str = parseString(); | 
 |  | 
 |                 // if special float parsing is enabled, check if string represents NaN/Inf | 
 |                 if ((options & JSONOptions.specialFloatLiterals) && | 
 |                     tryGetSpecialFloat(str, value.store.floating)) | 
 |                 { | 
 |                     // found a special float, its value was placed in value.store.floating | 
 |                     value.type_tag = JSON_TYPE.FLOAT; | 
 |                     break; | 
 |                 } | 
 |  | 
 |                 value.type_tag = JSON_TYPE.STRING; | 
 |                 value.store.str = str; | 
 |                 break; | 
 |  | 
 |             case '0': .. case '9': | 
 |             case '-': | 
 |                 auto number = appender!string(); | 
 |                 bool isFloat, isNegative; | 
 |  | 
 |                 void readInteger() | 
 |                 { | 
 |                     if (!isDigit(c)) error("Digit expected"); | 
 |  | 
 |                 Next: number.put(c); | 
 |  | 
 |                     if (isDigit(peekChar())) | 
 |                     { | 
 |                         c = getChar(); | 
 |                         goto Next; | 
 |                     } | 
 |                 } | 
 |  | 
 |                 if (c == '-') | 
 |                 { | 
 |                     number.put('-'); | 
 |                     c = getChar(); | 
 |                     isNegative = true; | 
 |                 } | 
 |  | 
 |                 readInteger(); | 
 |  | 
 |                 if (testChar('.')) | 
 |                 { | 
 |                     isFloat = true; | 
 |                     number.put('.'); | 
 |                     c = getChar(); | 
 |                     readInteger(); | 
 |                 } | 
 |                 if (testChar!(false, false)('e')) | 
 |                 { | 
 |                     isFloat = true; | 
 |                     number.put('e'); | 
 |                     if (testChar('+')) number.put('+'); | 
 |                     else if (testChar('-')) number.put('-'); | 
 |                     c = getChar(); | 
 |                     readInteger(); | 
 |                 } | 
 |  | 
 |                 string data = number.data; | 
 |                 if (isFloat) | 
 |                 { | 
 |                     value.type_tag = JSON_TYPE.FLOAT; | 
 |                     value.store.floating = parse!double(data); | 
 |                 } | 
 |                 else | 
 |                 { | 
 |                     if (isNegative) | 
 |                         value.store.integer = parse!long(data); | 
 |                     else | 
 |                         value.store.uinteger = parse!ulong(data); | 
 |  | 
 |                     value.type_tag = !isNegative && value.store.uinteger & (1UL << 63) ? | 
 |                         JSON_TYPE.UINTEGER : JSON_TYPE.INTEGER; | 
 |                 } | 
 |                 break; | 
 |  | 
 |             case 't': | 
 |             case 'T': | 
 |                 value.type_tag = JSON_TYPE.TRUE; | 
 |                 checkChar!(false, false)('r'); | 
 |                 checkChar!(false, false)('u'); | 
 |                 checkChar!(false, false)('e'); | 
 |                 break; | 
 |  | 
 |             case 'f': | 
 |             case 'F': | 
 |                 value.type_tag = JSON_TYPE.FALSE; | 
 |                 checkChar!(false, false)('a'); | 
 |                 checkChar!(false, false)('l'); | 
 |                 checkChar!(false, false)('s'); | 
 |                 checkChar!(false, false)('e'); | 
 |                 break; | 
 |  | 
 |             case 'n': | 
 |             case 'N': | 
 |                 value.type_tag = JSON_TYPE.NULL; | 
 |                 checkChar!(false, false)('u'); | 
 |                 checkChar!(false, false)('l'); | 
 |                 checkChar!(false, false)('l'); | 
 |                 break; | 
 |  | 
 |             default: | 
 |                 error(text("Unexpected character '", c, "'.")); | 
 |         } | 
 |  | 
 |         depth--; | 
 |     } | 
 |  | 
 |     parseValue(root); | 
 |     return root; | 
 | } | 
 |  | 
 | @safe unittest | 
 | { | 
 |     enum issue15742objectOfObject = `{ "key1": { "key2": 1 }}`; | 
 |     static assert(parseJSON(issue15742objectOfObject).type == JSON_TYPE.OBJECT); | 
 |  | 
 |     enum issue15742arrayOfArray = `[[1]]`; | 
 |     static assert(parseJSON(issue15742arrayOfArray).type == JSON_TYPE.ARRAY); | 
 | } | 
 |  | 
 | @safe unittest | 
 | { | 
 |     // Ensure we can parse and use JSON from @safe code | 
 |     auto a = `{ "key1": { "key2": 1 }}`.parseJSON; | 
 |     assert(a["key1"]["key2"].integer == 1); | 
 |     assert(a.toString == `{"key1":{"key2":1}}`); | 
 | } | 
 |  | 
 | @system unittest | 
 | { | 
 |     // Ensure we can parse JSON from a @system range. | 
 |     struct Range | 
 |     { | 
 |         string s; | 
 |         size_t index; | 
 |         @system | 
 |         { | 
 |             bool empty() { return index >= s.length; } | 
 |             void popFront() { index++; } | 
 |             char front() { return s[index]; } | 
 |         } | 
 |     } | 
 |     auto s = Range(`{ "key1": { "key2": 1 }}`); | 
 |     auto json = parseJSON(s); | 
 |     assert(json["key1"]["key2"].integer == 1); | 
 | } | 
 |  | 
 | /** | 
 | Parses a serialized string and returns a tree of JSON values. | 
 | Throws: $(REF JSONException, std,json) if the depth exceeds the max depth. | 
 | Params: | 
 |     json = json-formatted string to parse | 
 |     options = enable decoding string representations of NaN/Inf as float values | 
 | */ | 
 | JSONValue parseJSON(T)(T json, JSONOptions options) | 
 | if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) | 
 | { | 
 |     return parseJSON!T(json, -1, options); | 
 | } | 
 |  | 
 | /** | 
 | Takes a tree of JSON values and returns the serialized string. | 
 |  | 
 | Any Object types will be serialized in a key-sorted order. | 
 |  | 
 | If $(D pretty) is false no whitespaces are generated. | 
 | If $(D pretty) is true serialized string is formatted to be human-readable. | 
 | Set the $(LREF JSONOptions.specialFloatLiterals) flag is set in $(D options) to encode NaN/Infinity as strings. | 
 | */ | 
 | string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions options = JSONOptions.none) @safe | 
 | { | 
 |     auto json = appender!string(); | 
 |  | 
 |     void toStringImpl(Char)(string str) @safe | 
 |     { | 
 |         json.put('"'); | 
 |  | 
 |         foreach (Char c; str) | 
 |         { | 
 |             switch (c) | 
 |             { | 
 |                 case '"':       json.put("\\\"");       break; | 
 |                 case '\\':      json.put("\\\\");       break; | 
 |  | 
 |                 case '/': | 
 |                     if (!(options & JSONOptions.doNotEscapeSlashes)) | 
 |                         json.put('\\'); | 
 |                     json.put('/'); | 
 |                     break; | 
 |  | 
 |                 case '\b':      json.put("\\b");        break; | 
 |                 case '\f':      json.put("\\f");        break; | 
 |                 case '\n':      json.put("\\n");        break; | 
 |                 case '\r':      json.put("\\r");        break; | 
 |                 case '\t':      json.put("\\t");        break; | 
 |                 default: | 
 |                 { | 
 |                     import std.ascii : isControl; | 
 |                     import std.utf : encode; | 
 |  | 
 |                     // Make sure we do UTF decoding iff we want to | 
 |                     // escape Unicode characters. | 
 |                     assert(((options & JSONOptions.escapeNonAsciiChars) != 0) | 
 |                         == is(Char == dchar)); | 
 |  | 
 |                     with (JSONOptions) if (isControl(c) || | 
 |                         ((options & escapeNonAsciiChars) >= escapeNonAsciiChars && c >= 0x80)) | 
 |                     { | 
 |                         // Ensure non-BMP characters are encoded as a pair | 
 |                         // of UTF-16 surrogate characters, as per RFC 4627. | 
 |                         wchar[2] wchars; // 1 or 2 UTF-16 code units | 
 |                         size_t wNum = encode(wchars, c); // number of UTF-16 code units | 
 |                         foreach (wc; wchars[0 .. wNum]) | 
 |                         { | 
 |                             json.put("\\u"); | 
 |                             foreach_reverse (i; 0 .. 4) | 
 |                             { | 
 |                                 char ch = (wc >>> (4 * i)) & 0x0f; | 
 |                                 ch += ch < 10 ? '0' : 'A' - 10; | 
 |                                 json.put(ch); | 
 |                             } | 
 |                         } | 
 |                     } | 
 |                     else | 
 |                     { | 
 |                         json.put(c); | 
 |                     } | 
 |                 } | 
 |             } | 
 |         } | 
 |  | 
 |         json.put('"'); | 
 |     } | 
 |  | 
 |     void toString(string str) @safe | 
 |     { | 
 |         // Avoid UTF decoding when possible, as it is unnecessary when | 
 |         // processing JSON. | 
 |         if (options & JSONOptions.escapeNonAsciiChars) | 
 |             toStringImpl!dchar(str); | 
 |         else | 
 |             toStringImpl!char(str); | 
 |     } | 
 |  | 
 |     void toValue(ref in JSONValue value, ulong indentLevel) @safe | 
 |     { | 
 |         void putTabs(ulong additionalIndent = 0) | 
 |         { | 
 |             if (pretty) | 
 |                 foreach (i; 0 .. indentLevel + additionalIndent) | 
 |                     json.put("    "); | 
 |         } | 
 |         void putEOL() | 
 |         { | 
 |             if (pretty) | 
 |                 json.put('\n'); | 
 |         } | 
 |         void putCharAndEOL(char ch) | 
 |         { | 
 |             json.put(ch); | 
 |             putEOL(); | 
 |         } | 
 |  | 
 |         final switch (value.type) | 
 |         { | 
 |             case JSON_TYPE.OBJECT: | 
 |                 auto obj = value.objectNoRef; | 
 |                 if (!obj.length) | 
 |                 { | 
 |                     json.put("{}"); | 
 |                 } | 
 |                 else | 
 |                 { | 
 |                     putCharAndEOL('{'); | 
 |                     bool first = true; | 
 |  | 
 |                     void emit(R)(R names) | 
 |                     { | 
 |                         foreach (name; names) | 
 |                         { | 
 |                             auto member = obj[name]; | 
 |                             if (!first) | 
 |                                 putCharAndEOL(','); | 
 |                             first = false; | 
 |                             putTabs(1); | 
 |                             toString(name); | 
 |                             json.put(':'); | 
 |                             if (pretty) | 
 |                                 json.put(' '); | 
 |                             toValue(member, indentLevel + 1); | 
 |                         } | 
 |                     } | 
 |  | 
 |                     import std.algorithm.sorting : sort; | 
 |                     // @@@BUG@@@ 14439 | 
 |                     // auto names = obj.keys;  // aa.keys can't be called in @safe code | 
 |                     auto names = new string[obj.length]; | 
 |                     size_t i = 0; | 
 |                     foreach (k, v; obj) | 
 |                     { | 
 |                         names[i] = k; | 
 |                         i++; | 
 |                     } | 
 |                     sort(names); | 
 |                     emit(names); | 
 |  | 
 |                     putEOL(); | 
 |                     putTabs(); | 
 |                     json.put('}'); | 
 |                 } | 
 |                 break; | 
 |  | 
 |             case JSON_TYPE.ARRAY: | 
 |                 auto arr = value.arrayNoRef; | 
 |                 if (arr.empty) | 
 |                 { | 
 |                     json.put("[]"); | 
 |                 } | 
 |                 else | 
 |                 { | 
 |                     putCharAndEOL('['); | 
 |                     foreach (i, el; arr) | 
 |                     { | 
 |                         if (i) | 
 |                             putCharAndEOL(','); | 
 |                         putTabs(1); | 
 |                         toValue(el, indentLevel + 1); | 
 |                     } | 
 |                     putEOL(); | 
 |                     putTabs(); | 
 |                     json.put(']'); | 
 |                 } | 
 |                 break; | 
 |  | 
 |             case JSON_TYPE.STRING: | 
 |                 toString(value.str); | 
 |                 break; | 
 |  | 
 |             case JSON_TYPE.INTEGER: | 
 |                 json.put(to!string(value.store.integer)); | 
 |                 break; | 
 |  | 
 |             case JSON_TYPE.UINTEGER: | 
 |                 json.put(to!string(value.store.uinteger)); | 
 |                 break; | 
 |  | 
 |             case JSON_TYPE.FLOAT: | 
 |                 import std.math : isNaN, isInfinity; | 
 |  | 
 |                 auto val = value.store.floating; | 
 |  | 
 |                 if (val.isNaN) | 
 |                 { | 
 |                     if (options & JSONOptions.specialFloatLiterals) | 
 |                     { | 
 |                         toString(JSONFloatLiteral.nan); | 
 |                     } | 
 |                     else | 
 |                     { | 
 |                         throw new JSONException( | 
 |                             "Cannot encode NaN. Consider passing the specialFloatLiterals flag."); | 
 |                     } | 
 |                 } | 
 |                 else if (val.isInfinity) | 
 |                 { | 
 |                     if (options & JSONOptions.specialFloatLiterals) | 
 |                     { | 
 |                         toString((val > 0) ?  JSONFloatLiteral.inf : JSONFloatLiteral.negativeInf); | 
 |                     } | 
 |                     else | 
 |                     { | 
 |                         throw new JSONException( | 
 |                             "Cannot encode Infinity. Consider passing the specialFloatLiterals flag."); | 
 |                     } | 
 |                 } | 
 |                 else | 
 |                 { | 
 |                     import std.format : format; | 
 |                     // The correct formula for the number of decimal digits needed for lossless round | 
 |                     // trips is actually: | 
 |                     //     ceil(log(pow(2.0, double.mant_dig - 1)) / log(10.0) + 1) == (double.dig + 2) | 
 |                     // Anything less will round off (1 + double.epsilon) | 
 |                     json.put("%.18g".format(val)); | 
 |                 } | 
 |                 break; | 
 |  | 
 |             case JSON_TYPE.TRUE: | 
 |                 json.put("true"); | 
 |                 break; | 
 |  | 
 |             case JSON_TYPE.FALSE: | 
 |                 json.put("false"); | 
 |                 break; | 
 |  | 
 |             case JSON_TYPE.NULL: | 
 |                 json.put("null"); | 
 |                 break; | 
 |         } | 
 |     } | 
 |  | 
 |     toValue(root, 0); | 
 |     return json.data; | 
 | } | 
 |  | 
 | @safe unittest // bugzilla 12897 | 
 | { | 
 |     JSONValue jv0 = JSONValue("test测试"); | 
 |     assert(toJSON(jv0, false, JSONOptions.escapeNonAsciiChars) == `"test\u6D4B\u8BD5"`); | 
 |     JSONValue jv00 = JSONValue("test\u6D4B\u8BD5"); | 
 |     assert(toJSON(jv00, false, JSONOptions.none) == `"test测试"`); | 
 |     assert(toJSON(jv0, false, JSONOptions.none) == `"test测试"`); | 
 |     JSONValue jv1 = JSONValue("été"); | 
 |     assert(toJSON(jv1, false, JSONOptions.escapeNonAsciiChars) == `"\u00E9t\u00E9"`); | 
 |     JSONValue jv11 = JSONValue("\u00E9t\u00E9"); | 
 |     assert(toJSON(jv11, false, JSONOptions.none) == `"été"`); | 
 |     assert(toJSON(jv1, false, JSONOptions.none) == `"été"`); | 
 | } | 
 |  | 
 | /** | 
 | Exception thrown on JSON errors | 
 | */ | 
 | class JSONException : Exception | 
 | { | 
 |     this(string msg, int line = 0, int pos = 0) pure nothrow @safe | 
 |     { | 
 |         if (line) | 
 |             super(text(msg, " (Line ", line, ":", pos, ")")); | 
 |         else | 
 |             super(msg); | 
 |     } | 
 |  | 
 |     this(string msg, string file, size_t line) pure nothrow @safe | 
 |     { | 
 |         super(msg, file, line); | 
 |     } | 
 | } | 
 |  | 
 |  | 
 | @system unittest | 
 | { | 
 |     import std.exception; | 
 |     JSONValue jv = "123"; | 
 |     assert(jv.type == JSON_TYPE.STRING); | 
 |     assertNotThrown(jv.str); | 
 |     assertThrown!JSONException(jv.integer); | 
 |     assertThrown!JSONException(jv.uinteger); | 
 |     assertThrown!JSONException(jv.floating); | 
 |     assertThrown!JSONException(jv.object); | 
 |     assertThrown!JSONException(jv.array); | 
 |     assertThrown!JSONException(jv["aa"]); | 
 |     assertThrown!JSONException(jv[2]); | 
 |  | 
 |     jv = -3; | 
 |     assert(jv.type == JSON_TYPE.INTEGER); | 
 |     assertNotThrown(jv.integer); | 
 |  | 
 |     jv = cast(uint) 3; | 
 |     assert(jv.type == JSON_TYPE.UINTEGER); | 
 |     assertNotThrown(jv.uinteger); | 
 |  | 
 |     jv = 3.0; | 
 |     assert(jv.type == JSON_TYPE.FLOAT); | 
 |     assertNotThrown(jv.floating); | 
 |  | 
 |     jv = ["key" : "value"]; | 
 |     assert(jv.type == JSON_TYPE.OBJECT); | 
 |     assertNotThrown(jv.object); | 
 |     assertNotThrown(jv["key"]); | 
 |     assert("key" in jv); | 
 |     assert("notAnElement" !in jv); | 
 |     assertThrown!JSONException(jv["notAnElement"]); | 
 |     const cjv = jv; | 
 |     assert("key" in cjv); | 
 |     assertThrown!JSONException(cjv["notAnElement"]); | 
 |  | 
 |     foreach (string key, value; jv) | 
 |     { | 
 |         static assert(is(typeof(value) == JSONValue)); | 
 |         assert(key == "key"); | 
 |         assert(value.type == JSON_TYPE.STRING); | 
 |         assertNotThrown(value.str); | 
 |         assert(value.str == "value"); | 
 |     } | 
 |  | 
 |     jv = [3, 4, 5]; | 
 |     assert(jv.type == JSON_TYPE.ARRAY); | 
 |     assertNotThrown(jv.array); | 
 |     assertNotThrown(jv[2]); | 
 |     foreach (size_t index, value; jv) | 
 |     { | 
 |         static assert(is(typeof(value) == JSONValue)); | 
 |         assert(value.type == JSON_TYPE.INTEGER); | 
 |         assertNotThrown(value.integer); | 
 |         assert(index == (value.integer-3)); | 
 |     } | 
 |  | 
 |     jv = null; | 
 |     assert(jv.type == JSON_TYPE.NULL); | 
 |     assert(jv.isNull); | 
 |     jv = "foo"; | 
 |     assert(!jv.isNull); | 
 |  | 
 |     jv = JSONValue("value"); | 
 |     assert(jv.type == JSON_TYPE.STRING); | 
 |     assert(jv.str == "value"); | 
 |  | 
 |     JSONValue jv2 = JSONValue("value"); | 
 |     assert(jv2.type == JSON_TYPE.STRING); | 
 |     assert(jv2.str == "value"); | 
 |  | 
 |     JSONValue jv3 = JSONValue("\u001c"); | 
 |     assert(jv3.type == JSON_TYPE.STRING); | 
 |     assert(jv3.str == "\u001C"); | 
 | } | 
 |  | 
 | @system unittest | 
 | { | 
 |     // Bugzilla 11504 | 
 |  | 
 |     JSONValue jv = 1; | 
 |     assert(jv.type == JSON_TYPE.INTEGER); | 
 |  | 
 |     jv.str = "123"; | 
 |     assert(jv.type == JSON_TYPE.STRING); | 
 |     assert(jv.str == "123"); | 
 |  | 
 |     jv.integer = 1; | 
 |     assert(jv.type == JSON_TYPE.INTEGER); | 
 |     assert(jv.integer == 1); | 
 |  | 
 |     jv.uinteger = 2u; | 
 |     assert(jv.type == JSON_TYPE.UINTEGER); | 
 |     assert(jv.uinteger == 2u); | 
 |  | 
 |     jv.floating = 1.5; | 
 |     assert(jv.type == JSON_TYPE.FLOAT); | 
 |     assert(jv.floating == 1.5); | 
 |  | 
 |     jv.object = ["key" : JSONValue("value")]; | 
 |     assert(jv.type == JSON_TYPE.OBJECT); | 
 |     assert(jv.object == ["key" : JSONValue("value")]); | 
 |  | 
 |     jv.array = [JSONValue(1), JSONValue(2), JSONValue(3)]; | 
 |     assert(jv.type == JSON_TYPE.ARRAY); | 
 |     assert(jv.array == [JSONValue(1), JSONValue(2), JSONValue(3)]); | 
 |  | 
 |     jv = true; | 
 |     assert(jv.type == JSON_TYPE.TRUE); | 
 |  | 
 |     jv = false; | 
 |     assert(jv.type == JSON_TYPE.FALSE); | 
 |  | 
 |     enum E{True = true} | 
 |     jv = E.True; | 
 |     assert(jv.type == JSON_TYPE.TRUE); | 
 | } | 
 |  | 
 | @system pure unittest | 
 | { | 
 |     // Adding new json element via array() / object() directly | 
 |  | 
 |     JSONValue jarr = JSONValue([10]); | 
 |     foreach (i; 0 .. 9) | 
 |         jarr.array ~= JSONValue(i); | 
 |     assert(jarr.array.length == 10); | 
 |  | 
 |     JSONValue jobj = JSONValue(["key" : JSONValue("value")]); | 
 |     foreach (i; 0 .. 9) | 
 |         jobj.object[text("key", i)] = JSONValue(text("value", i)); | 
 |     assert(jobj.object.length == 10); | 
 | } | 
 |  | 
 | @system pure unittest | 
 | { | 
 |     // Adding new json element without array() / object() access | 
 |  | 
 |     JSONValue jarr = JSONValue([10]); | 
 |     foreach (i; 0 .. 9) | 
 |         jarr ~= [JSONValue(i)]; | 
 |     assert(jarr.array.length == 10); | 
 |  | 
 |     JSONValue jobj = JSONValue(["key" : JSONValue("value")]); | 
 |     foreach (i; 0 .. 9) | 
 |         jobj[text("key", i)] = JSONValue(text("value", i)); | 
 |     assert(jobj.object.length == 10); | 
 |  | 
 |     // No array alias | 
 |     auto jarr2 = jarr ~ [1,2,3]; | 
 |     jarr2[0] = 999; | 
 |     assert(jarr[0] == JSONValue(10)); | 
 | } | 
 |  | 
 | @system unittest | 
 | { | 
 |     // @system because JSONValue.array is @system | 
 |     import std.exception; | 
 |  | 
 |     // An overly simple test suite, if it can parse a serializated string and | 
 |     // then use the resulting values tree to generate an identical | 
 |     // serialization, both the decoder and encoder works. | 
 |  | 
 |     auto jsons = [ | 
 |         `null`, | 
 |         `true`, | 
 |         `false`, | 
 |         `0`, | 
 |         `123`, | 
 |         `-4321`, | 
 |         `0.25`, | 
 |         `-0.25`, | 
 |         `""`, | 
 |         `"hello\nworld"`, | 
 |         `"\"\\\/\b\f\n\r\t"`, | 
 |         `[]`, | 
 |         `[12,"foo",true,false]`, | 
 |         `{}`, | 
 |         `{"a":1,"b":null}`, | 
 |         `{"goodbye":[true,"or",false,["test",42,{"nested":{"a":23.5,"b":0.140625}}]],` | 
 |         ~`"hello":{"array":[12,null,{}],"json":"is great"}}`, | 
 |     ]; | 
 |  | 
 |     enum dbl1_844 = `1.8446744073709568`; | 
 |     version (MinGW) | 
 |         jsons ~= dbl1_844 ~ `e+019`; | 
 |     else | 
 |         jsons ~= dbl1_844 ~ `e+19`; | 
 |  | 
 |     JSONValue val; | 
 |     string result; | 
 |     foreach (json; jsons) | 
 |     { | 
 |         try | 
 |         { | 
 |             val = parseJSON(json); | 
 |             enum pretty = false; | 
 |             result = toJSON(val, pretty); | 
 |             assert(result == json, text(result, " should be ", json)); | 
 |         } | 
 |         catch (JSONException e) | 
 |         { | 
 |             import std.stdio : writefln; | 
 |             writefln(text(json, "\n", e.toString())); | 
 |         } | 
 |     } | 
 |  | 
 |     // Should be able to correctly interpret unicode entities | 
 |     val = parseJSON(`"\u003C\u003E"`); | 
 |     assert(toJSON(val) == "\"\<\>\""); | 
 |     assert(val.to!string() == "\"\<\>\""); | 
 |     val = parseJSON(`"\u0391\u0392\u0393"`); | 
 |     assert(toJSON(val) == "\"\Α\Β\Γ\""); | 
 |     assert(val.to!string() == "\"\Α\Β\Γ\""); | 
 |     val = parseJSON(`"\u2660\u2666"`); | 
 |     assert(toJSON(val) == "\"\♠\♦\""); | 
 |     assert(val.to!string() == "\"\♠\♦\""); | 
 |  | 
 |     //0x7F is a control character (see Unicode spec) | 
 |     val = parseJSON(`"\u007F"`); | 
 |     assert(toJSON(val) == "\"\\u007F\""); | 
 |     assert(val.to!string() == "\"\\u007F\""); | 
 |  | 
 |     with(parseJSON(`""`)) | 
 |         assert(str == "" && str !is null); | 
 |     with(parseJSON(`[]`)) | 
 |         assert(!array.length); | 
 |  | 
 |     // Formatting | 
 |     val = parseJSON(`{"a":[null,{"x":1},{},[]]}`); | 
 |     assert(toJSON(val, true) == `{ | 
 |     "a": [ | 
 |         null, | 
 |         { | 
 |             "x": 1 | 
 |         }, | 
 |         {}, | 
 |         [] | 
 |     ] | 
 | }`); | 
 | } | 
 |  | 
 | @safe unittest | 
 | { | 
 |   auto json = `"hello\nworld"`; | 
 |   const jv = parseJSON(json); | 
 |   assert(jv.toString == json); | 
 |   assert(jv.toPrettyString == json); | 
 | } | 
 |  | 
 | @system pure unittest | 
 | { | 
 |     // Bugzilla 12969 | 
 |  | 
 |     JSONValue jv; | 
 |     jv["int"] = 123; | 
 |  | 
 |     assert(jv.type == JSON_TYPE.OBJECT); | 
 |     assert("int" in jv); | 
 |     assert(jv["int"].integer == 123); | 
 |  | 
 |     jv["array"] = [1, 2, 3, 4, 5]; | 
 |  | 
 |     assert(jv["array"].type == JSON_TYPE.ARRAY); | 
 |     assert(jv["array"][2].integer == 3); | 
 |  | 
 |     jv["str"] = "D language"; | 
 |     assert(jv["str"].type == JSON_TYPE.STRING); | 
 |     assert(jv["str"].str == "D language"); | 
 |  | 
 |     jv["bool"] = false; | 
 |     assert(jv["bool"].type == JSON_TYPE.FALSE); | 
 |  | 
 |     assert(jv.object.length == 4); | 
 |  | 
 |     jv = [5, 4, 3, 2, 1]; | 
 |     assert( jv.type == JSON_TYPE.ARRAY ); | 
 |     assert( jv[3].integer == 2 ); | 
 | } | 
 |  | 
 | @safe unittest | 
 | { | 
 |     auto s = q"EOF | 
 | [ | 
 |   1, | 
 |   2, | 
 |   3, | 
 |   potato | 
 | ] | 
 | EOF"; | 
 |  | 
 |     import std.exception; | 
 |  | 
 |     auto e = collectException!JSONException(parseJSON(s)); | 
 |     assert(e.msg == "Unexpected character 'p'. (Line 5:3)", e.msg); | 
 | } | 
 |  | 
 | // handling of special float values (NaN, Inf, -Inf) | 
 | @safe unittest | 
 | { | 
 |     import std.exception : assertThrown; | 
 |     import std.math : isNaN, isInfinity; | 
 |  | 
 |     // expected representations of NaN and Inf | 
 |     enum { | 
 |         nanString         = '"' ~ JSONFloatLiteral.nan         ~ '"', | 
 |         infString         = '"' ~ JSONFloatLiteral.inf         ~ '"', | 
 |         negativeInfString = '"' ~ JSONFloatLiteral.negativeInf ~ '"', | 
 |     } | 
 |  | 
 |     // with the specialFloatLiterals option, encode NaN/Inf as strings | 
 |     assert(JSONValue(float.nan).toString(JSONOptions.specialFloatLiterals)       == nanString); | 
 |     assert(JSONValue(double.infinity).toString(JSONOptions.specialFloatLiterals) == infString); | 
 |     assert(JSONValue(-real.infinity).toString(JSONOptions.specialFloatLiterals)  == negativeInfString); | 
 |  | 
 |     // without the specialFloatLiterals option, throw on encoding NaN/Inf | 
 |     assertThrown!JSONException(JSONValue(float.nan).toString); | 
 |     assertThrown!JSONException(JSONValue(double.infinity).toString); | 
 |     assertThrown!JSONException(JSONValue(-real.infinity).toString); | 
 |  | 
 |     // when parsing json with specialFloatLiterals option, decode special strings as floats | 
 |     JSONValue jvNan    = parseJSON(nanString, JSONOptions.specialFloatLiterals); | 
 |     JSONValue jvInf    = parseJSON(infString, JSONOptions.specialFloatLiterals); | 
 |     JSONValue jvNegInf = parseJSON(negativeInfString, JSONOptions.specialFloatLiterals); | 
 |  | 
 |     assert(jvNan.floating.isNaN); | 
 |     assert(jvInf.floating.isInfinity    && jvInf.floating > 0); | 
 |     assert(jvNegInf.floating.isInfinity && jvNegInf.floating < 0); | 
 |  | 
 |     // when parsing json without the specialFloatLiterals option, decode special strings as strings | 
 |     jvNan    = parseJSON(nanString); | 
 |     jvInf    = parseJSON(infString); | 
 |     jvNegInf = parseJSON(negativeInfString); | 
 |  | 
 |     assert(jvNan.str    == JSONFloatLiteral.nan); | 
 |     assert(jvInf.str    == JSONFloatLiteral.inf); | 
 |     assert(jvNegInf.str == JSONFloatLiteral.negativeInf); | 
 | } | 
 |  | 
 | pure nothrow @safe @nogc unittest | 
 | { | 
 |     JSONValue testVal; | 
 |     testVal = "test"; | 
 |     testVal = 10; | 
 |     testVal = 10u; | 
 |     testVal = 1.0; | 
 |     testVal = (JSONValue[string]).init; | 
 |     testVal = JSONValue[].init; | 
 |     testVal = null; | 
 |     assert(testVal.isNull); | 
 | } | 
 |  | 
 | pure nothrow @safe unittest // issue 15884 | 
 | { | 
 |     import std.typecons; | 
 |     void Test(C)() { | 
 |         C[] a = ['x']; | 
 |         JSONValue testVal = a; | 
 |         assert(testVal.type == JSON_TYPE.STRING); | 
 |         testVal = a.idup; | 
 |         assert(testVal.type == JSON_TYPE.STRING); | 
 |     } | 
 |     Test!char(); | 
 |     Test!wchar(); | 
 |     Test!dchar(); | 
 | } | 
 |  | 
 | @safe unittest // issue 15885 | 
 | { | 
 |     enum bool realInDoublePrecision = real.mant_dig == double.mant_dig; | 
 |  | 
 |     static bool test(const double num0) | 
 |     { | 
 |         import std.math : feqrel; | 
 |         const json0 = JSONValue(num0); | 
 |         const num1 = to!double(toJSON(json0)); | 
 |         static if (realInDoublePrecision) | 
 |             return feqrel(num1, num0) >= (double.mant_dig - 1); | 
 |         else | 
 |             return num1 == num0; | 
 |     } | 
 |  | 
 |     assert(test( 0.23)); | 
 |     assert(test(-0.23)); | 
 |     assert(test(1.223e+24)); | 
 |     assert(test(23.4)); | 
 |     assert(test(0.0012)); | 
 |     assert(test(30738.22)); | 
 |  | 
 |     assert(test(1 + double.epsilon)); | 
 |     assert(test(double.min_normal)); | 
 |     static if (realInDoublePrecision) | 
 |         assert(test(-double.max / 2)); | 
 |     else | 
 |         assert(test(-double.max)); | 
 |  | 
 |     const minSub = double.min_normal * double.epsilon; | 
 |     assert(test(minSub)); | 
 |     assert(test(3*minSub)); | 
 | } | 
 |  | 
 | @safe unittest // issue 17555 | 
 | { | 
 |     import std.exception : assertThrown; | 
 |  | 
 |     assertThrown!JSONException(parseJSON("\"a\nb\"")); | 
 | } | 
 |  | 
 | @safe unittest // issue 17556 | 
 | { | 
 |     auto v = JSONValue("\U0001D11E"); | 
 |     auto j = toJSON(v, false, JSONOptions.escapeNonAsciiChars); | 
 |     assert(j == `"\uD834\uDD1E"`); | 
 | } | 
 |  | 
 | @safe unittest // issue 5904 | 
 | { | 
 |     string s = `"\uD834\uDD1E"`; | 
 |     auto j = parseJSON(s); | 
 |     assert(j.str == "\U0001D11E"); | 
 | } | 
 |  | 
 | @safe unittest // issue 17557 | 
 | { | 
 |     assert(parseJSON("\"\xFF\"").str == "\xFF"); | 
 |     assert(parseJSON("\"\U0001D11E\"").str == "\U0001D11E"); | 
 | } | 
 |  | 
 | @safe unittest // issue 17553 | 
 | { | 
 |     auto v = JSONValue("\xFF"); | 
 |     assert(toJSON(v) == "\"\xFF\""); | 
 | } | 
 |  | 
 | @safe unittest | 
 | { | 
 |     import std.utf; | 
 |     assert(parseJSON("\"\xFF\"".byChar).str == "\xFF"); | 
 |     assert(parseJSON("\"\U0001D11E\"".byChar).str == "\U0001D11E"); | 
 | } | 
 |  | 
 | @safe unittest // JSONOptions.doNotEscapeSlashes (issue 17587) | 
 | { | 
 |     assert(parseJSON(`"/"`).toString == `"\/"`); | 
 |     assert(parseJSON(`"\/"`).toString == `"\/"`); | 
 |     assert(parseJSON(`"/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`); | 
 |     assert(parseJSON(`"\/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`); | 
 | } |