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/)
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() == JSON_TYPE.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 }
81 
82 /**
83 JSON type enumeration
84 */
85 enum JSON_TYPE : byte
86 {
87     /// Indicates the type of a $(D JSONValue).
88     NULL,
89     STRING,  /// ditto
90     INTEGER, /// ditto
91     UINTEGER,/// ditto
92     FLOAT,   /// ditto
93     OBJECT,  /// ditto
94     ARRAY,   /// ditto
95     TRUE,    /// ditto
96     FALSE    /// ditto
97 }
98 
99 /**
100 JSON value node
101 */
102 struct JSONValue
103 {
104     import std.exception : enforceEx, enforce;
105 
106     union Store
107     {
108         string                          str;
109         long                            integer;
110         ulong                           uinteger;
111         double                          floating;
112         JSONValue[string]               object;
113         JSONValue[]                     array;
114     }
115     private Store store;
116     private JSON_TYPE type_tag;
117 
118     /**
119       Returns the JSON_TYPE of the value stored in this structure.
120     */
type()121     @property JSON_TYPE type() const pure nothrow @safe @nogc
122     {
123         return type_tag;
124     }
125     ///
126     @safe unittest
127     {
128           string s = "{ \"language\": \"D\" }";
129           JSONValue j = parseJSON(s);
130           assert(j.type == JSON_TYPE.OBJECT);
131           assert(j["language"].type == JSON_TYPE.STRING);
132     }
133 
134     /***
135      * Value getter/setter for $(D JSON_TYPE.STRING).
136      * Throws: $(D JSONException) for read access if $(D type) is not
137      * $(D JSON_TYPE.STRING).
138      */
str()139     @property string str() const pure @trusted
140     {
141         enforce!JSONException(type == JSON_TYPE.STRING,
142                                 "JSONValue is not a string");
143         return store.str;
144     }
145     /// ditto
str(string v)146     @property string str(string v) pure nothrow @nogc @safe
147     {
148         assign(v);
149         return v;
150     }
151     ///
152     @safe unittest
153     {
154         JSONValue j = [ "language": "D" ];
155 
156         // get value
157         assert(j["language"].str == "D");
158 
159         // change existing key to new string
160         j["language"].str = "Perl";
161         assert(j["language"].str == "Perl");
162     }
163 
164     /***
165      * Value getter/setter for $(D JSON_TYPE.INTEGER).
166      * Throws: $(D JSONException) for read access if $(D type) is not
167      * $(D JSON_TYPE.INTEGER).
168      */
inout(long)169     @property inout(long) integer() inout pure @safe
170     {
171         enforce!JSONException(type == JSON_TYPE.INTEGER,
172                                 "JSONValue is not an integer");
173         return store.integer;
174     }
175     /// ditto
integer(long v)176     @property long integer(long v) pure nothrow @safe @nogc
177     {
178         assign(v);
179         return store.integer;
180     }
181 
182     /***
183      * Value getter/setter for $(D JSON_TYPE.UINTEGER).
184      * Throws: $(D JSONException) for read access if $(D type) is not
185      * $(D JSON_TYPE.UINTEGER).
186      */
inout(ulong)187     @property inout(ulong) uinteger() inout pure @safe
188     {
189         enforce!JSONException(type == JSON_TYPE.UINTEGER,
190                                 "JSONValue is not an unsigned integer");
191         return store.uinteger;
192     }
193     /// ditto
uinteger(ulong v)194     @property ulong uinteger(ulong v) pure nothrow @safe @nogc
195     {
196         assign(v);
197         return store.uinteger;
198     }
199 
200     /***
201      * Value getter/setter for $(D JSON_TYPE.FLOAT). Note that despite
202      * the name, this is a $(B 64)-bit `double`, not a 32-bit `float`.
203      * Throws: $(D JSONException) for read access if $(D type) is not
204      * $(D JSON_TYPE.FLOAT).
205      */
inout(double)206     @property inout(double) floating() inout pure @safe
207     {
208         enforce!JSONException(type == JSON_TYPE.FLOAT,
209                                 "JSONValue is not a floating type");
210         return store.floating;
211     }
212     /// ditto
floating(double v)213     @property double floating(double v) pure nothrow @safe @nogc
214     {
215         assign(v);
216         return store.floating;
217     }
218 
219     /***
220      * Value getter/setter for $(D JSON_TYPE.OBJECT).
221      * Throws: $(D JSONException) for read access if $(D type) is not
222      * $(D JSON_TYPE.OBJECT).
223      * Note: this is @system because of the following pattern:
224        ---
225        auto a = &(json.object());
226        json.uinteger = 0;        // overwrite AA pointer
227        (*a)["hello"] = "world";  // segmentation fault
228        ---
229      */
inout(JSONValue[string])230     @property ref inout(JSONValue[string]) object() inout pure @system
231     {
232         enforce!JSONException(type == JSON_TYPE.OBJECT,
233                                 "JSONValue is not an object");
234         return store.object;
235     }
236     /// ditto
object(JSONValue[string]v)237     @property JSONValue[string] object(JSONValue[string] v) pure nothrow @nogc @safe
238     {
239         assign(v);
240         return v;
241     }
242 
243     /***
244      * Value getter for $(D JSON_TYPE.OBJECT).
245      * Unlike $(D object), this retrieves the object by value and can be used in @safe code.
246      *
247      * A caveat is that, if the returned value is null, modifications will not be visible:
248      * ---
249      * JSONValue json;
250      * json.object = null;
251      * json.objectNoRef["hello"] = JSONValue("world");
252      * assert("hello" !in json.object);
253      * ---
254      *
255      * Throws: $(D JSONException) for read access if $(D type) is not
256      * $(D JSON_TYPE.OBJECT).
257      */
inout(JSONValue[string])258     @property inout(JSONValue[string]) objectNoRef() inout pure @trusted
259     {
260         enforce!JSONException(type == JSON_TYPE.OBJECT,
261                                 "JSONValue is not an object");
262         return store.object;
263     }
264 
265     /***
266      * Value getter/setter for $(D JSON_TYPE.ARRAY).
267      * Throws: $(D JSONException) for read access if $(D type) is not
268      * $(D JSON_TYPE.ARRAY).
269      * Note: this is @system because of the following pattern:
270        ---
271        auto a = &(json.array());
272        json.uinteger = 0;  // overwrite array pointer
273        (*a)[0] = "world";  // segmentation fault
274        ---
275      */
inout(JSONValue[])276     @property ref inout(JSONValue[]) array() inout pure @system
277     {
278         enforce!JSONException(type == JSON_TYPE.ARRAY,
279                                 "JSONValue is not an array");
280         return store.array;
281     }
282     /// ditto
array(JSONValue[]v)283     @property JSONValue[] array(JSONValue[] v) pure nothrow @nogc @safe
284     {
285         assign(v);
286         return v;
287     }
288 
289     /***
290      * Value getter for $(D JSON_TYPE.ARRAY).
291      * Unlike $(D array), this retrieves the array by value and can be used in @safe code.
292      *
293      * A caveat is that, if you append to the returned array, the new values aren't visible in the
294      * JSONValue:
295      * ---
296      * JSONValue json;
297      * json.array = [JSONValue("hello")];
298      * json.arrayNoRef ~= JSONValue("world");
299      * assert(json.array.length == 1);
300      * ---
301      *
302      * Throws: $(D JSONException) for read access if $(D type) is not
303      * $(D JSON_TYPE.ARRAY).
304      */
inout(JSONValue[])305     @property inout(JSONValue[]) arrayNoRef() inout pure @trusted
306     {
307         enforce!JSONException(type == JSON_TYPE.ARRAY,
308                                 "JSONValue is not an array");
309         return store.array;
310     }
311 
312     /// Test whether the type is $(D JSON_TYPE.NULL)
isNull()313     @property bool isNull() const pure nothrow @safe @nogc
314     {
315         return type == JSON_TYPE.NULL;
316     }
317 
assign(T)318     private void assign(T)(T arg) @safe
319     {
320         static if (is(T : typeof(null)))
321         {
322             type_tag = JSON_TYPE.NULL;
323         }
324         else static if (is(T : string))
325         {
326             type_tag = JSON_TYPE.STRING;
327             string t = arg;
328             () @trusted { store.str = t; }();
329         }
330         else static if (isSomeString!T) // issue 15884
331         {
332             type_tag = JSON_TYPE.STRING;
333             // FIXME: std.array.array(Range) is not deduced as 'pure'
334             () @trusted {
335                 import std.utf : byUTF;
336                 store.str = cast(immutable)(arg.byUTF!char.array);
337             }();
338         }
339         else static if (is(T : bool))
340         {
341             type_tag = arg ? JSON_TYPE.TRUE : JSON_TYPE.FALSE;
342         }
343         else static if (is(T : ulong) && isUnsigned!T)
344         {
345             type_tag = JSON_TYPE.UINTEGER;
346             store.uinteger = arg;
347         }
348         else static if (is(T : long))
349         {
350             type_tag = JSON_TYPE.INTEGER;
351             store.integer = arg;
352         }
353         else static if (isFloatingPoint!T)
354         {
355             type_tag = JSON_TYPE.FLOAT;
356             store.floating = arg;
357         }
358         else static if (is(T : Value[Key], Key, Value))
359         {
360             static assert(is(Key : string), "AA key must be string");
361             type_tag = JSON_TYPE.OBJECT;
362             static if (is(Value : JSONValue))
363             {
364                 JSONValue[string] t = arg;
365                 () @trusted { store.object = t; }();
366             }
367             else
368             {
369                 JSONValue[string] aa;
370                 foreach (key, value; arg)
371                     aa[key] = JSONValue(value);
372                 () @trusted { store.object = aa; }();
373             }
374         }
375         else static if (isArray!T)
376         {
377             type_tag = JSON_TYPE.ARRAY;
378             static if (is(ElementEncodingType!T : JSONValue))
379             {
380                 JSONValue[] t = arg;
381                 () @trusted { store.array = t; }();
382             }
383             else
384             {
385                 JSONValue[] new_arg = new JSONValue[arg.length];
386                 foreach (i, e; arg)
387                     new_arg[i] = JSONValue(e);
388                 () @trusted { store.array = new_arg; }();
389             }
390         }
391         else static if (is(T : JSONValue))
392         {
393             type_tag = arg.type;
394             store = arg.store;
395         }
396         else
397         {
398             static assert(false, text(`unable to convert type "`, T.stringof, `" to json`));
399         }
400     }
401 
402     private void assignRef(T)(ref T arg) if (isStaticArray!T)
403     {
404         type_tag = JSON_TYPE.ARRAY;
405         static if (is(ElementEncodingType!T : JSONValue))
406         {
407             store.array = arg;
408         }
409         else
410         {
411             JSONValue[] new_arg = new JSONValue[arg.length];
412             foreach (i, e; arg)
413                 new_arg[i] = JSONValue(e);
414             store.array = new_arg;
415         }
416     }
417 
418     /**
419      * Constructor for $(D JSONValue). If $(D arg) is a $(D JSONValue)
420      * its value and type will be copied to the new $(D JSONValue).
421      * Note that this is a shallow copy: if type is $(D JSON_TYPE.OBJECT)
422      * or $(D JSON_TYPE.ARRAY) then only the reference to the data will
423      * be copied.
424      * Otherwise, $(D arg) must be implicitly convertible to one of the
425      * following types: $(D typeof(null)), $(D string), $(D ulong),
426      * $(D long), $(D double), an associative array $(D V[K]) for any $(D V)
427      * and $(D K) i.e. a JSON object, any array or $(D bool). The type will
428      * be set accordingly.
429      */
430     this(T)(T arg) if (!isStaticArray!T)
431     {
432         assign(arg);
433     }
434     /// Ditto
435     this(T)(ref T arg) if (isStaticArray!T)
436     {
437         assignRef(arg);
438     }
439     /// Ditto
440     this(T : JSONValue)(inout T arg) inout
441     {
442         store = arg.store;
443         type_tag = arg.type;
444     }
445     ///
446     @safe unittest
447     {
448         JSONValue j = JSONValue( "a string" );
449         j = JSONValue(42);
450 
451         j = JSONValue( [1, 2, 3] );
452         assert(j.type == JSON_TYPE.ARRAY);
453 
454         j = JSONValue( ["language": "D"] );
455         assert(j.type == JSON_TYPE.OBJECT);
456     }
457 
458     void opAssign(T)(T arg) if (!isStaticArray!T && !is(T : JSONValue))
459     {
460         assign(arg);
461     }
462 
463     void opAssign(T)(ref T arg) if (isStaticArray!T)
464     {
465         assignRef(arg);
466     }
467 
468     /***
469      * Array syntax for json arrays.
470      * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.ARRAY).
471      */
inout(JSONValue)472     ref inout(JSONValue) opIndex(size_t i) inout pure @safe
473     {
474         auto a = this.arrayNoRef;
475         enforceEx!JSONException(i < a.length,
476                                 "JSONValue array index is out of range");
477         return a[i];
478     }
479     ///
480     @safe unittest
481     {
482         JSONValue j = JSONValue( [42, 43, 44] );
483         assert( j[0].integer == 42 );
484         assert( j[1].integer == 43 );
485     }
486 
487     /***
488      * Hash syntax for json objects.
489      * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.OBJECT).
490      */
inout(JSONValue)491     ref inout(JSONValue) opIndex(string k) inout pure @safe
492     {
493         auto o = this.objectNoRef;
494         return *enforce!JSONException(k in o,
495                                         "Key not found: " ~ k);
496     }
497     ///
498     @safe unittest
499     {
500         JSONValue j = JSONValue( ["language": "D"] );
501         assert( j["language"].str == "D" );
502     }
503 
504     /***
505      * Operator sets $(D value) for element of JSON object by $(D key).
506      *
507      * If JSON value is null, then operator initializes it with object and then
508      * sets $(D value) for it.
509      *
510      * Throws: $(D JSONException) if $(D type) is not $(D JSON_TYPE.OBJECT)
511      * or $(D JSON_TYPE.NULL).
512      */
opIndexAssign(T)513     void opIndexAssign(T)(auto ref T value, string key) pure
514     {
515         enforceEx!JSONException(type == JSON_TYPE.OBJECT || type == JSON_TYPE.NULL,
516                                 "JSONValue must be object or null");
517         JSONValue[string] aa = null;
518         if (type == JSON_TYPE.OBJECT)
519         {
520             aa = this.objectNoRef;
521         }
522 
523         aa[key] = value;
524         this.object = aa;
525     }
526     ///
527     @safe unittest
528     {
529             JSONValue j = JSONValue( ["language": "D"] );
530             j["language"].str = "Perl";
531             assert( j["language"].str == "Perl" );
532     }
533 
opIndexAssign(T)534     void opIndexAssign(T)(T arg, size_t i) pure
535     {
536         auto a = this.arrayNoRef;
537         enforceEx!JSONException(i < a.length,
538                                 "JSONValue array index is out of range");
539         a[i] = arg;
540         this.array = a;
541     }
542     ///
543     @safe unittest
544     {
545             JSONValue j = JSONValue( ["Perl", "C"] );
546             j[1].str = "D";
547             assert( j[1].str == "D" );
548     }
549 
550     JSONValue opBinary(string op : "~", T)(T arg) @safe
551     {
552         auto a = this.arrayNoRef;
553         static if (isArray!T)
554         {
555             return JSONValue(a ~ JSONValue(arg).arrayNoRef);
556         }
557         else static if (is(T : JSONValue))
558         {
559             return JSONValue(a ~ arg.arrayNoRef);
560         }
561         else
562         {
563             static assert(false, "argument is not an array or a JSONValue array");
564         }
565     }
566 
567     void opOpAssign(string op : "~", T)(T arg) @safe
568     {
569         auto a = this.arrayNoRef;
570         static if (isArray!T)
571         {
572             a ~= JSONValue(arg).arrayNoRef;
573         }
574         else static if (is(T : JSONValue))
575         {
576             a ~= arg.arrayNoRef;
577         }
578         else
579         {
580             static assert(false, "argument is not an array or a JSONValue array");
581         }
582         this.array = a;
583     }
584 
585     /**
586      * Support for the $(D in) operator.
587      *
588      * Tests wether a key can be found in an object.
589      *
590      * Returns:
591      *      when found, the $(D const(JSONValue)*) that matches to the key,
592      *      otherwise $(D null).
593      *
594      * Throws: $(D JSONException) if the right hand side argument $(D JSON_TYPE)
595      * is not $(D OBJECT).
596      */
597     auto opBinaryRight(string op : "in")(string k) const @safe
598     {
599         return k in this.objectNoRef;
600     }
601     ///
602     @safe unittest
603     {
604         JSONValue j = [ "language": "D", "author": "walter" ];
605         string a = ("author" in j).str;
606     }
607 
opEquals(const JSONValue rhs)608     bool opEquals(const JSONValue rhs) const @nogc nothrow pure @safe
609     {
610         return opEquals(rhs);
611     }
612 
opEquals(ref const JSONValue rhs)613     bool opEquals(ref const JSONValue rhs) const @nogc nothrow pure @trusted
614     {
615         // Default doesn't work well since store is a union.  Compare only
616         // what should be in store.
617         // This is @trusted to remain nogc, nothrow, fast, and usable from @safe code.
618         if (type_tag != rhs.type_tag) return false;
619 
620         final switch (type_tag)
621         {
622         case JSON_TYPE.STRING:
623             return store.str == rhs.store.str;
624         case JSON_TYPE.INTEGER:
625             return store.integer == rhs.store.integer;
626         case JSON_TYPE.UINTEGER:
627             return store.uinteger == rhs.store.uinteger;
628         case JSON_TYPE.FLOAT:
629             return store.floating == rhs.store.floating;
630         case JSON_TYPE.OBJECT:
631             return store.object == rhs.store.object;
632         case JSON_TYPE.ARRAY:
633             return store.array == rhs.store.array;
634         case JSON_TYPE.TRUE:
635         case JSON_TYPE.FALSE:
636         case JSON_TYPE.NULL:
637             return true;
638         }
639     }
640 
641     /// Implements the foreach $(D opApply) interface for json arrays.
opApply(scope int delegate (size_t index,ref JSONValue)dg)642     int opApply(scope int delegate(size_t index, ref JSONValue) dg) @system
643     {
644         int result;
645 
646         foreach (size_t index, ref value; array)
647         {
648             result = dg(index, value);
649             if (result)
650                 break;
651         }
652 
653         return result;
654     }
655 
656     /// Implements the foreach $(D opApply) interface for json objects.
opApply(scope int delegate (string key,ref JSONValue)dg)657     int opApply(scope int delegate(string key, ref JSONValue) dg) @system
658     {
659         enforce!JSONException(type == JSON_TYPE.OBJECT,
660                                 "JSONValue is not an object");
661         int result;
662 
663         foreach (string key, ref value; object)
664         {
665             result = dg(key, value);
666             if (result)
667                 break;
668         }
669 
670         return result;
671     }
672 
673     /***
674      * Implicitly calls $(D toJSON) on this JSONValue.
675      *
676      * $(I options) can be used to tweak the conversion behavior.
677      */
678     string toString(in JSONOptions options = JSONOptions.none) const @safe
679     {
680         return toJSON(this, false, options);
681     }
682 
683     /***
684      * Implicitly calls $(D toJSON) on this JSONValue, like $(D toString), but
685      * also passes $(I true) as $(I pretty) argument.
686      *
687      * $(I options) can be used to tweak the conversion behavior
688      */
689     string toPrettyString(in JSONOptions options = JSONOptions.none) const @safe
690     {
691         return toJSON(this, true, options);
692     }
693 }
694 
695 /**
696 Parses a serialized string and returns a tree of JSON values.
697 Throws: $(LREF JSONException) if the depth exceeds the max depth.
698 Params:
699     json = json-formatted string to parse
700     maxDepth = maximum depth of nesting allowed, -1 disables depth checking
701     options = enable decoding string representations of NaN/Inf as float values
702 */
703 JSONValue parseJSON(T)(T json, int maxDepth = -1, JSONOptions options = JSONOptions.none)
704 if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T))
705 {
706     import std.ascii : isWhite, isDigit, isHexDigit, toUpper, toLower;
707     import std.typecons : Yes;
708     JSONValue root;
709     root.type_tag = JSON_TYPE.NULL;
710 
711     // Avoid UTF decoding when possible, as it is unnecessary when
712     // processing JSON.
713     static if (is(T : const(char)[]))
714         alias Char = char;
715     else
716         alias Char = Unqual!(ElementType!T);
717 
718     if (json.empty) return root;
719 
720     int depth = -1;
721     Char next = 0;
722     int line = 1, pos = 0;
723 
error(string msg)724     void error(string msg)
725     {
726         throw new JSONException(msg, line, pos);
727     }
728 
popChar()729     Char popChar()
730     {
731         if (json.empty) error("Unexpected end of data.");
732         static if (is(T : const(char)[]))
733         {
734             Char c = json[0];
735             json = json[1..$];
736         }
737         else
738         {
739             Char c = json.front;
740             json.popFront();
741         }
742 
743         if (c == '\n')
744         {
745             line++;
746             pos = 0;
747         }
748         else
749         {
750             pos++;
751         }
752 
753         return c;
754     }
755 
peekChar()756     Char peekChar()
757     {
758         if (!next)
759         {
760             if (json.empty) return '\0';
761             next = popChar();
762         }
763         return next;
764     }
765 
skipWhitespace()766     void skipWhitespace()
767     {
768         while (isWhite(peekChar())) next = 0;
769     }
770 
771     Char getChar(bool SkipWhitespace = false)()
772     {
773         static if (SkipWhitespace) skipWhitespace();
774 
775         Char c;
776         if (next)
777         {
778             c = next;
779             next = 0;
780         }
781         else
782             c = popChar();
783 
784         return c;
785     }
786 
787     void checkChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c)
788     {
789         static if (SkipWhitespace) skipWhitespace();
790         auto c2 = getChar();
791         static if (!CaseSensitive) c2 = toLower(c2);
792 
793         if (c2 != c) error(text("Found '", c2, "' when expecting '", c, "'."));
794     }
795 
796     bool testChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c)
797     {
798         static if (SkipWhitespace) skipWhitespace();
799         auto c2 = peekChar();
800         static if (!CaseSensitive) c2 = toLower(c2);
801 
802         if (c2 != c) return false;
803 
804         getChar();
805         return true;
806     }
807 
parseWChar()808     wchar parseWChar()
809     {
810         wchar val = 0;
811         foreach_reverse (i; 0 .. 4)
812         {
813             auto hex = toUpper(getChar());
814             if (!isHexDigit(hex)) error("Expecting hex character");
815             val += (isDigit(hex) ? hex - '0' : hex - ('A' - 10)) << (4 * i);
816         }
817         return val;
818     }
819 
parseString()820     string parseString()
821     {
822         import std.ascii : isControl;
823         import std.uni : isSurrogateHi, isSurrogateLo;
824         import std.utf : encode, decode;
825 
826         auto str = appender!string();
827 
828     Next:
829         switch (peekChar())
830         {
831             case '"':
832                 getChar();
833                 break;
834 
835             case '\\':
836                 getChar();
837                 auto c = getChar();
838                 switch (c)
839                 {
840                     case '"':       str.put('"');   break;
841                     case '\\':      str.put('\\');  break;
842                     case '/':       str.put('/');   break;
843                     case 'b':       str.put('\b');  break;
844                     case 'f':       str.put('\f');  break;
845                     case 'n':       str.put('\n');  break;
846                     case 'r':       str.put('\r');  break;
847                     case 't':       str.put('\t');  break;
848                     case 'u':
849                         wchar wc = parseWChar();
850                         dchar val;
851                         // Non-BMP characters are escaped as a pair of
852                         // UTF-16 surrogate characters (see RFC 4627).
853                         if (isSurrogateHi(wc))
854                         {
855                             wchar[2] pair;
856                             pair[0] = wc;
857                             if (getChar() != '\\') error("Expected escaped low surrogate after escaped high surrogate");
858                             if (getChar() != 'u') error("Expected escaped low surrogate after escaped high surrogate");
859                             pair[1] = parseWChar();
860                             size_t index = 0;
861                             val = decode(pair[], index);
862                             if (index != 2) error("Invalid escaped surrogate pair");
863                         }
864                         else
865                         if (isSurrogateLo(wc))
866                             error(text("Unexpected low surrogate"));
867                         else
868                             val = wc;
869 
870                         char[4] buf;
871                         immutable len = encode!(Yes.useReplacementDchar)(buf, val);
872                         str.put(buf[0 .. len]);
873                         break;
874 
875                     default:
876                         error(text("Invalid escape sequence '\\", c, "'."));
877                 }
878                 goto Next;
879 
880             default:
881                 // RFC 7159 states that control characters U+0000 through
882                 // U+001F must not appear unescaped in a JSON string.
883                 auto c = getChar();
884                 if (isControl(c))
885                     error("Illegal control character.");
886                 str.put(c);
887                 goto Next;
888         }
889 
890         return str.data.length ? str.data : "";
891     }
892 
tryGetSpecialFloat(string str,out double val)893     bool tryGetSpecialFloat(string str, out double val) {
894         switch (str)
895         {
896             case JSONFloatLiteral.nan:
897                 val = double.nan;
898                 return true;
899             case JSONFloatLiteral.inf:
900                 val = double.infinity;
901                 return true;
902             case JSONFloatLiteral.negativeInf:
903                 val = -double.infinity;
904                 return true;
905             default:
906                 return false;
907         }
908     }
909 
parseValue(ref JSONValue value)910     void parseValue(ref JSONValue value)
911     {
912         depth++;
913 
914         if (maxDepth != -1 && depth > maxDepth) error("Nesting too deep.");
915 
916         auto c = getChar!true();
917 
918         switch (c)
919         {
920             case '{':
921                 if (testChar('}'))
922                 {
923                     value.object = null;
924                     break;
925                 }
926 
927                 JSONValue[string] obj;
928                 do
929                 {
930                     checkChar('"');
931                     string name = parseString();
932                     checkChar(':');
933                     JSONValue member;
934                     parseValue(member);
935                     obj[name] = member;
936                 }
937                 while (testChar(','));
938                 value.object = obj;
939 
940                 checkChar('}');
941                 break;
942 
943             case '[':
944                 if (testChar(']'))
945                 {
946                     value.type_tag = JSON_TYPE.ARRAY;
947                     break;
948                 }
949 
950                 JSONValue[] arr;
951                 do
952                 {
953                     JSONValue element;
954                     parseValue(element);
955                     arr ~= element;
956                 }
957                 while (testChar(','));
958 
959                 checkChar(']');
960                 value.array = arr;
961                 break;
962 
963             case '"':
964                 auto str = parseString();
965 
966                 // if special float parsing is enabled, check if string represents NaN/Inf
967                 if ((options & JSONOptions.specialFloatLiterals) &&
968                     tryGetSpecialFloat(str, value.store.floating))
969                 {
970                     // found a special float, its value was placed in value.store.floating
971                     value.type_tag = JSON_TYPE.FLOAT;
972                     break;
973                 }
974 
975                 value.type_tag = JSON_TYPE.STRING;
976                 value.store.str = str;
977                 break;
978 
979             case '0': .. case '9':
980             case '-':
981                 auto number = appender!string();
982                 bool isFloat, isNegative;
983 
984                 void readInteger()
985                 {
986                     if (!isDigit(c)) error("Digit expected");
987 
988                 Next: number.put(c);
989 
990                     if (isDigit(peekChar()))
991                     {
992                         c = getChar();
993                         goto Next;
994                     }
995                 }
996 
997                 if (c == '-')
998                 {
999                     number.put('-');
1000                     c = getChar();
1001                     isNegative = true;
1002                 }
1003 
1004                 readInteger();
1005 
1006                 if (testChar('.'))
1007                 {
1008                     isFloat = true;
1009                     number.put('.');
1010                     c = getChar();
1011                     readInteger();
1012                 }
1013                 if (testChar!(false, false)('e'))
1014                 {
1015                     isFloat = true;
1016                     number.put('e');
1017                     if (testChar('+')) number.put('+');
1018                     else if (testChar('-')) number.put('-');
1019                     c = getChar();
1020                     readInteger();
1021                 }
1022 
1023                 string data = number.data;
1024                 if (isFloat)
1025                 {
1026                     value.type_tag = JSON_TYPE.FLOAT;
1027                     value.store.floating = parse!double(data);
1028                 }
1029                 else
1030                 {
1031                     if (isNegative)
1032                         value.store.integer = parse!long(data);
1033                     else
1034                         value.store.uinteger = parse!ulong(data);
1035 
1036                     value.type_tag = !isNegative && value.store.uinteger & (1UL << 63) ?
1037                         JSON_TYPE.UINTEGER : JSON_TYPE.INTEGER;
1038                 }
1039                 break;
1040 
1041             case 't':
1042             case 'T':
1043                 value.type_tag = JSON_TYPE.TRUE;
1044                 checkChar!(false, false)('r');
1045                 checkChar!(false, false)('u');
1046                 checkChar!(false, false)('e');
1047                 break;
1048 
1049             case 'f':
1050             case 'F':
1051                 value.type_tag = JSON_TYPE.FALSE;
1052                 checkChar!(false, false)('a');
1053                 checkChar!(false, false)('l');
1054                 checkChar!(false, false)('s');
1055                 checkChar!(false, false)('e');
1056                 break;
1057 
1058             case 'n':
1059             case 'N':
1060                 value.type_tag = JSON_TYPE.NULL;
1061                 checkChar!(false, false)('u');
1062                 checkChar!(false, false)('l');
1063                 checkChar!(false, false)('l');
1064                 break;
1065 
1066             default:
1067                 error(text("Unexpected character '", c, "'."));
1068         }
1069 
1070         depth--;
1071     }
1072 
1073     parseValue(root);
1074     return root;
1075 }
1076 
1077 @safe unittest
1078 {
1079     enum issue15742objectOfObject = `{ "key1": { "key2": 1 }}`;
1080     static assert(parseJSON(issue15742objectOfObject).type == JSON_TYPE.OBJECT);
1081 
1082     enum issue15742arrayOfArray = `[[1]]`;
1083     static assert(parseJSON(issue15742arrayOfArray).type == JSON_TYPE.ARRAY);
1084 }
1085 
1086 @safe unittest
1087 {
1088     // Ensure we can parse and use JSON from @safe code
1089     auto a = `{ "key1": { "key2": 1 }}`.parseJSON;
1090     assert(a["key1"]["key2"].integer == 1);
1091     assert(a.toString == `{"key1":{"key2":1}}`);
1092 }
1093 
1094 @system unittest
1095 {
1096     // Ensure we can parse JSON from a @system range.
1097     struct Range
1098     {
1099         string s;
1100         size_t index;
1101         @system
1102         {
emptyRange1103             bool empty() { return index >= s.length; }
popFrontRange1104             void popFront() { index++; }
frontRange1105             char front() { return s[index]; }
1106         }
1107     }
1108     auto s = Range(`{ "key1": { "key2": 1 }}`);
1109     auto json = parseJSON(s);
1110     assert(json["key1"]["key2"].integer == 1);
1111 }
1112 
1113 /**
1114 Parses a serialized string and returns a tree of JSON values.
1115 Throws: $(REF JSONException, std,json) if the depth exceeds the max depth.
1116 Params:
1117     json = json-formatted string to parse
1118     options = enable decoding string representations of NaN/Inf as float values
1119 */
1120 JSONValue parseJSON(T)(T json, JSONOptions options)
1121 if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T))
1122 {
1123     return parseJSON!T(json, -1, options);
1124 }
1125 
1126 /**
1127 Takes a tree of JSON values and returns the serialized string.
1128 
1129 Any Object types will be serialized in a key-sorted order.
1130 
1131 If $(D pretty) is false no whitespaces are generated.
1132 If $(D pretty) is true serialized string is formatted to be human-readable.
1133 Set the $(LREF JSONOptions.specialFloatLiterals) flag is set in $(D options) to encode NaN/Infinity as strings.
1134 */
1135 string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions options = JSONOptions.none) @safe
1136 {
1137     auto json = appender!string();
1138 
toStringImpl(Char)1139     void toStringImpl(Char)(string str) @safe
1140     {
1141         json.put('"');
1142 
1143         foreach (Char c; str)
1144         {
1145             switch (c)
1146             {
1147                 case '"':       json.put("\\\"");       break;
1148                 case '\\':      json.put("\\\\");       break;
1149 
1150                 case '/':
1151                     if (!(options & JSONOptions.doNotEscapeSlashes))
1152                         json.put('\\');
1153                     json.put('/');
1154                     break;
1155 
1156                 case '\b':      json.put("\\b");        break;
1157                 case '\f':      json.put("\\f");        break;
1158                 case '\n':      json.put("\\n");        break;
1159                 case '\r':      json.put("\\r");        break;
1160                 case '\t':      json.put("\\t");        break;
1161                 default:
1162                 {
1163                     import std.ascii : isControl;
1164                     import std.utf : encode;
1165 
1166                     // Make sure we do UTF decoding iff we want to
1167                     // escape Unicode characters.
1168                     assert(((options & JSONOptions.escapeNonAsciiChars) != 0)
1169                         == is(Char == dchar));
1170 
1171                     with (JSONOptions) if (isControl(c) ||
1172                         ((options & escapeNonAsciiChars) >= escapeNonAsciiChars && c >= 0x80))
1173                     {
1174                         // Ensure non-BMP characters are encoded as a pair
1175                         // of UTF-16 surrogate characters, as per RFC 4627.
1176                         wchar[2] wchars; // 1 or 2 UTF-16 code units
1177                         size_t wNum = encode(wchars, c); // number of UTF-16 code units
1178                         foreach (wc; wchars[0 .. wNum])
1179                         {
1180                             json.put("\\u");
1181                             foreach_reverse (i; 0 .. 4)
1182                             {
1183                                 char ch = (wc >>> (4 * i)) & 0x0f;
1184                                 ch += ch < 10 ? '0' : 'A' - 10;
1185                                 json.put(ch);
1186                             }
1187                         }
1188                     }
1189                     else
1190                     {
1191                         json.put(c);
1192                     }
1193                 }
1194             }
1195         }
1196 
1197         json.put('"');
1198     }
1199 
toString(string str)1200     void toString(string str) @safe
1201     {
1202         // Avoid UTF decoding when possible, as it is unnecessary when
1203         // processing JSON.
1204         if (options & JSONOptions.escapeNonAsciiChars)
1205             toStringImpl!dchar(str);
1206         else
1207             toStringImpl!char(str);
1208     }
1209 
toValue(ref in JSONValue value,ulong indentLevel)1210     void toValue(ref in JSONValue value, ulong indentLevel) @safe
1211     {
1212         void putTabs(ulong additionalIndent = 0)
1213         {
1214             if (pretty)
1215                 foreach (i; 0 .. indentLevel + additionalIndent)
1216                     json.put("    ");
1217         }
1218         void putEOL()
1219         {
1220             if (pretty)
1221                 json.put('\n');
1222         }
1223         void putCharAndEOL(char ch)
1224         {
1225             json.put(ch);
1226             putEOL();
1227         }
1228 
1229         final switch (value.type)
1230         {
1231             case JSON_TYPE.OBJECT:
1232                 auto obj = value.objectNoRef;
1233                 if (!obj.length)
1234                 {
1235                     json.put("{}");
1236                 }
1237                 else
1238                 {
1239                     putCharAndEOL('{');
1240                     bool first = true;
1241 
1242                     void emit(R)(R names)
1243                     {
1244                         foreach (name; names)
1245                         {
1246                             auto member = obj[name];
1247                             if (!first)
1248                                 putCharAndEOL(',');
1249                             first = false;
1250                             putTabs(1);
1251                             toString(name);
1252                             json.put(':');
1253                             if (pretty)
1254                                 json.put(' ');
1255                             toValue(member, indentLevel + 1);
1256                         }
1257                     }
1258 
1259                     import std.algorithm.sorting : sort;
1260                     // @@@BUG@@@ 14439
1261                     // auto names = obj.keys;  // aa.keys can't be called in @safe code
1262                     auto names = new string[obj.length];
1263                     size_t i = 0;
1264                     foreach (k, v; obj)
1265                     {
1266                         names[i] = k;
1267                         i++;
1268                     }
1269                     sort(names);
1270                     emit(names);
1271 
1272                     putEOL();
1273                     putTabs();
1274                     json.put('}');
1275                 }
1276                 break;
1277 
1278             case JSON_TYPE.ARRAY:
1279                 auto arr = value.arrayNoRef;
1280                 if (arr.empty)
1281                 {
1282                     json.put("[]");
1283                 }
1284                 else
1285                 {
1286                     putCharAndEOL('[');
1287                     foreach (i, el; arr)
1288                     {
1289                         if (i)
1290                             putCharAndEOL(',');
1291                         putTabs(1);
1292                         toValue(el, indentLevel + 1);
1293                     }
1294                     putEOL();
1295                     putTabs();
1296                     json.put(']');
1297                 }
1298                 break;
1299 
1300             case JSON_TYPE.STRING:
1301                 toString(value.str);
1302                 break;
1303 
1304             case JSON_TYPE.INTEGER:
1305                 json.put(to!string(value.store.integer));
1306                 break;
1307 
1308             case JSON_TYPE.UINTEGER:
1309                 json.put(to!string(value.store.uinteger));
1310                 break;
1311 
1312             case JSON_TYPE.FLOAT:
1313                 import std.math : isNaN, isInfinity;
1314 
1315                 auto val = value.store.floating;
1316 
1317                 if (val.isNaN)
1318                 {
1319                     if (options & JSONOptions.specialFloatLiterals)
1320                     {
1321                         toString(JSONFloatLiteral.nan);
1322                     }
1323                     else
1324                     {
1325                         throw new JSONException(
1326                             "Cannot encode NaN. Consider passing the specialFloatLiterals flag.");
1327                     }
1328                 }
1329                 else if (val.isInfinity)
1330                 {
1331                     if (options & JSONOptions.specialFloatLiterals)
1332                     {
1333                         toString((val > 0) ?  JSONFloatLiteral.inf : JSONFloatLiteral.negativeInf);
1334                     }
1335                     else
1336                     {
1337                         throw new JSONException(
1338                             "Cannot encode Infinity. Consider passing the specialFloatLiterals flag.");
1339                     }
1340                 }
1341                 else
1342                 {
1343                     import std.format : format;
1344                     // The correct formula for the number of decimal digits needed for lossless round
1345                     // trips is actually:
1346                     //     ceil(log(pow(2.0, double.mant_dig - 1)) / log(10.0) + 1) == (double.dig + 2)
1347                     // Anything less will round off (1 + double.epsilon)
1348                     json.put("%.18g".format(val));
1349                 }
1350                 break;
1351 
1352             case JSON_TYPE.TRUE:
1353                 json.put("true");
1354                 break;
1355 
1356             case JSON_TYPE.FALSE:
1357                 json.put("false");
1358                 break;
1359 
1360             case JSON_TYPE.NULL:
1361                 json.put("null");
1362                 break;
1363         }
1364     }
1365 
1366     toValue(root, 0);
1367     return json.data;
1368 }
1369 
1370 @safe unittest // bugzilla 12897
1371 {
1372     JSONValue jv0 = JSONValue("test测试");
1373     assert(toJSON(jv0, false, JSONOptions.escapeNonAsciiChars) == `"test\u6D4B\u8BD5"`);
1374     JSONValue jv00 = JSONValue("test\u6D4B\u8BD5");
1375     assert(toJSON(jv00, false, JSONOptions.none) == `"test测试"`);
1376     assert(toJSON(jv0, false, JSONOptions.none) == `"test测试"`);
1377     JSONValue jv1 = JSONValue("été");
1378     assert(toJSON(jv1, false, JSONOptions.escapeNonAsciiChars) == `"\u00E9t\u00E9"`);
1379     JSONValue jv11 = JSONValue("\u00E9t\u00E9");
1380     assert(toJSON(jv11, false, JSONOptions.none) == `"été"`);
1381     assert(toJSON(jv1, false, JSONOptions.none) == `"été"`);
1382 }
1383 
1384 /**
1385 Exception thrown on JSON errors
1386 */
1387 class JSONException : Exception
1388 {
1389     this(string msg, int line = 0, int pos = 0) pure nothrow @safe
1390     {
1391         if (line)
1392             super(text(msg, " (Line ", line, ":", pos, ")"));
1393         else
1394             super(msg);
1395     }
1396 
this(string msg,string file,size_t line)1397     this(string msg, string file, size_t line) pure nothrow @safe
1398     {
1399         super(msg, file, line);
1400     }
1401 }
1402 
1403 
1404 @system unittest
1405 {
1406     import std.exception;
1407     JSONValue jv = "123";
1408     assert(jv.type == JSON_TYPE.STRING);
1409     assertNotThrown(jv.str);
1410     assertThrown!JSONException(jv.integer);
1411     assertThrown!JSONException(jv.uinteger);
1412     assertThrown!JSONException(jv.floating);
1413     assertThrown!JSONException(jv.object);
1414     assertThrown!JSONException(jv.array);
1415     assertThrown!JSONException(jv["aa"]);
1416     assertThrown!JSONException(jv[2]);
1417 
1418     jv = -3;
1419     assert(jv.type == JSON_TYPE.INTEGER);
1420     assertNotThrown(jv.integer);
1421 
1422     jv = cast(uint) 3;
1423     assert(jv.type == JSON_TYPE.UINTEGER);
1424     assertNotThrown(jv.uinteger);
1425 
1426     jv = 3.0;
1427     assert(jv.type == JSON_TYPE.FLOAT);
1428     assertNotThrown(jv.floating);
1429 
1430     jv = ["key" : "value"];
1431     assert(jv.type == JSON_TYPE.OBJECT);
1432     assertNotThrown(jv.object);
1433     assertNotThrown(jv["key"]);
1434     assert("key" in jv);
1435     assert("notAnElement" !in jv);
1436     assertThrown!JSONException(jv["notAnElement"]);
1437     const cjv = jv;
1438     assert("key" in cjv);
1439     assertThrown!JSONException(cjv["notAnElement"]);
1440 
foreach(string key,value;jv)1441     foreach (string key, value; jv)
1442     {
1443         static assert(is(typeof(value) == JSONValue));
1444         assert(key == "key");
1445         assert(value.type == JSON_TYPE.STRING);
1446         assertNotThrown(value.str);
1447         assert(value.str == "value");
1448     }
1449 
1450     jv = [3, 4, 5];
1451     assert(jv.type == JSON_TYPE.ARRAY);
1452     assertNotThrown(jv.array);
1453     assertNotThrown(jv[2]);
foreach(size_t index,value;jv)1454     foreach (size_t index, value; jv)
1455     {
1456         static assert(is(typeof(value) == JSONValue));
1457         assert(value.type == JSON_TYPE.INTEGER);
1458         assertNotThrown(value.integer);
1459         assert(index == (value.integer-3));
1460     }
1461 
1462     jv = null;
1463     assert(jv.type == JSON_TYPE.NULL);
1464     assert(jv.isNull);
1465     jv = "foo";
1466     assert(!jv.isNull);
1467 
1468     jv = JSONValue("value");
1469     assert(jv.type == JSON_TYPE.STRING);
1470     assert(jv.str == "value");
1471 
1472     JSONValue jv2 = JSONValue("value");
1473     assert(jv2.type == JSON_TYPE.STRING);
1474     assert(jv2.str == "value");
1475 
1476     JSONValue jv3 = JSONValue("\u001c");
1477     assert(jv3.type == JSON_TYPE.STRING);
1478     assert(jv3.str == "\u001C");
1479 }
1480 
1481 @system unittest
1482 {
1483     // Bugzilla 11504
1484 
1485     JSONValue jv = 1;
1486     assert(jv.type == JSON_TYPE.INTEGER);
1487 
1488     jv.str = "123";
1489     assert(jv.type == JSON_TYPE.STRING);
1490     assert(jv.str == "123");
1491 
1492     jv.integer = 1;
1493     assert(jv.type == JSON_TYPE.INTEGER);
1494     assert(jv.integer == 1);
1495 
1496     jv.uinteger = 2u;
1497     assert(jv.type == JSON_TYPE.UINTEGER);
1498     assert(jv.uinteger == 2u);
1499 
1500     jv.floating = 1.5;
1501     assert(jv.type == JSON_TYPE.FLOAT);
1502     assert(jv.floating == 1.5);
1503 
1504     jv.object = ["key" : JSONValue("value")];
1505     assert(jv.type == JSON_TYPE.OBJECT);
1506     assert(jv.object == ["key" : JSONValue("value")]);
1507 
1508     jv.array = [JSONValue(1), JSONValue(2), JSONValue(3)];
1509     assert(jv.type == JSON_TYPE.ARRAY);
1510     assert(jv.array == [JSONValue(1), JSONValue(2), JSONValue(3)]);
1511 
1512     jv = true;
1513     assert(jv.type == JSON_TYPE.TRUE);
1514 
1515     jv = false;
1516     assert(jv.type == JSON_TYPE.FALSE);
1517 
1518     enum E{True = true}
1519     jv = E.True;
1520     assert(jv.type == JSON_TYPE.TRUE);
1521 }
1522 
1523 @system pure unittest
1524 {
1525     // Adding new json element via array() / object() directly
1526 
1527     JSONValue jarr = JSONValue([10]);
1528     foreach (i; 0 .. 9)
1529         jarr.array ~= JSONValue(i);
1530     assert(jarr.array.length == 10);
1531 
1532     JSONValue jobj = JSONValue(["key" : JSONValue("value")]);
1533     foreach (i; 0 .. 9)
1534         jobj.object[text("key", i)] = JSONValue(text("value", i));
1535     assert(jobj.object.length == 10);
1536 }
1537 
1538 @system pure unittest
1539 {
1540     // Adding new json element without array() / object() access
1541 
1542     JSONValue jarr = JSONValue([10]);
1543     foreach (i; 0 .. 9)
1544         jarr ~= [JSONValue(i)];
1545     assert(jarr.array.length == 10);
1546 
1547     JSONValue jobj = JSONValue(["key" : JSONValue("value")]);
1548     foreach (i; 0 .. 9)
1549         jobj[text("key", i)] = JSONValue(text("value", i));
1550     assert(jobj.object.length == 10);
1551 
1552     // No array alias
1553     auto jarr2 = jarr ~ [1,2,3];
1554     jarr2[0] = 999;
1555     assert(jarr[0] == JSONValue(10));
1556 }
1557 
1558 @system unittest
1559 {
1560     // @system because JSONValue.array is @system
1561     import std.exception;
1562 
1563     // An overly simple test suite, if it can parse a serializated string and
1564     // then use the resulting values tree to generate an identical
1565     // serialization, both the decoder and encoder works.
1566 
1567     auto jsons = [
1568         `null`,
1569         `true`,
1570         `false`,
1571         `0`,
1572         `123`,
1573         `-4321`,
1574         `0.25`,
1575         `-0.25`,
1576         `""`,
1577         `"hello\nworld"`,
1578         `"\"\\\/\b\f\n\r\t"`,
1579         `[]`,
1580         `[12,"foo",true,false]`,
1581         `{}`,
1582         `{"a":1,"b":null}`,
1583         `{"goodbye":[true,"or",false,["test",42,{"nested":{"a":23.5,"b":0.140625}}]],`
1584         ~`"hello":{"array":[12,null,{}],"json":"is great"}}`,
1585     ];
1586 
1587     enum dbl1_844 = `1.8446744073709568`;
1588     version (MinGW)
1589         jsons ~= dbl1_844 ~ `e+019`;
1590     else
1591         jsons ~= dbl1_844 ~ `e+19`;
1592 
1593     JSONValue val;
1594     string result;
foreach(json;jsons)1595     foreach (json; jsons)
1596     {
1597         try
1598         {
1599             val = parseJSON(json);
1600             enum pretty = false;
1601             result = toJSON(val, pretty);
1602             assert(result == json, text(result, " should be ", json));
1603         }
1604         catch (JSONException e)
1605         {
1606             import std.stdio : writefln;
1607             writefln(text(json, "\n", e.toString()));
1608         }
1609     }
1610 
1611     // Should be able to correctly interpret unicode entities
1612     val = parseJSON(`"\u003C\u003E"`);
1613     assert(toJSON(val) == "\"\&lt;\&gt;\"");
1614     assert(val.to!string() == "\"\&lt;\&gt;\"");
1615     val = parseJSON(`"\u0391\u0392\u0393"`);
1616     assert(toJSON(val) == "\"\&Alpha;\&Beta;\&Gamma;\"");
1617     assert(val.to!string() == "\"\&Alpha;\&Beta;\&Gamma;\"");
1618     val = parseJSON(`"\u2660\u2666"`);
1619     assert(toJSON(val) == "\"\&spades;\&diams;\"");
1620     assert(val.to!string() == "\"\&spades;\&diams;\"");
1621 
1622     //0x7F is a control character (see Unicode spec)
1623     val = parseJSON(`"\u007F"`);
1624     assert(toJSON(val) == "\"\\u007F\"");
1625     assert(val.to!string() == "\"\\u007F\"");
1626 
1627     with(parseJSON(`""`))
1628         assert(str == "" && str !is null);
1629     with(parseJSON(`[]`))
1630         assert(!array.length);
1631 
1632     // Formatting
1633     val = parseJSON(`{"a":[null,{"x":1},{},[]]}`);
1634     assert(toJSON(val, true) == `{
1635     "a": [
1636         null,
1637         {
1638             "x": 1
1639         },
1640         {},
1641         []
1642     ]
1643 }`);
1644 }
1645 
1646 @safe unittest
1647 {
1648   auto json = `"hello\nworld"`;
1649   const jv = parseJSON(json);
1650   assert(jv.toString == json);
1651   assert(jv.toPrettyString == json);
1652 }
1653 
1654 @system pure unittest
1655 {
1656     // Bugzilla 12969
1657 
1658     JSONValue jv;
1659     jv["int"] = 123;
1660 
1661     assert(jv.type == JSON_TYPE.OBJECT);
1662     assert("int" in jv);
1663     assert(jv["int"].integer == 123);
1664 
1665     jv["array"] = [1, 2, 3, 4, 5];
1666 
1667     assert(jv["array"].type == JSON_TYPE.ARRAY);
1668     assert(jv["array"][2].integer == 3);
1669 
1670     jv["str"] = "D language";
1671     assert(jv["str"].type == JSON_TYPE.STRING);
1672     assert(jv["str"].str == "D language");
1673 
1674     jv["bool"] = false;
1675     assert(jv["bool"].type == JSON_TYPE.FALSE);
1676 
1677     assert(jv.object.length == 4);
1678 
1679     jv = [5, 4, 3, 2, 1];
1680     assert( jv.type == JSON_TYPE.ARRAY );
1681     assert( jv[3].integer == 2 );
1682 }
1683 
1684 @safe unittest
1685 {
1686     auto s = q"EOF
1687 [
1688   1,
1689   2,
1690   3,
1691   potato
1692 ]
1693 EOF";
1694 
1695     import std.exception;
1696 
1697     auto e = collectException!JSONException(parseJSON(s));
1698     assert(e.msg == "Unexpected character 'p'. (Line 5:3)", e.msg);
1699 }
1700 
1701 // handling of special float values (NaN, Inf, -Inf)
1702 @safe unittest
1703 {
1704     import std.exception : assertThrown;
1705     import std.math : isNaN, isInfinity;
1706 
1707     // expected representations of NaN and Inf
1708     enum {
1709         nanString         = '"' ~ JSONFloatLiteral.nan         ~ '"',
1710         infString         = '"' ~ JSONFloatLiteral.inf         ~ '"',
1711         negativeInfString = '"' ~ JSONFloatLiteral.negativeInf ~ '"',
1712     }
1713 
1714     // with the specialFloatLiterals option, encode NaN/Inf as strings
1715     assert(JSONValue(float.nan).toString(JSONOptions.specialFloatLiterals)       == nanString);
1716     assert(JSONValue(double.infinity).toString(JSONOptions.specialFloatLiterals) == infString);
1717     assert(JSONValue(-real.infinity).toString(JSONOptions.specialFloatLiterals)  == negativeInfString);
1718 
1719     // without the specialFloatLiterals option, throw on encoding NaN/Inf
1720     assertThrown!JSONException(JSONValue(float.nan).toString);
1721     assertThrown!JSONException(JSONValue(double.infinity).toString);
1722     assertThrown!JSONException(JSONValue(-real.infinity).toString);
1723 
1724     // when parsing json with specialFloatLiterals option, decode special strings as floats
1725     JSONValue jvNan    = parseJSON(nanString, JSONOptions.specialFloatLiterals);
1726     JSONValue jvInf    = parseJSON(infString, JSONOptions.specialFloatLiterals);
1727     JSONValue jvNegInf = parseJSON(negativeInfString, JSONOptions.specialFloatLiterals);
1728 
1729     assert(jvNan.floating.isNaN);
1730     assert(jvInf.floating.isInfinity    && jvInf.floating > 0);
1731     assert(jvNegInf.floating.isInfinity && jvNegInf.floating < 0);
1732 
1733     // when parsing json without the specialFloatLiterals option, decode special strings as strings
1734     jvNan    = parseJSON(nanString);
1735     jvInf    = parseJSON(infString);
1736     jvNegInf = parseJSON(negativeInfString);
1737 
1738     assert(jvNan.str    == JSONFloatLiteral.nan);
1739     assert(jvInf.str    == JSONFloatLiteral.inf);
1740     assert(jvNegInf.str == JSONFloatLiteral.negativeInf);
1741 }
1742 
1743 pure nothrow @safe @nogc unittest
1744 {
1745     JSONValue testVal;
1746     testVal = "test";
1747     testVal = 10;
1748     testVal = 10u;
1749     testVal = 1.0;
1750     testVal = (JSONValue[string]).init;
1751     testVal = JSONValue[].init;
1752     testVal = null;
1753     assert(testVal.isNull);
1754 }
1755 
1756 pure nothrow @safe unittest // issue 15884
1757 {
1758     import std.typecons;
Test(C)1759     void Test(C)() {
1760         C[] a = ['x'];
1761         JSONValue testVal = a;
1762         assert(testVal.type == JSON_TYPE.STRING);
1763         testVal = a.idup;
1764         assert(testVal.type == JSON_TYPE.STRING);
1765     }
1766     Test!char();
1767     Test!wchar();
1768     Test!dchar();
1769 }
1770 
1771 @safe unittest // issue 15885
1772 {
1773     enum bool realInDoublePrecision = real.mant_dig == double.mant_dig;
1774 
test(const double num0)1775     static bool test(const double num0)
1776     {
1777         import std.math : feqrel;
1778         const json0 = JSONValue(num0);
1779         const num1 = to!double(toJSON(json0));
1780         static if (realInDoublePrecision)
1781             return feqrel(num1, num0) >= (double.mant_dig - 1);
1782         else
1783             return num1 == num0;
1784     }
1785 
1786     assert(test( 0.23));
1787     assert(test(-0.23));
1788     assert(test(1.223e+24));
1789     assert(test(23.4));
1790     assert(test(0.0012));
1791     assert(test(30738.22));
1792 
1793     assert(test(1 + double.epsilon));
1794     assert(test(double.min_normal));
1795     static if (realInDoublePrecision)
1796         assert(test(-double.max / 2));
1797     else
1798         assert(test(-double.max));
1799 
1800     const minSub = double.min_normal * double.epsilon;
1801     assert(test(minSub));
1802     assert(test(3*minSub));
1803 }
1804 
1805 @safe unittest // issue 17555
1806 {
1807     import std.exception : assertThrown;
1808 
1809     assertThrown!JSONException(parseJSON("\"a\nb\""));
1810 }
1811 
1812 @safe unittest // issue 17556
1813 {
1814     auto v = JSONValue("\U0001D11E");
1815     auto j = toJSON(v, false, JSONOptions.escapeNonAsciiChars);
1816     assert(j == `"\uD834\uDD1E"`);
1817 }
1818 
1819 @safe unittest // issue 5904
1820 {
1821     string s = `"\uD834\uDD1E"`;
1822     auto j = parseJSON(s);
1823     assert(j.str == "\U0001D11E");
1824 }
1825 
1826 @safe unittest // issue 17557
1827 {
1828     assert(parseJSON("\"\xFF\"").str == "\xFF");
1829     assert(parseJSON("\"\U0001D11E\"").str == "\U0001D11E");
1830 }
1831 
1832 @safe unittest // issue 17553
1833 {
1834     auto v = JSONValue("\xFF");
1835     assert(toJSON(v) == "\"\xFF\"");
1836 }
1837 
1838 @safe unittest
1839 {
1840     import std.utf;
1841     assert(parseJSON("\"\xFF\"".byChar).str == "\xFF");
1842     assert(parseJSON("\"\U0001D11E\"".byChar).str == "\U0001D11E");
1843 }
1844 
1845 @safe unittest // JSONOptions.doNotEscapeSlashes (issue 17587)
1846 {
1847     assert(parseJSON(`"/"`).toString == `"\/"`);
1848     assert(parseJSON(`"\/"`).toString == `"\/"`);
1849     assert(parseJSON(`"/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`);
1850     assert(parseJSON(`"\/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`);
1851 }
1852