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