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