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