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