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