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