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