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 http://seriot.ch/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     bool opEquals(const JSONValue rhs) const @nogc nothrow pure @safe
809     {
810         return opEquals(rhs);
811     }
812 
813     /// ditto
814     bool opEquals(ref const JSONValue rhs) const @nogc nothrow pure @trusted
815     {
816         // Default doesn't work well since store is a union.  Compare only
817         // what should be in store.
818         // This is @trusted to remain nogc, nothrow, fast, and usable from @safe code.
819 
820         final switch (type_tag)
821         {
822         case JSONType.integer:
823             switch (rhs.type_tag)
824             {
825                 case JSONType.integer:
826                     return store.integer == rhs.store.integer;
827                 case JSONType.uinteger:
828                     return store.integer == rhs.store.uinteger;
829                 case JSONType.float_:
830                     return store.integer == rhs.store.floating;
831                 default:
832                     return false;
833             }
834         case JSONType.uinteger:
835             switch (rhs.type_tag)
836             {
837                 case JSONType.integer:
838                     return store.uinteger == rhs.store.integer;
839                 case JSONType.uinteger:
840                     return store.uinteger == rhs.store.uinteger;
841                 case JSONType.float_:
842                     return store.uinteger == rhs.store.floating;
843                 default:
844                     return false;
845             }
846         case JSONType.float_:
847             switch (rhs.type_tag)
848             {
849                 case JSONType.integer:
850                     return store.floating == rhs.store.integer;
851                 case JSONType.uinteger:
852                     return store.floating == rhs.store.uinteger;
853                 case JSONType.float_:
854                     return store.floating == rhs.store.floating;
855                 default:
856                     return false;
857             }
858         case JSONType..string:
859             return type_tag == rhs.type_tag && store.str == rhs.store.str;
860         case JSONType.object:
861             return type_tag == rhs.type_tag && store.object == rhs.store.object;
862         case JSONType.array:
863             return type_tag == rhs.type_tag && store.array == rhs.store.array;
864         case JSONType.true_:
865         case JSONType.false_:
866         case JSONType.null_:
867             return type_tag == rhs.type_tag;
868         }
869     }
870 
871     ///
872     @safe unittest
873     {
874         assert(JSONValue(0u) == JSONValue(0));
875         assert(JSONValue(0u) == JSONValue(0.0));
876         assert(JSONValue(0) == JSONValue(0.0));
877     }
878 
879     /// Implements the foreach `opApply` interface for json arrays.
880     int opApply(scope int delegate(size_t index, ref JSONValue) dg) @system
881     {
882         int result;
883 
884         foreach (size_t index, ref value; array)
885         {
886             result = dg(index, value);
887             if (result)
888                 break;
889         }
890 
891         return result;
892     }
893 
894     /// Implements the foreach `opApply` interface for json objects.
895     int opApply(scope int delegate(string key, ref JSONValue) dg) @system
896     {
897         enforce!JSONException(type == JSONType.object,
898                                 "JSONValue is not an object");
899         int result;
900 
901         foreach (string key, ref value; object)
902         {
903             result = dg(key, value);
904             if (result)
905                 break;
906         }
907 
908         return result;
909     }
910 
911     /***
912      * Implicitly calls `toJSON` on this JSONValue.
913      *
914      * $(I options) can be used to tweak the conversion behavior.
915      */
916     string toString(in JSONOptions options = JSONOptions.none) const @safe
917     {
918         return toJSON(this, false, options);
919     }
920 
921     ///
922     void toString(Out)(Out sink, in JSONOptions options = JSONOptions.none) const
923     {
924         toJSON(sink, this, false, options);
925     }
926 
927     /***
928      * Implicitly calls `toJSON` on this JSONValue, like `toString`, but
929      * also passes $(I true) as $(I pretty) argument.
930      *
931      * $(I options) can be used to tweak the conversion behavior
932      */
933     string toPrettyString(in JSONOptions options = JSONOptions.none) const @safe
934     {
935         return toJSON(this, true, options);
936     }
937 
938     ///
939     void toPrettyString(Out)(Out sink, in JSONOptions options = JSONOptions.none) const
940     {
941         toJSON(sink, this, true, options);
942     }
943 }
944 
945 // https://issues.dlang.org/show_bug.cgi?id=20874
946 @system unittest
947 {
948     static struct MyCustomType
949     {
950         public string toString () const @system { return null; }
951         alias toString this;
952     }
953 
954     static struct B
955     {
956         public JSONValue asJSON() const @system { return JSONValue.init; }
957         alias asJSON this;
958     }
959 
960     if (false) // Just checking attributes
961     {
962         JSONValue json;
963         MyCustomType ilovedlang;
964         json = ilovedlang;
965         json["foo"] = ilovedlang;
966         auto s = ilovedlang in json;
967 
968         B b;
969         json ~= b;
970         json ~ b;
971     }
972 }
973 
974 /**
975 Parses a serialized string and returns a tree of JSON values.
976 Throws: $(LREF JSONException) if string does not follow the JSON grammar or the depth exceeds the max depth,
977         $(LREF ConvException) if a number in the input cannot be represented by a native D type.
978 Params:
979     json = json-formatted string to parse
980     maxDepth = maximum depth of nesting allowed, -1 disables depth checking
981     options = enable decoding string representations of NaN/Inf as float values
982 */
983 JSONValue parseJSON(T)(T json, int maxDepth = -1, JSONOptions options = JSONOptions.none)
984 if (isSomeFiniteCharInputRange!T)
985 {
986     import std.ascii : isDigit, isHexDigit, toUpper, toLower;
987     import std.typecons : Nullable, Yes;
988     JSONValue root;
989     root.type_tag = JSONType.null_;
990 
991     // Avoid UTF decoding when possible, as it is unnecessary when
992     // processing JSON.
993     static if (is(T : const(char)[]))
994         alias Char = char;
995     else
996         alias Char = Unqual!(ElementType!T);
997 
998     int depth = -1;
999     Nullable!Char next;
1000     int line = 1, pos = 0;
1001     immutable bool strict = (options & JSONOptions.strictParsing) != 0;
1002 
1003     void error(string msg)
1004     {
1005         throw new JSONException(msg, line, pos);
1006     }
1007 
1008     if (json.empty)
1009     {
1010         if (strict)
1011         {
1012             error("Empty JSON body");
1013         }
1014         return root;
1015     }
1016 
1017     bool isWhite(dchar c)
1018     {
1019         if (strict)
1020         {
1021             // RFC 7159 has a stricter definition of whitespace than general ASCII.
1022             return c == ' ' || c == '\t' || c == '\n' || c == '\r';
1023         }
1024         import std.ascii : isWhite;
1025         // Accept ASCII NUL as whitespace in non-strict mode.
1026         return c == 0 || isWhite(c);
1027     }
1028 
1029     Char popChar()
1030     {
1031         if (json.empty) error("Unexpected end of data.");
1032         static if (is(T : const(char)[]))
1033         {
1034             Char c = json[0];
1035             json = json[1..$];
1036         }
1037         else
1038         {
1039             Char c = json.front;
1040             json.popFront();
1041         }
1042 
1043         if (c == '\n')
1044         {
1045             line++;
1046             pos = 0;
1047         }
1048         else
1049         {
1050             pos++;
1051         }
1052 
1053         return c;
1054     }
1055 
1056     Char peekChar()
1057     {
1058         if (next.isNull)
1059         {
1060             if (json.empty) return '\0';
1061             next = popChar();
1062         }
1063         return next.get;
1064     }
1065 
1066     Nullable!Char peekCharNullable()
1067     {
1068         if (next.isNull && !json.empty)
1069         {
1070             next = popChar();
1071         }
1072         return next;
1073     }
1074 
1075     void skipWhitespace()
1076     {
1077         while (true)
1078         {
1079             auto c = peekCharNullable();
1080             if (c.isNull ||
1081                 !isWhite(c.get))
1082             {
1083                 return;
1084             }
1085             next.nullify();
1086         }
1087     }
1088 
1089     Char getChar(bool SkipWhitespace = false)()
1090     {
1091         static if (SkipWhitespace) skipWhitespace();
1092 
1093         Char c;
1094         if (!next.isNull)
1095         {
1096             c = next.get;
1097             next.nullify();
1098         }
1099         else
1100             c = popChar();
1101 
1102         return c;
1103     }
1104 
1105     void checkChar(bool SkipWhitespace = true)(char c, bool caseSensitive = true)
1106     {
1107         static if (SkipWhitespace) skipWhitespace();
1108         auto c2 = getChar();
1109         if (!caseSensitive) c2 = toLower(c2);
1110 
1111         if (c2 != c) error(text("Found '", c2, "' when expecting '", c, "'."));
1112     }
1113 
1114     bool testChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c)
1115     {
1116         static if (SkipWhitespace) skipWhitespace();
1117         auto c2 = peekChar();
1118         static if (!CaseSensitive) c2 = toLower(c2);
1119 
1120         if (c2 != c) return false;
1121 
1122         getChar();
1123         return true;
1124     }
1125 
1126     wchar parseWChar()
1127     {
1128         wchar val = 0;
1129         foreach_reverse (i; 0 .. 4)
1130         {
1131             auto hex = toUpper(getChar());
1132             if (!isHexDigit(hex)) error("Expecting hex character");
1133             val += (isDigit(hex) ? hex - '0' : hex - ('A' - 10)) << (4 * i);
1134         }
1135         return val;
1136     }
1137 
1138     string parseString()
1139     {
1140         import std.uni : isSurrogateHi, isSurrogateLo;
1141         import std.utf : encode, decode;
1142 
1143         auto str = appender!string();
1144 
1145     Next:
1146         switch (peekChar())
1147         {
1148             case '"':
1149                 getChar();
1150                 break;
1151 
1152             case '\\':
1153                 getChar();
1154                 auto c = getChar();
1155                 switch (c)
1156                 {
1157                     case '"':       str.put('"');   break;
1158                     case '\\':      str.put('\\');  break;
1159                     case '/':       str.put('/');   break;
1160                     case 'b':       str.put('\b');  break;
1161                     case 'f':       str.put('\f');  break;
1162                     case 'n':       str.put('\n');  break;
1163                     case 'r':       str.put('\r');  break;
1164                     case 't':       str.put('\t');  break;
1165                     case 'u':
1166                         wchar wc = parseWChar();
1167                         dchar val;
1168                         // Non-BMP characters are escaped as a pair of
1169                         // UTF-16 surrogate characters (see RFC 4627).
1170                         if (isSurrogateHi(wc))
1171                         {
1172                             wchar[2] pair;
1173                             pair[0] = wc;
1174                             if (getChar() != '\\') error("Expected escaped low surrogate after escaped high surrogate");
1175                             if (getChar() != 'u') error("Expected escaped low surrogate after escaped high surrogate");
1176                             pair[1] = parseWChar();
1177                             size_t index = 0;
1178                             val = decode(pair[], index);
1179                             if (index != 2) error("Invalid escaped surrogate pair");
1180                         }
1181                         else
1182                         if (isSurrogateLo(wc))
1183                             error(text("Unexpected low surrogate"));
1184                         else
1185                             val = wc;
1186 
1187                         char[4] buf;
1188                         immutable len = encode!(Yes.useReplacementDchar)(buf, val);
1189                         str.put(buf[0 .. len]);
1190                         break;
1191 
1192                     default:
1193                         error(text("Invalid escape sequence '\\", c, "'."));
1194                 }
1195                 goto Next;
1196 
1197             default:
1198                 // RFC 7159 states that control characters U+0000 through
1199                 // U+001F must not appear unescaped in a JSON string.
1200                 // Note: std.ascii.isControl can't be used for this test
1201                 // because it considers ASCII DEL (0x7f) to be a control
1202                 // character but RFC 7159 does not.
1203                 // Accept unescaped ASCII NULs in non-strict mode.
1204                 auto c = getChar();
1205                 if (c < 0x20 && (strict || c != 0))
1206                     error("Illegal control character.");
1207                 str.put(c);
1208                 goto Next;
1209         }
1210 
1211         return str.data.length ? str.data : "";
1212     }
1213 
1214     bool tryGetSpecialFloat(string str, out double val) {
1215         switch (str)
1216         {
1217             case JSONFloatLiteral.nan:
1218                 val = double.nan;
1219                 return true;
1220             case JSONFloatLiteral.inf:
1221                 val = double.infinity;
1222                 return true;
1223             case JSONFloatLiteral.negativeInf:
1224                 val = -double.infinity;
1225                 return true;
1226             default:
1227                 return false;
1228         }
1229     }
1230 
1231     void parseValue(ref JSONValue value)
1232     {
1233         depth++;
1234 
1235         if (maxDepth != -1 && depth > maxDepth) error("Nesting too deep.");
1236 
1237         auto c = getChar!true();
1238 
1239         switch (c)
1240         {
1241             case '{':
1242                 if (testChar('}'))
1243                 {
1244                     value.object = null;
1245                     break;
1246                 }
1247 
1248                 JSONValue[string] obj;
1249                 do
1250                 {
1251                     skipWhitespace();
1252                     if (!strict && peekChar() == '}')
1253                     {
1254                         break;
1255                     }
1256                     checkChar('"');
1257                     string name = parseString();
1258                     checkChar(':');
1259                     JSONValue member;
1260                     parseValue(member);
1261                     obj[name] = member;
1262                 }
1263                 while (testChar(','));
1264                 value.object = obj;
1265 
1266                 checkChar('}');
1267                 break;
1268 
1269             case '[':
1270                 if (testChar(']'))
1271                 {
1272                     value.type_tag = JSONType.array;
1273                     break;
1274                 }
1275 
1276                 JSONValue[] arr;
1277                 do
1278                 {
1279                     skipWhitespace();
1280                     if (!strict && peekChar() == ']')
1281                     {
1282                         break;
1283                     }
1284                     JSONValue element;
1285                     parseValue(element);
1286                     arr ~= element;
1287                 }
1288                 while (testChar(','));
1289 
1290                 checkChar(']');
1291                 value.array = arr;
1292                 break;
1293 
1294             case '"':
1295                 auto str = parseString();
1296 
1297                 // if special float parsing is enabled, check if string represents NaN/Inf
1298                 if ((options & JSONOptions.specialFloatLiterals) &&
1299                     tryGetSpecialFloat(str, value.store.floating))
1300                 {
1301                     // found a special float, its value was placed in value.store.floating
1302                     value.type_tag = JSONType.float_;
1303                     break;
1304                 }
1305 
1306                 value.assign(str);
1307                 break;
1308 
1309             case '0': .. case '9':
1310             case '-':
1311                 auto number = appender!string();
1312                 bool isFloat, isNegative;
1313 
1314                 void readInteger()
1315                 {
1316                     if (!isDigit(c)) error("Digit expected");
1317 
1318                 Next: number.put(c);
1319 
1320                     if (isDigit(peekChar()))
1321                     {
1322                         c = getChar();
1323                         goto Next;
1324                     }
1325                 }
1326 
1327                 if (c == '-')
1328                 {
1329                     number.put('-');
1330                     c = getChar();
1331                     isNegative = true;
1332                 }
1333 
1334                 if (strict && c == '0')
1335                 {
1336                     number.put('0');
1337                     if (isDigit(peekChar()))
1338                     {
1339                         error("Additional digits not allowed after initial zero digit");
1340                     }
1341                 }
1342                 else
1343                 {
1344                     readInteger();
1345                 }
1346 
1347                 if (testChar('.'))
1348                 {
1349                     isFloat = true;
1350                     number.put('.');
1351                     c = getChar();
1352                     readInteger();
1353                 }
1354                 if (testChar!(false, false)('e'))
1355                 {
1356                     isFloat = true;
1357                     number.put('e');
1358                     if (testChar('+')) number.put('+');
1359                     else if (testChar('-')) number.put('-');
1360                     c = getChar();
1361                     readInteger();
1362                 }
1363 
1364                 string data = number.data;
1365                 if (isFloat)
1366                 {
1367                     value.type_tag = JSONType.float_;
1368                     value.store.floating = parse!double(data);
1369                 }
1370                 else
1371                 {
1372                     if (isNegative)
1373                     {
1374                         value.store.integer = parse!long(data);
1375                         value.type_tag = JSONType.integer;
1376                     }
1377                     else
1378                     {
1379                         // only set the correct union member to not confuse CTFE
1380                         ulong u = parse!ulong(data);
1381                         if (u & (1UL << 63))
1382                         {
1383                             value.store.uinteger = u;
1384                             value.type_tag = JSONType.uinteger;
1385                         }
1386                         else
1387                         {
1388                             value.store.integer = u;
1389                             value.type_tag = JSONType.integer;
1390                         }
1391                     }
1392                 }
1393                 break;
1394 
1395             case 'T':
1396                 if (strict) goto default;
1397                 goto case;
1398             case 't':
1399                 value.type_tag = JSONType.true_;
1400                 checkChar!false('r', strict);
1401                 checkChar!false('u', strict);
1402                 checkChar!false('e', strict);
1403                 break;
1404 
1405             case 'F':
1406                 if (strict) goto default;
1407                 goto case;
1408             case 'f':
1409                 value.type_tag = JSONType.false_;
1410                 checkChar!false('a', strict);
1411                 checkChar!false('l', strict);
1412                 checkChar!false('s', strict);
1413                 checkChar!false('e', strict);
1414                 break;
1415 
1416             case 'N':
1417                 if (strict) goto default;
1418                 goto case;
1419             case 'n':
1420                 value.type_tag = JSONType.null_;
1421                 checkChar!false('u', strict);
1422                 checkChar!false('l', strict);
1423                 checkChar!false('l', strict);
1424                 break;
1425 
1426             default:
1427                 error(text("Unexpected character '", c, "'."));
1428         }
1429 
1430         depth--;
1431     }
1432 
1433     parseValue(root);
1434     if (strict)
1435     {
1436         skipWhitespace();
1437         if (!peekCharNullable().isNull) error("Trailing non-whitespace characters");
1438     }
1439     return root;
1440 }
1441 
1442 @safe unittest
1443 {
1444     enum issue15742objectOfObject = `{ "key1": { "key2": 1 }}`;
1445     static assert(parseJSON(issue15742objectOfObject).type == JSONType.object);
1446 
1447     enum issue15742arrayOfArray = `[[1]]`;
1448     static assert(parseJSON(issue15742arrayOfArray).type == JSONType.array);
1449 }
1450 
1451 @safe unittest
1452 {
1453     // Ensure we can parse and use JSON from @safe code
1454     auto a = `{ "key1": { "key2": 1 }}`.parseJSON;
1455     assert(a["key1"]["key2"].integer == 1);
1456     assert(a.toString == `{"key1":{"key2":1}}`);
1457 }
1458 
1459 @system unittest
1460 {
1461     // Ensure we can parse JSON from a @system range.
1462     struct Range
1463     {
1464         string s;
1465         size_t index;
1466         @system
1467         {
1468             bool empty() { return index >= s.length; }
1469             void popFront() { index++; }
1470             char front() { return s[index]; }
1471         }
1472     }
1473     auto s = Range(`{ "key1": { "key2": 1 }}`);
1474     auto json = parseJSON(s);
1475     assert(json["key1"]["key2"].integer == 1);
1476 }
1477 
1478 // https://issues.dlang.org/show_bug.cgi?id=20527
1479 @safe unittest
1480 {
1481     static assert(parseJSON(`{"a" : 2}`)["a"].integer == 2);
1482 }
1483 
1484 /**
1485 Parses a serialized string and returns a tree of JSON values.
1486 Throws: $(LREF JSONException) if the depth exceeds the max depth.
1487 Params:
1488     json = json-formatted string to parse
1489     options = enable decoding string representations of NaN/Inf as float values
1490 */
1491 JSONValue parseJSON(T)(T json, JSONOptions options)
1492 if (isSomeFiniteCharInputRange!T)
1493 {
1494     return parseJSON!T(json, -1, options);
1495 }
1496 
1497 /**
1498 Takes a tree of JSON values and returns the serialized string.
1499 
1500 Any Object types will be serialized in a key-sorted order.
1501 
1502 If `pretty` is false no whitespaces are generated.
1503 If `pretty` is true serialized string is formatted to be human-readable.
1504 Set the $(LREF JSONOptions.specialFloatLiterals) flag is set in `options` to encode NaN/Infinity as strings.
1505 */
1506 string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions options = JSONOptions.none) @safe
1507 {
1508     auto json = appender!string();
1509     toJSON(json, root, pretty, options);
1510     return json.data;
1511 }
1512 
1513 ///
1514 void toJSON(Out)(
1515     auto ref Out json,
1516     const ref JSONValue root,
1517     in bool pretty = false,
1518     in JSONOptions options = JSONOptions.none)
1519 if (isOutputRange!(Out,char))
1520 {
1521     void toStringImpl(Char)(string str)
1522     {
1523         json.put('"');
1524 
1525         foreach (Char c; str)
1526         {
1527             switch (c)
1528             {
1529                 case '"':       json.put("\\\"");       break;
1530                 case '\\':      json.put("\\\\");       break;
1531 
1532                 case '/':
1533                     if (!(options & JSONOptions.doNotEscapeSlashes))
1534                         json.put('\\');
1535                     json.put('/');
1536                     break;
1537 
1538                 case '\b':      json.put("\\b");        break;
1539                 case '\f':      json.put("\\f");        break;
1540                 case '\n':      json.put("\\n");        break;
1541                 case '\r':      json.put("\\r");        break;
1542                 case '\t':      json.put("\\t");        break;
1543                 default:
1544                 {
1545                     import std.ascii : isControl;
1546                     import std.utf : encode;
1547 
1548                     // Make sure we do UTF decoding iff we want to
1549                     // escape Unicode characters.
1550                     assert(((options & JSONOptions.escapeNonAsciiChars) != 0)
1551                         == is(Char == dchar), "JSONOptions.escapeNonAsciiChars needs dchar strings");
1552 
1553                     with (JSONOptions) if (isControl(c) ||
1554                         ((options & escapeNonAsciiChars) >= escapeNonAsciiChars && c >= 0x80))
1555                     {
1556                         // Ensure non-BMP characters are encoded as a pair
1557                         // of UTF-16 surrogate characters, as per RFC 4627.
1558                         wchar[2] wchars; // 1 or 2 UTF-16 code units
1559                         size_t wNum = encode(wchars, c); // number of UTF-16 code units
1560                         foreach (wc; wchars[0 .. wNum])
1561                         {
1562                             json.put("\\u");
1563                             foreach_reverse (i; 0 .. 4)
1564                             {
1565                                 char ch = (wc >>> (4 * i)) & 0x0f;
1566                                 ch += ch < 10 ? '0' : 'A' - 10;
1567                                 json.put(ch);
1568                             }
1569                         }
1570                     }
1571                     else
1572                     {
1573                         json.put(c);
1574                     }
1575                 }
1576             }
1577         }
1578 
1579         json.put('"');
1580     }
1581 
1582     void toString(string str)
1583     {
1584         // Avoid UTF decoding when possible, as it is unnecessary when
1585         // processing JSON.
1586         if (options & JSONOptions.escapeNonAsciiChars)
1587             toStringImpl!dchar(str);
1588         else
1589             toStringImpl!char(str);
1590     }
1591 
1592     /* make the function infer @system when json.put() is @system
1593      */
1594     if (0)
1595         json.put(' ');
1596 
1597     /* Mark as @trusted because json.put() may be @system. This has difficulty
1598      * inferring @safe because it is recursive.
1599      */
1600     void toValueImpl(ref const JSONValue value, ulong indentLevel) @trusted
1601     {
1602         void putTabs(ulong additionalIndent = 0)
1603         {
1604             if (pretty)
1605                 foreach (i; 0 .. indentLevel + additionalIndent)
1606                     json.put("    ");
1607         }
1608         void putEOL()
1609         {
1610             if (pretty)
1611                 json.put('\n');
1612         }
1613         void putCharAndEOL(char ch)
1614         {
1615             json.put(ch);
1616             putEOL();
1617         }
1618 
1619         final switch (value.type)
1620         {
1621             case JSONType.object:
1622                 auto obj = value.objectNoRef;
1623                 if (!obj.length)
1624                 {
1625                     json.put("{}");
1626                 }
1627                 else
1628                 {
1629                     putCharAndEOL('{');
1630                     bool first = true;
1631 
1632                     void emit(R)(R names)
1633                     {
1634                         foreach (name; names)
1635                         {
1636                             auto member = obj[name];
1637                             if (!first)
1638                                 putCharAndEOL(',');
1639                             first = false;
1640                             putTabs(1);
1641                             toString(name);
1642                             json.put(':');
1643                             if (pretty)
1644                                 json.put(' ');
1645                             toValueImpl(member, indentLevel + 1);
1646                         }
1647                     }
1648 
1649                     import std.algorithm.sorting : sort;
1650                     // https://issues.dlang.org/show_bug.cgi?id=14439
1651                     // auto names = obj.keys;  // aa.keys can't be called in @safe code
1652                     auto names = new string[obj.length];
1653                     size_t i = 0;
1654                     foreach (k, v; obj)
1655                     {
1656                         names[i] = k;
1657                         i++;
1658                     }
1659                     sort(names);
1660                     emit(names);
1661 
1662                     putEOL();
1663                     putTabs();
1664                     json.put('}');
1665                 }
1666                 break;
1667 
1668             case JSONType.array:
1669                 auto arr = value.arrayNoRef;
1670                 if (arr.empty)
1671                 {
1672                     json.put("[]");
1673                 }
1674                 else
1675                 {
1676                     putCharAndEOL('[');
1677                     foreach (i, el; arr)
1678                     {
1679                         if (i)
1680                             putCharAndEOL(',');
1681                         putTabs(1);
1682                         toValueImpl(el, indentLevel + 1);
1683                     }
1684                     putEOL();
1685                     putTabs();
1686                     json.put(']');
1687                 }
1688                 break;
1689 
1690             case JSONType..string:
1691                 toString(value.str);
1692                 break;
1693 
1694             case JSONType.integer:
1695                 json.put(to!string(value.store.integer));
1696                 break;
1697 
1698             case JSONType.uinteger:
1699                 json.put(to!string(value.store.uinteger));
1700                 break;
1701 
1702             case JSONType.float_:
1703                 import std.math.traits : isNaN, isInfinity;
1704 
1705                 auto val = value.store.floating;
1706 
1707                 if (val.isNaN)
1708                 {
1709                     if (options & JSONOptions.specialFloatLiterals)
1710                     {
1711                         toString(JSONFloatLiteral.nan);
1712                     }
1713                     else
1714                     {
1715                         throw new JSONException(
1716                             "Cannot encode NaN. Consider passing the specialFloatLiterals flag.");
1717                     }
1718                 }
1719                 else if (val.isInfinity)
1720                 {
1721                     if (options & JSONOptions.specialFloatLiterals)
1722                     {
1723                         toString((val > 0) ?  JSONFloatLiteral.inf : JSONFloatLiteral.negativeInf);
1724                     }
1725                     else
1726                     {
1727                         throw new JSONException(
1728                             "Cannot encode Infinity. Consider passing the specialFloatLiterals flag.");
1729                     }
1730                 }
1731                 else
1732                 {
1733                     import std.algorithm.searching : canFind;
1734                     import std.format : sformat;
1735                     // The correct formula for the number of decimal digits needed for lossless round
1736                     // trips is actually:
1737                     //     ceil(log(pow(2.0, double.mant_dig - 1)) / log(10.0) + 1) == (double.dig + 2)
1738                     // Anything less will round off (1 + double.epsilon)
1739                     char[25] buf;
1740                     auto result = buf[].sformat!"%.18g"(val);
1741                     json.put(result);
1742                     if (!result.canFind('e') && !result.canFind('.'))
1743                         json.put(".0");
1744                 }
1745                 break;
1746 
1747             case JSONType.true_:
1748                 json.put("true");
1749                 break;
1750 
1751             case JSONType.false_:
1752                 json.put("false");
1753                 break;
1754 
1755             case JSONType.null_:
1756                 json.put("null");
1757                 break;
1758         }
1759     }
1760 
1761     toValueImpl(root, 0);
1762 }
1763 
1764  // https://issues.dlang.org/show_bug.cgi?id=12897
1765 @safe unittest
1766 {
1767     JSONValue jv0 = JSONValue("test测试");
1768     assert(toJSON(jv0, false, JSONOptions.escapeNonAsciiChars) == `"test\u6D4B\u8BD5"`);
1769     JSONValue jv00 = JSONValue("test\u6D4B\u8BD5");
1770     assert(toJSON(jv00, false, JSONOptions.none) == `"test测试"`);
1771     assert(toJSON(jv0, false, JSONOptions.none) == `"test测试"`);
1772     JSONValue jv1 = JSONValue("été");
1773     assert(toJSON(jv1, false, JSONOptions.escapeNonAsciiChars) == `"\u00E9t\u00E9"`);
1774     JSONValue jv11 = JSONValue("\u00E9t\u00E9");
1775     assert(toJSON(jv11, false, JSONOptions.none) == `"été"`);
1776     assert(toJSON(jv1, false, JSONOptions.none) == `"été"`);
1777 }
1778 
1779 // https://issues.dlang.org/show_bug.cgi?id=20511
1780 @system unittest
1781 {
1782     import std.format.write : formattedWrite;
1783     import std.range : nullSink, outputRangeObject;
1784 
1785     outputRangeObject!(const(char)[])(nullSink)
1786         .formattedWrite!"%s"(JSONValue.init);
1787 }
1788 
1789 // Issue 16432 - JSON incorrectly parses to string
1790 @safe unittest
1791 {
1792     // Floating points numbers are rounded to the nearest integer and thus get
1793     // incorrectly parsed
1794 
1795     import std.math.operations : isClose;
1796 
1797     string s = "{\"rating\": 3.0 }";
1798     JSONValue j = parseJSON(s);
1799     assert(j["rating"].type == JSONType.float_);
1800     j = j.toString.parseJSON;
1801     assert(j["rating"].type == JSONType.float_);
1802     assert(isClose(j["rating"].floating, 3.0));
1803 
1804     s = "{\"rating\": -3.0 }";
1805     j = parseJSON(s);
1806     assert(j["rating"].type == JSONType.float_);
1807     j = j.toString.parseJSON;
1808     assert(j["rating"].type == JSONType.float_);
1809     assert(isClose(j["rating"].floating, -3.0));
1810 
1811     // https://issues.dlang.org/show_bug.cgi?id=13660
1812     auto jv1 = JSONValue(4.0);
1813     auto textual = jv1.toString();
1814     auto jv2 = parseJSON(textual);
1815     assert(jv1.type == JSONType.float_);
1816     assert(textual == "4.0");
1817     assert(jv2.type == JSONType.float_);
1818 }
1819 
1820 @safe unittest
1821 {
1822     // Adapted from https://github.com/dlang/phobos/pull/5005
1823     // Result from toString is not checked here, because this
1824     // might differ (%e-like or %f-like output) depending
1825     // on OS and compiler optimization.
1826     import std.math.operations : isClose;
1827 
1828     // test positive extreme values
1829     JSONValue j;
1830     j["rating"] = 1e18 - 65;
1831     assert(isClose(j.toString.parseJSON["rating"].floating, 1e18 - 65));
1832 
1833     j["rating"] = 1e18 - 64;
1834     assert(isClose(j.toString.parseJSON["rating"].floating, 1e18 - 64));
1835 
1836     // negative extreme values
1837     j["rating"] = -1e18 + 65;
1838     assert(isClose(j.toString.parseJSON["rating"].floating, -1e18 + 65));
1839 
1840     j["rating"] = -1e18 + 64;
1841     assert(isClose(j.toString.parseJSON["rating"].floating, -1e18 + 64));
1842 }
1843 
1844 /**
1845 Exception thrown on JSON errors
1846 */
1847 class JSONException : Exception
1848 {
1849     this(string msg, int line = 0, int pos = 0) pure nothrow @safe
1850     {
1851         if (line)
1852             super(text(msg, " (Line ", line, ":", pos, ")"));
1853         else
1854             super(msg);
1855     }
1856 
1857     this(string msg, string file, size_t line) pure nothrow @safe
1858     {
1859         super(msg, file, line);
1860     }
1861 }
1862 
1863 
1864 @system unittest
1865 {
1866     import std.exception;
1867     JSONValue jv = "123";
1868     assert(jv.type == JSONType..string);
1869     assertNotThrown(jv.str);
1870     assertThrown!JSONException(jv.integer);
1871     assertThrown!JSONException(jv.uinteger);
1872     assertThrown!JSONException(jv.floating);
1873     assertThrown!JSONException(jv.object);
1874     assertThrown!JSONException(jv.array);
1875     assertThrown!JSONException(jv["aa"]);
1876     assertThrown!JSONException(jv[2]);
1877 
1878     jv = -3;
1879     assert(jv.type == JSONType.integer);
1880     assertNotThrown(jv.integer);
1881 
1882     jv = cast(uint) 3;
1883     assert(jv.type == JSONType.uinteger);
1884     assertNotThrown(jv.uinteger);
1885 
1886     jv = 3.0;
1887     assert(jv.type == JSONType.float_);
1888     assertNotThrown(jv.floating);
1889 
1890     jv = ["key" : "value"];
1891     assert(jv.type == JSONType.object);
1892     assertNotThrown(jv.object);
1893     assertNotThrown(jv["key"]);
1894     assert("key" in jv);
1895     assert("notAnElement" !in jv);
1896     assertThrown!JSONException(jv["notAnElement"]);
1897     const cjv = jv;
1898     assert("key" in cjv);
1899     assertThrown!JSONException(cjv["notAnElement"]);
1900 
1901     foreach (string key, value; jv)
1902     {
1903         static assert(is(typeof(value) == JSONValue));
1904         assert(key == "key");
1905         assert(value.type == JSONType..string);
1906         assertNotThrown(value.str);
1907         assert(value.str == "value");
1908     }
1909 
1910     jv = [3, 4, 5];
1911     assert(jv.type == JSONType.array);
1912     assertNotThrown(jv.array);
1913     assertNotThrown(jv[2]);
1914     foreach (size_t index, value; jv)
1915     {
1916         static assert(is(typeof(value) == JSONValue));
1917         assert(value.type == JSONType.integer);
1918         assertNotThrown(value.integer);
1919         assert(index == (value.integer-3));
1920     }
1921 
1922     jv = null;
1923     assert(jv.type == JSONType.null_);
1924     assert(jv.isNull);
1925     jv = "foo";
1926     assert(!jv.isNull);
1927 
1928     jv = JSONValue("value");
1929     assert(jv.type == JSONType..string);
1930     assert(jv.str == "value");
1931 
1932     JSONValue jv2 = JSONValue("value");
1933     assert(jv2.type == JSONType..string);
1934     assert(jv2.str == "value");
1935 
1936     JSONValue jv3 = JSONValue("\u001c");
1937     assert(jv3.type == JSONType..string);
1938     assert(jv3.str == "\u001C");
1939 }
1940 
1941 // https://issues.dlang.org/show_bug.cgi?id=11504
1942 @system unittest
1943 {
1944     JSONValue jv = 1;
1945     assert(jv.type == JSONType.integer);
1946 
1947     jv.str = "123";
1948     assert(jv.type == JSONType..string);
1949     assert(jv.str == "123");
1950 
1951     jv.integer = 1;
1952     assert(jv.type == JSONType.integer);
1953     assert(jv.integer == 1);
1954 
1955     jv.uinteger = 2u;
1956     assert(jv.type == JSONType.uinteger);
1957     assert(jv.uinteger == 2u);
1958 
1959     jv.floating = 1.5;
1960     assert(jv.type == JSONType.float_);
1961     assert(jv.floating == 1.5);
1962 
1963     jv.object = ["key" : JSONValue("value")];
1964     assert(jv.type == JSONType.object);
1965     assert(jv.object == ["key" : JSONValue("value")]);
1966 
1967     jv.array = [JSONValue(1), JSONValue(2), JSONValue(3)];
1968     assert(jv.type == JSONType.array);
1969     assert(jv.array == [JSONValue(1), JSONValue(2), JSONValue(3)]);
1970 
1971     jv = true;
1972     assert(jv.type == JSONType.true_);
1973 
1974     jv = false;
1975     assert(jv.type == JSONType.false_);
1976 
1977     enum E{True = true}
1978     jv = E.True;
1979     assert(jv.type == JSONType.true_);
1980 }
1981 
1982 @system pure unittest
1983 {
1984     // Adding new json element via array() / object() directly
1985 
1986     JSONValue jarr = JSONValue([10]);
1987     foreach (i; 0 .. 9)
1988         jarr.array ~= JSONValue(i);
1989     assert(jarr.array.length == 10);
1990 
1991     JSONValue jobj = JSONValue(["key" : JSONValue("value")]);
1992     foreach (i; 0 .. 9)
1993         jobj.object[text("key", i)] = JSONValue(text("value", i));
1994     assert(jobj.object.length == 10);
1995 }
1996 
1997 @system pure unittest
1998 {
1999     // Adding new json element without array() / object() access
2000 
2001     JSONValue jarr = JSONValue([10]);
2002     foreach (i; 0 .. 9)
2003         jarr ~= [JSONValue(i)];
2004     assert(jarr.array.length == 10);
2005 
2006     JSONValue jobj = JSONValue(["key" : JSONValue("value")]);
2007     foreach (i; 0 .. 9)
2008         jobj[text("key", i)] = JSONValue(text("value", i));
2009     assert(jobj.object.length == 10);
2010 
2011     // No array alias
2012     auto jarr2 = jarr ~ [1,2,3];
2013     jarr2[0] = 999;
2014     assert(jarr[0] == JSONValue(10));
2015 }
2016 
2017 @system unittest
2018 {
2019     // @system because JSONValue.array is @system
2020     import std.exception;
2021 
2022     // An overly simple test suite, if it can parse a serializated string and
2023     // then use the resulting values tree to generate an identical
2024     // serialization, both the decoder and encoder works.
2025 
2026     auto jsons = [
2027         `null`,
2028         `true`,
2029         `false`,
2030         `0`,
2031         `123`,
2032         `-4321`,
2033         `0.25`,
2034         `-0.25`,
2035         `""`,
2036         `"hello\nworld"`,
2037         `"\"\\\/\b\f\n\r\t"`,
2038         `[]`,
2039         `[12,"foo",true,false]`,
2040         `{}`,
2041         `{"a":1,"b":null}`,
2042         `{"goodbye":[true,"or",false,["test",42,{"nested":{"a":23.5,"b":0.140625}}]],`
2043         ~`"hello":{"array":[12,null,{}],"json":"is great"}}`,
2044     ];
2045 
2046     enum dbl1_844 = `1.8446744073709568`;
2047     version (MinGW)
2048         jsons ~= dbl1_844 ~ `e+019`;
2049     else
2050         jsons ~= dbl1_844 ~ `e+19`;
2051 
2052     JSONValue val;
2053     string result;
2054     foreach (json; jsons)
2055     {
2056         try
2057         {
2058             val = parseJSON(json);
2059             enum pretty = false;
2060             result = toJSON(val, pretty);
2061             assert(result == json, text(result, " should be ", json));
2062         }
2063         catch (JSONException e)
2064         {
2065             import std.stdio : writefln;
2066             writefln(text(json, "\n", e.toString()));
2067         }
2068     }
2069 
2070     // Should be able to correctly interpret unicode entities
2071     val = parseJSON(`"\u003C\u003E"`);
2072     assert(toJSON(val) == "\"\&lt;\&gt;\"");
2073     assert(val.to!string() == "\"\&lt;\&gt;\"");
2074     val = parseJSON(`"\u0391\u0392\u0393"`);
2075     assert(toJSON(val) == "\"\&Alpha;\&Beta;\&Gamma;\"");
2076     assert(val.to!string() == "\"\&Alpha;\&Beta;\&Gamma;\"");
2077     val = parseJSON(`"\u2660\u2666"`);
2078     assert(toJSON(val) == "\"\&spades;\&diams;\"");
2079     assert(val.to!string() == "\"\&spades;\&diams;\"");
2080 
2081     //0x7F is a control character (see Unicode spec)
2082     val = parseJSON(`"\u007F"`);
2083     assert(toJSON(val) == "\"\\u007F\"");
2084     assert(val.to!string() == "\"\\u007F\"");
2085 
2086     with(parseJSON(`""`))
2087         assert(str == "" && str !is null);
2088     with(parseJSON(`[]`))
2089         assert(!array.length);
2090 
2091     // Formatting
2092     val = parseJSON(`{"a":[null,{"x":1},{},[]]}`);
2093     assert(toJSON(val, true) == `{
2094     "a": [
2095         null,
2096         {
2097             "x": 1
2098         },
2099         {},
2100         []
2101     ]
2102 }`);
2103 }
2104 
2105 @safe unittest
2106 {
2107   auto json = `"hello\nworld"`;
2108   const jv = parseJSON(json);
2109   assert(jv.toString == json);
2110   assert(jv.toPrettyString == json);
2111 }
2112 
2113 @system pure unittest
2114 {
2115     // https://issues.dlang.org/show_bug.cgi?id=12969
2116 
2117     JSONValue jv;
2118     jv["int"] = 123;
2119 
2120     assert(jv.type == JSONType.object);
2121     assert("int" in jv);
2122     assert(jv["int"].integer == 123);
2123 
2124     jv["array"] = [1, 2, 3, 4, 5];
2125 
2126     assert(jv["array"].type == JSONType.array);
2127     assert(jv["array"][2].integer == 3);
2128 
2129     jv["str"] = "D language";
2130     assert(jv["str"].type == JSONType..string);
2131     assert(jv["str"].str == "D language");
2132 
2133     jv["bool"] = false;
2134     assert(jv["bool"].type == JSONType.false_);
2135 
2136     assert(jv.object.length == 4);
2137 
2138     jv = [5, 4, 3, 2, 1];
2139     assert(jv.type == JSONType.array);
2140     assert(jv[3].integer == 2);
2141 }
2142 
2143 @safe unittest
2144 {
2145     auto s = q"EOF
2146 [
2147   1,
2148   2,
2149   3,
2150   potato
2151 ]
2152 EOF";
2153 
2154     import std.exception;
2155 
2156     auto e = collectException!JSONException(parseJSON(s));
2157     assert(e.msg == "Unexpected character 'p'. (Line 5:3)", e.msg);
2158 }
2159 
2160 // handling of special float values (NaN, Inf, -Inf)
2161 @safe unittest
2162 {
2163     import std.exception : assertThrown;
2164     import std.math.traits : isNaN, isInfinity;
2165 
2166     // expected representations of NaN and Inf
2167     enum {
2168         nanString         = '"' ~ JSONFloatLiteral.nan         ~ '"',
2169         infString         = '"' ~ JSONFloatLiteral.inf         ~ '"',
2170         negativeInfString = '"' ~ JSONFloatLiteral.negativeInf ~ '"',
2171     }
2172 
2173     // with the specialFloatLiterals option, encode NaN/Inf as strings
2174     assert(JSONValue(float.nan).toString(JSONOptions.specialFloatLiterals)       == nanString);
2175     assert(JSONValue(double.infinity).toString(JSONOptions.specialFloatLiterals) == infString);
2176     assert(JSONValue(-real.infinity).toString(JSONOptions.specialFloatLiterals)  == negativeInfString);
2177 
2178     // without the specialFloatLiterals option, throw on encoding NaN/Inf
2179     assertThrown!JSONException(JSONValue(float.nan).toString);
2180     assertThrown!JSONException(JSONValue(double.infinity).toString);
2181     assertThrown!JSONException(JSONValue(-real.infinity).toString);
2182 
2183     // when parsing json with specialFloatLiterals option, decode special strings as floats
2184     JSONValue jvNan    = parseJSON(nanString, JSONOptions.specialFloatLiterals);
2185     JSONValue jvInf    = parseJSON(infString, JSONOptions.specialFloatLiterals);
2186     JSONValue jvNegInf = parseJSON(negativeInfString, JSONOptions.specialFloatLiterals);
2187 
2188     assert(jvNan.floating.isNaN);
2189     assert(jvInf.floating.isInfinity    && jvInf.floating > 0);
2190     assert(jvNegInf.floating.isInfinity && jvNegInf.floating < 0);
2191 
2192     // when parsing json without the specialFloatLiterals option, decode special strings as strings
2193     jvNan    = parseJSON(nanString);
2194     jvInf    = parseJSON(infString);
2195     jvNegInf = parseJSON(negativeInfString);
2196 
2197     assert(jvNan.str    == JSONFloatLiteral.nan);
2198     assert(jvInf.str    == JSONFloatLiteral.inf);
2199     assert(jvNegInf.str == JSONFloatLiteral.negativeInf);
2200 }
2201 
2202 pure nothrow @safe @nogc unittest
2203 {
2204     JSONValue testVal;
2205     testVal = "test";
2206     testVal = 10;
2207     testVal = 10u;
2208     testVal = 1.0;
2209     testVal = (JSONValue[string]).init;
2210     testVal = JSONValue[].init;
2211     testVal = null;
2212     assert(testVal.isNull);
2213 }
2214 
2215 // https://issues.dlang.org/show_bug.cgi?id=15884
2216 pure nothrow @safe unittest
2217 {
2218     import std.typecons;
2219     void Test(C)() {
2220         C[] a = ['x'];
2221         JSONValue testVal = a;
2222         assert(testVal.type == JSONType..string);
2223         testVal = a.idup;
2224         assert(testVal.type == JSONType..string);
2225     }
2226     Test!char();
2227     Test!wchar();
2228     Test!dchar();
2229 }
2230 
2231 // https://issues.dlang.org/show_bug.cgi?id=15885
2232 @safe unittest
2233 {
2234     enum bool realInDoublePrecision = real.mant_dig == double.mant_dig;
2235 
2236     static bool test(const double num0)
2237     {
2238         import std.math.operations : feqrel;
2239         const json0 = JSONValue(num0);
2240         const num1 = to!double(toJSON(json0));
2241         static if (realInDoublePrecision)
2242             return feqrel(num1, num0) >= (double.mant_dig - 1);
2243         else
2244             return num1 == num0;
2245     }
2246 
2247     assert(test( 0.23));
2248     assert(test(-0.23));
2249     assert(test(1.223e+24));
2250     assert(test(23.4));
2251     assert(test(0.0012));
2252     assert(test(30738.22));
2253 
2254     assert(test(1 + double.epsilon));
2255     assert(test(double.min_normal));
2256     static if (realInDoublePrecision)
2257         assert(test(-double.max / 2));
2258     else
2259         assert(test(-double.max));
2260 
2261     const minSub = double.min_normal * double.epsilon;
2262     assert(test(minSub));
2263     assert(test(3*minSub));
2264 }
2265 
2266 // https://issues.dlang.org/show_bug.cgi?id=17555
2267 @safe unittest
2268 {
2269     import std.exception : assertThrown;
2270 
2271     assertThrown!JSONException(parseJSON("\"a\nb\""));
2272 }
2273 
2274 // https://issues.dlang.org/show_bug.cgi?id=17556
2275 @safe unittest
2276 {
2277     auto v = JSONValue("\U0001D11E");
2278     auto j = toJSON(v, false, JSONOptions.escapeNonAsciiChars);
2279     assert(j == `"\uD834\uDD1E"`);
2280 }
2281 
2282 // https://issues.dlang.org/show_bug.cgi?id=5904
2283 @safe unittest
2284 {
2285     string s = `"\uD834\uDD1E"`;
2286     auto j = parseJSON(s);
2287     assert(j.str == "\U0001D11E");
2288 }
2289 
2290 // https://issues.dlang.org/show_bug.cgi?id=17557
2291 @safe unittest
2292 {
2293     assert(parseJSON("\"\xFF\"").str == "\xFF");
2294     assert(parseJSON("\"\U0001D11E\"").str == "\U0001D11E");
2295 }
2296 
2297 // https://issues.dlang.org/show_bug.cgi?id=17553
2298 @safe unittest
2299 {
2300     auto v = JSONValue("\xFF");
2301     assert(toJSON(v) == "\"\xFF\"");
2302 }
2303 
2304 @safe unittest
2305 {
2306     import std.utf;
2307     assert(parseJSON("\"\xFF\"".byChar).str == "\xFF");
2308     assert(parseJSON("\"\U0001D11E\"".byChar).str == "\U0001D11E");
2309 }
2310 
2311 // JSONOptions.doNotEscapeSlashes (https://issues.dlang.org/show_bug.cgi?id=17587)
2312 @safe unittest
2313 {
2314     assert(parseJSON(`"/"`).toString == `"\/"`);
2315     assert(parseJSON(`"\/"`).toString == `"\/"`);
2316     assert(parseJSON(`"/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`);
2317     assert(parseJSON(`"\/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`);
2318 }
2319 
2320 // JSONOptions.strictParsing (https://issues.dlang.org/show_bug.cgi?id=16639)
2321 @safe unittest
2322 {
2323     import std.exception : assertThrown;
2324 
2325     // Unescaped ASCII NULs
2326     assert(parseJSON("[\0]").type == JSONType.array);
2327     assertThrown!JSONException(parseJSON("[\0]", JSONOptions.strictParsing));
2328     assert(parseJSON("\"\0\"").str == "\0");
2329     assertThrown!JSONException(parseJSON("\"\0\"", JSONOptions.strictParsing));
2330 
2331     // Unescaped ASCII DEL (0x7f) in strings
2332     assert(parseJSON("\"\x7f\"").str == "\x7f");
2333     assert(parseJSON("\"\x7f\"", JSONOptions.strictParsing).str == "\x7f");
2334 
2335     // "true", "false", "null" case sensitivity
2336     assert(parseJSON("true").type == JSONType.true_);
2337     assert(parseJSON("true", JSONOptions.strictParsing).type == JSONType.true_);
2338     assert(parseJSON("True").type == JSONType.true_);
2339     assertThrown!JSONException(parseJSON("True", JSONOptions.strictParsing));
2340     assert(parseJSON("tRUE").type == JSONType.true_);
2341     assertThrown!JSONException(parseJSON("tRUE", JSONOptions.strictParsing));
2342 
2343     assert(parseJSON("false").type == JSONType.false_);
2344     assert(parseJSON("false", JSONOptions.strictParsing).type == JSONType.false_);
2345     assert(parseJSON("False").type == JSONType.false_);
2346     assertThrown!JSONException(parseJSON("False", JSONOptions.strictParsing));
2347     assert(parseJSON("fALSE").type == JSONType.false_);
2348     assertThrown!JSONException(parseJSON("fALSE", JSONOptions.strictParsing));
2349 
2350     assert(parseJSON("null").type == JSONType.null_);
2351     assert(parseJSON("null", JSONOptions.strictParsing).type == JSONType.null_);
2352     assert(parseJSON("Null").type == JSONType.null_);
2353     assertThrown!JSONException(parseJSON("Null", JSONOptions.strictParsing));
2354     assert(parseJSON("nULL").type == JSONType.null_);
2355     assertThrown!JSONException(parseJSON("nULL", JSONOptions.strictParsing));
2356 
2357     // Whitespace characters
2358     assert(parseJSON("[\f\v]").type == JSONType.array);
2359     assertThrown!JSONException(parseJSON("[\f\v]", JSONOptions.strictParsing));
2360     assert(parseJSON("[ \t\r\n]").type == JSONType.array);
2361     assert(parseJSON("[ \t\r\n]", JSONOptions.strictParsing).type == JSONType.array);
2362 
2363     // Empty input
2364     assert(parseJSON("").type == JSONType.null_);
2365     assertThrown!JSONException(parseJSON("", JSONOptions.strictParsing));
2366 
2367     // Numbers with leading '0's
2368     assert(parseJSON("01").integer == 1);
2369     assertThrown!JSONException(parseJSON("01", JSONOptions.strictParsing));
2370     assert(parseJSON("-01").integer == -1);
2371     assertThrown!JSONException(parseJSON("-01", JSONOptions.strictParsing));
2372     assert(parseJSON("0.01").floating == 0.01);
2373     assert(parseJSON("0.01", JSONOptions.strictParsing).floating == 0.01);
2374     assert(parseJSON("0e1").floating == 0);
2375     assert(parseJSON("0e1", JSONOptions.strictParsing).floating == 0);
2376 
2377     // Trailing characters after JSON value
2378     assert(parseJSON(`""asdf`).str == "");
2379     assertThrown!JSONException(parseJSON(`""asdf`, JSONOptions.strictParsing));
2380     assert(parseJSON("987\0").integer == 987);
2381     assertThrown!JSONException(parseJSON("987\0", JSONOptions.strictParsing));
2382     assert(parseJSON("987\0\0").integer == 987);
2383     assertThrown!JSONException(parseJSON("987\0\0", JSONOptions.strictParsing));
2384     assert(parseJSON("[]]").type == JSONType.array);
2385     assertThrown!JSONException(parseJSON("[]]", JSONOptions.strictParsing));
2386     assert(parseJSON("123 \t\r\n").integer == 123); // Trailing whitespace is OK
2387     assert(parseJSON("123 \t\r\n", JSONOptions.strictParsing).integer == 123);
2388 }
2389 
2390 @system unittest
2391 {
2392     import std.algorithm.iteration : map;
2393     import std.array : array;
2394     import std.exception : assertThrown;
2395 
2396     string s = `{ "a" : [1,2,3,], }`;
2397     JSONValue j = parseJSON(s);
2398     assert(j["a"].array().map!(i => i.integer()).array == [1,2,3]);
2399 
2400     assertThrown(parseJSON(s, -1, JSONOptions.strictParsing));
2401 }
2402 
2403 @system unittest
2404 {
2405     import std.algorithm.iteration : map;
2406     import std.array : array;
2407     import std.exception : assertThrown;
2408 
2409     string s = `{ "a" : { }  , }`;
2410     JSONValue j = parseJSON(s);
2411     assert("a" in j);
2412     auto t = j["a"].object();
2413     assert(t.empty);
2414 
2415     assertThrown(parseJSON(s, -1, JSONOptions.strictParsing));
2416 }
2417 
2418 // https://issues.dlang.org/show_bug.cgi?id=20330
2419 @safe unittest
2420 {
2421     import std.array : appender;
2422 
2423     string s = `{"a":[1,2,3]}`;
2424     JSONValue j = parseJSON(s);
2425 
2426     auto app = appender!string();
2427     j.toString(app);
2428 
2429     assert(app.data == s, app.data);
2430 }
2431 
2432 // https://issues.dlang.org/show_bug.cgi?id=20330
2433 @safe unittest
2434 {
2435     import std.array : appender;
2436     import std.format.write : formattedWrite;
2437 
2438     string s =
2439 `{
2440     "a": [
2441         1,
2442         2,
2443         3
2444     ]
2445 }`;
2446     JSONValue j = parseJSON(s);
2447 
2448     auto app = appender!string();
2449     j.toPrettyString(app);
2450 
2451     assert(app.data == s, app.data);
2452 }