1 /** 2 * JSON encoding. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <ae@cy.md> 12 */ 13 14 module ae.utils.json; 15 16 import std.exception; 17 import std.math : isFinite; 18 import std.string; 19 import std.traits; 20 import std.typecons; 21 22 import ae.utils.appender; 23 import ae.utils.array : isIdentical; 24 import ae.utils.exception; 25 import ae.utils.functor.primitives : functor; 26 import ae.utils.meta; 27 import ae.utils.textout; 28 29 // ************************************************************************ 30 31 /// JSON serialization / deserialization options 32 struct JsonOptions 33 { 34 /// What to do with associative arrays with non-string keys 35 enum NonStringKeys 36 { 37 /// Fail compilation. 38 error, 39 40 /// Serialize keys as-is - results in non-compliant JSON. 41 asIs, 42 43 /// Serialize keys as strings. 44 /// Note that this may result in multiple levels of quoting. 45 stringify, 46 } 47 NonStringKeys nonStringKeys = NonStringKeys.error; /// ditto 48 } 49 50 // ************************************************************************ 51 52 /// Basic JSON writer. 53 struct JsonWriter(Output) 54 { 55 /// You can set this to something to e.g. write to another buffer. 56 Output output; 57 58 private void putChars(S...)(S strings) 59 { 60 static if (is(typeof(output.putEx(strings)))) 61 output.putEx(strings); 62 else 63 foreach (str; strings) 64 static if (is(typeof(output.put(str)))) 65 output.put(str); 66 else 67 foreach (dchar c; str) 68 { 69 alias C = char; // TODO: get char type of output 70 C[4 / C.sizeof] buf = void; 71 auto size = encode(buf, c); 72 output.put(buf[0..size]); 73 } 74 } 75 76 /// Write a string literal. 77 private void putString(C)(in C[] s) 78 { 79 // TODO: escape Unicode characters? 80 // TODO: Handle U+2028 and U+2029 ( http://timelessrepo.com/json-isnt-a-javascript-subset ) 81 82 output.putEx('"'); 83 auto start = s.ptr, p = start, end = start+s.length; 84 85 while (p < end) 86 { 87 auto c = *p++; 88 if (c < Escapes.escaped.length && Escapes.escaped[c]) 89 { 90 putChars(start[0..p-start-1], Escapes.chars[c]); 91 start = p; 92 } 93 } 94 95 putChars(start[0..p-start], '"'); 96 } 97 98 /// Write a value of a simple type. 99 void putValue(T)(T v) 100 { 101 static if (is(typeof(v is null))) 102 if (v is null) 103 return output.put("null"); 104 static if (is(T == typeof(null))) 105 return output.put("null"); 106 else 107 static if (isSomeString!T) 108 putString(v); 109 else 110 static if (isSomeChar!(Unqual!T)) 111 return putString((&v)[0..1]); 112 else 113 static if (is(Unqual!T == bool)) 114 return output.put(v ? "true" : "false"); 115 else 116 static if (is(Unqual!T : long)) 117 return .put(output, v); 118 else 119 static if (is(Unqual!T : real)) 120 if (v.isFinite) 121 return output.putFP(v); 122 else 123 return putString(v.to!string); 124 else 125 static assert(0, "Don't know how to write " ~ T.stringof); 126 } 127 128 void beginArray() 129 { 130 output.putEx('['); 131 } /// 132 133 void endArray() 134 { 135 output.putEx(']'); 136 } /// 137 138 void beginObject() 139 { 140 output.putEx('{'); 141 } /// 142 143 void endObject() 144 { 145 output.putEx('}'); 146 } /// 147 148 void endKey() 149 { 150 output.putEx(':'); 151 } /// 152 153 void putComma() 154 { 155 output.putEx(','); 156 } /// 157 } 158 159 /// JSON writer with indentation. 160 struct PrettyJsonWriter(Output, alias indent = '\t', alias newLine = '\n', alias pad = ' ') 161 { 162 JsonWriter!Output jsonWriter; /// Underlying writer. 163 alias jsonWriter this; 164 165 private bool indentPending; 166 private uint indentLevel; 167 168 private void putIndent() 169 { 170 if (indentPending) 171 { 172 foreach (n; 0..indentLevel) 173 output.putEx(indent); 174 indentPending = false; 175 } 176 } 177 178 private void putNewline() 179 { 180 if (!indentPending) 181 { 182 output.putEx(newLine); 183 indentPending = true; 184 } 185 } 186 187 void putValue(T)(T v) 188 { 189 putIndent(); 190 jsonWriter.putValue(v); 191 } /// 192 193 void beginArray() 194 { 195 putIndent(); 196 jsonWriter.beginArray(); 197 indentLevel++; 198 putNewline(); 199 } /// 200 201 void endArray() 202 { 203 indentLevel--; 204 putNewline(); 205 putIndent(); 206 jsonWriter.endArray(); 207 } /// 208 209 void beginObject() 210 { 211 putIndent(); 212 jsonWriter.beginObject(); 213 indentLevel++; 214 putNewline(); 215 } /// 216 217 void endObject() 218 { 219 indentLevel--; 220 putNewline(); 221 putIndent(); 222 jsonWriter.endObject(); 223 } /// 224 225 void endKey() 226 { 227 output.putEx(pad, ':', pad); 228 } /// 229 230 void putComma() 231 { 232 jsonWriter.putComma(); 233 putNewline(); 234 } /// 235 } 236 237 /// Abstract JSON serializer based on `Writer`. 238 struct CustomJsonSerializer(Writer, JsonOptions options = JsonOptions.init) 239 { 240 Writer writer; /// Output. 241 242 /// Put a serializable value. 243 void put(T)(auto ref T v) 244 { 245 static if (__traits(hasMember, T, "toJSON")) 246 static if (is(typeof(v.toJSON()))) 247 put(v.toJSON()); 248 else 249 v.toJSON((&this).functor!((self, ref j) => self.put(j))); 250 else 251 static if (is(T X == Nullable!X)) 252 if (v.isNull) 253 writer.putValue(null); 254 else 255 put(v.get); 256 else 257 static if (is(T == enum)) 258 put(to!string(v)); 259 else 260 static if (isSomeString!T || is(Unqual!T : real)) 261 writer.putValue(v); 262 else 263 static if (is(T == typeof(null))) 264 writer.putValue(null); 265 else 266 static if (is(T U : U[])) 267 { 268 writer.beginArray(); 269 if (v.length) 270 { 271 put(v[0]); 272 foreach (i; v[1..$]) 273 { 274 writer.putComma(); 275 put(i); 276 } 277 } 278 writer.endArray(); 279 } 280 else 281 static if (isTuple!T) 282 { 283 // TODO: serialize as object if tuple has names 284 enum N = v.expand.length; 285 static if (N == 0) 286 return; 287 else 288 static if (N == 1) 289 put(v.expand[0]); 290 else 291 { 292 writer.beginArray(); 293 foreach (n; rangeTuple!N) 294 { 295 static if (n) 296 writer.putComma(); 297 put(v.expand[n]); 298 } 299 writer.endArray(); 300 } 301 } 302 else 303 static if (is(typeof(T.init.keys)) && is(typeof(T.init.values))) 304 { 305 writer.beginObject(); 306 bool first = true; 307 foreach (key, value; v) 308 { 309 if (!first) 310 writer.putComma(); 311 else 312 first = false; 313 static if (is(typeof({string s = T.init.keys[0];}))) 314 writer.putValue(key); 315 else 316 static if (options.nonStringKeys == JsonOptions.NonStringKeys.asIs) 317 put(key); 318 else 319 static if (options.nonStringKeys == JsonOptions.NonStringKeys.stringify) 320 writer.putValue(key.toJson!options); 321 else 322 static assert(false, "Cannot serialize associative array with non-string key " ~ K.stringof); 323 writer.endKey(); 324 put(value); 325 } 326 writer.endObject(); 327 } 328 else 329 static if (is(T==JSONFragment)) 330 writer.output.put(v.json); 331 else 332 static if (is(T==struct)) 333 { 334 writer.beginObject(); 335 bool first = true; 336 foreach (i, ref field; v.tupleof) 337 { 338 static if (!doSkipSerialize!(T, v.tupleof[i].stringof[2..$])) 339 { 340 static if (hasAttribute!(JSONOptional, v.tupleof[i])) 341 if (isIdentical(v.tupleof[i], T.init.tupleof[i])) 342 continue; 343 if (!first) 344 writer.putComma(); 345 else 346 first = false; 347 writer.putValue(getJsonName!(T, v.tupleof[i].stringof[2..$])); 348 writer.endKey(); 349 put(field); 350 } 351 } 352 writer.endObject(); 353 } 354 else 355 static if (is(typeof(*v))) 356 { 357 if (v) 358 put(*v); 359 else 360 writer.putValue(null); 361 } 362 else 363 static assert(0, "Can't serialize " ~ T.stringof ~ " to JSON"); 364 } 365 } 366 367 /// JSON serializer with `StringBuilder` output. 368 alias JsonSerializer = CustomJsonSerializer!(JsonWriter!StringBuilder); 369 370 private struct Escapes 371 { 372 static immutable string[256] chars; 373 static immutable bool[256] escaped; 374 375 shared static this() 376 { 377 import std.string : format; 378 379 escaped[] = true; 380 foreach (c; 0..256) 381 if (c=='\\') 382 chars[c] = `\\`; 383 else 384 if (c=='\"') 385 chars[c] = `\"`; 386 else 387 if (c=='\b') 388 chars[c] = `\b`; 389 else 390 if (c=='\f') 391 chars[c] = `\f`; 392 else 393 if (c=='\n') 394 chars[c] = `\n`; 395 else 396 if (c=='\r') 397 chars[c] = `\r`; 398 else 399 if (c=='\t') 400 chars[c] = `\t`; 401 else 402 if (c<'\x20' || c == '\x7F' || c=='<' || c=='>' || c=='&') 403 chars[c] = format(`\u%04x`, c); 404 else 405 chars[c] = [cast(char)c], 406 escaped[c] = false; 407 } 408 } 409 410 // ************************************************************************ 411 412 /// Serialize `T` to JSON, and return the result as a string. 413 string toJson(T)(auto ref T v) 414 { 415 JsonSerializer serializer; 416 serializer.put(v); 417 return serializer.writer.output.get(); 418 } 419 420 /// ditto 421 string toJson(JsonOptions options, T)(auto ref T v) 422 { 423 CustomJsonSerializer!(JsonWriter!StringBuilder, options) serializer; 424 serializer.put(v); 425 return serializer.writer.output.get(); 426 } 427 428 /// 429 debug(ae_unittest) unittest 430 { 431 struct X { int a; string b; } 432 X x = {17, "aoeu"}; 433 assert(toJson(x) == `{"a":17,"b":"aoeu"}`, toJson(x)); 434 int[] arr = [1,5,7]; 435 assert(toJson(arr) == `[1,5,7]`); 436 assert(toJson(true) == `true`); 437 438 assert(toJson(tuple()) == ``); 439 assert(toJson(tuple(42)) == `42`); 440 assert(toJson(tuple(42, "banana")) == `[42,"banana"]`); 441 } 442 443 debug(ae_unittest) unittest 444 { 445 struct A 446 { 447 } 448 449 struct B 450 { 451 A[] a; 452 deprecated alias a this; 453 JSONFragment toJSON() const { return JSONFragment(`null`); } 454 } 455 456 B b; 457 b.toJson(); 458 } 459 460 // ************************************************************************ 461 462 /// Serialize `T` to a pretty (indented) JSON string. 463 string toPrettyJson(T)(T v) 464 { 465 CustomJsonSerializer!(PrettyJsonWriter!StringBuilder) serializer; 466 serializer.put(v); 467 return serializer.writer.output.get(); 468 } 469 470 /// ditto 471 string toPrettyJson(JsonOptions options, T)(T v) 472 { 473 CustomJsonSerializer!(PrettyJsonWriter!StringBuilder, options) serializer; 474 serializer.put(v); 475 return serializer.writer.output.get(); 476 } 477 478 /// 479 debug(ae_unittest) unittest 480 { 481 struct X { int a; string b; int[] c, d; } 482 X x = {17, "aoeu", [1, 2, 3]}; 483 assert(toPrettyJson(x) == 484 `{ 485 "a" : 17, 486 "b" : "aoeu", 487 "c" : [ 488 1, 489 2, 490 3 491 ], 492 "d" : [ 493 ] 494 }`, toPrettyJson(x)); 495 } 496 497 // ************************************************************************ 498 499 import std.ascii; 500 import std.utf; 501 import std.conv; 502 503 import ae.utils.text; 504 505 private struct JsonParser(C, JsonOptions options = JsonOptions.init) 506 { 507 C[] s; 508 size_t p; 509 510 Unqual!C next() 511 { 512 enforce(p < s.length, "Out of data while parsing JSON stream"); 513 return s[p++]; 514 } 515 516 string readN(uint n) 517 { 518 string r; 519 for (int i=0; i<n; i++) 520 r ~= next(); 521 return r; 522 } 523 524 Unqual!C peek() 525 { 526 enforce(p < s.length, "Out of data while parsing JSON stream"); 527 return s[p]; 528 } 529 530 @property bool eof() { return p == s.length; } 531 532 void skipWhitespace() 533 { 534 while (isWhite(peek())) 535 p++; 536 } 537 538 void expect(C c) 539 { 540 auto n = next(); 541 enforce(n==c, text("Expected ", c, ", got ", n)); 542 } 543 544 void read(T)(ref T value) 545 { 546 static if (is(T == typeof(null))) 547 value = readNull(); 548 else 549 static if (is(T X == Nullable!X)) 550 readNullable!X(value); 551 else 552 static if (is(T==enum)) 553 value = readEnum!(T)(); 554 else 555 static if (isSomeString!T) 556 value = readString().to!T; 557 else 558 static if (is(T==bool)) 559 value = readBool(); 560 else 561 static if (is(T : real)) 562 value = readNumber!(T)(); 563 else 564 static if (isDynamicArray!T) 565 value = readArray!(typeof(T.init[0]))(); 566 else 567 static if (isStaticArray!T) 568 readStaticArray(value); 569 else 570 static if (isTuple!T) 571 readTuple!T(value); 572 else 573 static if (is(typeof(T.init.keys)) && is(typeof(T.init.values))) 574 readAA!(T)(value); 575 else 576 static if (is(T==JSONFragment)) 577 { 578 auto start = p; 579 skipValue(); 580 value = JSONFragment(s[start..p]); 581 } 582 else 583 static if (is(T U : U*)) 584 value = readPointer!T(); 585 else 586 static if (__traits(hasMember, T, "fromJSON")) 587 { 588 alias Q = Parameters!(T.fromJSON)[0]; 589 Q tempValue; 590 read!Q(tempValue); 591 static if (is(typeof(value = T.fromJSON(tempValue)))) 592 value = T.fromJSON(tempValue); 593 else 594 { 595 import core.lifetime : move; 596 auto convertedValue = T.fromJSON(tempValue); 597 move(convertedValue, value); 598 } 599 } 600 else 601 static if (is(T==struct)) 602 readObject!(T)(value); 603 else 604 static assert(0, "Can't decode " ~ T.stringof ~ " from JSON"); 605 } 606 607 void readTuple(T)(ref T value) 608 { 609 // TODO: serialize as object if tuple has names 610 enum N = T.expand.length; 611 static if (N == 0) 612 return; 613 else 614 static if (N == 1) 615 read(value.expand[0]); 616 else 617 { 618 expect('['); 619 foreach (n, ref f; value.expand) 620 { 621 static if (n) 622 expect(','); 623 read(f); 624 } 625 expect(']'); 626 } 627 } 628 629 typeof(null) readNull() 630 { 631 expect('n'); 632 expect('u'); 633 expect('l'); 634 expect('l'); 635 return null; 636 } 637 638 void readNullable(T)(ref Nullable!T value) 639 { 640 skipWhitespace(); 641 if (peek() == 'n') 642 { 643 readNull(); 644 value = Nullable!T(); 645 } 646 else 647 { 648 if (value.isNull) 649 { 650 T subvalue; 651 read!T(subvalue); 652 value = subvalue; 653 } 654 else 655 read!T(value.get()); 656 } 657 } 658 659 C[] readSimpleString() /// i.e. without escapes 660 { 661 skipWhitespace(); 662 expect('"'); 663 auto start = p; 664 while (true) 665 { 666 auto c = next(); 667 if (c=='"') 668 break; 669 else 670 if (c=='\\') 671 throw new Exception("Unexpected escaped character"); 672 } 673 return s[start..p-1]; 674 } 675 676 C[] readString() 677 { 678 skipWhitespace(); 679 auto c = peek(); 680 if (c == '"') 681 { 682 next(); // '"' 683 C[] result; 684 auto start = p; 685 while (true) 686 { 687 c = next(); 688 if (c=='"') 689 break; 690 else 691 if (c=='\\') 692 { 693 result ~= s[start..p-1]; 694 switch (next()) 695 { 696 case '"': result ~= '"'; break; 697 case '/': result ~= '/'; break; 698 case '\\': result ~= '\\'; break; 699 case 'b': result ~= '\b'; break; 700 case 'f': result ~= '\f'; break; 701 case 'n': result ~= '\n'; break; 702 case 'r': result ~= '\r'; break; 703 case 't': result ~= '\t'; break; 704 case 'u': 705 { 706 wstring buf; 707 goto Unicode_start; 708 709 while (s[p..$].startsWith(`\u`)) 710 { 711 p+=2; 712 Unicode_start: 713 buf ~= cast(wchar)fromHex!ushort(readN(4)); 714 } 715 result ~= buf.to!(C[]); 716 break; 717 } 718 default: enforce(false, "Unknown escape"); 719 } 720 start = p; 721 } 722 } 723 result ~= s[start..p-1]; 724 return result; 725 } 726 else 727 if (isDigit(c) || c=='-') // For languages that don't distinguish numeric strings from numbers 728 { 729 static immutable bool[256] numeric = 730 [ 731 '0':true, 732 '1':true, 733 '2':true, 734 '3':true, 735 '4':true, 736 '5':true, 737 '6':true, 738 '7':true, 739 '8':true, 740 '9':true, 741 '.':true, 742 '-':true, 743 '+':true, 744 'e':true, 745 'E':true, 746 ]; 747 748 auto start = p; 749 while (numeric[c = peek()]) 750 p++; 751 return s[start..p].dup; 752 } 753 else 754 { 755 foreach (n; "null") 756 expect(n); 757 return null; 758 } 759 } 760 761 bool readBool() 762 { 763 skipWhitespace(); 764 if (peek()=='t') 765 { 766 enforce(readN(4) == "true", "Bad boolean"); 767 return true; 768 } 769 else 770 if (peek()=='f') 771 { 772 enforce(readN(5) == "false", "Bad boolean"); 773 return false; 774 } 775 else 776 { 777 ubyte i = readNumber!ubyte(); 778 enforce(i < 2, "Bad digit for implicit number-to-bool conversion"); 779 return !!i; 780 } 781 } 782 783 T readNumber(T)() 784 { 785 skipWhitespace(); 786 const(C)[] n; 787 auto start = p; 788 Unqual!C c = peek(); 789 if (c == '"') // TODO: implicit type conversion should be optional 790 n = readSimpleString(); 791 else 792 { 793 while (c=='+' || c=='-' || (c>='0' && c<='9') || c=='e' || c=='E' || c=='.') 794 { 795 p++; 796 if (eof) break; 797 c = peek(); 798 } 799 n = s[start..p]; 800 } 801 static if (is(T : long)) 802 return to!T(n); 803 else 804 static if (is(T : real)) 805 return fpParse!T(n); 806 else 807 static assert(0, "Don't know how to parse numerical type " ~ T.stringof); 808 } 809 810 T[] readArray(T)() 811 { 812 skipWhitespace(); 813 expect('['); 814 skipWhitespace(); 815 T[] result; 816 if (peek()==']') 817 { 818 p++; 819 return result; 820 } 821 while(true) 822 { 823 T subvalue; 824 read!T(subvalue); 825 result ~= subvalue; 826 827 skipWhitespace(); 828 if (peek()==']') 829 { 830 p++; 831 return result; 832 } 833 else 834 expect(','); 835 } 836 } 837 838 void readStaticArray(T, size_t n)(ref T[n] value) 839 { 840 skipWhitespace(); 841 expect('['); 842 skipWhitespace(); 843 foreach (i, ref subvalue; value) 844 { 845 if (i) 846 { 847 expect(','); 848 skipWhitespace(); 849 } 850 read(subvalue); 851 skipWhitespace(); 852 } 853 expect(']'); 854 } 855 856 void readObject(T)(ref T v) 857 { 858 skipWhitespace(); 859 expect('{'); 860 skipWhitespace(); 861 if (peek()=='}') 862 { 863 p++; 864 return; 865 } 866 867 while (true) 868 { 869 auto jsonField = readSimpleString(); 870 mixin(exceptionContext(q{"Error with field " ~ to!string(jsonField)})); 871 skipWhitespace(); 872 expect(':'); 873 874 bool found; 875 foreach (i, ref field; v.tupleof) 876 { 877 enum name = getJsonName!(T, v.tupleof[i].stringof[2..$]); 878 if (name == jsonField) 879 { 880 read(field); 881 found = true; 882 break; 883 } 884 } 885 886 if (!found) 887 { 888 static if (hasAttribute!(JSONPartial, T)) 889 skipValue(); 890 else 891 throw new Exception(cast(string)("Unknown field " ~ jsonField)); 892 } 893 894 skipWhitespace(); 895 if (peek()=='}') 896 { 897 p++; 898 return; 899 } 900 else 901 expect(','); 902 } 903 } 904 905 void readAA(T)(ref T v) 906 { 907 skipWhitespace(); 908 static if (is(typeof(T.init is null))) 909 if (peek() == 'n') 910 { 911 v = readNull(); 912 return; 913 } 914 expect('{'); 915 skipWhitespace(); 916 if (peek()=='}') 917 { 918 p++; 919 return; 920 } 921 alias K = typeof(v.keys[0]); 922 923 while (true) 924 { 925 K jsonField; 926 static if (is(K == string)) 927 jsonField = readString().to!K; 928 else 929 { 930 static if (options.nonStringKeys == JsonOptions.NonStringKeys.asIs) 931 read(jsonField); 932 else 933 static if (options.nonStringKeys == JsonOptions.NonStringKeys.stringify) 934 jsonField = readString().jsonParse!(K, options); 935 else 936 static assert(false, "Cannot parse associative array with non-string key " ~ K.stringof); 937 } 938 939 skipWhitespace(); 940 expect(':'); 941 942 // TODO: elide copy 943 typeof(v.values[0]) subvalue; 944 read(subvalue); 945 v[jsonField] = subvalue; 946 947 skipWhitespace(); 948 if (peek()=='}') 949 { 950 p++; 951 return; 952 } 953 else 954 expect(','); 955 } 956 } 957 958 T readEnum(T)() 959 { 960 return to!T(readSimpleString()); 961 } 962 963 T readPointer(T)() 964 { 965 skipWhitespace(); 966 if (peek()=='n') 967 { 968 enforce(readN(4) == "null", "Null expected"); 969 return null; 970 } 971 alias S = typeof(*T.init); 972 T v = new S; 973 read!S(*v); 974 return v; 975 } 976 977 void skipValue() 978 { 979 skipWhitespace(); 980 Unqual!C c = peek(); 981 switch (c) 982 { 983 case '"': 984 readString(); // TODO: Optimize 985 break; 986 case '0': .. case '9': 987 case '-': 988 while (c=='+' || c=='-' || (c>='0' && c<='9') || c=='e' || c=='E' || c=='.') 989 { 990 p++; 991 if (eof) break; 992 c = peek(); 993 } 994 break; 995 case '{': 996 next(); 997 skipWhitespace(); 998 bool first = true; 999 while (peek() != '}') 1000 { 1001 if (first) 1002 first = false; 1003 else 1004 expect(','); 1005 skipValue(); // key 1006 skipWhitespace(); 1007 expect(':'); 1008 skipValue(); // value 1009 skipWhitespace(); 1010 } 1011 expect('}'); 1012 break; 1013 case '[': 1014 next(); 1015 skipWhitespace(); 1016 bool first = true; 1017 while (peek() != ']') 1018 { 1019 if (first) 1020 first = false; 1021 else 1022 expect(','); 1023 skipValue(); 1024 skipWhitespace(); 1025 } 1026 expect(']'); 1027 break; 1028 case 't': 1029 foreach (l; "true") 1030 expect(l); 1031 break; 1032 case 'f': 1033 foreach (l; "false") 1034 expect(l); 1035 break; 1036 case 'n': 1037 foreach (l; "null") 1038 expect(l); 1039 break; 1040 default: 1041 throw new Exception(text("Can't parse: ", c)); 1042 } 1043 } 1044 } 1045 1046 /// Parse the JSON in string `s` and deserialize it into an instance of `T`. 1047 template jsonParse(T, JsonOptions options = JsonOptions.init) 1048 { 1049 T jsonParse(C)(C[] s) 1050 { 1051 auto parser = JsonParser!(C, options)(s); 1052 mixin(exceptionContext(q{format("Error at position %d", parser.p)})); 1053 T result; 1054 parser.read!T(result); 1055 return result; 1056 } 1057 } 1058 1059 debug(ae_unittest) unittest 1060 { 1061 struct S { int i; S[] arr; S* p0, p1; } 1062 S s = S(42, [S(1), S(2)], null, new S(15)); 1063 auto s2 = jsonParse!S(toJson(s)); 1064 //assert(s == s2); // Issue 3789 1065 assert(s.i == s2.i && s.arr == s2.arr && s.p0 is s2.p0 && *s.p1 == *s2.p1); 1066 jsonParse!S(toJson(s).dup); 1067 1068 assert(jsonParse!(Tuple!())(``) == tuple()); 1069 assert(jsonParse!(Tuple!int)(`42`) == tuple(42)); 1070 assert(jsonParse!(Tuple!(int, string))(`[42, "banana"]`) == tuple(42, "banana")); 1071 1072 assert(jsonParse!(string[string])(`null`) is null); 1073 } 1074 1075 debug(ae_unittest) unittest 1076 { 1077 struct T { string s; wstring w; dstring d; } 1078 T t; 1079 auto s = t.toJson; 1080 assert(s == `{"s":null,"w":null,"d":null}`, s); 1081 1082 t.s = "foo"; 1083 t.w = "bar"w; 1084 t.d = "baz"d; 1085 s = t.toJson; 1086 assert(s == `{"s":"foo","w":"bar","d":"baz"}`, s); 1087 1088 jsonParse!T(s); 1089 jsonParse!T(cast(char[]) s); 1090 jsonParse!T(cast(const(char)[]) s); 1091 jsonParse!T(s.to!wstring); 1092 jsonParse!T(s.to!dstring); 1093 } 1094 1095 debug(ae_unittest) unittest 1096 { 1097 jsonParse!(int[2])(`[ 1 , 2 ]`); 1098 } 1099 1100 // NaNs and infinities are serialized as strings. 1101 debug(ae_unittest) unittest 1102 { 1103 void check(double f, string s) 1104 { 1105 assert(f.toJson() == s); 1106 assert(s.jsonParse!double is f); 1107 } 1108 check(double.init, `"nan"`); 1109 check(double.infinity, `"inf"`); 1110 check(-double.infinity, `"-inf"`); 1111 } 1112 1113 /// Parse the JSON in string `s` and deserialize it into `T`. 1114 void jsonParse(T, C)(C[] s, ref T result) 1115 { 1116 auto parser = JsonParser!C(s); 1117 mixin(exceptionContext(q{format("Error at position %d", parser.p)})); 1118 parser.read!T(result); 1119 } 1120 1121 debug(ae_unittest) unittest 1122 { 1123 struct S { int a, b; } 1124 S s; 1125 s.a = 1; 1126 jsonParse(`{"b":2}`, s); 1127 assert(s == S(1, 2)); 1128 } 1129 1130 // ************************************************************************ 1131 1132 // TODO: migrate to UDAs 1133 1134 /** 1135 * A template that designates fields which should not be serialized to Json. 1136 * 1137 * Example: 1138 * --- 1139 * struct Point { int x, y, z; mixin NonSerialized!(x, z); } 1140 * assert(jsonParse!Point(toJson(Point(1, 2, 3))) == Point(0, 2, 0)); 1141 * --- 1142 */ 1143 template NonSerialized(fields...) 1144 { 1145 import ae.utils.meta : stringofArray; 1146 mixin(mixNonSerializedFields(stringofArray!fields())); 1147 } 1148 1149 private string mixNonSerializedFields(string[] fields) 1150 { 1151 string result; 1152 foreach (field; fields) 1153 result ~= "enum bool " ~ field ~ "_nonSerialized = 1;"; 1154 return result; 1155 } 1156 1157 private template doSkipSerialize(T, string member) 1158 { 1159 enum bool doSkipSerialize = __traits(hasMember, T, member ~ "_nonSerialized"); 1160 } 1161 1162 debug(ae_unittest) unittest 1163 { 1164 struct Point { int x, y, z; mixin NonSerialized!(x, z); } 1165 assert(jsonParse!Point(toJson(Point(1, 2, 3))) == Point(0, 2, 0)); 1166 } 1167 1168 debug(ae_unittest) unittest 1169 { 1170 enum En { one, two } 1171 assert(En.one.toJson() == `"one"`); 1172 struct S { int i1, i2; S[] arr1, arr2; string[string] dic; En en; mixin NonSerialized!(i2, arr2); } 1173 S s = S(42, 5, [S(1), S(2)], [S(3), S(4)], ["apple":"fruit", "pizza":"vegetable"], En.two); 1174 auto s2 = jsonParse!S(toJson(s)); 1175 assert(s.i1 == s2.i1); 1176 assert(s2.i2 is int.init); 1177 assert(s.arr1 == s2.arr1); 1178 assert(s2.arr2 is null); 1179 assert(s.dic == s2.dic, s2.dic.text); 1180 assert(s.en == En.two); 1181 } 1182 1183 debug(ae_unittest) unittest 1184 { 1185 alias B = Nullable!bool; 1186 B b; 1187 1188 b = jsonParse!B("true"); 1189 assert(!b.isNull); 1190 assert(b.get == true); 1191 assert(b.toJson == "true"); 1192 1193 b = jsonParse!B("false"); 1194 assert(!b.isNull); 1195 assert(b.get == false); 1196 assert(b.toJson == "false"); 1197 1198 b = jsonParse!B("null"); 1199 assert(b.isNull); 1200 assert(b.toJson == "null"); 1201 1202 struct S {} 1203 alias NS = Nullable!S; 1204 assert(NS.init.toJson == "null"); 1205 } 1206 1207 debug(ae_unittest) unittest // Issue 49 1208 { 1209 immutable bool b; 1210 assert(toJson(b) == "false"); 1211 } 1212 1213 debug(ae_unittest) unittest 1214 { 1215 import ae.utils.aa : OrderedMap; 1216 alias M = OrderedMap!(string, int); 1217 M m; 1218 m["one"] = 1; 1219 m["two"] = 2; 1220 auto j = (cast(const)m).toJson(); 1221 assert(j == `{"one":1,"two":2}`, j); 1222 assert(j.jsonParse!M == m); 1223 } 1224 1225 debug(ae_unittest) unittest 1226 { 1227 assert(string.init.toJson.jsonParse!string is null); 1228 assert("" .toJson.jsonParse!string !is null); 1229 } 1230 1231 debug(ae_unittest) unittest 1232 { 1233 char[] s = "{}".dup; 1234 assert(s.jsonParse!(string[string]) == null); 1235 } 1236 1237 debug(ae_unittest) unittest 1238 { 1239 typeof(null) n; 1240 assert(n.toJson.jsonParse!(typeof(null)) is null); 1241 } 1242 1243 debug(ae_unittest) unittest 1244 { 1245 double f = 1.5; 1246 assert(f.toJson() == "1.5"); 1247 } 1248 1249 debug(ae_unittest) unittest 1250 { 1251 dchar c = '😸'; 1252 assert(c.toJson() == `"😸"`); 1253 } 1254 1255 /// `fromJSON` / `toJSON` can be added to a type to control their serialized representation. 1256 debug(ae_unittest) unittest 1257 { 1258 static struct S 1259 { 1260 string value; 1261 static S fromJSON(string value) { return S(value); } 1262 string toJSON() { return value; } 1263 } 1264 auto s = S("test"); 1265 assert(s.toJson == `"test"`); 1266 assert(s.toJson.jsonParse!S == s); 1267 } 1268 1269 debug(ae_unittest) unittest 1270 { 1271 static struct S 1272 { 1273 string value; 1274 static S fromJSON(string value) { return S(value); } 1275 void toJSON(F)(F f) { f(value); } 1276 } 1277 auto s = S("test"); 1278 auto p = &s; 1279 assert(p.toJson == `"test"`); 1280 assert(*p.toJson.jsonParse!(S*) == s); 1281 } 1282 1283 /// `fromJSON` / `toJSON` can also accept/return a `JSONFragment`, 1284 /// which allows full control over JSON serialization. 1285 debug(ae_unittest) unittest 1286 { 1287 static struct BigInt 1288 { 1289 string decimalDigits; 1290 static BigInt fromJSON(JSONFragment value) { return BigInt(value.json); } 1291 JSONFragment toJSON() { return JSONFragment(decimalDigits); } 1292 } 1293 auto n = BigInt("12345678901234567890"); 1294 assert(n.toJson == `12345678901234567890`); 1295 assert(n.toJson.jsonParse!BigInt == n); 1296 } 1297 1298 // ************************************************************************ 1299 1300 /// User-defined attribute - specify name for JSON object field. 1301 /// Useful when a JSON object may contain fields, the name of which are not valid D identifiers. 1302 struct JSONName { string name; /***/ } 1303 1304 private template getJsonName(S, string FIELD) 1305 { 1306 static if (hasAttribute!(JSONName, __traits(getMember, S, FIELD))) 1307 enum getJsonName = getAttribute!(JSONName, __traits(getMember, S, FIELD)).name; 1308 else 1309 enum getJsonName = FIELD; 1310 } 1311 1312 // ************************************************************************ 1313 1314 /// User-defined attribute - only serialize this field if its value is different from its .init value. 1315 struct JSONOptional {} 1316 1317 debug(ae_unittest) unittest 1318 { 1319 static struct S { @JSONOptional bool a=true, b=false; } 1320 assert(S().toJson == `{}`, S().toJson); 1321 assert(S(false, true).toJson == `{"a":false,"b":true}`); 1322 } 1323 1324 debug(ae_unittest) unittest 1325 { 1326 static struct S { @JSONOptional float f; } 1327 assert(S().toJson == `{}`, S().toJson); 1328 } 1329 1330 debug(ae_unittest) unittest 1331 { 1332 static struct S { @JSONOptional int[1] a; } 1333 assert(S().toJson == `{}`, S().toJson); 1334 } 1335 1336 // ************************************************************************ 1337 1338 /// User-defined attribute - skip unknown fields when deserializing. 1339 struct JSONPartial {} 1340 1341 debug(ae_unittest) unittest 1342 { 1343 @JSONPartial static struct S { int b; } 1344 assert(`{"a":1,"b":2,"c":3.4,"d":[5,"x"],"de":[],"e":{"k":"v"},"ee":{},"f":true,"g":false,"h":null}`.jsonParse!S == S(2)); 1345 } 1346 1347 // ************************************************************************ 1348 1349 /// Fragment of raw JSON. 1350 /// When serialized, the .json field is inserted into the resulting 1351 /// string verbatim, without any validation. 1352 /// When deserialized, will contain the raw JSON of one JSON object of 1353 /// any type. 1354 struct JSONFragment 1355 { 1356 string json; /// 1357 bool opCast(T)() const if (is(T==bool)) { return !!json; } /// 1358 } 1359 1360 debug(ae_unittest) unittest 1361 { 1362 JSONFragment[] arr = [JSONFragment(`1`), JSONFragment(`true`), JSONFragment(`"foo"`), JSONFragment(`[55]`)]; 1363 assert(arr.toJson == `[1,true,"foo",[55]]`); 1364 assert(arr.toJson.jsonParse!(JSONFragment[]) == arr); 1365 } 1366 1367 // ************************************************************************ 1368 1369 debug(ae_unittest) unittest 1370 { 1371 int[int] aa = [3: 4]; 1372 { 1373 enum JsonOptions options = { nonStringKeys: JsonOptions.NonStringKeys.error }; 1374 static assert(!__traits(compiles, aa.toJson!options)); 1375 static assert(!__traits(compiles, "".jsonParse!(typeof(aa), options))); 1376 } 1377 { 1378 enum JsonOptions options = { nonStringKeys: JsonOptions.NonStringKeys.asIs }; 1379 auto s = aa.toJson!options; 1380 assert(s == `{3:4}`); 1381 assert(s.jsonParse!(typeof(aa), options) == aa); 1382 } 1383 { 1384 enum JsonOptions options = { nonStringKeys: JsonOptions.NonStringKeys.stringify }; 1385 auto s = aa.toJson!options; 1386 assert(s == `{"3":4}`); 1387 assert(s.jsonParse!(typeof(aa), options) == aa); 1388 } 1389 }