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 (is(T U : U*)) 513 value = readPointer!T(); 514 else 515 static if (__traits(hasMember, T, "fromJSON")) 516 { 517 alias Q = Parameters!(T.fromJSON)[0]; 518 Q tempValue; 519 read!Q(tempValue); 520 static if (is(typeof(value = T.fromJSON(tempValue)))) 521 value = T.fromJSON(tempValue); 522 else 523 { 524 import core.lifetime : move; 525 auto convertedValue = T.fromJSON(tempValue); 526 move(convertedValue, value); 527 } 528 } 529 else 530 static if (is(T==struct)) 531 readObject!(T)(value); 532 else 533 static assert(0, "Can't decode " ~ T.stringof ~ " from JSON"); 534 } 535 536 void readTuple(T)(ref T value) 537 { 538 // TODO: serialize as object if tuple has names 539 enum N = T.expand.length; 540 static if (N == 0) 541 return; 542 else 543 static if (N == 1) 544 read(value.expand[0]); 545 else 546 { 547 expect('['); 548 foreach (n, ref f; value.expand) 549 { 550 static if (n) 551 expect(','); 552 read(f); 553 } 554 expect(']'); 555 } 556 } 557 558 typeof(null) readNull() 559 { 560 expect('n'); 561 expect('u'); 562 expect('l'); 563 expect('l'); 564 return null; 565 } 566 567 void readNullable(T)(ref Nullable!T value) 568 { 569 skipWhitespace(); 570 if (peek() == 'n') 571 { 572 readNull(); 573 value = Nullable!T(); 574 } 575 else 576 { 577 if (value.isNull) 578 { 579 T subvalue; 580 read!T(subvalue); 581 value = subvalue; 582 } 583 else 584 read!T(value.get()); 585 } 586 } 587 588 C[] readSimpleString() /// i.e. without escapes 589 { 590 skipWhitespace(); 591 expect('"'); 592 auto start = p; 593 while (true) 594 { 595 auto c = next(); 596 if (c=='"') 597 break; 598 else 599 if (c=='\\') 600 throw new Exception("Unexpected escaped character"); 601 } 602 return s[start..p-1]; 603 } 604 605 C[] readString() 606 { 607 skipWhitespace(); 608 auto c = peek(); 609 if (c == '"') 610 { 611 next(); // '"' 612 C[] result; 613 auto start = p; 614 while (true) 615 { 616 c = next(); 617 if (c=='"') 618 break; 619 else 620 if (c=='\\') 621 { 622 result ~= s[start..p-1]; 623 switch (next()) 624 { 625 case '"': result ~= '"'; break; 626 case '/': result ~= '/'; break; 627 case '\\': result ~= '\\'; break; 628 case 'b': result ~= '\b'; break; 629 case 'f': result ~= '\f'; break; 630 case 'n': result ~= '\n'; break; 631 case 'r': result ~= '\r'; break; 632 case 't': result ~= '\t'; break; 633 case 'u': 634 { 635 wstring buf; 636 goto Unicode_start; 637 638 while (s[p..$].startsWith(`\u`)) 639 { 640 p+=2; 641 Unicode_start: 642 buf ~= cast(wchar)fromHex!ushort(readN(4)); 643 } 644 result ~= buf.to!(C[]); 645 break; 646 } 647 default: enforce(false, "Unknown escape"); 648 } 649 start = p; 650 } 651 } 652 result ~= s[start..p-1]; 653 return result; 654 } 655 else 656 if (isDigit(c) || c=='-') // For languages that don't distinguish numeric strings from numbers 657 { 658 static immutable bool[256] numeric = 659 [ 660 '0':true, 661 '1':true, 662 '2':true, 663 '3':true, 664 '4':true, 665 '5':true, 666 '6':true, 667 '7':true, 668 '8':true, 669 '9':true, 670 '.':true, 671 '-':true, 672 '+':true, 673 'e':true, 674 'E':true, 675 ]; 676 677 auto start = p; 678 while (numeric[c = peek()]) 679 p++; 680 return s[start..p].dup; 681 } 682 else 683 { 684 foreach (n; "null") 685 expect(n); 686 return null; 687 } 688 } 689 690 bool readBool() 691 { 692 skipWhitespace(); 693 if (peek()=='t') 694 { 695 enforce(readN(4) == "true", "Bad boolean"); 696 return true; 697 } 698 else 699 if (peek()=='f') 700 { 701 enforce(readN(5) == "false", "Bad boolean"); 702 return false; 703 } 704 else 705 { 706 ubyte i = readNumber!ubyte(); 707 enforce(i < 2, "Bad digit for implicit number-to-bool conversion"); 708 return !!i; 709 } 710 } 711 712 T readNumber(T)() 713 { 714 skipWhitespace(); 715 const(C)[] n; 716 auto start = p; 717 Unqual!C c = peek(); 718 if (c == '"') 719 n = readSimpleString(); 720 else 721 { 722 while (c=='+' || c=='-' || (c>='0' && c<='9') || c=='e' || c=='E' || c=='.') 723 { 724 p++; 725 if (eof) break; 726 c = peek(); 727 } 728 n = s[start..p]; 729 } 730 static if (is(T : real)) 731 return to!T(n); 732 else 733 static assert(0, "Don't know how to parse numerical type " ~ T.stringof); 734 } 735 736 T[] readArray(T)() 737 { 738 skipWhitespace(); 739 expect('['); 740 skipWhitespace(); 741 T[] result; 742 if (peek()==']') 743 { 744 p++; 745 return result; 746 } 747 while(true) 748 { 749 T subvalue; 750 read!T(subvalue); 751 result ~= subvalue; 752 753 skipWhitespace(); 754 if (peek()==']') 755 { 756 p++; 757 return result; 758 } 759 else 760 expect(','); 761 } 762 } 763 764 void readStaticArray(T, size_t n)(ref T[n] value) 765 { 766 skipWhitespace(); 767 expect('['); 768 skipWhitespace(); 769 foreach (i, ref subvalue; value) 770 { 771 if (i) 772 { 773 expect(','); 774 skipWhitespace(); 775 } 776 read(subvalue); 777 skipWhitespace(); 778 } 779 expect(']'); 780 } 781 782 void readObject(T)(ref T v) 783 { 784 skipWhitespace(); 785 expect('{'); 786 skipWhitespace(); 787 if (peek()=='}') 788 { 789 p++; 790 return; 791 } 792 793 while (true) 794 { 795 auto jsonField = readSimpleString(); 796 mixin(exceptionContext(q{"Error with field " ~ to!string(jsonField)})); 797 skipWhitespace(); 798 expect(':'); 799 800 bool found; 801 foreach (i, ref field; v.tupleof) 802 { 803 enum name = getJsonName!(T, v.tupleof[i].stringof[2..$]); 804 if (name == jsonField) 805 { 806 read(field); 807 found = true; 808 break; 809 } 810 } 811 812 if (!found) 813 { 814 static if (hasAttribute!(JSONPartial, T)) 815 skipValue(); 816 else 817 throw new Exception(cast(string)("Unknown field " ~ jsonField)); 818 } 819 820 skipWhitespace(); 821 if (peek()=='}') 822 { 823 p++; 824 return; 825 } 826 else 827 expect(','); 828 } 829 } 830 831 void readAA(T)(ref T v) 832 { 833 skipWhitespace(); 834 static if (is(typeof(T.init is null))) 835 if (peek() == 'n') 836 { 837 v = readNull(); 838 return; 839 } 840 expect('{'); 841 skipWhitespace(); 842 if (peek()=='}') 843 { 844 p++; 845 return; 846 } 847 alias K = typeof(v.keys[0]); 848 849 while (true) 850 { 851 auto jsonField = readString(); 852 skipWhitespace(); 853 expect(':'); 854 855 // TODO: elide copy 856 typeof(v.values[0]) subvalue; 857 read(subvalue); 858 v[jsonField.to!K] = subvalue; 859 860 skipWhitespace(); 861 if (peek()=='}') 862 { 863 p++; 864 return; 865 } 866 else 867 expect(','); 868 } 869 } 870 871 T readEnum(T)() 872 { 873 return to!T(readSimpleString()); 874 } 875 876 T readPointer(T)() 877 { 878 skipWhitespace(); 879 if (peek()=='n') 880 { 881 enforce(readN(4) == "null", "Null expected"); 882 return null; 883 } 884 alias S = typeof(*T.init); 885 T v = new S; 886 read!S(*v); 887 return v; 888 } 889 890 void skipValue() 891 { 892 skipWhitespace(); 893 C c = peek(); 894 switch (c) 895 { 896 case '"': 897 readString(); // TODO: Optimize 898 break; 899 case '0': .. case '9': 900 case '-': 901 readNumber!real(); // TODO: Optimize 902 break; 903 case '{': 904 next(); 905 skipWhitespace(); 906 bool first = true; 907 while (peek() != '}') 908 { 909 if (first) 910 first = false; 911 else 912 expect(','); 913 skipValue(); // key 914 skipWhitespace(); 915 expect(':'); 916 skipValue(); // value 917 skipWhitespace(); 918 } 919 expect('}'); 920 break; 921 case '[': 922 next(); 923 skipWhitespace(); 924 bool first = true; 925 while (peek() != ']') 926 { 927 if (first) 928 first = false; 929 else 930 expect(','); 931 skipValue(); 932 skipWhitespace(); 933 } 934 expect(']'); 935 break; 936 case 't': 937 foreach (l; "true") 938 expect(l); 939 break; 940 case 'f': 941 foreach (l; "false") 942 expect(l); 943 break; 944 case 'n': 945 foreach (l; "null") 946 expect(l); 947 break; 948 default: 949 throw new Exception(text("Can't parse: ", c)); 950 } 951 } 952 } 953 954 /// Parse the JSON in string `s` and deserialize it into an instance of `T`. 955 T jsonParse(T, C)(C[] s) 956 { 957 auto parser = JsonParser!C(s); 958 mixin(exceptionContext(q{format("Error at position %d", parser.p)})); 959 T result; 960 parser.read!T(result); 961 return result; 962 } 963 964 unittest 965 { 966 struct S { int i; S[] arr; S* p0, p1; } 967 S s = S(42, [S(1), S(2)], null, new S(15)); 968 auto s2 = jsonParse!S(toJson(s)); 969 //assert(s == s2); // Issue 3789 970 assert(s.i == s2.i && s.arr == s2.arr && s.p0 is s2.p0 && *s.p1 == *s2.p1); 971 jsonParse!S(toJson(s).dup); 972 973 assert(jsonParse!(Tuple!())(``) == tuple()); 974 assert(jsonParse!(Tuple!int)(`42`) == tuple(42)); 975 assert(jsonParse!(Tuple!(int, string))(`[42, "banana"]`) == tuple(42, "banana")); 976 977 assert(jsonParse!(string[string])(`null`) is null); 978 } 979 980 unittest 981 { 982 struct T { string s; wstring w; dstring d; } 983 T t; 984 auto s = t.toJson; 985 assert(s == `{"s":null,"w":null,"d":null}`, s); 986 987 t.s = "foo"; 988 t.w = "bar"w; 989 t.d = "baz"d; 990 s = t.toJson; 991 assert(s == `{"s":"foo","w":"bar","d":"baz"}`, s); 992 993 jsonParse!T(s); 994 jsonParse!T(cast(char[]) s); 995 jsonParse!T(cast(const(char)[]) s); 996 jsonParse!T(s.to!wstring); 997 jsonParse!T(s.to!dstring); 998 } 999 1000 unittest 1001 { 1002 jsonParse!(int[2])(`[ 1 , 2 ]`); 1003 } 1004 1005 /// Parse the JSON in string `s` and deserialize it into `T`. 1006 void jsonParse(T, C)(C[] s, ref T result) 1007 { 1008 auto parser = JsonParser!C(s); 1009 mixin(exceptionContext(q{format("Error at position %d", parser.p)})); 1010 parser.read!T(result); 1011 } 1012 1013 unittest 1014 { 1015 struct S { int a, b; } 1016 S s; 1017 s.a = 1; 1018 jsonParse(`{"b":2}`, s); 1019 assert(s == S(1, 2)); 1020 } 1021 1022 // ************************************************************************ 1023 1024 // TODO: migrate to UDAs 1025 1026 /** 1027 * A template that designates fields which should not be serialized to Json. 1028 * 1029 * Example: 1030 * --- 1031 * struct Point { int x, y, z; mixin NonSerialized!(x, z); } 1032 * assert(jsonParse!Point(toJson(Point(1, 2, 3))) == Point(0, 2, 0)); 1033 * --- 1034 */ 1035 template NonSerialized(fields...) 1036 { 1037 import ae.utils.meta : stringofArray; 1038 mixin(mixNonSerializedFields(stringofArray!fields())); 1039 } 1040 1041 private string mixNonSerializedFields(string[] fields) 1042 { 1043 string result; 1044 foreach (field; fields) 1045 result ~= "enum bool " ~ field ~ "_nonSerialized = 1;"; 1046 return result; 1047 } 1048 1049 private template doSkipSerialize(T, string member) 1050 { 1051 enum bool doSkipSerialize = __traits(hasMember, T, member ~ "_nonSerialized"); 1052 } 1053 1054 unittest 1055 { 1056 struct Point { int x, y, z; mixin NonSerialized!(x, z); } 1057 assert(jsonParse!Point(toJson(Point(1, 2, 3))) == Point(0, 2, 0)); 1058 } 1059 1060 unittest 1061 { 1062 enum En { one, two } 1063 assert(En.one.toJson() == `"one"`); 1064 struct S { int i1, i2; S[] arr1, arr2; string[string] dic; En en; mixin NonSerialized!(i2, arr2); } 1065 S s = S(42, 5, [S(1), S(2)], [S(3), S(4)], ["apple":"fruit", "pizza":"vegetable"], En.two); 1066 auto s2 = jsonParse!S(toJson(s)); 1067 assert(s.i1 == s2.i1); 1068 assert(s2.i2 is int.init); 1069 assert(s.arr1 == s2.arr1); 1070 assert(s2.arr2 is null); 1071 assert(s.dic == s2.dic, s2.dic.text); 1072 assert(s.en == En.two); 1073 } 1074 1075 unittest 1076 { 1077 alias B = Nullable!bool; 1078 B b; 1079 1080 b = jsonParse!B("true"); 1081 assert(!b.isNull); 1082 assert(b.get == true); 1083 assert(b.toJson == "true"); 1084 1085 b = jsonParse!B("false"); 1086 assert(!b.isNull); 1087 assert(b.get == false); 1088 assert(b.toJson == "false"); 1089 1090 b = jsonParse!B("null"); 1091 assert(b.isNull); 1092 assert(b.toJson == "null"); 1093 1094 struct S {} 1095 alias NS = Nullable!S; 1096 assert(NS.init.toJson == "null"); 1097 } 1098 1099 unittest // Issue 49 1100 { 1101 immutable bool b; 1102 assert(toJson(b) == "false"); 1103 } 1104 1105 unittest 1106 { 1107 import ae.utils.aa : OrderedMap; 1108 alias M = OrderedMap!(string, int); 1109 M m; 1110 m["one"] = 1; 1111 m["two"] = 2; 1112 auto j = (cast(const)m).toJson(); 1113 assert(j == `{"one":1,"two":2}`, j); 1114 assert(j.jsonParse!M == m); 1115 } 1116 1117 unittest 1118 { 1119 assert(string.init.toJson.jsonParse!string is null); 1120 assert("" .toJson.jsonParse!string !is null); 1121 } 1122 1123 unittest 1124 { 1125 char[] s = "{}".dup; 1126 assert(s.jsonParse!(string[string]) == null); 1127 } 1128 1129 unittest 1130 { 1131 typeof(null) n; 1132 assert(n.toJson.jsonParse!(typeof(null)) is null); 1133 } 1134 1135 unittest 1136 { 1137 double f = 1.5; 1138 assert(f.toJson() == "1.5"); 1139 } 1140 1141 unittest 1142 { 1143 dchar c = '😸'; 1144 assert(c.toJson() == `"😸"`); 1145 } 1146 1147 /// `fromJSON` / `toJSON` can be added to a type to control their serialized representation. 1148 unittest 1149 { 1150 static struct S 1151 { 1152 string value; 1153 static S fromJSON(string value) { return S(value); } 1154 string toJSON() { return value; } 1155 } 1156 auto s = S("test"); 1157 assert(s.toJson == `"test"`); 1158 assert(s.toJson.jsonParse!S == s); 1159 } 1160 1161 unittest 1162 { 1163 static struct S 1164 { 1165 string value; 1166 static S fromJSON(string value) { return S(value); } 1167 string toJSON() { return value; } 1168 } 1169 auto s = S("test"); 1170 auto p = &s; 1171 assert(p.toJson == `"test"`); 1172 assert(*p.toJson.jsonParse!(S*) == s); 1173 } 1174 1175 /// `fromJSON` / `toJSON` can also accept/return a `JSONFragment`, 1176 /// which allows full control over JSON serialization. 1177 unittest 1178 { 1179 static struct BigInt 1180 { 1181 string decimalDigits; 1182 static BigInt fromJSON(JSONFragment value) { return BigInt(value.json); } 1183 JSONFragment toJSON() { return JSONFragment(decimalDigits); } 1184 } 1185 auto n = BigInt("12345678901234567890"); 1186 assert(n.toJson == `12345678901234567890`); 1187 assert(n.toJson.jsonParse!BigInt == n); 1188 } 1189 1190 // ************************************************************************ 1191 1192 /// User-defined attribute - specify name for JSON object field. 1193 /// Useful when a JSON object may contain fields, the name of which are not valid D identifiers. 1194 struct JSONName { string name; /***/ } 1195 1196 private template getJsonName(S, string FIELD) 1197 { 1198 static if (hasAttribute!(JSONName, __traits(getMember, S, FIELD))) 1199 enum getJsonName = getAttribute!(JSONName, __traits(getMember, S, FIELD)).name; 1200 else 1201 enum getJsonName = FIELD; 1202 } 1203 1204 // ************************************************************************ 1205 1206 /// User-defined attribute - only serialize this field if its value is different from its .init value. 1207 struct JSONOptional {} 1208 1209 unittest 1210 { 1211 static struct S { @JSONOptional bool a=true, b=false; } 1212 assert(S().toJson == `{}`, S().toJson); 1213 assert(S(false, true).toJson == `{"a":false,"b":true}`); 1214 } 1215 1216 // ************************************************************************ 1217 1218 /// User-defined attribute - skip unknown fields when deserializing. 1219 struct JSONPartial {} 1220 1221 unittest 1222 { 1223 @JSONPartial static struct S { int b; } 1224 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)); 1225 } 1226 1227 // ************************************************************************ 1228 1229 /// Fragment of raw JSON. 1230 /// When serialized, the .json field is inserted into the resulting 1231 /// string verbatim, without any validation. 1232 /// When deserialized, will contain the raw JSON of one JSON object of 1233 /// any type. 1234 struct JSONFragment 1235 { 1236 string json; /// 1237 bool opCast(T)() const if (is(T==bool)) { return !!json; } /// 1238 } 1239 1240 unittest 1241 { 1242 JSONFragment[] arr = [JSONFragment(`1`), JSONFragment(`true`), JSONFragment(`"foo"`), JSONFragment(`[55]`)]; 1243 assert(arr.toJson == `[1,true,"foo",[55]]`); 1244 assert(arr.toJson.jsonParse!(JSONFragment[]) == arr); 1245 }