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