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