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