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