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) == "\"\<\>\""); 1614*627f7eb2Smrg assert(val.to!string() == "\"\<\>\""); 1615*627f7eb2Smrg val = parseJSON(`"\u0391\u0392\u0393"`); 1616*627f7eb2Smrg assert(toJSON(val) == "\"\Α\Β\Γ\""); 1617*627f7eb2Smrg assert(val.to!string() == "\"\Α\Β\Γ\""); 1618*627f7eb2Smrg val = parseJSON(`"\u2660\u2666"`); 1619*627f7eb2Smrg assert(toJSON(val) == "\"\♠\♦\""); 1620*627f7eb2Smrg assert(val.to!string() == "\"\♠\♦\""); 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