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