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