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