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