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