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