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