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