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