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