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