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/), $(LINK http://seriot.ch/parsing_json.html) 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; 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() == JSONType.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 strictParsing = 0x8, /// Strictly follow RFC-8259 grammar when parsing 81 } 82 83 /** 84 JSON type enumeration 85 */ 86 enum JSONType : byte 87 { 88 /// Indicates the type of a `JSONValue`. 89 null_, 90 string, /// ditto 91 integer, /// ditto 92 uinteger, /// ditto 93 float_, /// ditto 94 array, /// ditto 95 object, /// ditto 96 true_, /// ditto 97 false_, /// ditto 98 // FIXME: Find some way to deprecate the enum members below, which does NOT 99 // create lots of spam-like deprecation warnings, which can't be fixed 100 // by the user. See discussion on this issue at 101 // https://forum.dlang.org/post/feudrhtxkaxxscwhhhff@forum.dlang.org 102 /* deprecated("Use .null_") */ NULL = null_, 103 /* deprecated("Use .string") */ STRING = string, 104 /* deprecated("Use .integer") */ INTEGER = integer, 105 /* deprecated("Use .uinteger") */ UINTEGER = uinteger, 106 /* deprecated("Use .float_") */ FLOAT = float_, 107 /* deprecated("Use .array") */ ARRAY = array, 108 /* deprecated("Use .object") */ OBJECT = object, 109 /* deprecated("Use .true_") */ TRUE = true_, 110 /* deprecated("Use .false_") */ FALSE = false_, 111 } 112 113 deprecated("Use JSONType and the new enum member names") alias JSON_TYPE = JSONType; 114 115 /** 116 JSON value node 117 */ 118 struct JSONValue 119 { 120 import std.exception : enforce; 121 122 union Store 123 { 124 string str; 125 long integer; 126 ulong uinteger; 127 double floating; 128 JSONValue[string] object; 129 JSONValue[] array; 130 } 131 private Store store; 132 private JSONType type_tag; 133 134 /** 135 Returns the JSONType of the value stored in this structure. 136 */ typeJSONValue137 @property JSONType type() const pure nothrow @safe @nogc 138 { 139 return type_tag; 140 } 141 /// 142 @safe unittest 143 { 144 string s = "{ \"language\": \"D\" }"; 145 JSONValue j = parseJSON(s); 146 assert(j.type == JSONType.object); 147 assert(j["language"].type == JSONType.string); 148 } 149 150 /*** 151 * Value getter/setter for `JSONType.string`. 152 * Throws: `JSONException` for read access if `type` is not 153 * `JSONType.string`. 154 */ 155 @property string str() const pure @trusted return scope 156 { 157 enforce!JSONException(type == JSONType.string, 158 "JSONValue is not a string"); 159 return store.str; 160 } 161 /// ditto 162 @property string str(return scope string v) pure nothrow @nogc @trusted return // TODO make @safe 163 { 164 assign(v); 165 return v; 166 } 167 /// 168 @safe unittest 169 { 170 JSONValue j = [ "language": "D" ]; 171 172 // get value 173 assert(j["language"].str == "D"); 174 175 // change existing key to new string 176 j["language"].str = "Perl"; 177 assert(j["language"].str == "Perl"); 178 } 179 180 /*** 181 * Value getter/setter for `JSONType.integer`. 182 * Throws: `JSONException` for read access if `type` is not 183 * `JSONType.integer`. 184 */ 185 @property long integer() const pure @safe 186 { 187 enforce!JSONException(type == JSONType.integer, 188 "JSONValue is not an integer"); 189 return store.integer; 190 } 191 /// ditto 192 @property long integer(long v) pure nothrow @safe @nogc 193 { 194 assign(v); 195 return store.integer; 196 } 197 198 /*** 199 * Value getter/setter for `JSONType.uinteger`. 200 * Throws: `JSONException` for read access if `type` is not 201 * `JSONType.uinteger`. 202 */ 203 @property ulong uinteger() const pure @safe 204 { 205 enforce!JSONException(type == JSONType.uinteger, 206 "JSONValue is not an unsigned integer"); 207 return store.uinteger; 208 } 209 /// ditto 210 @property ulong uinteger(ulong v) pure nothrow @safe @nogc 211 { 212 assign(v); 213 return store.uinteger; 214 } 215 216 /*** 217 * Value getter/setter for `JSONType.float_`. Note that despite 218 * the name, this is a $(B 64)-bit `double`, not a 32-bit `float`. 219 * Throws: `JSONException` for read access if `type` is not 220 * `JSONType.float_`. 221 */ 222 @property double floating() const pure @safe 223 { 224 enforce!JSONException(type == JSONType.float_, 225 "JSONValue is not a floating type"); 226 return store.floating; 227 } 228 /// ditto 229 @property double floating(double v) pure nothrow @safe @nogc 230 { 231 assign(v); 232 return store.floating; 233 } 234 235 /*** 236 * Value getter/setter for boolean stored in JSON. 237 * Throws: `JSONException` for read access if `this.type` is not 238 * `JSONType.true_` or `JSONType.false_`. 239 */ 240 @property bool boolean() const pure @safe 241 { 242 if (type == JSONType.true_) return true; 243 if (type == JSONType.false_) return false; 244 245 throw new JSONException("JSONValue is not a boolean type"); 246 } 247 /// ditto 248 @property bool boolean(bool v) pure nothrow @safe @nogc 249 { 250 assign(v); 251 return v; 252 } 253 /// 254 @safe unittest 255 { 256 JSONValue j = true; 257 assert(j.boolean == true); 258 259 j.boolean = false; 260 assert(j.boolean == false); 261 262 j.integer = 12; 263 import std.exception : assertThrown; 264 assertThrown!JSONException(j.boolean); 265 } 266 267 /*** 268 * Value getter/setter for `JSONType.object`. 269 * Throws: `JSONException` for read access if `type` is not 270 * `JSONType.object`. 271 * Note: this is @system because of the following pattern: 272 --- 273 auto a = &(json.object()); 274 json.uinteger = 0; // overwrite AA pointer 275 (*a)["hello"] = "world"; // segmentation fault 276 --- 277 */ 278 @property ref inout(JSONValue[string]) object() inout pure @system return 279 { 280 enforce!JSONException(type == JSONType.object, 281 "JSONValue is not an object"); 282 return store.object; 283 } 284 /// ditto 285 @property JSONValue[string] object(return scope JSONValue[string] v) pure nothrow @nogc @trusted // TODO make @safe 286 { 287 assign(v); 288 return v; 289 } 290 291 /*** 292 * Value getter for `JSONType.object`. 293 * Unlike `object`, this retrieves the object by value and can be used in @safe code. 294 * 295 * A caveat is that, if the returned value is null, modifications will not be visible: 296 * --- 297 * JSONValue json; 298 * json.object = null; 299 * json.objectNoRef["hello"] = JSONValue("world"); 300 * assert("hello" !in json.object); 301 * --- 302 * 303 * Throws: `JSONException` for read access if `type` is not 304 * `JSONType.object`. 305 */ 306 @property inout(JSONValue[string]) objectNoRef() inout pure @trusted 307 { 308 enforce!JSONException(type == JSONType.object, 309 "JSONValue is not an object"); 310 return store.object; 311 } 312 313 /*** 314 * Value getter/setter for `JSONType.array`. 315 * Throws: `JSONException` for read access if `type` is not 316 * `JSONType.array`. 317 * Note: this is @system because of the following pattern: 318 --- 319 auto a = &(json.array()); 320 json.uinteger = 0; // overwrite array pointer 321 (*a)[0] = "world"; // segmentation fault 322 --- 323 */ 324 @property ref inout(JSONValue[]) array() scope return inout pure @system 325 { 326 enforce!JSONException(type == JSONType.array, 327 "JSONValue is not an array"); 328 return store.array; 329 } 330 /// ditto 331 @property JSONValue[] array(return scope JSONValue[] v) pure nothrow @nogc @trusted scope // TODO make @safe 332 { 333 assign(v); 334 return v; 335 } 336 337 /*** 338 * Value getter for `JSONType.array`. 339 * Unlike `array`, this retrieves the array by value and can be used in @safe code. 340 * 341 * A caveat is that, if you append to the returned array, the new values aren't visible in the 342 * JSONValue: 343 * --- 344 * JSONValue json; 345 * json.array = [JSONValue("hello")]; 346 * json.arrayNoRef ~= JSONValue("world"); 347 * assert(json.array.length == 1); 348 * --- 349 * 350 * Throws: `JSONException` for read access if `type` is not 351 * `JSONType.array`. 352 */ 353 @property inout(JSONValue[]) arrayNoRef() inout pure @trusted 354 { 355 enforce!JSONException(type == JSONType.array, 356 "JSONValue is not an array"); 357 return store.array; 358 } 359 360 /// Test whether the type is `JSONType.null_` 361 @property bool isNull() const pure nothrow @safe @nogc 362 { 363 return type == JSONType.null_; 364 } 365 366 /*** 367 * Generic type value getter 368 * A convenience getter that returns this `JSONValue` as the specified D type. 369 * Note: only numeric, `bool`, `string`, `JSONValue[string]` and `JSONValue[]` types are accepted 370 * Throws: `JSONException` if `T` cannot hold the contents of this `JSONValue` 371 * `ConvException` in case of integer overflow when converting to `T` 372 */ 373 @property inout(T) get(T)() inout const pure @safe 374 { 375 static if (is(immutable T == immutable string)) 376 { 377 return str; 378 } 379 else static if (is(immutable T == immutable bool)) 380 { 381 return boolean; 382 } 383 else static if (isFloatingPoint!T) 384 { 385 switch (type) 386 { 387 case JSONType.float_: 388 return cast(T) floating; 389 case JSONType.uinteger: 390 return cast(T) uinteger; 391 case JSONType.integer: 392 return cast(T) integer; 393 default: 394 throw new JSONException("JSONValue is not a number type"); 395 } 396 } 397 else static if (isIntegral!T) 398 { 399 switch (type) 400 { 401 case JSONType.uinteger: 402 return uinteger.to!T; 403 case JSONType.integer: 404 return integer.to!T; 405 default: 406 throw new JSONException("JSONValue is not a an integral type"); 407 } 408 } 409 else 410 { 411 static assert(false, "Unsupported type"); 412 } 413 } 414 // This specialization is needed because arrayNoRef requires inout 415 @property inout(T) get(T : JSONValue[])() inout pure @trusted /// ditto 416 { 417 return arrayNoRef; 418 } 419 /// ditto 420 @property inout(T) get(T : JSONValue[string])() inout pure @trusted 421 { 422 return object; 423 } 424 /// 425 @safe unittest 426 { 427 import std.exception; 428 import std.conv; 429 string s = 430 `{ 431 "a": 123, 432 "b": 3.1415, 433 "c": "text", 434 "d": true, 435 "e": [1, 2, 3], 436 "f": { "a": 1 }, 437 "g": -45, 438 "h": ` ~ ulong.max.to!string ~ `, 439 }`; 440 441 struct a { } 442 443 immutable json = parseJSON(s); 444 assert(json["a"].get!double == 123.0); 445 assert(json["a"].get!int == 123); 446 assert(json["a"].get!uint == 123); 447 assert(json["b"].get!double == 3.1415); 448 assertThrown!JSONException(json["b"].get!int); 449 assert(json["c"].get!string == "text"); 450 assert(json["d"].get!bool == true); 451 assertNotThrown(json["e"].get!(JSONValue[])); 452 assertNotThrown(json["f"].get!(JSONValue[string])); 453 static assert(!__traits(compiles, json["a"].get!a)); 454 assertThrown!JSONException(json["e"].get!float); 455 assertThrown!JSONException(json["d"].get!(JSONValue[string])); 456 assertThrown!JSONException(json["f"].get!(JSONValue[])); 457 assert(json["g"].get!int == -45); 458 assertThrown!ConvException(json["g"].get!uint); 459 assert(json["h"].get!ulong == ulong.max); 460 assertThrown!ConvException(json["h"].get!uint); 461 assertNotThrown(json["h"].get!float); 462 } 463 464 private void assign(T)(T arg) 465 { 466 static if (is(T : typeof(null))) 467 { 468 type_tag = JSONType.null_; 469 } 470 else static if (is(T : string)) 471 { 472 type_tag = JSONType.string; 473 string t = arg; 474 () @trusted { store.str = t; }(); 475 } 476 // https://issues.dlang.org/show_bug.cgi?id=15884 477 else static if (isSomeString!T) 478 { 479 type_tag = JSONType.string; 480 // FIXME: std.Array.Array(Range) is not deduced as 'pure' 481 () @trusted { 482 import std.utf : byUTF; 483 store.str = cast(immutable)(arg.byUTF!char.array); 484 }(); 485 } 486 else static if (is(T : bool)) 487 { 488 type_tag = arg ? JSONType.true_ : JSONType.false_; 489 } 490 else static if (is(T : ulong) && isUnsigned!T) 491 { 492 type_tag = JSONType.uinteger; 493 store.uinteger = arg; 494 } 495 else static if (is(T : long)) 496 { 497 type_tag = JSONType.integer; 498 store.integer = arg; 499 } 500 else static if (isFloatingPoint!T) 501 { 502 type_tag = JSONType.float_; 503 store.floating = arg; 504 } 505 else static if (is(T : Value[Key], Key, Value)) 506 { 507 static assert(is(Key : string), "AA key must be string"); 508 type_tag = JSONType.object; 509 static if (is(Value : JSONValue)) 510 { 511 JSONValue[string] t = arg; 512 () @trusted { store.object = t; }(); 513 } 514 else 515 { 516 JSONValue[string] aa; 517 foreach (key, value; arg) 518 aa[key] = JSONValue(value); 519 () @trusted { store.object = aa; }(); 520 } 521 } 522 else static if (isArray!T) 523 { 524 type_tag = JSONType.array; 525 static if (is(ElementEncodingType!T : JSONValue)) 526 { 527 JSONValue[] t = arg; 528 () @trusted { store.array = t; }(); 529 } 530 else 531 { 532 JSONValue[] new_arg = new JSONValue[arg.length]; 533 foreach (i, e; arg) 534 new_arg[i] = JSONValue(e); 535 () @trusted { store.array = new_arg; }(); 536 } 537 } 538 else static if (is(T : JSONValue)) 539 { 540 type_tag = arg.type; 541 store = arg.store; 542 } 543 else 544 { 545 static assert(false, text(`unable to convert type "`, T.stringof, `" to json`)); 546 } 547 } 548 549 private void assignRef(T)(ref T arg) if (isStaticArray!T) 550 { 551 type_tag = JSONType.array; 552 static if (is(ElementEncodingType!T : JSONValue)) 553 { 554 store.array = arg; 555 } 556 else 557 { 558 JSONValue[] new_arg = new JSONValue[arg.length]; 559 foreach (i, e; arg) 560 new_arg[i] = JSONValue(e); 561 store.array = new_arg; 562 } 563 } 564 565 /** 566 * Constructor for `JSONValue`. If `arg` is a `JSONValue` 567 * its value and type will be copied to the new `JSONValue`. 568 * Note that this is a shallow copy: if type is `JSONType.object` 569 * or `JSONType.array` then only the reference to the data will 570 * be copied. 571 * Otherwise, `arg` must be implicitly convertible to one of the 572 * following types: `typeof(null)`, `string`, `ulong`, 573 * `long`, `double`, an associative array `V[K]` for any `V` 574 * and `K` i.e. a JSON object, any array or `bool`. The type will 575 * be set accordingly. 576 */ 577 this(T)(T arg) if (!isStaticArray!T) 578 { 579 assign(arg); 580 } 581 /// Ditto 582 this(T)(ref T arg) if (isStaticArray!T) 583 { 584 assignRef(arg); 585 } 586 /// Ditto 587 this(T : JSONValue)(inout T arg) inout 588 { 589 store = arg.store; 590 type_tag = arg.type; 591 } 592 /// 593 @safe unittest 594 { 595 JSONValue j = JSONValue( "a string" ); 596 j = JSONValue(42); 597 598 j = JSONValue( [1, 2, 3] ); 599 assert(j.type == JSONType.array); 600 601 j = JSONValue( ["language": "D"] ); 602 assert(j.type == JSONType.object); 603 } 604 605 void opAssign(T)(T arg) if (!isStaticArray!T && !is(T : JSONValue)) 606 { 607 assign(arg); 608 } 609 610 void opAssign(T)(ref T arg) if (isStaticArray!T) 611 { 612 assignRef(arg); 613 } 614 615 /*** 616 * Array syntax for json arrays. 617 * Throws: `JSONException` if `type` is not `JSONType.array`. 618 */ 619 ref inout(JSONValue) opIndex(size_t i) inout pure @safe 620 { 621 auto a = this.arrayNoRef; 622 enforce!JSONException(i < a.length, 623 "JSONValue array index is out of range"); 624 return a[i]; 625 } 626 /// 627 @safe unittest 628 { 629 JSONValue j = JSONValue( [42, 43, 44] ); 630 assert( j[0].integer == 42 ); 631 assert( j[1].integer == 43 ); 632 } 633 634 /*** 635 * Hash syntax for json objects. 636 * Throws: `JSONException` if `type` is not `JSONType.object`. 637 */ 638 ref inout(JSONValue) opIndex(return scope string k) inout pure @safe 639 { 640 auto o = this.objectNoRef; 641 return *enforce!JSONException(k in o, 642 "Key not found: " ~ k); 643 } 644 /// 645 @safe unittest 646 { 647 JSONValue j = JSONValue( ["language": "D"] ); 648 assert( j["language"].str == "D" ); 649 } 650 651 /*** 652 * Operator sets `value` for element of JSON object by `key`. 653 * 654 * If JSON value is null, then operator initializes it with object and then 655 * sets `value` for it. 656 * 657 * Throws: `JSONException` if `type` is not `JSONType.object` 658 * or `JSONType.null_`. 659 */ 660 void opIndexAssign(T)(auto ref T value, string key) 661 { 662 enforce!JSONException(type == JSONType.object || type == JSONType.null_, 663 "JSONValue must be object or null"); 664 JSONValue[string] aa = null; 665 if (type == JSONType.object) 666 { 667 aa = this.objectNoRef; 668 } 669 670 aa[key] = value; 671 this.object = aa; 672 } 673 /// 674 @safe unittest 675 { 676 JSONValue j = JSONValue( ["language": "D"] ); 677 j["language"].str = "Perl"; 678 assert( j["language"].str == "Perl" ); 679 } 680 681 void opIndexAssign(T)(T arg, size_t i) 682 { 683 auto a = this.arrayNoRef; 684 enforce!JSONException(i < a.length, 685 "JSONValue array index is out of range"); 686 a[i] = arg; 687 this.array = a; 688 } 689 /// 690 @safe unittest 691 { 692 JSONValue j = JSONValue( ["Perl", "C"] ); 693 j[1].str = "D"; 694 assert( j[1].str == "D" ); 695 } 696 697 JSONValue opBinary(string op : "~", T)(T arg) 698 { 699 auto a = this.arrayNoRef; 700 static if (isArray!T) 701 { 702 return JSONValue(a ~ JSONValue(arg).arrayNoRef); 703 } 704 else static if (is(T : JSONValue)) 705 { 706 return JSONValue(a ~ arg.arrayNoRef); 707 } 708 else 709 { 710 static assert(false, "argument is not an array or a JSONValue array"); 711 } 712 } 713 714 void opOpAssign(string op : "~", T)(T arg) 715 { 716 auto a = this.arrayNoRef; 717 static if (isArray!T) 718 { 719 a ~= JSONValue(arg).arrayNoRef; 720 } 721 else static if (is(T : JSONValue)) 722 { 723 a ~= arg.arrayNoRef; 724 } 725 else 726 { 727 static assert(false, "argument is not an array or a JSONValue array"); 728 } 729 this.array = a; 730 } 731 732 /** 733 * Support for the `in` operator. 734 * 735 * Tests wether a key can be found in an object. 736 * 737 * Returns: 738 * when found, the `inout(JSONValue)*` that matches to the key, 739 * otherwise `null`. 740 * 741 * Throws: `JSONException` if the right hand side argument `JSONType` 742 * is not `object`. 743 */ 744 inout(JSONValue)* opBinaryRight(string op : "in")(string k) inout @safe 745 { 746 return k in this.objectNoRef; 747 } 748 /// 749 @safe unittest 750 { 751 JSONValue j = [ "language": "D", "author": "walter" ]; 752 string a = ("author" in j).str; 753 *("author" in j) = "Walter"; 754 assert(j["author"].str == "Walter"); 755 } 756 757 bool opEquals(const JSONValue rhs) const @nogc nothrow pure @safe 758 { 759 return opEquals(rhs); 760 } 761 762 bool opEquals(ref const JSONValue rhs) const @nogc nothrow pure @trusted 763 { 764 // Default doesn't work well since store is a union. Compare only 765 // what should be in store. 766 // This is @trusted to remain nogc, nothrow, fast, and usable from @safe code. 767 768 final switch (type_tag) 769 { 770 case JSONType.integer: 771 switch (rhs.type_tag) 772 { 773 case JSONType.integer: 774 return store.integer == rhs.store.integer; 775 case JSONType.uinteger: 776 return store.integer == rhs.store.uinteger; 777 case JSONType.float_: 778 return store.integer == rhs.store.floating; 779 default: 780 return false; 781 } 782 case JSONType.uinteger: 783 switch (rhs.type_tag) 784 { 785 case JSONType.integer: 786 return store.uinteger == rhs.store.integer; 787 case JSONType.uinteger: 788 return store.uinteger == rhs.store.uinteger; 789 case JSONType.float_: 790 return store.uinteger == rhs.store.floating; 791 default: 792 return false; 793 } 794 case JSONType.float_: 795 switch (rhs.type_tag) 796 { 797 case JSONType.integer: 798 return store.floating == rhs.store.integer; 799 case JSONType.uinteger: 800 return store.floating == rhs.store.uinteger; 801 case JSONType.float_: 802 return store.floating == rhs.store.floating; 803 default: 804 return false; 805 } 806 case JSONType.string: 807 return type_tag == rhs.type_tag && store.str == rhs.store.str; 808 case JSONType.object: 809 return type_tag == rhs.type_tag && store.object == rhs.store.object; 810 case JSONType.array: 811 return type_tag == rhs.type_tag && store.array == rhs.store.array; 812 case JSONType.true_: 813 case JSONType.false_: 814 case JSONType.null_: 815 return type_tag == rhs.type_tag; 816 } 817 } 818 819 /// 820 @safe unittest 821 { 822 assert(JSONValue(0u) == JSONValue(0)); 823 assert(JSONValue(0u) == JSONValue(0.0)); 824 assert(JSONValue(0) == JSONValue(0.0)); 825 } 826 827 /// Implements the foreach `opApply` interface for json arrays. 828 int opApply(scope int delegate(size_t index, ref JSONValue) dg) @system 829 { 830 int result; 831 832 foreach (size_t index, ref value; array) 833 { 834 result = dg(index, value); 835 if (result) 836 break; 837 } 838 839 return result; 840 } 841 842 /// Implements the foreach `opApply` interface for json objects. 843 int opApply(scope int delegate(string key, ref JSONValue) dg) @system 844 { 845 enforce!JSONException(type == JSONType.object, 846 "JSONValue is not an object"); 847 int result; 848 849 foreach (string key, ref value; object) 850 { 851 result = dg(key, value); 852 if (result) 853 break; 854 } 855 856 return result; 857 } 858 859 /*** 860 * Implicitly calls `toJSON` on this JSONValue. 861 * 862 * $(I options) can be used to tweak the conversion behavior. 863 */ 864 string toString(in JSONOptions options = JSONOptions.none) const @safe 865 { 866 return toJSON(this, false, options); 867 } 868 869 /// 870 void toString(Out)(Out sink, in JSONOptions options = JSONOptions.none) const 871 { 872 toJSON(sink, this, false, options); 873 } 874 875 /*** 876 * Implicitly calls `toJSON` on this JSONValue, like `toString`, but 877 * also passes $(I true) as $(I pretty) argument. 878 * 879 * $(I options) can be used to tweak the conversion behavior 880 */ 881 string toPrettyString(in JSONOptions options = JSONOptions.none) const @safe 882 { 883 return toJSON(this, true, options); 884 } 885 886 /// 887 void toPrettyString(Out)(Out sink, in JSONOptions options = JSONOptions.none) const 888 { 889 toJSON(sink, this, true, options); 890 } 891 } 892 893 // https://issues.dlang.org/show_bug.cgi?id=20874 894 @system unittest 895 { 896 static struct MyCustomType 897 { 898 public string toString () const @system { return null; } 899 alias toString this; 900 } 901 902 static struct B 903 { 904 public JSONValue asJSON() const @system { return JSONValue.init; } 905 alias asJSON this; 906 } 907 908 if (false) // Just checking attributes 909 { 910 JSONValue json; 911 MyCustomType ilovedlang; 912 json = ilovedlang; 913 json["foo"] = ilovedlang; 914 auto s = ilovedlang in json; 915 916 B b; 917 json ~= b; 918 json ~ b; 919 } 920 } 921 922 /** 923 Parses a serialized string and returns a tree of JSON values. 924 Throws: $(LREF JSONException) if string does not follow the JSON grammar or the depth exceeds the max depth, 925 $(LREF ConvException) if a number in the input cannot be represented by a native D type. 926 Params: 927 json = json-formatted string to parse 928 maxDepth = maximum depth of nesting allowed, -1 disables depth checking 929 options = enable decoding string representations of NaN/Inf as float values 930 */ 931 JSONValue parseJSON(T)(T json, int maxDepth = -1, JSONOptions options = JSONOptions.none) 932 if (isSomeFiniteCharInputRange!T) 933 { 934 import std.ascii : isDigit, isHexDigit, toUpper, toLower; 935 import std.typecons : Nullable, Yes; 936 JSONValue root; 937 root.type_tag = JSONType.null_; 938 939 // Avoid UTF decoding when possible, as it is unnecessary when 940 // processing JSON. 941 static if (is(T : const(char)[])) 942 alias Char = char; 943 else 944 alias Char = Unqual!(ElementType!T); 945 946 int depth = -1; 947 Nullable!Char next; 948 int line = 1, pos = 0; 949 immutable bool strict = (options & JSONOptions.strictParsing) != 0; 950 951 void error(string msg) 952 { 953 throw new JSONException(msg, line, pos); 954 } 955 956 if (json.empty) 957 { 958 if (strict) 959 { 960 error("Empty JSON body"); 961 } 962 return root; 963 } 964 965 bool isWhite(dchar c) 966 { 967 if (strict) 968 { 969 // RFC 7159 has a stricter definition of whitespace than general ASCII. 970 return c == ' ' || c == '\t' || c == '\n' || c == '\r'; 971 } 972 import std.ascii : isWhite; 973 // Accept ASCII NUL as whitespace in non-strict mode. 974 return c == 0 || isWhite(c); 975 } 976 977 Char popChar() 978 { 979 if (json.empty) error("Unexpected end of data."); 980 static if (is(T : const(char)[])) 981 { 982 Char c = json[0]; 983 json = json[1..$]; 984 } 985 else 986 { 987 Char c = json.front; 988 json.popFront(); 989 } 990 991 if (c == '\n') 992 { 993 line++; 994 pos = 0; 995 } 996 else 997 { 998 pos++; 999 } 1000 1001 return c; 1002 } 1003 1004 Char peekChar() 1005 { 1006 if (next.isNull) 1007 { 1008 if (json.empty) return '\0'; 1009 next = popChar(); 1010 } 1011 return next.get; 1012 } 1013 1014 Nullable!Char peekCharNullable() 1015 { 1016 if (next.isNull && !json.empty) 1017 { 1018 next = popChar(); 1019 } 1020 return next; 1021 } 1022 1023 void skipWhitespace() 1024 { 1025 while (true) 1026 { 1027 auto c = peekCharNullable(); 1028 if (c.isNull || 1029 !isWhite(c.get)) 1030 { 1031 return; 1032 } 1033 next.nullify(); 1034 } 1035 } 1036 1037 Char getChar(bool SkipWhitespace = false)() 1038 { 1039 static if (SkipWhitespace) skipWhitespace(); 1040 1041 Char c; 1042 if (!next.isNull) 1043 { 1044 c = next.get; 1045 next.nullify(); 1046 } 1047 else 1048 c = popChar(); 1049 1050 return c; 1051 } 1052 1053 void checkChar(bool SkipWhitespace = true)(char c, bool caseSensitive = true) 1054 { 1055 static if (SkipWhitespace) skipWhitespace(); 1056 auto c2 = getChar(); 1057 if (!caseSensitive) c2 = toLower(c2); 1058 1059 if (c2 != c) error(text("Found '", c2, "' when expecting '", c, "'.")); 1060 } 1061 1062 bool testChar(bool SkipWhitespace = true, bool CaseSensitive = true)(char c) 1063 { 1064 static if (SkipWhitespace) skipWhitespace(); 1065 auto c2 = peekChar(); 1066 static if (!CaseSensitive) c2 = toLower(c2); 1067 1068 if (c2 != c) return false; 1069 1070 getChar(); 1071 return true; 1072 } 1073 1074 wchar parseWChar() 1075 { 1076 wchar val = 0; 1077 foreach_reverse (i; 0 .. 4) 1078 { 1079 auto hex = toUpper(getChar()); 1080 if (!isHexDigit(hex)) error("Expecting hex character"); 1081 val += (isDigit(hex) ? hex - '0' : hex - ('A' - 10)) << (4 * i); 1082 } 1083 return val; 1084 } 1085 1086 string parseString() 1087 { 1088 import std.uni : isSurrogateHi, isSurrogateLo; 1089 import std.utf : encode, decode; 1090 1091 auto str = appender!string(); 1092 1093 Next: 1094 switch (peekChar()) 1095 { 1096 case '"': 1097 getChar(); 1098 break; 1099 1100 case '\\': 1101 getChar(); 1102 auto c = getChar(); 1103 switch (c) 1104 { 1105 case '"': str.put('"'); break; 1106 case '\\': str.put('\\'); break; 1107 case '/': str.put('/'); break; 1108 case 'b': str.put('\b'); break; 1109 case 'f': str.put('\f'); break; 1110 case 'n': str.put('\n'); break; 1111 case 'r': str.put('\r'); break; 1112 case 't': str.put('\t'); break; 1113 case 'u': 1114 wchar wc = parseWChar(); 1115 dchar val; 1116 // Non-BMP characters are escaped as a pair of 1117 // UTF-16 surrogate characters (see RFC 4627). 1118 if (isSurrogateHi(wc)) 1119 { 1120 wchar[2] pair; 1121 pair[0] = wc; 1122 if (getChar() != '\\') error("Expected escaped low surrogate after escaped high surrogate"); 1123 if (getChar() != 'u') error("Expected escaped low surrogate after escaped high surrogate"); 1124 pair[1] = parseWChar(); 1125 size_t index = 0; 1126 val = decode(pair[], index); 1127 if (index != 2) error("Invalid escaped surrogate pair"); 1128 } 1129 else 1130 if (isSurrogateLo(wc)) 1131 error(text("Unexpected low surrogate")); 1132 else 1133 val = wc; 1134 1135 char[4] buf; 1136 immutable len = encode!(Yes.useReplacementDchar)(buf, val); 1137 str.put(buf[0 .. len]); 1138 break; 1139 1140 default: 1141 error(text("Invalid escape sequence '\\", c, "'.")); 1142 } 1143 goto Next; 1144 1145 default: 1146 // RFC 7159 states that control characters U+0000 through 1147 // U+001F must not appear unescaped in a JSON string. 1148 // Note: std.ascii.isControl can't be used for this test 1149 // because it considers ASCII DEL (0x7f) to be a control 1150 // character but RFC 7159 does not. 1151 // Accept unescaped ASCII NULs in non-strict mode. 1152 auto c = getChar(); 1153 if (c < 0x20 && (strict || c != 0)) 1154 error("Illegal control character."); 1155 str.put(c); 1156 goto Next; 1157 } 1158 1159 return str.data.length ? str.data : ""; 1160 } 1161 1162 bool tryGetSpecialFloat(string str, out double val) { 1163 switch (str) 1164 { 1165 case JSONFloatLiteral.nan: 1166 val = double.nan; 1167 return true; 1168 case JSONFloatLiteral.inf: 1169 val = double.infinity; 1170 return true; 1171 case JSONFloatLiteral.negativeInf: 1172 val = -double.infinity; 1173 return true; 1174 default: 1175 return false; 1176 } 1177 } 1178 1179 void parseValue(ref JSONValue value) 1180 { 1181 depth++; 1182 1183 if (maxDepth != -1 && depth > maxDepth) error("Nesting too deep."); 1184 1185 auto c = getChar!true(); 1186 1187 switch (c) 1188 { 1189 case '{': 1190 if (testChar('}')) 1191 { 1192 value.object = null; 1193 break; 1194 } 1195 1196 JSONValue[string] obj; 1197 do 1198 { 1199 skipWhitespace(); 1200 if (!strict && peekChar() == '}') 1201 { 1202 break; 1203 } 1204 checkChar('"'); 1205 string name = parseString(); 1206 checkChar(':'); 1207 JSONValue member; 1208 parseValue(member); 1209 obj[name] = member; 1210 } 1211 while (testChar(',')); 1212 value.object = obj; 1213 1214 checkChar('}'); 1215 break; 1216 1217 case '[': 1218 if (testChar(']')) 1219 { 1220 value.type_tag = JSONType.array; 1221 break; 1222 } 1223 1224 JSONValue[] arr; 1225 do 1226 { 1227 skipWhitespace(); 1228 if (!strict && peekChar() == ']') 1229 { 1230 break; 1231 } 1232 JSONValue element; 1233 parseValue(element); 1234 arr ~= element; 1235 } 1236 while (testChar(',')); 1237 1238 checkChar(']'); 1239 value.array = arr; 1240 break; 1241 1242 case '"': 1243 auto str = parseString(); 1244 1245 // if special float parsing is enabled, check if string represents NaN/Inf 1246 if ((options & JSONOptions.specialFloatLiterals) && 1247 tryGetSpecialFloat(str, value.store.floating)) 1248 { 1249 // found a special float, its value was placed in value.store.floating 1250 value.type_tag = JSONType.float_; 1251 break; 1252 } 1253 1254 value.assign(str); 1255 break; 1256 1257 case '0': .. case '9': 1258 case '-': 1259 auto number = appender!string(); 1260 bool isFloat, isNegative; 1261 1262 void readInteger() 1263 { 1264 if (!isDigit(c)) error("Digit expected"); 1265 1266 Next: number.put(c); 1267 1268 if (isDigit(peekChar())) 1269 { 1270 c = getChar(); 1271 goto Next; 1272 } 1273 } 1274 1275 if (c == '-') 1276 { 1277 number.put('-'); 1278 c = getChar(); 1279 isNegative = true; 1280 } 1281 1282 if (strict && c == '0') 1283 { 1284 number.put('0'); 1285 if (isDigit(peekChar())) 1286 { 1287 error("Additional digits not allowed after initial zero digit"); 1288 } 1289 } 1290 else 1291 { 1292 readInteger(); 1293 } 1294 1295 if (testChar('.')) 1296 { 1297 isFloat = true; 1298 number.put('.'); 1299 c = getChar(); 1300 readInteger(); 1301 } 1302 if (testChar!(false, false)('e')) 1303 { 1304 isFloat = true; 1305 number.put('e'); 1306 if (testChar('+')) number.put('+'); 1307 else if (testChar('-')) number.put('-'); 1308 c = getChar(); 1309 readInteger(); 1310 } 1311 1312 string data = number.data; 1313 if (isFloat) 1314 { 1315 value.type_tag = JSONType.float_; 1316 value.store.floating = parse!double(data); 1317 } 1318 else 1319 { 1320 if (isNegative) 1321 { 1322 value.store.integer = parse!long(data); 1323 value.type_tag = JSONType.integer; 1324 } 1325 else 1326 { 1327 // only set the correct union member to not confuse CTFE 1328 ulong u = parse!ulong(data); 1329 if (u & (1UL << 63)) 1330 { 1331 value.store.uinteger = u; 1332 value.type_tag = JSONType.uinteger; 1333 } 1334 else 1335 { 1336 value.store.integer = u; 1337 value.type_tag = JSONType.integer; 1338 } 1339 } 1340 } 1341 break; 1342 1343 case 'T': 1344 if (strict) goto default; 1345 goto case; 1346 case 't': 1347 value.type_tag = JSONType.true_; 1348 checkChar!false('r', strict); 1349 checkChar!false('u', strict); 1350 checkChar!false('e', strict); 1351 break; 1352 1353 case 'F': 1354 if (strict) goto default; 1355 goto case; 1356 case 'f': 1357 value.type_tag = JSONType.false_; 1358 checkChar!false('a', strict); 1359 checkChar!false('l', strict); 1360 checkChar!false('s', strict); 1361 checkChar!false('e', strict); 1362 break; 1363 1364 case 'N': 1365 if (strict) goto default; 1366 goto case; 1367 case 'n': 1368 value.type_tag = JSONType.null_; 1369 checkChar!false('u', strict); 1370 checkChar!false('l', strict); 1371 checkChar!false('l', strict); 1372 break; 1373 1374 default: 1375 error(text("Unexpected character '", c, "'.")); 1376 } 1377 1378 depth--; 1379 } 1380 1381 parseValue(root); 1382 if (strict) 1383 { 1384 skipWhitespace(); 1385 if (!peekCharNullable().isNull) error("Trailing non-whitespace characters"); 1386 } 1387 return root; 1388 } 1389 1390 @safe unittest 1391 { 1392 enum issue15742objectOfObject = `{ "key1": { "key2": 1 }}`; 1393 static assert(parseJSON(issue15742objectOfObject).type == JSONType.object); 1394 1395 enum issue15742arrayOfArray = `[[1]]`; 1396 static assert(parseJSON(issue15742arrayOfArray).type == JSONType.array); 1397 } 1398 1399 @safe unittest 1400 { 1401 // Ensure we can parse and use JSON from @safe code 1402 auto a = `{ "key1": { "key2": 1 }}`.parseJSON; 1403 assert(a["key1"]["key2"].integer == 1); 1404 assert(a.toString == `{"key1":{"key2":1}}`); 1405 } 1406 1407 @system unittest 1408 { 1409 // Ensure we can parse JSON from a @system range. 1410 struct Range 1411 { 1412 string s; 1413 size_t index; 1414 @system 1415 { 1416 bool empty() { return index >= s.length; } 1417 void popFront() { index++; } 1418 char front() { return s[index]; } 1419 } 1420 } 1421 auto s = Range(`{ "key1": { "key2": 1 }}`); 1422 auto json = parseJSON(s); 1423 assert(json["key1"]["key2"].integer == 1); 1424 } 1425 1426 // https://issues.dlang.org/show_bug.cgi?id=20527 1427 @safe unittest 1428 { 1429 static assert(parseJSON(`{"a" : 2}`)["a"].integer == 2); 1430 } 1431 1432 /** 1433 Parses a serialized string and returns a tree of JSON values. 1434 Throws: $(LREF JSONException) if the depth exceeds the max depth. 1435 Params: 1436 json = json-formatted string to parse 1437 options = enable decoding string representations of NaN/Inf as float values 1438 */ 1439 JSONValue parseJSON(T)(T json, JSONOptions options) 1440 if (isSomeFiniteCharInputRange!T) 1441 { 1442 return parseJSON!T(json, -1, options); 1443 } 1444 1445 /** 1446 Takes a tree of JSON values and returns the serialized string. 1447 1448 Any Object types will be serialized in a key-sorted order. 1449 1450 If `pretty` is false no whitespaces are generated. 1451 If `pretty` is true serialized string is formatted to be human-readable. 1452 Set the $(LREF JSONOptions.specialFloatLiterals) flag is set in `options` to encode NaN/Infinity as strings. 1453 */ 1454 string toJSON(const ref JSONValue root, in bool pretty = false, in JSONOptions options = JSONOptions.none) @safe 1455 { 1456 auto json = appender!string(); 1457 toJSON(json, root, pretty, options); 1458 return json.data; 1459 } 1460 1461 /// 1462 void toJSON(Out)( 1463 auto ref Out json, 1464 const ref JSONValue root, 1465 in bool pretty = false, 1466 in JSONOptions options = JSONOptions.none) 1467 if (isOutputRange!(Out,char)) 1468 { 1469 void toStringImpl(Char)(string str) 1470 { 1471 json.put('"'); 1472 1473 foreach (Char c; str) 1474 { 1475 switch (c) 1476 { 1477 case '"': json.put("\\\""); break; 1478 case '\\': json.put("\\\\"); break; 1479 1480 case '/': 1481 if (!(options & JSONOptions.doNotEscapeSlashes)) 1482 json.put('\\'); 1483 json.put('/'); 1484 break; 1485 1486 case '\b': json.put("\\b"); break; 1487 case '\f': json.put("\\f"); break; 1488 case '\n': json.put("\\n"); break; 1489 case '\r': json.put("\\r"); break; 1490 case '\t': json.put("\\t"); break; 1491 default: 1492 { 1493 import std.ascii : isControl; 1494 import std.utf : encode; 1495 1496 // Make sure we do UTF decoding iff we want to 1497 // escape Unicode characters. 1498 assert(((options & JSONOptions.escapeNonAsciiChars) != 0) 1499 == is(Char == dchar), "JSONOptions.escapeNonAsciiChars needs dchar strings"); 1500 1501 with (JSONOptions) if (isControl(c) || 1502 ((options & escapeNonAsciiChars) >= escapeNonAsciiChars && c >= 0x80)) 1503 { 1504 // Ensure non-BMP characters are encoded as a pair 1505 // of UTF-16 surrogate characters, as per RFC 4627. 1506 wchar[2] wchars; // 1 or 2 UTF-16 code units 1507 size_t wNum = encode(wchars, c); // number of UTF-16 code units 1508 foreach (wc; wchars[0 .. wNum]) 1509 { 1510 json.put("\\u"); 1511 foreach_reverse (i; 0 .. 4) 1512 { 1513 char ch = (wc >>> (4 * i)) & 0x0f; 1514 ch += ch < 10 ? '0' : 'A' - 10; 1515 json.put(ch); 1516 } 1517 } 1518 } 1519 else 1520 { 1521 json.put(c); 1522 } 1523 } 1524 } 1525 } 1526 1527 json.put('"'); 1528 } 1529 1530 void toString(string str) 1531 { 1532 // Avoid UTF decoding when possible, as it is unnecessary when 1533 // processing JSON. 1534 if (options & JSONOptions.escapeNonAsciiChars) 1535 toStringImpl!dchar(str); 1536 else 1537 toStringImpl!char(str); 1538 } 1539 1540 // recursive @safe inference is broken here 1541 // workaround: if json.put is @safe, we should be too, 1542 // so annotate the recursion as @safe manually 1543 static if (isSafe!({ json.put(""); })) 1544 { 1545 void delegate(ref const JSONValue, ulong) @safe toValue; 1546 } 1547 else 1548 { 1549 void delegate(ref const JSONValue, ulong) @system toValue; 1550 } 1551 1552 void toValueImpl(ref const JSONValue value, ulong indentLevel) 1553 { 1554 void putTabs(ulong additionalIndent = 0) 1555 { 1556 if (pretty) 1557 foreach (i; 0 .. indentLevel + additionalIndent) 1558 json.put(" "); 1559 } 1560 void putEOL() 1561 { 1562 if (pretty) 1563 json.put('\n'); 1564 } 1565 void putCharAndEOL(char ch) 1566 { 1567 json.put(ch); 1568 putEOL(); 1569 } 1570 1571 final switch (value.type) 1572 { 1573 case JSONType.object: 1574 auto obj = value.objectNoRef; 1575 if (!obj.length) 1576 { 1577 json.put("{}"); 1578 } 1579 else 1580 { 1581 putCharAndEOL('{'); 1582 bool first = true; 1583 1584 void emit(R)(R names) 1585 { 1586 foreach (name; names) 1587 { 1588 auto member = obj[name]; 1589 if (!first) 1590 putCharAndEOL(','); 1591 first = false; 1592 putTabs(1); 1593 toString(name); 1594 json.put(':'); 1595 if (pretty) 1596 json.put(' '); 1597 toValue(member, indentLevel + 1); 1598 } 1599 } 1600 1601 import std.algorithm.sorting : sort; 1602 // https://issues.dlang.org/show_bug.cgi?id=14439 1603 // auto names = obj.keys; // aa.keys can't be called in @safe code 1604 auto names = new string[obj.length]; 1605 size_t i = 0; 1606 foreach (k, v; obj) 1607 { 1608 names[i] = k; 1609 i++; 1610 } 1611 sort(names); 1612 emit(names); 1613 1614 putEOL(); 1615 putTabs(); 1616 json.put('}'); 1617 } 1618 break; 1619 1620 case JSONType.array: 1621 auto arr = value.arrayNoRef; 1622 if (arr.empty) 1623 { 1624 json.put("[]"); 1625 } 1626 else 1627 { 1628 putCharAndEOL('['); 1629 foreach (i, el; arr) 1630 { 1631 if (i) 1632 putCharAndEOL(','); 1633 putTabs(1); 1634 toValue(el, indentLevel + 1); 1635 } 1636 putEOL(); 1637 putTabs(); 1638 json.put(']'); 1639 } 1640 break; 1641 1642 case JSONType.string: 1643 toString(value.str); 1644 break; 1645 1646 case JSONType.integer: 1647 json.put(to!string(value.store.integer)); 1648 break; 1649 1650 case JSONType.uinteger: 1651 json.put(to!string(value.store.uinteger)); 1652 break; 1653 1654 case JSONType.float_: 1655 import std.math.traits : isNaN, isInfinity; 1656 1657 auto val = value.store.floating; 1658 1659 if (val.isNaN) 1660 { 1661 if (options & JSONOptions.specialFloatLiterals) 1662 { 1663 toString(JSONFloatLiteral.nan); 1664 } 1665 else 1666 { 1667 throw new JSONException( 1668 "Cannot encode NaN. Consider passing the specialFloatLiterals flag."); 1669 } 1670 } 1671 else if (val.isInfinity) 1672 { 1673 if (options & JSONOptions.specialFloatLiterals) 1674 { 1675 toString((val > 0) ? JSONFloatLiteral.inf : JSONFloatLiteral.negativeInf); 1676 } 1677 else 1678 { 1679 throw new JSONException( 1680 "Cannot encode Infinity. Consider passing the specialFloatLiterals flag."); 1681 } 1682 } 1683 else 1684 { 1685 import std.algorithm.searching : canFind; 1686 import std.format : sformat; 1687 // The correct formula for the number of decimal digits needed for lossless round 1688 // trips is actually: 1689 // ceil(log(pow(2.0, double.mant_dig - 1)) / log(10.0) + 1) == (double.dig + 2) 1690 // Anything less will round off (1 + double.epsilon) 1691 char[25] buf; 1692 auto result = buf[].sformat!"%.18g"(val); 1693 json.put(result); 1694 if (!result.canFind('e') && !result.canFind('.')) 1695 json.put(".0"); 1696 } 1697 break; 1698 1699 case JSONType.true_: 1700 json.put("true"); 1701 break; 1702 1703 case JSONType.false_: 1704 json.put("false"); 1705 break; 1706 1707 case JSONType.null_: 1708 json.put("null"); 1709 break; 1710 } 1711 } 1712 1713 toValue = &toValueImpl; 1714 1715 toValue(root, 0); 1716 } 1717 1718 // https://issues.dlang.org/show_bug.cgi?id=12897 1719 @safe unittest 1720 { 1721 JSONValue jv0 = JSONValue("test测试"); 1722 assert(toJSON(jv0, false, JSONOptions.escapeNonAsciiChars) == `"test\u6D4B\u8BD5"`); 1723 JSONValue jv00 = JSONValue("test\u6D4B\u8BD5"); 1724 assert(toJSON(jv00, false, JSONOptions.none) == `"test测试"`); 1725 assert(toJSON(jv0, false, JSONOptions.none) == `"test测试"`); 1726 JSONValue jv1 = JSONValue("été"); 1727 assert(toJSON(jv1, false, JSONOptions.escapeNonAsciiChars) == `"\u00E9t\u00E9"`); 1728 JSONValue jv11 = JSONValue("\u00E9t\u00E9"); 1729 assert(toJSON(jv11, false, JSONOptions.none) == `"été"`); 1730 assert(toJSON(jv1, false, JSONOptions.none) == `"été"`); 1731 } 1732 1733 // https://issues.dlang.org/show_bug.cgi?id=20511 1734 @system unittest 1735 { 1736 import std.format.write : formattedWrite; 1737 import std.range : nullSink, outputRangeObject; 1738 1739 outputRangeObject!(const(char)[])(nullSink) 1740 .formattedWrite!"%s"(JSONValue.init); 1741 } 1742 1743 // Issue 16432 - JSON incorrectly parses to string 1744 @safe unittest 1745 { 1746 // Floating points numbers are rounded to the nearest integer and thus get 1747 // incorrectly parsed 1748 1749 import std.math.operations : isClose; 1750 1751 string s = "{\"rating\": 3.0 }"; 1752 JSONValue j = parseJSON(s); 1753 assert(j["rating"].type == JSONType.float_); 1754 j = j.toString.parseJSON; 1755 assert(j["rating"].type == JSONType.float_); 1756 assert(isClose(j["rating"].floating, 3.0)); 1757 1758 s = "{\"rating\": -3.0 }"; 1759 j = parseJSON(s); 1760 assert(j["rating"].type == JSONType.float_); 1761 j = j.toString.parseJSON; 1762 assert(j["rating"].type == JSONType.float_); 1763 assert(isClose(j["rating"].floating, -3.0)); 1764 1765 // https://issues.dlang.org/show_bug.cgi?id=13660 1766 auto jv1 = JSONValue(4.0); 1767 auto textual = jv1.toString(); 1768 auto jv2 = parseJSON(textual); 1769 assert(jv1.type == JSONType.float_); 1770 assert(textual == "4.0"); 1771 assert(jv2.type == JSONType.float_); 1772 } 1773 1774 @safe unittest 1775 { 1776 // Adapted from https://github.com/dlang/phobos/pull/5005 1777 // Result from toString is not checked here, because this 1778 // might differ (%e-like or %f-like output) depending 1779 // on OS and compiler optimization. 1780 import std.math.operations : isClose; 1781 1782 // test positive extreme values 1783 JSONValue j; 1784 j["rating"] = 1e18 - 65; 1785 assert(isClose(j.toString.parseJSON["rating"].floating, 1e18 - 65)); 1786 1787 j["rating"] = 1e18 - 64; 1788 assert(isClose(j.toString.parseJSON["rating"].floating, 1e18 - 64)); 1789 1790 // negative extreme values 1791 j["rating"] = -1e18 + 65; 1792 assert(isClose(j.toString.parseJSON["rating"].floating, -1e18 + 65)); 1793 1794 j["rating"] = -1e18 + 64; 1795 assert(isClose(j.toString.parseJSON["rating"].floating, -1e18 + 64)); 1796 } 1797 1798 /** 1799 Exception thrown on JSON errors 1800 */ 1801 class JSONException : Exception 1802 { 1803 this(string msg, int line = 0, int pos = 0) pure nothrow @safe 1804 { 1805 if (line) 1806 super(text(msg, " (Line ", line, ":", pos, ")")); 1807 else 1808 super(msg); 1809 } 1810 1811 this(string msg, string file, size_t line) pure nothrow @safe 1812 { 1813 super(msg, file, line); 1814 } 1815 } 1816 1817 1818 @system unittest 1819 { 1820 import std.exception; 1821 JSONValue jv = "123"; 1822 assert(jv.type == JSONType.string); 1823 assertNotThrown(jv.str); 1824 assertThrown!JSONException(jv.integer); 1825 assertThrown!JSONException(jv.uinteger); 1826 assertThrown!JSONException(jv.floating); 1827 assertThrown!JSONException(jv.object); 1828 assertThrown!JSONException(jv.array); 1829 assertThrown!JSONException(jv["aa"]); 1830 assertThrown!JSONException(jv[2]); 1831 1832 jv = -3; 1833 assert(jv.type == JSONType.integer); 1834 assertNotThrown(jv.integer); 1835 1836 jv = cast(uint) 3; 1837 assert(jv.type == JSONType.uinteger); 1838 assertNotThrown(jv.uinteger); 1839 1840 jv = 3.0; 1841 assert(jv.type == JSONType.float_); 1842 assertNotThrown(jv.floating); 1843 1844 jv = ["key" : "value"]; 1845 assert(jv.type == JSONType.object); 1846 assertNotThrown(jv.object); 1847 assertNotThrown(jv["key"]); 1848 assert("key" in jv); 1849 assert("notAnElement" !in jv); 1850 assertThrown!JSONException(jv["notAnElement"]); 1851 const cjv = jv; 1852 assert("key" in cjv); 1853 assertThrown!JSONException(cjv["notAnElement"]); 1854 1855 foreach (string key, value; jv) 1856 { 1857 static assert(is(typeof(value) == JSONValue)); 1858 assert(key == "key"); 1859 assert(value.type == JSONType.string); 1860 assertNotThrown(value.str); 1861 assert(value.str == "value"); 1862 } 1863 1864 jv = [3, 4, 5]; 1865 assert(jv.type == JSONType.array); 1866 assertNotThrown(jv.array); 1867 assertNotThrown(jv[2]); 1868 foreach (size_t index, value; jv) 1869 { 1870 static assert(is(typeof(value) == JSONValue)); 1871 assert(value.type == JSONType.integer); 1872 assertNotThrown(value.integer); 1873 assert(index == (value.integer-3)); 1874 } 1875 1876 jv = null; 1877 assert(jv.type == JSONType.null_); 1878 assert(jv.isNull); 1879 jv = "foo"; 1880 assert(!jv.isNull); 1881 1882 jv = JSONValue("value"); 1883 assert(jv.type == JSONType.string); 1884 assert(jv.str == "value"); 1885 1886 JSONValue jv2 = JSONValue("value"); 1887 assert(jv2.type == JSONType.string); 1888 assert(jv2.str == "value"); 1889 1890 JSONValue jv3 = JSONValue("\u001c"); 1891 assert(jv3.type == JSONType.string); 1892 assert(jv3.str == "\u001C"); 1893 } 1894 1895 // https://issues.dlang.org/show_bug.cgi?id=11504 1896 @system unittest 1897 { 1898 JSONValue jv = 1; 1899 assert(jv.type == JSONType.integer); 1900 1901 jv.str = "123"; 1902 assert(jv.type == JSONType.string); 1903 assert(jv.str == "123"); 1904 1905 jv.integer = 1; 1906 assert(jv.type == JSONType.integer); 1907 assert(jv.integer == 1); 1908 1909 jv.uinteger = 2u; 1910 assert(jv.type == JSONType.uinteger); 1911 assert(jv.uinteger == 2u); 1912 1913 jv.floating = 1.5; 1914 assert(jv.type == JSONType.float_); 1915 assert(jv.floating == 1.5); 1916 1917 jv.object = ["key" : JSONValue("value")]; 1918 assert(jv.type == JSONType.object); 1919 assert(jv.object == ["key" : JSONValue("value")]); 1920 1921 jv.array = [JSONValue(1), JSONValue(2), JSONValue(3)]; 1922 assert(jv.type == JSONType.array); 1923 assert(jv.array == [JSONValue(1), JSONValue(2), JSONValue(3)]); 1924 1925 jv = true; 1926 assert(jv.type == JSONType.true_); 1927 1928 jv = false; 1929 assert(jv.type == JSONType.false_); 1930 1931 enum E{True = true} 1932 jv = E.True; 1933 assert(jv.type == JSONType.true_); 1934 } 1935 1936 @system pure unittest 1937 { 1938 // Adding new json element via array() / object() directly 1939 1940 JSONValue jarr = JSONValue([10]); 1941 foreach (i; 0 .. 9) 1942 jarr.array ~= JSONValue(i); 1943 assert(jarr.array.length == 10); 1944 1945 JSONValue jobj = JSONValue(["key" : JSONValue("value")]); 1946 foreach (i; 0 .. 9) 1947 jobj.object[text("key", i)] = JSONValue(text("value", i)); 1948 assert(jobj.object.length == 10); 1949 } 1950 1951 @system pure unittest 1952 { 1953 // Adding new json element without array() / object() access 1954 1955 JSONValue jarr = JSONValue([10]); 1956 foreach (i; 0 .. 9) 1957 jarr ~= [JSONValue(i)]; 1958 assert(jarr.array.length == 10); 1959 1960 JSONValue jobj = JSONValue(["key" : JSONValue("value")]); 1961 foreach (i; 0 .. 9) 1962 jobj[text("key", i)] = JSONValue(text("value", i)); 1963 assert(jobj.object.length == 10); 1964 1965 // No array alias 1966 auto jarr2 = jarr ~ [1,2,3]; 1967 jarr2[0] = 999; 1968 assert(jarr[0] == JSONValue(10)); 1969 } 1970 1971 @system unittest 1972 { 1973 // @system because JSONValue.array is @system 1974 import std.exception; 1975 1976 // An overly simple test suite, if it can parse a serializated string and 1977 // then use the resulting values tree to generate an identical 1978 // serialization, both the decoder and encoder works. 1979 1980 auto jsons = [ 1981 `null`, 1982 `true`, 1983 `false`, 1984 `0`, 1985 `123`, 1986 `-4321`, 1987 `0.25`, 1988 `-0.25`, 1989 `""`, 1990 `"hello\nworld"`, 1991 `"\"\\\/\b\f\n\r\t"`, 1992 `[]`, 1993 `[12,"foo",true,false]`, 1994 `{}`, 1995 `{"a":1,"b":null}`, 1996 `{"goodbye":[true,"or",false,["test",42,{"nested":{"a":23.5,"b":0.140625}}]],` 1997 ~`"hello":{"array":[12,null,{}],"json":"is great"}}`, 1998 ]; 1999 2000 enum dbl1_844 = `1.8446744073709568`; 2001 version (MinGW) 2002 jsons ~= dbl1_844 ~ `e+019`; 2003 else 2004 jsons ~= dbl1_844 ~ `e+19`; 2005 2006 JSONValue val; 2007 string result; 2008 foreach (json; jsons) 2009 { 2010 try 2011 { 2012 val = parseJSON(json); 2013 enum pretty = false; 2014 result = toJSON(val, pretty); 2015 assert(result == json, text(result, " should be ", json)); 2016 } 2017 catch (JSONException e) 2018 { 2019 import std.stdio : writefln; 2020 writefln(text(json, "\n", e.toString())); 2021 } 2022 } 2023 2024 // Should be able to correctly interpret unicode entities 2025 val = parseJSON(`"\u003C\u003E"`); 2026 assert(toJSON(val) == "\"\<\>\""); 2027 assert(val.to!string() == "\"\<\>\""); 2028 val = parseJSON(`"\u0391\u0392\u0393"`); 2029 assert(toJSON(val) == "\"\Α\Β\Γ\""); 2030 assert(val.to!string() == "\"\Α\Β\Γ\""); 2031 val = parseJSON(`"\u2660\u2666"`); 2032 assert(toJSON(val) == "\"\♠\♦\""); 2033 assert(val.to!string() == "\"\♠\♦\""); 2034 2035 //0x7F is a control character (see Unicode spec) 2036 val = parseJSON(`"\u007F"`); 2037 assert(toJSON(val) == "\"\\u007F\""); 2038 assert(val.to!string() == "\"\\u007F\""); 2039 2040 with(parseJSON(`""`)) 2041 assert(str == "" && str !is null); 2042 with(parseJSON(`[]`)) 2043 assert(!array.length); 2044 2045 // Formatting 2046 val = parseJSON(`{"a":[null,{"x":1},{},[]]}`); 2047 assert(toJSON(val, true) == `{ 2048 "a": [ 2049 null, 2050 { 2051 "x": 1 2052 }, 2053 {}, 2054 [] 2055 ] 2056 }`); 2057 } 2058 2059 @safe unittest 2060 { 2061 auto json = `"hello\nworld"`; 2062 const jv = parseJSON(json); 2063 assert(jv.toString == json); 2064 assert(jv.toPrettyString == json); 2065 } 2066 2067 @system pure unittest 2068 { 2069 // https://issues.dlang.org/show_bug.cgi?id=12969 2070 2071 JSONValue jv; 2072 jv["int"] = 123; 2073 2074 assert(jv.type == JSONType.object); 2075 assert("int" in jv); 2076 assert(jv["int"].integer == 123); 2077 2078 jv["array"] = [1, 2, 3, 4, 5]; 2079 2080 assert(jv["array"].type == JSONType.array); 2081 assert(jv["array"][2].integer == 3); 2082 2083 jv["str"] = "D language"; 2084 assert(jv["str"].type == JSONType.string); 2085 assert(jv["str"].str == "D language"); 2086 2087 jv["bool"] = false; 2088 assert(jv["bool"].type == JSONType.false_); 2089 2090 assert(jv.object.length == 4); 2091 2092 jv = [5, 4, 3, 2, 1]; 2093 assert(jv.type == JSONType.array); 2094 assert(jv[3].integer == 2); 2095 } 2096 2097 @safe unittest 2098 { 2099 auto s = q"EOF 2100 [ 2101 1, 2102 2, 2103 3, 2104 potato 2105 ] 2106 EOF"; 2107 2108 import std.exception; 2109 2110 auto e = collectException!JSONException(parseJSON(s)); 2111 assert(e.msg == "Unexpected character 'p'. (Line 5:3)", e.msg); 2112 } 2113 2114 // handling of special float values (NaN, Inf, -Inf) 2115 @safe unittest 2116 { 2117 import std.exception : assertThrown; 2118 import std.math.traits : isNaN, isInfinity; 2119 2120 // expected representations of NaN and Inf 2121 enum { 2122 nanString = '"' ~ JSONFloatLiteral.nan ~ '"', 2123 infString = '"' ~ JSONFloatLiteral.inf ~ '"', 2124 negativeInfString = '"' ~ JSONFloatLiteral.negativeInf ~ '"', 2125 } 2126 2127 // with the specialFloatLiterals option, encode NaN/Inf as strings 2128 assert(JSONValue(float.nan).toString(JSONOptions.specialFloatLiterals) == nanString); 2129 assert(JSONValue(double.infinity).toString(JSONOptions.specialFloatLiterals) == infString); 2130 assert(JSONValue(-real.infinity).toString(JSONOptions.specialFloatLiterals) == negativeInfString); 2131 2132 // without the specialFloatLiterals option, throw on encoding NaN/Inf 2133 assertThrown!JSONException(JSONValue(float.nan).toString); 2134 assertThrown!JSONException(JSONValue(double.infinity).toString); 2135 assertThrown!JSONException(JSONValue(-real.infinity).toString); 2136 2137 // when parsing json with specialFloatLiterals option, decode special strings as floats 2138 JSONValue jvNan = parseJSON(nanString, JSONOptions.specialFloatLiterals); 2139 JSONValue jvInf = parseJSON(infString, JSONOptions.specialFloatLiterals); 2140 JSONValue jvNegInf = parseJSON(negativeInfString, JSONOptions.specialFloatLiterals); 2141 2142 assert(jvNan.floating.isNaN); 2143 assert(jvInf.floating.isInfinity && jvInf.floating > 0); 2144 assert(jvNegInf.floating.isInfinity && jvNegInf.floating < 0); 2145 2146 // when parsing json without the specialFloatLiterals option, decode special strings as strings 2147 jvNan = parseJSON(nanString); 2148 jvInf = parseJSON(infString); 2149 jvNegInf = parseJSON(negativeInfString); 2150 2151 assert(jvNan.str == JSONFloatLiteral.nan); 2152 assert(jvInf.str == JSONFloatLiteral.inf); 2153 assert(jvNegInf.str == JSONFloatLiteral.negativeInf); 2154 } 2155 2156 pure nothrow @safe @nogc unittest 2157 { 2158 JSONValue testVal; 2159 testVal = "test"; 2160 testVal = 10; 2161 testVal = 10u; 2162 testVal = 1.0; 2163 testVal = (JSONValue[string]).init; 2164 testVal = JSONValue[].init; 2165 testVal = null; 2166 assert(testVal.isNull); 2167 } 2168 2169 // https://issues.dlang.org/show_bug.cgi?id=15884 2170 pure nothrow @safe unittest 2171 { 2172 import std.typecons; 2173 void Test(C)() { 2174 C[] a = ['x']; 2175 JSONValue testVal = a; 2176 assert(testVal.type == JSONType.string); 2177 testVal = a.idup; 2178 assert(testVal.type == JSONType.string); 2179 } 2180 Test!char(); 2181 Test!wchar(); 2182 Test!dchar(); 2183 } 2184 2185 // https://issues.dlang.org/show_bug.cgi?id=15885 2186 @safe unittest 2187 { 2188 enum bool realInDoublePrecision = real.mant_dig == double.mant_dig; 2189 2190 static bool test(const double num0) 2191 { 2192 import std.math.operations : feqrel; 2193 const json0 = JSONValue(num0); 2194 const num1 = to!double(toJSON(json0)); 2195 static if (realInDoublePrecision) 2196 return feqrel(num1, num0) >= (double.mant_dig - 1); 2197 else 2198 return num1 == num0; 2199 } 2200 2201 assert(test( 0.23)); 2202 assert(test(-0.23)); 2203 assert(test(1.223e+24)); 2204 assert(test(23.4)); 2205 assert(test(0.0012)); 2206 assert(test(30738.22)); 2207 2208 assert(test(1 + double.epsilon)); 2209 assert(test(double.min_normal)); 2210 static if (realInDoublePrecision) 2211 assert(test(-double.max / 2)); 2212 else 2213 assert(test(-double.max)); 2214 2215 const minSub = double.min_normal * double.epsilon; 2216 assert(test(minSub)); 2217 assert(test(3*minSub)); 2218 } 2219 2220 // https://issues.dlang.org/show_bug.cgi?id=17555 2221 @safe unittest 2222 { 2223 import std.exception : assertThrown; 2224 2225 assertThrown!JSONException(parseJSON("\"a\nb\"")); 2226 } 2227 2228 // https://issues.dlang.org/show_bug.cgi?id=17556 2229 @safe unittest 2230 { 2231 auto v = JSONValue("\U0001D11E"); 2232 auto j = toJSON(v, false, JSONOptions.escapeNonAsciiChars); 2233 assert(j == `"\uD834\uDD1E"`); 2234 } 2235 2236 // https://issues.dlang.org/show_bug.cgi?id=5904 2237 @safe unittest 2238 { 2239 string s = `"\uD834\uDD1E"`; 2240 auto j = parseJSON(s); 2241 assert(j.str == "\U0001D11E"); 2242 } 2243 2244 // https://issues.dlang.org/show_bug.cgi?id=17557 2245 @safe unittest 2246 { 2247 assert(parseJSON("\"\xFF\"").str == "\xFF"); 2248 assert(parseJSON("\"\U0001D11E\"").str == "\U0001D11E"); 2249 } 2250 2251 // https://issues.dlang.org/show_bug.cgi?id=17553 2252 @safe unittest 2253 { 2254 auto v = JSONValue("\xFF"); 2255 assert(toJSON(v) == "\"\xFF\""); 2256 } 2257 2258 @safe unittest 2259 { 2260 import std.utf; 2261 assert(parseJSON("\"\xFF\"".byChar).str == "\xFF"); 2262 assert(parseJSON("\"\U0001D11E\"".byChar).str == "\U0001D11E"); 2263 } 2264 2265 // JSONOptions.doNotEscapeSlashes (https://issues.dlang.org/show_bug.cgi?id=17587) 2266 @safe unittest 2267 { 2268 assert(parseJSON(`"/"`).toString == `"\/"`); 2269 assert(parseJSON(`"\/"`).toString == `"\/"`); 2270 assert(parseJSON(`"/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`); 2271 assert(parseJSON(`"\/"`).toString(JSONOptions.doNotEscapeSlashes) == `"/"`); 2272 } 2273 2274 // JSONOptions.strictParsing (https://issues.dlang.org/show_bug.cgi?id=16639) 2275 @safe unittest 2276 { 2277 import std.exception : assertThrown; 2278 2279 // Unescaped ASCII NULs 2280 assert(parseJSON("[\0]").type == JSONType.array); 2281 assertThrown!JSONException(parseJSON("[\0]", JSONOptions.strictParsing)); 2282 assert(parseJSON("\"\0\"").str == "\0"); 2283 assertThrown!JSONException(parseJSON("\"\0\"", JSONOptions.strictParsing)); 2284 2285 // Unescaped ASCII DEL (0x7f) in strings 2286 assert(parseJSON("\"\x7f\"").str == "\x7f"); 2287 assert(parseJSON("\"\x7f\"", JSONOptions.strictParsing).str == "\x7f"); 2288 2289 // "true", "false", "null" case sensitivity 2290 assert(parseJSON("true").type == JSONType.true_); 2291 assert(parseJSON("true", JSONOptions.strictParsing).type == JSONType.true_); 2292 assert(parseJSON("True").type == JSONType.true_); 2293 assertThrown!JSONException(parseJSON("True", JSONOptions.strictParsing)); 2294 assert(parseJSON("tRUE").type == JSONType.true_); 2295 assertThrown!JSONException(parseJSON("tRUE", JSONOptions.strictParsing)); 2296 2297 assert(parseJSON("false").type == JSONType.false_); 2298 assert(parseJSON("false", JSONOptions.strictParsing).type == JSONType.false_); 2299 assert(parseJSON("False").type == JSONType.false_); 2300 assertThrown!JSONException(parseJSON("False", JSONOptions.strictParsing)); 2301 assert(parseJSON("fALSE").type == JSONType.false_); 2302 assertThrown!JSONException(parseJSON("fALSE", JSONOptions.strictParsing)); 2303 2304 assert(parseJSON("null").type == JSONType.null_); 2305 assert(parseJSON("null", JSONOptions.strictParsing).type == JSONType.null_); 2306 assert(parseJSON("Null").type == JSONType.null_); 2307 assertThrown!JSONException(parseJSON("Null", JSONOptions.strictParsing)); 2308 assert(parseJSON("nULL").type == JSONType.null_); 2309 assertThrown!JSONException(parseJSON("nULL", JSONOptions.strictParsing)); 2310 2311 // Whitespace characters 2312 assert(parseJSON("[\f\v]").type == JSONType.array); 2313 assertThrown!JSONException(parseJSON("[\f\v]", JSONOptions.strictParsing)); 2314 assert(parseJSON("[ \t\r\n]").type == JSONType.array); 2315 assert(parseJSON("[ \t\r\n]", JSONOptions.strictParsing).type == JSONType.array); 2316 2317 // Empty input 2318 assert(parseJSON("").type == JSONType.null_); 2319 assertThrown!JSONException(parseJSON("", JSONOptions.strictParsing)); 2320 2321 // Numbers with leading '0's 2322 assert(parseJSON("01").integer == 1); 2323 assertThrown!JSONException(parseJSON("01", JSONOptions.strictParsing)); 2324 assert(parseJSON("-01").integer == -1); 2325 assertThrown!JSONException(parseJSON("-01", JSONOptions.strictParsing)); 2326 assert(parseJSON("0.01").floating == 0.01); 2327 assert(parseJSON("0.01", JSONOptions.strictParsing).floating == 0.01); 2328 assert(parseJSON("0e1").floating == 0); 2329 assert(parseJSON("0e1", JSONOptions.strictParsing).floating == 0); 2330 2331 // Trailing characters after JSON value 2332 assert(parseJSON(`""asdf`).str == ""); 2333 assertThrown!JSONException(parseJSON(`""asdf`, JSONOptions.strictParsing)); 2334 assert(parseJSON("987\0").integer == 987); 2335 assertThrown!JSONException(parseJSON("987\0", JSONOptions.strictParsing)); 2336 assert(parseJSON("987\0\0").integer == 987); 2337 assertThrown!JSONException(parseJSON("987\0\0", JSONOptions.strictParsing)); 2338 assert(parseJSON("[]]").type == JSONType.array); 2339 assertThrown!JSONException(parseJSON("[]]", JSONOptions.strictParsing)); 2340 assert(parseJSON("123 \t\r\n").integer == 123); // Trailing whitespace is OK 2341 assert(parseJSON("123 \t\r\n", JSONOptions.strictParsing).integer == 123); 2342 } 2343 2344 @system unittest 2345 { 2346 import std.algorithm.iteration : map; 2347 import std.array : array; 2348 import std.exception : assertThrown; 2349 2350 string s = `{ "a" : [1,2,3,], }`; 2351 JSONValue j = parseJSON(s); 2352 assert(j["a"].array().map!(i => i.integer()).array == [1,2,3]); 2353 2354 assertThrown(parseJSON(s, -1, JSONOptions.strictParsing)); 2355 } 2356 2357 @system unittest 2358 { 2359 import std.algorithm.iteration : map; 2360 import std.array : array; 2361 import std.exception : assertThrown; 2362 2363 string s = `{ "a" : { } , }`; 2364 JSONValue j = parseJSON(s); 2365 assert("a" in j); 2366 auto t = j["a"].object(); 2367 assert(t.empty); 2368 2369 assertThrown(parseJSON(s, -1, JSONOptions.strictParsing)); 2370 } 2371 2372 // https://issues.dlang.org/show_bug.cgi?id=20330 2373 @safe unittest 2374 { 2375 import std.array : appender; 2376 2377 string s = `{"a":[1,2,3]}`; 2378 JSONValue j = parseJSON(s); 2379 2380 auto app = appender!string(); 2381 j.toString(app); 2382 2383 assert(app.data == s, app.data); 2384 } 2385 2386 // https://issues.dlang.org/show_bug.cgi?id=20330 2387 @safe unittest 2388 { 2389 import std.array : appender; 2390 import std.format.write : formattedWrite; 2391 2392 string s = 2393 `{ 2394 "a": [ 2395 1, 2396 2, 2397 3 2398 ] 2399 }`; 2400 JSONValue j = parseJSON(s); 2401 2402 auto app = appender!string(); 2403 j.toPrettyString(app); 2404 2405 assert(app.data == s, app.data); 2406 } 2407