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