xref: /netbsd-src/external/gpl3/gcc/dist/libphobos/src/std/json.d (revision b1e838363e3c6fc78a55519254d99869742dd33c)
1 // Written in the D programming language.
2 
3 /**
4 JavaScript Object Notation
5 
6 Copyright: Copyright Jeremie Pelletier 2008 - 2009.
7 License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
8 Authors:   Jeremie Pelletier, David Herberth
9 References: $(LINK http://json.org/), $(LINK http://seriot.ch/parsing_json.html)
10 Source:    $(PHOBOSSRC std/json.d)
11 */
12 /*
13          Copyright Jeremie Pelletier 2008 - 2009.
14 Distributed under the Boost Software License, Version 1.0.
15    (See accompanying file LICENSE_1_0.txt or copy at
16          http://www.boost.org/LICENSE_1_0.txt)
17 */
18 module std.json;
19 
20 import std.array;
21 import std.conv;
22 import std.range;
23 import std.traits;
24 
25 ///
26 @system unittest
27 {
28     import std.conv : to;
29 
30     // parse a file or string of json into a usable structure
31     string s = `{ "language": "D", "rating": 3.5, "code": "42" }`;
32     JSONValue j = parseJSON(s);
33     // j and j["language"] return JSONValue,
34     // j["language"].str returns a string
35     assert(j["language"].str == "D");
36     assert(j["rating"].floating == 3.5);
37 
38     // check a type
39     long x;
40     if (const(JSONValue)* code = "code" in j)
41     {
42         if (code.type() == JSONType.integer)
43             x = code.integer;
44         else
45             x = to!int(code.str);
46     }
47 
48     // create a json struct
49     JSONValue jj = [ "language": "D" ];
50     // rating doesnt exist yet, so use .object to assign
51     jj.object["rating"] = JSONValue(3.5);
52     // create an array to assign to list
53     jj.object["list"] = JSONValue( ["a", "b", "c"] );
54     // list already exists, so .object optional
55     jj["list"].array ~= JSONValue("D");
56 
57     string jjStr = `{"language":"D","list":["a","b","c","D"],"rating":3.5}`;
58     assert(jj.toString == jjStr);
59 }
60 
61 /**
62 String literals used to represent special float values within JSON strings.
63 */
64 enum JSONFloatLiteral : string
65 {
66     nan         = "NaN",       /// string representation of floating-point NaN
67     inf         = "Infinite",  /// string representation of floating-point Infinity
68     negativeInf = "-Infinite", /// string representation of floating-point negative Infinity
69 }
70 
71 /**
72 Flags that control how json is encoded and parsed.
73 */
74 enum JSONOptions
75 {
76     none,                       /// standard parsing
77     specialFloatLiterals = 0x1, /// encode NaN and Inf float values as strings
78     escapeNonAsciiChars = 0x2,  /// encode non ascii characters with an unicode escape sequence
79     doNotEscapeSlashes = 0x4,   /// do not escape slashes ('/')
80     strictParsing = 0x8,        /// Strictly follow RFC-8259 grammar when parsing
81 }
82 
83 /**
84 JSON type enumeration
85 */
86 enum JSONType : byte
87 {
88     /// Indicates the type of a `JSONValue`.
89     null_,
90     string,   /// ditto
91     integer,  /// ditto
92     uinteger, /// ditto
93     float_,   /// ditto
94     array,    /// ditto
95     object,   /// ditto
96     true_,    /// ditto
97     false_,   /// ditto
98     // FIXME: Find some way to deprecate the enum members below, which does NOT
99     // create lots of spam-like deprecation warnings, which can't be fixed
100     // by the user. See discussion on this issue at
101     // https://forum.dlang.org/post/feudrhtxkaxxscwhhhff@forum.dlang.org
102     /* deprecated("Use .null_")    */ NULL = null_,
103     /* deprecated("Use .string")   */ STRING = string,
104     /* deprecated("Use .integer")  */ INTEGER = integer,
105     /* deprecated("Use .uinteger") */ UINTEGER = uinteger,
106     /* deprecated("Use .float_")   */ FLOAT = float_,
107     /* deprecated("Use .array")    */ ARRAY = array,
108     /* deprecated("Use .object")   */ OBJECT = object,
109     /* deprecated("Use .true_")    */ TRUE = true_,
110     /* deprecated("Use .false_")   */ FALSE = false_,
111 }
112 
113 deprecated("Use JSONType and the new enum member names") alias JSON_TYPE = JSONType;
114 
115 /**
116 JSON value node
117 */
118 struct JSONValue
119 {
120     import std.exception : enforce;
121 
122     union Store
123     {
124         string                          str;
125         long                            integer;
126         ulong                           uinteger;
127         double                          floating;
128         JSONValue[string]               object;
129         JSONValue[]                     array;
130     }
131     private Store store;
132     private JSONType type_tag;
133 
134     /**
135       Returns the JSONType of the value stored in this structure.
136     */
typeJSONValue137     @property JSONType type() const pure nothrow @safe @nogc
138     {
139         return type_tag;
140     }
141     ///
142     @safe unittest
143     {
144           string s = "{ \"language\": \"D\" }";
145           JSONValue j = parseJSON(s);
146           assert(j.type == JSONType.object);
147           assert(j["language"].type == JSONType.string);
148     }
149 
150     /***
151      * Value getter/setter for `JSONType.string`.
152      * Throws: `JSONException` for read access if `type` is not
153      * `JSONType.string`.
154      */
155     @property string str() const pure @trusted return scope
156     {
157         enforce!JSONException(type == JSONType.string,
158                                 "JSONValue is not a string");
159         return store.str;
160     }
161     /// ditto
162     @property string str(return scope string v) pure nothrow @nogc @trusted return // TODO make @safe
163     {
164         assign(v);
165         return v;
166     }
167     ///
168     @safe unittest
169     {
170         JSONValue j = [ "language": "D" ];
171 
172         // get value
173         assert(j["language"].str == "D");
174 
175         // change existing key to new string
176         j["language"].str = "Perl";
177         assert(j["language"].str == "Perl");
178     }
179 
180     /***
181      * Value getter/setter for `JSONType.integer`.
182      * Throws: `JSONException` for read access if `type` is not
183      * `JSONType.integer`.
184      */
185     @property long integer() const pure @safe
186     {
187         enforce!JSONException(type == JSONType.integer,
188                                 "JSONValue is not an integer");
189         return store.integer;
190     }
191     /// ditto
192     @property long integer(long v) pure nothrow @safe @nogc
193     {
194         assign(v);
195         return store.integer;
196     }
197 
198     /***
199      * Value getter/setter for `JSONType.uinteger`.
200      * Throws: `JSONException` for read access if `type` is not
201      * `JSONType.uinteger`.
202      */
203     @property ulong uinteger() const pure @safe
204     {
205         enforce!JSONException(type == JSONType.uinteger,
206                                 "JSONValue is not an unsigned integer");
207         return store.uinteger;
208     }
209     /// ditto
210     @property ulong uinteger(ulong v) pure nothrow @safe @nogc
211     {
212         assign(v);
213         return store.uinteger;
214     }
215 
216     /***
217      * Value getter/setter for `JSONType.float_`. Note that despite
218      * the name, this is a $(B 64)-bit `double`, not a 32-bit `float`.
219      * Throws: `JSONException` for read access if `type` is not
220      * `JSONType.float_`.
221      */
222     @property double floating() const pure @safe
223     {
224         enforce!JSONException(type == JSONType.float_,
225                                 "JSONValue is not a floating type");
226         return store.floating;
227     }
228     /// ditto
229     @property double floating(double v) pure nothrow @safe @nogc
230     {
231         assign(v);
232         return store.floating;
233     }
234 
235     /***
236      * Value getter/setter for boolean stored in JSON.
237      * Throws: `JSONException` for read access if `this.type` is not
238      * `JSONType.true_` or `JSONType.false_`.
239      */
240     @property bool boolean() const pure @safe
241     {
242         if (type == JSONType.true_) return true;
243         if (type == JSONType.false_) return false;
244 
245         throw new JSONException("JSONValue is not a boolean type");
246     }
247     /// ditto
248     @property bool boolean(bool v) pure nothrow @safe @nogc
249     {
250         assign(v);
251         return v;
252     }
253     ///
254     @safe unittest
255     {
256         JSONValue j = true;
257         assert(j.boolean == true);
258 
259         j.boolean = false;
260         assert(j.boolean == false);
261 
262         j.integer = 12;
263         import std.exception : assertThrown;
264         assertThrown!JSONException(j.boolean);
265     }
266 
267     /***
268      * Value getter/setter for `JSONType.object`.
269      * Throws: `JSONException` for read access if `type` is not
270      * `JSONType.object`.
271      * Note: this is @system because of the following pattern:
272        ---
273        auto a = &(json.object());
274        json.uinteger = 0;        // overwrite AA pointer
275        (*a)["hello"] = "world";  // segmentation fault
276        ---
277      */
278     @property ref inout(JSONValue[string]) object() inout pure @system return
279     {
280         enforce!JSONException(type == JSONType.object,
281                                 "JSONValue is not an object");
282         return store.object;
283     }
284     /// ditto
285     @property JSONValue[string] object(return scope JSONValue[string] v) pure nothrow @nogc @trusted // TODO make @safe
286     {
287         assign(v);
288         return v;
289     }
290 
291     /***
292      * Value getter for `JSONType.object`.
293      * Unlike `object`, this retrieves the object by value and can be used in @safe code.
294      *
295      * A caveat is that, if the returned value is null, modifications will not be visible:
296      * ---
297      * JSONValue json;
298      * json.object = null;
299      * json.objectNoRef["hello"] = JSONValue("world");
300      * assert("hello" !in json.object);
301      * ---
302      *
303      * Throws: `JSONException` for read access if `type` is not
304      * `JSONType.object`.
305      */
306     @property inout(JSONValue[string]) objectNoRef() inout pure @trusted
307     {
308         enforce!JSONException(type == JSONType.object,
309                                 "JSONValue is not an object");
310         return store.object;
311     }
312 
313     /***
314      * Value getter/setter for `JSONType.array`.
315      * Throws: `JSONException` for read access if `type` is not
316      * `JSONType.array`.
317      * Note: this is @system because of the following pattern:
318        ---
319        auto a = &(json.array());
320        json.uinteger = 0;  // overwrite array pointer
321        (*a)[0] = "world";  // segmentation fault
322        ---
323      */
324     @property ref inout(JSONValue[]) array() scope return inout pure @system
325     {
326         enforce!JSONException(type == JSONType.array,
327                                 "JSONValue is not an array");
328         return store.array;
329     }
330     /// ditto
331     @property JSONValue[] array(return scope JSONValue[] v) pure nothrow @nogc @trusted scope // TODO make @safe
332     {
333         assign(v);
334         return v;
335     }
336 
337     /***
338      * Value getter for `JSONType.array`.
339      * Unlike `array`, this retrieves the array by value and can be used in @safe code.
340      *
341      * A caveat is that, if you append to the returned array, the new values aren't visible in the
342      * JSONValue:
343      * ---
344      * JSONValue json;
345      * json.array = [JSONValue("hello")];
346      * json.arrayNoRef ~= JSONValue("world");
347      * assert(json.array.length == 1);
348      * ---
349      *
350      * Throws: `JSONException` for read access if `type` is not
351      * `JSONType.array`.
352      */
353     @property inout(JSONValue[]) arrayNoRef() inout pure @trusted
354     {
355         enforce!JSONException(type == JSONType.array,
356                                 "JSONValue is not an array");
357         return store.array;
358     }
359 
360     /// Test whether the type is `JSONType.null_`
361     @property bool isNull() const pure nothrow @safe @nogc
362     {
363         return type == JSONType.null_;
364     }
365 
366     /***
367      * Generic type value getter
368      * A convenience getter that returns this `JSONValue` as the specified D type.
369      * Note: only numeric, `bool`, `string`, `JSONValue[string]` and `JSONValue[]` types are accepted
370      * Throws: `JSONException` if `T` cannot hold the contents of this `JSONValue`
371      *         `ConvException` in case of integer overflow when converting to `T`
372      */
373     @property inout(T) get(T)() inout const pure @safe
374     {
375         static if (is(immutable T == immutable string))
376         {
377             return str;
378         }
379         else static if (is(immutable T == immutable bool))
380         {
381             return boolean;
382         }
383         else static if (isFloatingPoint!T)
384         {
385             switch (type)
386             {
387             case JSONType.float_:
388                 return cast(T) floating;
389             case JSONType.uinteger:
390                 return cast(T) uinteger;
391             case JSONType.integer:
392                 return cast(T) integer;
393             default:
394                 throw new JSONException("JSONValue is not a number type");
395             }
396         }
397         else static if (isIntegral!T)
398         {
399             switch (type)
400             {
401             case JSONType.uinteger:
402                 return uinteger.to!T;
403             case JSONType.integer:
404                 return integer.to!T;
405             default:
406                 throw new JSONException("JSONValue is not a an integral type");
407             }
408         }
409         else
410         {
411             static assert(false, "Unsupported type");
412         }
413     }
414     // This specialization is needed because arrayNoRef requires inout
415     @property inout(T) get(T : JSONValue[])() inout pure @trusted /// ditto
416     {
417         return arrayNoRef;
418     }
419     /// ditto
420     @property inout(T) get(T : JSONValue[string])() inout pure @trusted
421     {
422         return object;
423     }
424     ///
425     @safe unittest
426     {
427         import std.exception;
428         import std.conv;
429         string s =
430         `{
431             "a": 123,
432             "b": 3.1415,
433             "c": "text",
434             "d": true,
435             "e": [1, 2, 3],
436             "f": { "a": 1 },
437             "g": -45,
438             "h": ` ~ ulong.max.to!string ~ `,
439          }`;
440 
441         struct a { }
442 
443         immutable json = parseJSON(s);
444         assert(json["a"].get!double == 123.0);
445         assert(json["a"].get!int == 123);
446         assert(json["a"].get!uint == 123);
447         assert(json["b"].get!double == 3.1415);
448         assertThrown!JSONException(json["b"].get!int);
449         assert(json["c"].get!string == "text");
450         assert(json["d"].get!bool == true);
451         assertNotThrown(json["e"].get!(JSONValue[]));
452         assertNotThrown(json["f"].get!(JSONValue[string]));
453         static assert(!__traits(compiles, json["a"].get!a));
454         assertThrown!JSONException(json["e"].get!float);
455         assertThrown!JSONException(json["d"].get!(JSONValue[string]));
456         assertThrown!JSONException(json["f"].get!(JSONValue[]));
457         assert(json["g"].get!int == -45);
458         assertThrown!ConvException(json["g"].get!uint);
459         assert(json["h"].get!ulong == ulong.max);
460         assertThrown!ConvException(json["h"].get!uint);
461         assertNotThrown(json["h"].get!float);
462     }
463 
464     private void assign(T)(T arg)
465     {
466         static if (is(T : typeof(null)))
467         {
468             type_tag = JSONType.null_;
469         }
470         else static if (is(T : string))
471         {
472             type_tag = JSONType.string;
473             string t = arg;
474             () @trusted { store.str = t; }();
475         }
476         // https://issues.dlang.org/show_bug.cgi?id=15884
477         else static if (isSomeString!T)
478         {
479             type_tag = JSONType.string;
480             // FIXME: std.Array.Array(Range) is not deduced as 'pure'
481             () @trusted {
482                 import std.utf : byUTF;
483                 store.str = cast(immutable)(arg.byUTF!char.array);
484             }();
485         }
486         else static if (is(T : bool))
487         {
488             type_tag = arg ? JSONType.true_ : JSONType.false_;
489         }
490         else static if (is(T : ulong) && isUnsigned!T)
491         {
492             type_tag = JSONType.uinteger;
493             store.uinteger = arg;
494         }
495         else static if (is(T : long))
496         {
497             type_tag = JSONType.integer;
498             store.integer = arg;
499         }
500         else static if (isFloatingPoint!T)
501         {
502             type_tag = JSONType.float_;
503             store.floating = arg;
504         }
505         else static if (is(T : Value[Key], Key, Value))
506         {
507             static assert(is(Key : string), "AA key must be string");
508             type_tag = JSONType.object;
509             static if (is(Value : JSONValue))
510             {
511                 JSONValue[string] t = arg;
512                 () @trusted { store.object = t; }();
513             }
514             else
515             {
516                 JSONValue[string] aa;
517                 foreach (key, value; arg)
518                     aa[key] = JSONValue(value);
519                 () @trusted { store.object = aa; }();
520             }
521         }
522         else static if (isArray!T)
523         {
524             type_tag = JSONType.array;
525             static if (is(ElementEncodingType!T : JSONValue))
526             {
527                 JSONValue[] t = arg;
528                 () @trusted { store.array = t; }();
529             }
530             else
531             {
532                 JSONValue[] new_arg = new JSONValue[arg.length];
533                 foreach (i, e; arg)
534                     new_arg[i] = JSONValue(e);
535                 () @trusted { store.array = new_arg; }();
536             }
537         }
538         else static if (is(T : JSONValue))
539         {
540             type_tag = arg.type;
541             store = arg.store;
542         }
543         else
544         {
545             static assert(false, text(`unable to convert type "`, T.stringof, `" to json`));
546         }
547     }
548 
549     private void assignRef(T)(ref T arg) if (isStaticArray!T)
550     {
551         type_tag = JSONType.array;
552         static if (is(ElementEncodingType!T : JSONValue))
553         {
554             store.array = arg;
555         }
556         else
557         {
558             JSONValue[] new_arg = new JSONValue[arg.length];
559             foreach (i, e; arg)
560                 new_arg[i] = JSONValue(e);
561             store.array = new_arg;
562         }
563     }
564 
565     /**
566      * Constructor for `JSONValue`. If `arg` is a `JSONValue`
567      * its value and type will be copied to the new `JSONValue`.
568      * Note that this is a shallow copy: if type is `JSONType.object`
569      * or `JSONType.array` then only the reference to the data will
570      * be copied.
571      * Otherwise, `arg` must be implicitly convertible to one of the
572      * following types: `typeof(null)`, `string`, `ulong`,
573      * `long`, `double`, an associative array `V[K]` for any `V`
574      * and `K` i.e. a JSON object, any array or `bool`. The type will
575      * be set accordingly.
576      */
577     this(T)(T arg) if (!isStaticArray!T)
578     {
579         assign(arg);
580     }
581     /// Ditto
582     this(T)(ref T arg) if (isStaticArray!T)
583     {
584         assignRef(arg);
585     }
586     /// Ditto
587     this(T : JSONValue)(inout T arg) inout
588     {
589         store = arg.store;
590         type_tag = arg.type;
591     }
592     ///
593     @safe unittest
594     {
595         JSONValue j = JSONValue( "a string" );
596         j = JSONValue(42);
597 
598         j = JSONValue( [1, 2, 3] );
599         assert(j.type == JSONType.array);
600 
601         j = JSONValue( ["language": "D"] );
602         assert(j.type == JSONType.object);
603     }
604 
605     void opAssign(T)(T arg) if (!isStaticArray!T && !is(T : JSONValue))
606     {
607         assign(arg);
608     }
609 
610     void opAssign(T)(ref T arg) if (isStaticArray!T)
611     {
612         assignRef(arg);
613     }
614 
615     /***
616      * Array syntax for json arrays.
617      * Throws: `JSONException` if `type` is not `JSONType.array`.
618      */
619     ref inout(JSONValue) opIndex(size_t i) inout pure @safe
620     {
621         auto a = this.arrayNoRef;
622         enforce!JSONException(i < a.length,
623                                 "JSONValue array index is out of range");
624         return a[i];
625     }
626     ///
627     @safe unittest
628     {
629         JSONValue j = JSONValue( [42, 43, 44] );
630         assert( j[0].integer == 42 );
631         assert( j[1].integer == 43 );
632     }
633 
634     /***
635      * Hash syntax for json objects.
636      * Throws: `JSONException` if `type` is not `JSONType.object`.
637      */
638     ref inout(JSONValue) opIndex(return scope string k) inout pure @safe
639     {
640         auto o = this.objectNoRef;
641         return *enforce!JSONException(k in o,
642                                         "Key not found: " ~ k);
643     }
644     ///
645     @safe unittest
646     {
647         JSONValue j = JSONValue( ["language": "D"] );
648         assert( j["language"].str == "D" );
649     }
650 
651     /***
652      * Operator sets `value` for element of JSON object by `key`.
653      *
654      * If JSON value is null, then operator initializes it with object and then
655      * sets `value` for it.
656      *
657      * Throws: `JSONException` if `type` is not `JSONType.object`
658      * or `JSONType.null_`.
659      */
660     void opIndexAssign(T)(auto ref T value, string key)
661     {
662         enforce!JSONException(type == JSONType.object || type == JSONType.null_,
663                                 "JSONValue must be object or null");
664         JSONValue[string] aa = null;
665         if (type == JSONType.object)
666         {
667             aa = this.objectNoRef;
668         }
669 
670         aa[key] = value;
671         this.object = aa;
672     }
673     ///
674     @safe unittest
675     {
676             JSONValue j = JSONValue( ["language": "D"] );
677             j["language"].str = "Perl";
678             assert( j["language"].str == "Perl" );
679     }
680 
681     void opIndexAssign(T)(T arg, size_t i)
682     {
683         auto a = this.arrayNoRef;
684         enforce!JSONException(i < a.length,
685                                 "JSONValue array index is out of range");
686         a[i] = arg;
687         this.array = a;
688     }
689     ///
690     @safe unittest
691     {
692             JSONValue j = JSONValue( ["Perl", "C"] );
693             j[1].str = "D";
694             assert( j[1].str == "D" );
695     }
696 
697     JSONValue opBinary(string op : "~", T)(T arg)
698     {
699         auto a = this.arrayNoRef;
700         static if (isArray!T)
701         {
702             return JSONValue(a ~ JSONValue(arg).arrayNoRef);
703         }
704         else static if (is(T : JSONValue))
705         {
706             return JSONValue(a ~ arg.arrayNoRef);
707         }
708         else
709         {
710             static assert(false, "argument is not an array or a JSONValue array");
711         }
712     }
713 
714     void opOpAssign(string op : "~", T)(T arg)
715     {
716         auto a = this.arrayNoRef;
717         static if (isArray!T)
718         {
719             a ~= JSONValue(arg).arrayNoRef;
720         }
721         else static if (is(T : JSONValue))
722         {
723             a ~= arg.arrayNoRef;
724         }
725         else
726         {
727             static assert(false, "argument is not an array or a JSONValue array");
728         }
729         this.array = a;
730     }
731 
732     /**
733      * Support for the `in` operator.
734      *
735      * Tests wether a key can be found in an object.
736      *
737      * Returns:
738      *      when found, the `inout(JSONValue)*` that matches to the key,
739      *      otherwise `null`.
740      *
741      * Throws: `JSONException` if the right hand side argument `JSONType`
742      * is not `object`.
743      */
744     inout(JSONValue)* opBinaryRight(string op : "in")(string k) inout @safe
745     {
746         return k in this.objectNoRef;
747     }
748     ///
749     @safe unittest
750     {
751         JSONValue j = [ "language": "D", "author": "walter" ];
752         string a = ("author" in j).str;
753         *("author" in j) = "Walter";
754         assert(j["author"].str == "Walter");
755     }
756 
757     bool opEquals(const JSONValue rhs) const @nogc nothrow pure @safe
758     {
759         return opEquals(rhs);
760     }
761 
762     bool opEquals(ref const JSONValue rhs) const @nogc nothrow pure @trusted
763     {
764         // Default doesn't work well since store is a union.  Compare only
765         // what should be in store.
766         // This is @trusted to remain nogc, nothrow, fast, and usable from @safe code.
767 
768         final switch (type_tag)
769         {
770         case JSONType.integer:
771             switch (rhs.type_tag)
772             {
773                 case JSONType.integer:
774                     return store.integer == rhs.store.integer;
775                 case JSONType.uinteger:
776                     return store.integer == rhs.store.uinteger;
777                 case JSONType.float_:
778                     return store.integer == rhs.store.floating;
779                 default:
780                     return false;
781             }
782         case JSONType.uinteger:
783             switch (rhs.type_tag)
784             {
785                 case JSONType.integer:
786                     return store.uinteger == rhs.store.integer;
787                 case JSONType.uinteger:
788                     return store.uinteger == rhs.store.uinteger;
789                 case JSONType.float_:
790                     return store.uinteger == rhs.store.floating;
791                 default:
792                     return false;
793             }
794         case JSONType.float_:
795             switch (rhs.type_tag)
796             {
797                 case JSONType.integer:
798                     return store.floating == rhs.store.integer;
799                 case JSONType.uinteger:
800                     return store.floating == rhs.store.uinteger;
801                 case JSONType.float_:
802                     return store.floating == rhs.store.floating;
803                 default:
804                     return false;
805             }
806         case JSONType.string:
807             return type_tag == rhs.type_tag && store.str == rhs.store.str;
808         case JSONType.object:
809             return type_tag == rhs.type_tag && store.object == rhs.store.object;
810         case JSONType.array:
811             return type_tag == rhs.type_tag && store.array == rhs.store.array;
812         case JSONType.true_:
813         case JSONType.false_:
814         case JSONType.null_:
815             return type_tag == rhs.type_tag;
816         }
817     }
818 
819     ///
820     @safe unittest
821     {
822         assert(JSONValue(0u) == JSONValue(0));
823         assert(JSONValue(0u) == JSONValue(0.0));
824         assert(JSONValue(0) == JSONValue(0.0));
825     }
826 
827     /// Implements the foreach `opApply` interface for json arrays.
828     int opApply(scope int delegate(size_t index, ref JSONValue) dg) @system
829     {
830         int result;
831 
832         foreach (size_t index, ref value; array)
833         {
834             result = dg(index, value);
835             if (result)
836                 break;
837         }
838 
839         return result;
840     }
841 
842     /// Implements the foreach `opApply` interface for json objects.
843     int opApply(scope int delegate(string key, ref JSONValue) dg) @system
844     {
845         enforce!JSONException(type == JSONType.object,
846                                 "JSONValue is not an object");
847         int result;
848 
849         foreach (string key, ref value; object)
850         {
851             result = dg(key, value);
852             if (result)
853                 break;
854         }
855 
856         return result;
857     }
858 
859     /***
860      * Implicitly calls `toJSON` on this JSONValue.
861      *
862      * $(I options) can be used to tweak the conversion behavior.
863      */
864     string toString(in JSONOptions options = JSONOptions.none) const @safe
865     {
866         return toJSON(this, false, options);
867     }
868 
869     ///
870     void toString(Out)(Out sink, in JSONOptions options = JSONOptions.none) const
871     {
872         toJSON(sink, this, false, options);
873     }
874 
875     /***
876      * Implicitly calls `toJSON` on this JSONValue, like `toString`, but
877      * also passes $(I true) as $(I pretty) argument.
878      *
879      * $(I options) can be used to tweak the conversion behavior
880      */
881     string toPrettyString(in JSONOptions options = JSONOptions.none) const @safe
882     {
883         return toJSON(this, true, options);
884     }
885 
886     ///
887     void toPrettyString(Out)(Out sink, in JSONOptions options = JSONOptions.none) const
888     {
889         toJSON(sink, this, true, options);
890     }
891 }
892 
893 // https://issues.dlang.org/show_bug.cgi?id=20874
894 @system unittest
895 {
896     static struct MyCustomType
897     {
898         public string toString () const @system { return null; }
899         alias toString this;
900     }
901 
902     static struct B
903     {
904         public JSONValue asJSON() const @system { return JSONValue.init; }
905         alias asJSON this;
906     }
907 
908     if (false) // Just checking attributes
909     {
910         JSONValue json;
911         MyCustomType ilovedlang;
912         json = ilovedlang;
913         json["foo"] = ilovedlang;
914         auto s = ilovedlang in json;
915 
916         B b;
917         json ~= b;
918         json ~ b;
919     }
920 }
921 
922 /**
923 Parses a serialized string and returns a tree of JSON values.
924 Throws: $(LREF JSONException) if string does not follow the JSON grammar or the depth exceeds the max depth,
925         $(LREF ConvException) if a number in the input cannot be represented by a native D type.
926 Params:
927     json = json-formatted string to parse
928     maxDepth = maximum depth of nesting allowed, -1 disables depth checking
929     options = enable decoding string representations of NaN/Inf as float values
930 */
931 JSONValue parseJSON(T)(T json, int maxDepth = -1, JSONOptions options = JSONOptions.none)
932 if (isSomeFiniteCharInputRange!T)
933 {
934     import std.ascii : isDigit, isHexDigit, toUpper, toLower;
935     import std.typecons : Nullable, Yes;
936     JSONValue root;
937     root.type_tag = JSONType.null_;
938 
939     // Avoid UTF decoding when possible, as it is unnecessary when
940     // processing JSON.
941     static if (is(T : const(char)[]))
942         alias Char = char;
943     else
944         alias Char = Unqual!(ElementType!T);
945 
946     int depth = -1;
947     Nullable!Char next;
948     int line = 1, pos = 0;
949     immutable bool strict = (options & JSONOptions.strictParsing) != 0;
950 
951     void error(string msg)
952     {
953         throw new JSONException(msg, line, pos);
954     }
955 
956     if (json.empty)
957     {
958         if (strict)
959         {
960             error("Empty JSON body");
961         }
962         return root;
963     }
964 
965     bool isWhite(dchar c)
966     {
967         if (strict)
968         {
969             // RFC 7159 has a stricter definition of whitespace than general ASCII.
970             return c == ' ' || c == '\t' || c == '\n' || c == '\r';
971         }
972         import std.ascii : isWhite;
973         // Accept ASCII NUL as whitespace in non-strict mode.
974         return c == 0 || isWhite(c);
975     }
976 
977     Char popChar()
978     {
979         if (json.empty) error("Unexpected end of data.");
980         static if (is(T : const(char)[]))
981         {
982             Char c = json[0];
983             json = json[1..$];
984         }
985         else
986         {
987             Char c = json.front;
988             json.popFront();
989         }
990 
991         if (c == '\n')
992         {
993             line++;
994             pos = 0;
995         }
996         else
997         {
998             pos++;
999         }
1000 
1001         return c;
1002     }
1003 
1004     Char peekChar()
1005     {
1006         if (next.isNull)
1007         {
1008             if (json.empty) return '\0';
1009             next = popChar();
1010         }
1011         return next.get;
1012     }
1013 
1014     Nullable!Char peekCharNullable()
1015     {
1016         if (next.isNull && !json.empty)
1017         {
1018             next = popChar();
1019         }
1020         return next;
1021     }
1022 
1023     void skipWhitespace()
1024     {
1025         while (true)
1026         {
1027             auto c = peekCharNullable();
1028             if (c.isNull ||
1029                 !isWhite(c.get))
1030             {
1031                 return;
1032             }
1033             next.nullify();
1034         }
1035     }
1036 
1037     Char getChar(bool SkipWhitespace = false)()
1038     {
1039         static if (SkipWhitespace) skipWhitespace();
1040 
1041         Char c;
1042         if (!next.isNull)
1043         {
1044             c = next.get;
1045             next.nullify();
1046         }
1047         else
1048             c = popChar();
1049 
1050         return c;
1051     }
1052 
1053     void checkChar(bool SkipWhitespace = true)(char c, bool caseSensitive = true)
1054     {
1055         static if (SkipWhitespace) skipWhitespace();
1056         auto c2 = getChar();
1057         if (!caseSensitive) c2 = toLower(c2);
1058 
1059         if (c2 != c) error(text("Found '", c2, "' when expecting '", c, "'."));
1060     }
1061 
1062     bool testChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c)
1063     {
1064         static if (SkipWhitespace) skipWhitespace();
1065         auto c2 = peekChar();
1066         static if (!CaseSensitive) c2 = toLower(c2);
1067 
1068         if (c2 != c) return false;
1069 
1070         getChar();
1071         return true;
1072     }
1073 
1074     wchar parseWChar()
1075     {
1076         wchar val = 0;
1077         foreach_reverse (i; 0 .. 4)
1078         {
1079             auto hex = toUpper(getChar());
1080             if (!isHexDigit(hex)) error("Expecting hex character");
1081             val += (isDigit(hex) ? hex - '0' : hex - ('A' - 10)) << (4 * i);
1082         }
1083         return val;
1084     }
1085 
1086     string parseString()
1087     {
1088         import std.uni : isSurrogateHi, isSurrogateLo;
1089         import std.utf : encode, decode;
1090 
1091         auto str = appender!string();
1092 
1093     Next:
1094         switch (peekChar())
1095         {
1096             case '"':
1097                 getChar();
1098                 break;
1099 
1100             case '\\':
1101                 getChar();
1102                 auto c = getChar();
1103                 switch (c)
1104                 {
1105                     case '"':       str.put('"');   break;
1106                     case '\\':      str.put('\\');  break;
1107                     case '/':       str.put('/');   break;
1108                     case 'b':       str.put('\b');  break;
1109                     case 'f':       str.put('\f');  break;
1110                     case 'n':       str.put('\n');  break;
1111                     case 'r':       str.put('\r');  break;
1112                     case 't':       str.put('\t');  break;
1113                     case 'u':
1114                         wchar wc = parseWChar();
1115                         dchar val;
1116                         // Non-BMP characters are escaped as a pair of
1117                         // UTF-16 surrogate characters (see RFC 4627).
1118                         if (isSurrogateHi(wc))
1119                         {
1120                             wchar[2] pair;
1121                             pair[0] = wc;
1122                             if (getChar() != '\\') error("Expected escaped low surrogate after escaped high surrogate");
1123                             if (getChar() != 'u') error("Expected escaped low surrogate after escaped high surrogate");
1124                             pair[1] = parseWChar();
1125                             size_t index = 0;
1126                             val = decode(pair[], index);
1127                             if (index != 2) error("Invalid escaped surrogate pair");
1128                         }
1129                         else
1130                         if (isSurrogateLo(wc))
1131                             error(text("Unexpected low surrogate"));
1132                         else
1133                             val = wc;
1134 
1135                         char[4] buf;
1136                         immutable len = encode!(Yes.useReplacementDchar)(buf, val);
1137                         str.put(buf[0 .. len]);
1138                         break;
1139 
1140                     default:
1141                         error(text("Invalid escape sequence '\\", c, "'."));
1142                 }
1143                 goto Next;
1144 
1145             default:
1146                 // RFC 7159 states that control characters U+0000 through
1147                 // U+001F must not appear unescaped in a JSON string.
1148                 // Note: std.ascii.isControl can't be used for this test
1149                 // because it considers ASCII DEL (0x7f) to be a control
1150                 // character but RFC 7159 does not.
1151                 // Accept unescaped ASCII NULs in non-strict mode.
1152                 auto c = getChar();
1153                 if (c < 0x20 && (strict || c != 0))
1154                     error("Illegal control character.");
1155                 str.put(c);
1156                 goto Next;
1157         }
1158 
1159         return str.data.length ? str.data : "";
1160     }
1161 
1162     bool tryGetSpecialFloat(string str, out double val) {
1163         switch (str)
1164         {
1165             case JSONFloatLiteral.nan:
1166                 val = double.nan;
1167                 return true;
1168             case JSONFloatLiteral.inf:
1169                 val = double.infinity;
1170                 return true;
1171             case JSONFloatLiteral.negativeInf:
1172                 val = -double.infinity;
1173                 return true;
1174             default:
1175                 return false;
1176         }
1177     }
1178 
1179     void parseValue(ref JSONValue value)
1180     {
1181         depth++;
1182 
1183         if (maxDepth != -1 && depth > maxDepth) error("Nesting too deep.");
1184 
1185         auto c = getChar!true();
1186 
1187         switch (c)
1188         {
1189             case '{':
1190                 if (testChar('}'))
1191                 {
1192                     value.object = null;
1193                     break;
1194                 }
1195 
1196                 JSONValue[string] obj;
1197                 do
1198                 {
1199                     skipWhitespace();
1200                     if (!strict && peekChar() == '}')
1201                     {
1202                         break;
1203                     }
1204                     checkChar('"');
1205                     string name = parseString();
1206                     checkChar(':');
1207                     JSONValue member;
1208                     parseValue(member);
1209                     obj[name] = member;
1210                 }
1211                 while (testChar(','));
1212                 value.object = obj;
1213 
1214                 checkChar('}');
1215                 break;
1216 
1217             case '[':
1218                 if (testChar(']'))
1219                 {
1220                     value.type_tag = JSONType.array;
1221                     break;
1222                 }
1223 
1224                 JSONValue[] arr;
1225                 do
1226                 {
1227                     skipWhitespace();
1228                     if (!strict && peekChar() == ']')
1229                     {
1230                         break;
1231                     }
1232                     JSONValue element;
1233                     parseValue(element);
1234                     arr ~= element;
1235                 }
1236                 while (testChar(','));
1237 
1238                 checkChar(']');
1239                 value.array = arr;
1240                 break;
1241 
1242             case '"':
1243                 auto str = parseString();
1244 
1245                 // if special float parsing is enabled, check if string represents NaN/Inf
1246                 if ((options & JSONOptions.specialFloatLiterals) &&
1247                     tryGetSpecialFloat(str, value.store.floating))
1248                 {
1249                     // found a special float, its value was placed in value.store.floating
1250                     value.type_tag = JSONType.float_;
1251                     break;
1252                 }
1253 
1254                 value.assign(str);
1255                 break;
1256 
1257             case '0': .. case '9':
1258             case '-':
1259                 auto number = appender!string();
1260                 bool isFloat, isNegative;
1261 
1262                 void readInteger()
1263                 {
1264                     if (!isDigit(c)) error("Digit expected");
1265 
1266                 Next: number.put(c);
1267 
1268                     if (isDigit(peekChar()))
1269                     {
1270                         c = getChar();
1271                         goto Next;
1272                     }
1273                 }
1274 
1275                 if (c == '-')
1276                 {
1277                     number.put('-');
1278                     c = getChar();
1279                     isNegative = true;
1280                 }
1281 
1282                 if (strict && c == '0')
1283                 {
1284                     number.put('0');
1285                     if (isDigit(peekChar()))
1286                     {
1287                         error("Additional digits not allowed after initial zero digit");
1288                     }
1289                 }
1290                 else
1291                 {
1292                     readInteger();
1293                 }
1294 
1295                 if (testChar('.'))
1296                 {
1297                     isFloat = true;
1298                     number.put('.');
1299                     c = getChar();
1300                     readInteger();
1301                 }
1302                 if (testChar!(false, false)('e'))
1303                 {
1304                     isFloat = true;
1305                     number.put('e');
1306                     if (testChar('+')) number.put('+');
1307                     else if (testChar('-')) number.put('-');
1308                     c = getChar();
1309                     readInteger();
1310                 }
1311 
1312                 string data = number.data;
1313                 if (isFloat)
1314                 {
1315                     value.type_tag = JSONType.float_;
1316                     value.store.floating = parse!double(data);
1317                 }
1318                 else
1319                 {
1320                     if (isNegative)
1321                     {
1322                         value.store.integer = parse!long(data);
1323                         value.type_tag = JSONType.integer;
1324                     }
1325                     else
1326                     {
1327                         // only set the correct union member to not confuse CTFE
1328                         ulong u = parse!ulong(data);
1329                         if (u & (1UL << 63))
1330                         {
1331                             value.store.uinteger = u;
1332                             value.type_tag = JSONType.uinteger;
1333                         }
1334                         else
1335                         {
1336                             value.store.integer = u;
1337                             value.type_tag = JSONType.integer;
1338                         }
1339                     }
1340                 }
1341                 break;
1342 
1343             case 'T':
1344                 if (strict) goto default;
1345                 goto case;
1346             case 't':
1347                 value.type_tag = JSONType.true_;
1348                 checkChar!false('r', strict);
1349                 checkChar!false('u', strict);
1350                 checkChar!false('e', strict);
1351                 break;
1352 
1353             case 'F':
1354                 if (strict) goto default;
1355                 goto case;
1356             case 'f':
1357                 value.type_tag = JSONType.false_;
1358                 checkChar!false('a', strict);
1359                 checkChar!false('l', strict);
1360                 checkChar!false('s', strict);
1361                 checkChar!false('e', strict);
1362                 break;
1363 
1364             case 'N':
1365                 if (strict) goto default;
1366                 goto case;
1367             case 'n':
1368                 value.type_tag = JSONType.null_;
1369                 checkChar!false('u', strict);
1370                 checkChar!false('l', strict);
1371                 checkChar!false('l', strict);
1372                 break;
1373 
1374             default:
1375                 error(text("Unexpected character '", c, "'."));
1376         }
1377 
1378         depth--;
1379     }
1380 
1381     parseValue(root);
1382     if (strict)
1383     {
1384         skipWhitespace();
1385         if (!peekCharNullable().isNull) error("Trailing non-whitespace characters");
1386     }
1387     return root;
1388 }
1389 
1390 @safe unittest
1391 {
1392     enum issue15742objectOfObject = `{ "key1": { "key2": 1 }}`;
1393     static assert(parseJSON(issue15742objectOfObject).type == JSONType.object);
1394 
1395     enum issue15742arrayOfArray = `[[1]]`;
1396     static assert(parseJSON(issue15742arrayOfArray).type == JSONType.array);
1397 }
1398 
1399 @safe unittest
1400 {
1401     // Ensure we can parse and use JSON from @safe code
1402     auto a = `{ "key1": { "key2": 1 }}`.parseJSON;
1403     assert(a["key1"]["key2"].integer == 1);
1404     assert(a.toString == `{"key1":{"key2":1}}`);
1405 }
1406 
1407 @system unittest
1408 {
1409     // Ensure we can parse JSON from a @system range.
1410     struct Range
1411     {
1412         string s;
1413         size_t index;
1414         @system
1415         {
1416             bool empty() { return index >= s.length; }
1417             void popFront() { index++; }
1418             char front() { return s[index]; }
1419         }
1420     }
1421     auto s = Range(`{ "key1": { "key2": 1 }}`);
1422     auto json = parseJSON(s);
1423     assert(json["key1"]["key2"].integer == 1);
1424 }
1425 
1426 // https://issues.dlang.org/show_bug.cgi?id=20527
1427 @safe unittest
1428 {
1429     static assert(parseJSON(`{"a" : 2}`)["a"].integer == 2);
1430 }
1431 
1432 /**
1433 Parses a serialized string and returns a tree of JSON values.
1434 Throws: $(LREF JSONException) if the depth exceeds the max depth.
1435 Params:
1436     json = json-formatted string to parse
1437     options = enable decoding string representations of NaN/Inf as float values
1438 */
1439 JSONValue parseJSON(T)(T json, JSONOptions options)
1440 if (isSomeFiniteCharInputRange!T)
1441 {
1442     return parseJSON!T(json, -1, options);
1443 }
1444 
1445 /**
1446 Takes a tree of JSON values and returns the serialized string.
1447 
1448 Any Object types will be serialized in a key-sorted order.
1449 
1450 If `pretty` is false no whitespaces are generated.
1451 If `pretty` is true serialized string is formatted to be human-readable.
1452 Set the $(LREF JSONOptions.specialFloatLiterals) flag is set in `options` to encode NaN/Infinity as strings.
1453 */
1454 string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions options = JSONOptions.none) @safe
1455 {
1456     auto json = appender!string();
1457     toJSON(json, root, pretty, options);
1458     return json.data;
1459 }
1460 
1461 ///
1462 void toJSON(Out)(
1463     auto ref Out json,
1464     const ref JSONValue root,
1465     in bool pretty = false,
1466     in JSONOptions options = JSONOptions.none)
1467 if (isOutputRange!(Out,char))
1468 {
1469     void toStringImpl(Char)(string str)
1470     {
1471         json.put('"');
1472 
1473         foreach (Char c; str)
1474         {
1475             switch (c)
1476             {
1477                 case '"':       json.put("\\\"");       break;
1478                 case '\\':      json.put("\\\\");       break;
1479 
1480                 case '/':
1481                     if (!(options & JSONOptions.doNotEscapeSlashes))
1482                         json.put('\\');
1483                     json.put('/');
1484                     break;
1485 
1486                 case '\b':      json.put("\\b");        break;
1487                 case '\f':      json.put("\\f");        break;
1488                 case '\n':      json.put("\\n");        break;
1489                 case '\r':      json.put("\\r");        break;
1490                 case '\t':      json.put("\\t");        break;
1491                 default:
1492                 {
1493                     import std.ascii : isControl;
1494                     import std.utf : encode;
1495 
1496                     // Make sure we do UTF decoding iff we want to
1497                     // escape Unicode characters.
1498                     assert(((options & JSONOptions.escapeNonAsciiChars) != 0)
1499                         == is(Char == dchar), "JSONOptions.escapeNonAsciiChars needs dchar strings");
1500 
1501                     with (JSONOptions) if (isControl(c) ||
1502                         ((options & escapeNonAsciiChars) >= escapeNonAsciiChars && c >= 0x80))
1503                     {
1504                         // Ensure non-BMP characters are encoded as a pair
1505                         // of UTF-16 surrogate characters, as per RFC 4627.
1506                         wchar[2] wchars; // 1 or 2 UTF-16 code units
1507                         size_t wNum = encode(wchars, c); // number of UTF-16 code units
1508                         foreach (wc; wchars[0 .. wNum])
1509                         {
1510                             json.put("\\u");
1511                             foreach_reverse (i; 0 .. 4)
1512                             {
1513                                 char ch = (wc >>> (4 * i)) & 0x0f;
1514                                 ch += ch < 10 ? '0' : 'A' - 10;
1515                                 json.put(ch);
1516                             }
1517                         }
1518                     }
1519                     else
1520                     {
1521                         json.put(c);
1522                     }
1523                 }
1524             }
1525         }
1526 
1527         json.put('"');
1528     }
1529 
1530     void toString(string str)
1531     {
1532         // Avoid UTF decoding when possible, as it is unnecessary when
1533         // processing JSON.
1534         if (options & JSONOptions.escapeNonAsciiChars)
1535             toStringImpl!dchar(str);
1536         else
1537             toStringImpl!char(str);
1538     }
1539 
1540     // recursive @safe inference is broken here
1541     // workaround: if json.put is @safe, we should be too,
1542     // so annotate the recursion as @safe manually
1543     static if (isSafe!({ json.put(""); }))
1544     {
1545         void delegate(ref const JSONValue, ulong) @safe toValue;
1546     }
1547     else
1548     {
1549         void delegate(ref const JSONValue, ulong) @system toValue;
1550     }
1551 
1552     void toValueImpl(ref const JSONValue value, ulong indentLevel)
1553     {
1554         void putTabs(ulong additionalIndent = 0)
1555         {
1556             if (pretty)
1557                 foreach (i; 0 .. indentLevel + additionalIndent)
1558                     json.put("    ");
1559         }
1560         void putEOL()
1561         {
1562             if (pretty)
1563                 json.put('\n');
1564         }
1565         void putCharAndEOL(char ch)
1566         {
1567             json.put(ch);
1568             putEOL();
1569         }
1570 
1571         final switch (value.type)
1572         {
1573             case JSONType.object:
1574                 auto obj = value.objectNoRef;
1575                 if (!obj.length)
1576                 {
1577                     json.put("{}");
1578                 }
1579                 else
1580                 {
1581                     putCharAndEOL('{');
1582                     bool first = true;
1583 
1584                     void emit(R)(R names)
1585                     {
1586                         foreach (name; names)
1587                         {
1588                             auto member = obj[name];
1589                             if (!first)
1590                                 putCharAndEOL(',');
1591                             first = false;
1592                             putTabs(1);
1593                             toString(name);
1594                             json.put(':');
1595                             if (pretty)
1596                                 json.put(' ');
1597                             toValue(member, indentLevel + 1);
1598                         }
1599                     }
1600 
1601                     import std.algorithm.sorting : sort;
1602                     // https://issues.dlang.org/show_bug.cgi?id=14439
1603                     // auto names = obj.keys;  // aa.keys can't be called in @safe code
1604                     auto names = new string[obj.length];
1605                     size_t i = 0;
1606                     foreach (k, v; obj)
1607                     {
1608                         names[i] = k;
1609                         i++;
1610                     }
1611                     sort(names);
1612                     emit(names);
1613 
1614                     putEOL();
1615                     putTabs();
1616                     json.put('}');
1617                 }
1618                 break;
1619 
1620             case JSONType.array:
1621                 auto arr = value.arrayNoRef;
1622                 if (arr.empty)
1623                 {
1624                     json.put("[]");
1625                 }
1626                 else
1627                 {
1628                     putCharAndEOL('[');
1629                     foreach (i, el; arr)
1630                     {
1631                         if (i)
1632                             putCharAndEOL(',');
1633                         putTabs(1);
1634                         toValue(el, indentLevel + 1);
1635                     }
1636                     putEOL();
1637                     putTabs();
1638                     json.put(']');
1639                 }
1640                 break;
1641 
1642             case JSONType.string:
1643                 toString(value.str);
1644                 break;
1645 
1646             case JSONType.integer:
1647                 json.put(to!string(value.store.integer));
1648                 break;
1649 
1650             case JSONType.uinteger:
1651                 json.put(to!string(value.store.uinteger));
1652                 break;
1653 
1654             case JSONType.float_:
1655                 import std.math.traits : isNaN, isInfinity;
1656 
1657                 auto val = value.store.floating;
1658 
1659                 if (val.isNaN)
1660                 {
1661                     if (options & JSONOptions.specialFloatLiterals)
1662                     {
1663                         toString(JSONFloatLiteral.nan);
1664                     }
1665                     else
1666                     {
1667                         throw new JSONException(
1668                             "Cannot encode NaN. Consider passing the specialFloatLiterals flag.");
1669                     }
1670                 }
1671                 else if (val.isInfinity)
1672                 {
1673                     if (options & JSONOptions.specialFloatLiterals)
1674                     {
1675                         toString((val > 0) ?  JSONFloatLiteral.inf : JSONFloatLiteral.negativeInf);
1676                     }
1677                     else
1678                     {
1679                         throw new JSONException(
1680                             "Cannot encode Infinity. Consider passing the specialFloatLiterals flag.");
1681                     }
1682                 }
1683                 else
1684                 {
1685                     import std.algorithm.searching : canFind;
1686                     import std.format : sformat;
1687                     // The correct formula for the number of decimal digits needed for lossless round
1688                     // trips is actually:
1689                     //     ceil(log(pow(2.0, double.mant_dig - 1)) / log(10.0) + 1) == (double.dig + 2)
1690                     // Anything less will round off (1 + double.epsilon)
1691                     char[25] buf;
1692                     auto result = buf[].sformat!"%.18g"(val);
1693                     json.put(result);
1694                     if (!result.canFind('e') && !result.canFind('.'))
1695                         json.put(".0");
1696                 }
1697                 break;
1698 
1699             case JSONType.true_:
1700                 json.put("true");
1701                 break;
1702 
1703             case JSONType.false_:
1704                 json.put("false");
1705                 break;
1706 
1707             case JSONType.null_:
1708                 json.put("null");
1709                 break;
1710         }
1711     }
1712 
1713     toValue = &toValueImpl;
1714 
1715     toValue(root, 0);
1716 }
1717 
1718  // https://issues.dlang.org/show_bug.cgi?id=12897
1719 @safe unittest
1720 {
1721     JSONValue jv0 = JSONValue("test测试");
1722     assert(toJSON(jv0, false, JSONOptions.escapeNonAsciiChars) == `"test\u6D4B\u8BD5"`);
1723     JSONValue jv00 = JSONValue("test\u6D4B\u8BD5");
1724     assert(toJSON(jv00, false, JSONOptions.none) == `"test测试"`);
1725     assert(toJSON(jv0, false, JSONOptions.none) == `"test测试"`);
1726     JSONValue jv1 = JSONValue("été");
1727     assert(toJSON(jv1, false, JSONOptions.escapeNonAsciiChars) == `"\u00E9t\u00E9"`);
1728     JSONValue jv11 = JSONValue("\u00E9t\u00E9");
1729     assert(toJSON(jv11, false, JSONOptions.none) == `"été"`);
1730     assert(toJSON(jv1, false, JSONOptions.none) == `"été"`);
1731 }
1732 
1733 // https://issues.dlang.org/show_bug.cgi?id=20511
1734 @system unittest
1735 {
1736     import std.format.write : formattedWrite;
1737     import std.range : nullSink, outputRangeObject;
1738 
1739     outputRangeObject!(const(char)[])(nullSink)
1740         .formattedWrite!"%s"(JSONValue.init);
1741 }
1742 
1743 // Issue 16432 - JSON incorrectly parses to string
1744 @safe unittest
1745 {
1746     // Floating points numbers are rounded to the nearest integer and thus get
1747     // incorrectly parsed
1748 
1749     import std.math.operations : isClose;
1750 
1751     string s = "{\"rating\": 3.0 }";
1752     JSONValue j = parseJSON(s);
1753     assert(j["rating"].type == JSONType.float_);
1754     j = j.toString.parseJSON;
1755     assert(j["rating"].type == JSONType.float_);
1756     assert(isClose(j["rating"].floating, 3.0));
1757 
1758     s = "{\"rating\": -3.0 }";
1759     j = parseJSON(s);
1760     assert(j["rating"].type == JSONType.float_);
1761     j = j.toString.parseJSON;
1762     assert(j["rating"].type == JSONType.float_);
1763     assert(isClose(j["rating"].floating, -3.0));
1764 
1765     // https://issues.dlang.org/show_bug.cgi?id=13660
1766     auto jv1 = JSONValue(4.0);
1767     auto textual = jv1.toString();
1768     auto jv2 = parseJSON(textual);
1769     assert(jv1.type == JSONType.float_);
1770     assert(textual == "4.0");
1771     assert(jv2.type == JSONType.float_);
1772 }
1773 
1774 @safe unittest
1775 {
1776     // Adapted from https://github.com/dlang/phobos/pull/5005
1777     // Result from toString is not checked here, because this
1778     // might differ (%e-like or %f-like output) depending
1779     // on OS and compiler optimization.
1780     import std.math.operations : isClose;
1781 
1782     // test positive extreme values
1783     JSONValue j;
1784     j["rating"] = 1e18 - 65;
1785     assert(isClose(j.toString.parseJSON["rating"].floating, 1e18 - 65));
1786 
1787     j["rating"] = 1e18 - 64;
1788     assert(isClose(j.toString.parseJSON["rating"].floating, 1e18 - 64));
1789 
1790     // negative extreme values
1791     j["rating"] = -1e18 + 65;
1792     assert(isClose(j.toString.parseJSON["rating"].floating, -1e18 + 65));
1793 
1794     j["rating"] = -1e18 + 64;
1795     assert(isClose(j.toString.parseJSON["rating"].floating, -1e18 + 64));
1796 }
1797 
1798 /**
1799 Exception thrown on JSON errors
1800 */
1801 class JSONException : Exception
1802 {
1803     this(string msg, int line = 0, int pos = 0) pure nothrow @safe
1804     {
1805         if (line)
1806             super(text(msg, " (Line ", line, ":", pos, ")"));
1807         else
1808             super(msg);
1809     }
1810 
1811     this(string msg, string file, size_t line) pure nothrow @safe
1812     {
1813         super(msg, file, line);
1814     }
1815 }
1816 
1817 
1818 @system unittest
1819 {
1820     import std.exception;
1821     JSONValue jv = "123";
1822     assert(jv.type == JSONType.string);
1823     assertNotThrown(jv.str);
1824     assertThrown!JSONException(jv.integer);
1825     assertThrown!JSONException(jv.uinteger);
1826     assertThrown!JSONException(jv.floating);
1827     assertThrown!JSONException(jv.object);
1828     assertThrown!JSONException(jv.array);
1829     assertThrown!JSONException(jv["aa"]);
1830     assertThrown!JSONException(jv[2]);
1831 
1832     jv = -3;
1833     assert(jv.type == JSONType.integer);
1834     assertNotThrown(jv.integer);
1835 
1836     jv = cast(uint) 3;
1837     assert(jv.type == JSONType.uinteger);
1838     assertNotThrown(jv.uinteger);
1839 
1840     jv = 3.0;
1841     assert(jv.type == JSONType.float_);
1842     assertNotThrown(jv.floating);
1843 
1844     jv = ["key" : "value"];
1845     assert(jv.type == JSONType.object);
1846     assertNotThrown(jv.object);
1847     assertNotThrown(jv["key"]);
1848     assert("key" in jv);
1849     assert("notAnElement" !in jv);
1850     assertThrown!JSONException(jv["notAnElement"]);
1851     const cjv = jv;
1852     assert("key" in cjv);
1853     assertThrown!JSONException(cjv["notAnElement"]);
1854 
1855     foreach (string key, value; jv)
1856     {
1857         static assert(is(typeof(value) == JSONValue));
1858         assert(key == "key");
1859         assert(value.type == JSONType.string);
1860         assertNotThrown(value.str);
1861         assert(value.str == "value");
1862     }
1863 
1864     jv = [3, 4, 5];
1865     assert(jv.type == JSONType.array);
1866     assertNotThrown(jv.array);
1867     assertNotThrown(jv[2]);
1868     foreach (size_t index, value; jv)
1869     {
1870         static assert(is(typeof(value) == JSONValue));
1871         assert(value.type == JSONType.integer);
1872         assertNotThrown(value.integer);
1873         assert(index == (value.integer-3));
1874     }
1875 
1876     jv = null;
1877     assert(jv.type == JSONType.null_);
1878     assert(jv.isNull);
1879     jv = "foo";
1880     assert(!jv.isNull);
1881 
1882     jv = JSONValue("value");
1883     assert(jv.type == JSONType.string);
1884     assert(jv.str == "value");
1885 
1886     JSONValue jv2 = JSONValue("value");
1887     assert(jv2.type == JSONType.string);
1888     assert(jv2.str == "value");
1889 
1890     JSONValue jv3 = JSONValue("\u001c");
1891     assert(jv3.type == JSONType.string);
1892     assert(jv3.str == "\u001C");
1893 }
1894 
1895 // https://issues.dlang.org/show_bug.cgi?id=11504
1896 @system unittest
1897 {
1898     JSONValue jv = 1;
1899     assert(jv.type == JSONType.integer);
1900 
1901     jv.str = "123";
1902     assert(jv.type == JSONType.string);
1903     assert(jv.str == "123");
1904 
1905     jv.integer = 1;
1906     assert(jv.type == JSONType.integer);
1907     assert(jv.integer == 1);
1908 
1909     jv.uinteger = 2u;
1910     assert(jv.type == JSONType.uinteger);
1911     assert(jv.uinteger == 2u);
1912 
1913     jv.floating = 1.5;
1914     assert(jv.type == JSONType.float_);
1915     assert(jv.floating == 1.5);
1916 
1917     jv.object = ["key" : JSONValue("value")];
1918     assert(jv.type == JSONType.object);
1919     assert(jv.object == ["key" : JSONValue("value")]);
1920 
1921     jv.array = [JSONValue(1), JSONValue(2), JSONValue(3)];
1922     assert(jv.type == JSONType.array);
1923     assert(jv.array == [JSONValue(1), JSONValue(2), JSONValue(3)]);
1924 
1925     jv = true;
1926     assert(jv.type == JSONType.true_);
1927 
1928     jv = false;
1929     assert(jv.type == JSONType.false_);
1930 
1931     enum E{True = true}
1932     jv = E.True;
1933     assert(jv.type == JSONType.true_);
1934 }
1935 
1936 @system pure unittest
1937 {
1938     // Adding new json element via array() / object() directly
1939 
1940     JSONValue jarr = JSONValue([10]);
1941     foreach (i; 0 .. 9)
1942         jarr.array ~= JSONValue(i);
1943     assert(jarr.array.length == 10);
1944 
1945     JSONValue jobj = JSONValue(["key" : JSONValue("value")]);
1946     foreach (i; 0 .. 9)
1947         jobj.object[text("key", i)] = JSONValue(text("value", i));
1948     assert(jobj.object.length == 10);
1949 }
1950 
1951 @system pure unittest
1952 {
1953     // Adding new json element without array() / object() access
1954 
1955     JSONValue jarr = JSONValue([10]);
1956     foreach (i; 0 .. 9)
1957         jarr ~= [JSONValue(i)];
1958     assert(jarr.array.length == 10);
1959 
1960     JSONValue jobj = JSONValue(["key" : JSONValue("value")]);
1961     foreach (i; 0 .. 9)
1962         jobj[text("key", i)] = JSONValue(text("value", i));
1963     assert(jobj.object.length == 10);
1964 
1965     // No array alias
1966     auto jarr2 = jarr ~ [1,2,3];
1967     jarr2[0] = 999;
1968     assert(jarr[0] == JSONValue(10));
1969 }
1970 
1971 @system unittest
1972 {
1973     // @system because JSONValue.array is @system
1974     import std.exception;
1975 
1976     // An overly simple test suite, if it can parse a serializated string and
1977     // then use the resulting values tree to generate an identical
1978     // serialization, both the decoder and encoder works.
1979 
1980     auto jsons = [
1981         `null`,
1982         `true`,
1983         `false`,
1984         `0`,
1985         `123`,
1986         `-4321`,
1987         `0.25`,
1988         `-0.25`,
1989         `""`,
1990         `"hello\nworld"`,
1991         `"\"\\\/\b\f\n\r\t"`,
1992         `[]`,
1993         `[12,"foo",true,false]`,
1994         `{}`,
1995         `{"a":1,"b":null}`,
1996         `{"goodbye":[true,"or",false,["test",42,{"nested":{"a":23.5,"b":0.140625}}]],`
1997         ~`"hello":{"array":[12,null,{}],"json":"is great"}}`,
1998     ];
1999 
2000     enum dbl1_844 = `1.8446744073709568`;
2001     version (MinGW)
2002         jsons ~= dbl1_844 ~ `e+019`;
2003     else
2004         jsons ~= dbl1_844 ~ `e+19`;
2005 
2006     JSONValue val;
2007     string result;
2008     foreach (json; jsons)
2009     {
2010         try
2011         {
2012             val = parseJSON(json);
2013             enum pretty = false;
2014             result = toJSON(val, pretty);
2015             assert(result == json, text(result, " should be ", json));
2016         }
2017         catch (JSONException e)
2018         {
2019             import std.stdio : writefln;
2020             writefln(text(json, "\n", e.toString()));
2021         }
2022     }
2023 
2024     // Should be able to correctly interpret unicode entities
2025     val = parseJSON(`"\u003C\u003E"`);
2026     assert(toJSON(val) == "\"\&lt;\&gt;\"");
2027     assert(val.to!string() == "\"\&lt;\&gt;\"");
2028     val = parseJSON(`"\u0391\u0392\u0393"`);
2029     assert(toJSON(val) == "\"\&Alpha;\&Beta;\&Gamma;\"");
2030     assert(val.to!string() == "\"\&Alpha;\&Beta;\&Gamma;\"");
2031     val = parseJSON(`"\u2660\u2666"`);
2032     assert(toJSON(val) == "\"\&spades;\&diams;\"");
2033     assert(val.to!string() == "\"\&spades;\&diams;\"");
2034 
2035     //0x7F is a control character (see Unicode spec)
2036     val = parseJSON(`"\u007F"`);
2037     assert(toJSON(val) == "\"\\u007F\"");
2038     assert(val.to!string() == "\"\\u007F\"");
2039 
2040     with(parseJSON(`""`))
2041         assert(str == "" && str !is null);
2042     with(parseJSON(`[]`))
2043         assert(!array.length);
2044 
2045     // Formatting
2046     val = parseJSON(`{"a":[null,{"x":1},{},[]]}`);
2047     assert(toJSON(val, true) == `{
2048     "a": [
2049         null,
2050         {
2051             "x": 1
2052         },
2053         {},
2054         []
2055     ]
2056 }`);
2057 }
2058 
2059 @safe unittest
2060 {
2061   auto json = `"hello\nworld"`;
2062   const jv = parseJSON(json);
2063   assert(jv.toString == json);
2064   assert(jv.toPrettyString == json);
2065 }
2066 
2067 @system pure unittest
2068 {
2069     // https://issues.dlang.org/show_bug.cgi?id=12969
2070 
2071     JSONValue jv;
2072     jv["int"] = 123;
2073 
2074     assert(jv.type == JSONType.object);
2075     assert("int" in jv);
2076     assert(jv["int"].integer == 123);
2077 
2078     jv["array"] = [1, 2, 3, 4, 5];
2079 
2080     assert(jv["array"].type == JSONType.array);
2081     assert(jv["array"][2].integer == 3);
2082 
2083     jv["str"] = "D language";
2084     assert(jv["str"].type == JSONType.string);
2085     assert(jv["str"].str == "D language");
2086 
2087     jv["bool"] = false;
2088     assert(jv["bool"].type == JSONType.false_);
2089 
2090     assert(jv.object.length == 4);
2091 
2092     jv = [5, 4, 3, 2, 1];
2093     assert(jv.type == JSONType.array);
2094     assert(jv[3].integer == 2);
2095 }
2096 
2097 @safe unittest
2098 {
2099     auto s = q"EOF
2100 [
2101   1,
2102   2,
2103   3,
2104   potato
2105 ]
2106 EOF";
2107 
2108     import std.exception;
2109 
2110     auto e = collectException!JSONException(parseJSON(s));
2111     assert(e.msg == "Unexpected character 'p'. (Line 5:3)", e.msg);
2112 }
2113 
2114 // handling of special float values (NaN, Inf, -Inf)
2115 @safe unittest
2116 {
2117     import std.exception : assertThrown;
2118     import std.math.traits : isNaN, isInfinity;
2119 
2120     // expected representations of NaN and Inf
2121     enum {
2122         nanString         = '"' ~ JSONFloatLiteral.nan         ~ '"',
2123         infString         = '"' ~ JSONFloatLiteral.inf         ~ '"',
2124         negativeInfString = '"' ~ JSONFloatLiteral.negativeInf ~ '"',
2125     }
2126 
2127     // with the specialFloatLiterals option, encode NaN/Inf as strings
2128     assert(JSONValue(float.nan).toString(JSONOptions.specialFloatLiterals)       == nanString);
2129     assert(JSONValue(double.infinity).toString(JSONOptions.specialFloatLiterals) == infString);
2130     assert(JSONValue(-real.infinity).toString(JSONOptions.specialFloatLiterals)  == negativeInfString);
2131 
2132     // without the specialFloatLiterals option, throw on encoding NaN/Inf
2133     assertThrown!JSONException(JSONValue(float.nan).toString);
2134     assertThrown!JSONException(JSONValue(double.infinity).toString);
2135     assertThrown!JSONException(JSONValue(-real.infinity).toString);
2136 
2137     // when parsing json with specialFloatLiterals option, decode special strings as floats
2138     JSONValue jvNan    = parseJSON(nanString, JSONOptions.specialFloatLiterals);
2139     JSONValue jvInf    = parseJSON(infString, JSONOptions.specialFloatLiterals);
2140     JSONValue jvNegInf = parseJSON(negativeInfString, JSONOptions.specialFloatLiterals);
2141 
2142     assert(jvNan.floating.isNaN);
2143     assert(jvInf.floating.isInfinity    && jvInf.floating > 0);
2144     assert(jvNegInf.floating.isInfinity && jvNegInf.floating < 0);
2145 
2146     // when parsing json without the specialFloatLiterals option, decode special strings as strings
2147     jvNan    = parseJSON(nanString);
2148     jvInf    = parseJSON(infString);
2149     jvNegInf = parseJSON(negativeInfString);
2150 
2151     assert(jvNan.str    == JSONFloatLiteral.nan);
2152     assert(jvInf.str    == JSONFloatLiteral.inf);
2153     assert(jvNegInf.str == JSONFloatLiteral.negativeInf);
2154 }
2155 
2156 pure nothrow @safe @nogc unittest
2157 {
2158     JSONValue testVal;
2159     testVal = "test";
2160     testVal = 10;
2161     testVal = 10u;
2162     testVal = 1.0;
2163     testVal = (JSONValue[string]).init;
2164     testVal = JSONValue[].init;
2165     testVal = null;
2166     assert(testVal.isNull);
2167 }
2168 
2169 // https://issues.dlang.org/show_bug.cgi?id=15884
2170 pure nothrow @safe unittest
2171 {
2172     import std.typecons;
2173     void Test(C)() {
2174         C[] a = ['x'];
2175         JSONValue testVal = a;
2176         assert(testVal.type == JSONType.string);
2177         testVal = a.idup;
2178         assert(testVal.type == JSONType.string);
2179     }
2180     Test!char();
2181     Test!wchar();
2182     Test!dchar();
2183 }
2184 
2185 // https://issues.dlang.org/show_bug.cgi?id=15885
2186 @safe unittest
2187 {
2188     enum bool realInDoublePrecision = real.mant_dig == double.mant_dig;
2189 
2190     static bool test(const double num0)
2191     {
2192         import std.math.operations : feqrel;
2193         const json0 = JSONValue(num0);
2194         const num1 = to!double(toJSON(json0));
2195         static if (realInDoublePrecision)
2196             return feqrel(num1, num0) >= (double.mant_dig - 1);
2197         else
2198             return num1 == num0;
2199     }
2200 
2201     assert(test( 0.23));
2202     assert(test(-0.23));
2203     assert(test(1.223e+24));
2204     assert(test(23.4));
2205     assert(test(0.0012));
2206     assert(test(30738.22));
2207 
2208     assert(test(1 + double.epsilon));
2209     assert(test(double.min_normal));
2210     static if (realInDoublePrecision)
2211         assert(test(-double.max / 2));
2212     else
2213         assert(test(-double.max));
2214 
2215     const minSub = double.min_normal * double.epsilon;
2216     assert(test(minSub));
2217     assert(test(3*minSub));
2218 }
2219 
2220 // https://issues.dlang.org/show_bug.cgi?id=17555
2221 @safe unittest
2222 {
2223     import std.exception : assertThrown;
2224 
2225     assertThrown!JSONException(parseJSON("\"a\nb\""));
2226 }
2227 
2228 // https://issues.dlang.org/show_bug.cgi?id=17556
2229 @safe unittest
2230 {
2231     auto v = JSONValue("\U0001D11E");
2232     auto j = toJSON(v, false, JSONOptions.escapeNonAsciiChars);
2233     assert(j == `"\uD834\uDD1E"`);
2234 }
2235 
2236 // https://issues.dlang.org/show_bug.cgi?id=5904
2237 @safe unittest
2238 {
2239     string s = `"\uD834\uDD1E"`;
2240     auto j = parseJSON(s);
2241     assert(j.str == "\U0001D11E");
2242 }
2243 
2244 // https://issues.dlang.org/show_bug.cgi?id=17557
2245 @safe unittest
2246 {
2247     assert(parseJSON("\"\xFF\"").str == "\xFF");
2248     assert(parseJSON("\"\U0001D11E\"").str == "\U0001D11E");
2249 }
2250 
2251 // https://issues.dlang.org/show_bug.cgi?id=17553
2252 @safe unittest
2253 {
2254     auto v = JSONValue("\xFF");
2255     assert(toJSON(v) == "\"\xFF\"");
2256 }
2257 
2258 @safe unittest
2259 {
2260     import std.utf;
2261     assert(parseJSON("\"\xFF\"".byChar).str == "\xFF");
2262     assert(parseJSON("\"\U0001D11E\"".byChar).str == "\U0001D11E");
2263 }
2264 
2265 // JSONOptions.doNotEscapeSlashes (https://issues.dlang.org/show_bug.cgi?id=17587)
2266 @safe unittest
2267 {
2268     assert(parseJSON(`"/"`).toString == `"\/"`);
2269     assert(parseJSON(`"\/"`).toString == `"\/"`);
2270     assert(parseJSON(`"/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`);
2271     assert(parseJSON(`"\/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`);
2272 }
2273 
2274 // JSONOptions.strictParsing (https://issues.dlang.org/show_bug.cgi?id=16639)
2275 @safe unittest
2276 {
2277     import std.exception : assertThrown;
2278 
2279     // Unescaped ASCII NULs
2280     assert(parseJSON("[\0]").type == JSONType.array);
2281     assertThrown!JSONException(parseJSON("[\0]", JSONOptions.strictParsing));
2282     assert(parseJSON("\"\0\"").str == "\0");
2283     assertThrown!JSONException(parseJSON("\"\0\"", JSONOptions.strictParsing));
2284 
2285     // Unescaped ASCII DEL (0x7f) in strings
2286     assert(parseJSON("\"\x7f\"").str == "\x7f");
2287     assert(parseJSON("\"\x7f\"", JSONOptions.strictParsing).str == "\x7f");
2288 
2289     // "true", "false", "null" case sensitivity
2290     assert(parseJSON("true").type == JSONType.true_);
2291     assert(parseJSON("true", JSONOptions.strictParsing).type == JSONType.true_);
2292     assert(parseJSON("True").type == JSONType.true_);
2293     assertThrown!JSONException(parseJSON("True", JSONOptions.strictParsing));
2294     assert(parseJSON("tRUE").type == JSONType.true_);
2295     assertThrown!JSONException(parseJSON("tRUE", JSONOptions.strictParsing));
2296 
2297     assert(parseJSON("false").type == JSONType.false_);
2298     assert(parseJSON("false", JSONOptions.strictParsing).type == JSONType.false_);
2299     assert(parseJSON("False").type == JSONType.false_);
2300     assertThrown!JSONException(parseJSON("False", JSONOptions.strictParsing));
2301     assert(parseJSON("fALSE").type == JSONType.false_);
2302     assertThrown!JSONException(parseJSON("fALSE", JSONOptions.strictParsing));
2303 
2304     assert(parseJSON("null").type == JSONType.null_);
2305     assert(parseJSON("null", JSONOptions.strictParsing).type == JSONType.null_);
2306     assert(parseJSON("Null").type == JSONType.null_);
2307     assertThrown!JSONException(parseJSON("Null", JSONOptions.strictParsing));
2308     assert(parseJSON("nULL").type == JSONType.null_);
2309     assertThrown!JSONException(parseJSON("nULL", JSONOptions.strictParsing));
2310 
2311     // Whitespace characters
2312     assert(parseJSON("[\f\v]").type == JSONType.array);
2313     assertThrown!JSONException(parseJSON("[\f\v]", JSONOptions.strictParsing));
2314     assert(parseJSON("[ \t\r\n]").type == JSONType.array);
2315     assert(parseJSON("[ \t\r\n]", JSONOptions.strictParsing).type == JSONType.array);
2316 
2317     // Empty input
2318     assert(parseJSON("").type == JSONType.null_);
2319     assertThrown!JSONException(parseJSON("", JSONOptions.strictParsing));
2320 
2321     // Numbers with leading '0's
2322     assert(parseJSON("01").integer == 1);
2323     assertThrown!JSONException(parseJSON("01", JSONOptions.strictParsing));
2324     assert(parseJSON("-01").integer == -1);
2325     assertThrown!JSONException(parseJSON("-01", JSONOptions.strictParsing));
2326     assert(parseJSON("0.01").floating == 0.01);
2327     assert(parseJSON("0.01", JSONOptions.strictParsing).floating == 0.01);
2328     assert(parseJSON("0e1").floating == 0);
2329     assert(parseJSON("0e1", JSONOptions.strictParsing).floating == 0);
2330 
2331     // Trailing characters after JSON value
2332     assert(parseJSON(`""asdf`).str == "");
2333     assertThrown!JSONException(parseJSON(`""asdf`, JSONOptions.strictParsing));
2334     assert(parseJSON("987\0").integer == 987);
2335     assertThrown!JSONException(parseJSON("987\0", JSONOptions.strictParsing));
2336     assert(parseJSON("987\0\0").integer == 987);
2337     assertThrown!JSONException(parseJSON("987\0\0", JSONOptions.strictParsing));
2338     assert(parseJSON("[]]").type == JSONType.array);
2339     assertThrown!JSONException(parseJSON("[]]", JSONOptions.strictParsing));
2340     assert(parseJSON("123 \t\r\n").integer == 123); // Trailing whitespace is OK
2341     assert(parseJSON("123 \t\r\n", JSONOptions.strictParsing).integer == 123);
2342 }
2343 
2344 @system unittest
2345 {
2346     import std.algorithm.iteration : map;
2347     import std.array : array;
2348     import std.exception : assertThrown;
2349 
2350     string s = `{ "a" : [1,2,3,], }`;
2351     JSONValue j = parseJSON(s);
2352     assert(j["a"].array().map!(i => i.integer()).array == [1,2,3]);
2353 
2354     assertThrown(parseJSON(s, -1, JSONOptions.strictParsing));
2355 }
2356 
2357 @system unittest
2358 {
2359     import std.algorithm.iteration : map;
2360     import std.array : array;
2361     import std.exception : assertThrown;
2362 
2363     string s = `{ "a" : { }  , }`;
2364     JSONValue j = parseJSON(s);
2365     assert("a" in j);
2366     auto t = j["a"].object();
2367     assert(t.empty);
2368 
2369     assertThrown(parseJSON(s, -1, JSONOptions.strictParsing));
2370 }
2371 
2372 // https://issues.dlang.org/show_bug.cgi?id=20330
2373 @safe unittest
2374 {
2375     import std.array : appender;
2376 
2377     string s = `{"a":[1,2,3]}`;
2378     JSONValue j = parseJSON(s);
2379 
2380     auto app = appender!string();
2381     j.toString(app);
2382 
2383     assert(app.data == s, app.data);
2384 }
2385 
2386 // https://issues.dlang.org/show_bug.cgi?id=20330
2387 @safe unittest
2388 {
2389     import std.array : appender;
2390     import std.format.write : formattedWrite;
2391 
2392     string s =
2393 `{
2394     "a": [
2395         1,
2396         2,
2397         3
2398     ]
2399 }`;
2400     JSONValue j = parseJSON(s);
2401 
2402     auto app = appender!string();
2403     j.toPrettyString(app);
2404 
2405     assert(app.data == s, app.data);
2406 }
2407