1 module iopipe.json.serialize; 2 import iopipe.json.parser; 3 import iopipe.json.dom; 4 public import iopipe.json.common; 5 import iopipe.traits; 6 import iopipe.bufpipe; 7 import std.range.primitives; 8 9 import std.traits; 10 import std.typecons : Nullable; 11 12 // define some UDAs to affect serialization 13 struct IgnoredMembers { string[] ignoredMembers; } 14 15 /** 16 * This UDA will cause the specified member to be substituted in for 17 * serializing a struct or class type. 18 */ 19 enum serializeAs; 20 21 /** 22 * Ignore the member when serializing a struct or class. 23 */ 24 enum ignore; 25 26 /** 27 * If this member is not present when deserializing a type, do not consider it 28 * an error. 29 */ 30 enum optional; 31 32 /** 33 * if on an enum, this serializes the enum as the base type instead of the enum 34 * name (default). 35 */ 36 enum enumBaseType; 37 38 /** 39 * This UDA, when applied to a JSONValue type member, will consume all items 40 * that do not have a member name in that aggregate. Only one of these should 41 * be in a type. 42 */ 43 enum extras; 44 45 struct alternateName 46 { 47 string name; 48 } 49 50 /** 51 * Apply this to a struct or class for members of a JSON object that you want 52 * to be ignored. For example, metadata that aids in deciding a concrete class 53 * type. 54 */ 55 IgnoredMembers ignoredMembers(string[] m...) { return IgnoredMembers(m.dup); } 56 57 /** 58 * Expect the given JSONItem to be a specific token. 59 */ 60 void jsonExpect(ref JSONItem item, JSONToken expectedToken, string msg, string file = __FILE__, size_t line = __LINE__) pure @safe 61 { 62 if(item.token != expectedToken) 63 { 64 import std.format; 65 throw new Exception(format("%s: expected %s, got %s", msg, expectedToken, item.token), file, line); 66 } 67 } 68 69 private void deserializeImpl(T, JT)(ref JT tokenizer, ref T item, ReleasePolicy relPol) if (__traits(isStaticArray, T)) 70 { 71 auto jsonItem = tokenizer.next; 72 jsonExpect(jsonItem, JSONToken.ArrayStart, "Parsing " ~ T.stringof); 73 74 bool first = true; 75 foreach(ref elem; item) 76 { 77 if(!first) 78 { 79 // verify there's a comma 80 jsonItem = tokenizer.next; 81 jsonExpect(jsonItem, JSONToken.Comma, "Parsing " ~ T.stringof); 82 } 83 first = false; 84 deserializeImpl(tokenizer, elem, relPol); 85 if(relPol == ReleasePolicy.afterMembers) 86 tokenizer.releaseParsed(); 87 } 88 89 // verify we got an end array element 90 jsonItem = tokenizer.next; 91 jsonExpect(jsonItem, JSONToken.ArrayEnd, "Parsing " ~ T.stringof); 92 } 93 94 private void deserializeImpl(T, JT)(ref JT tokenizer, ref T item, ReleasePolicy pol) if (is(T == enum)) 95 { 96 // enums are special, we can serialize them based on the enum name, or the 97 // base type. 98 import std.conv : to; 99 static if(hasUDA!(T, enumBaseType)) 100 { 101 deserializeImpl(tokenizer, *(cast(OriginalType!T*)&item), pol); 102 } 103 else 104 { 105 // convert to the enum via the string name 106 auto jsonItem = tokenizer.next; 107 jsonExpect(jsonItem, JSONToken.String, "Parsing " ~ T.stringof); 108 item = jsonItem.data(tokenizer.chain).to!T; 109 } 110 } 111 112 // TODO: should deal with writable input ranges and output ranges 113 private void deserializeImpl(T, JT)(ref JT tokenizer, ref T item, ReleasePolicy relPol) if (isDynamicArray!T && !isSomeString!T && !is(T == enum)) 114 { 115 auto jsonItem = tokenizer.next; 116 jsonExpect(jsonItem, JSONToken.ArrayStart, "Parsing " ~ T.stringof); 117 118 import std.array : Appender; 119 auto app = Appender!T(); 120 121 // check for an empty array (special case) 122 if(tokenizer.peek == JSONToken.ArrayEnd) 123 { 124 // parse it off 125 jsonItem = tokenizer.next; 126 // nothing left to do 127 return; 128 } 129 // parse items and commas until we get an array end. 130 while(true) 131 { 132 typeof(item[0]) elem; 133 deserializeImpl(tokenizer, elem, relPol); 134 app ~= elem; 135 if(relPol == ReleasePolicy.afterMembers) 136 tokenizer.releaseParsed(); 137 jsonItem = tokenizer.next; 138 if(jsonItem.token == JSONToken.ArrayEnd) 139 break; 140 jsonExpect(jsonItem, JSONToken.Comma, "Parsing " ~ T.stringof); 141 } 142 143 // fill in the data. 144 item = app.data; 145 } 146 147 private void deserializeImpl(T, JT)(ref JT tokenizer, ref T item, ReleasePolicy) if (!is(T == enum) && isNumeric!T) 148 { 149 import std.conv : parse; 150 import std.format : format; 151 auto jsonItem = tokenizer.next; 152 jsonExpect(jsonItem, JSONToken.Number, "Parsing " ~ T.stringof); 153 154 auto str = jsonItem.data(tokenizer.chain); 155 static if(isIntegral!T) 156 { 157 if(jsonItem.hint != JSONParseHint.Int) 158 { 159 throw new Exception(format("Cannot parse `%s` from '%s'", T.stringof, jsonItem.data(tokenizer.chain))); 160 } 161 } 162 163 // get the string from the buffer that contains the number 164 auto window = jsonItem.data(tokenizer.chain); 165 item = window.parse!T; 166 if(!window.empty) 167 { 168 throw new Exception(format("Parsing of `%s` from source '%s' failed near '%s'", T.stringof, jsonItem.data(tokenizer.chain), window)); 169 } 170 } 171 172 private void deserializeImpl(T, JT)(ref JT tokenizer, ref T item, ReleasePolicy) if (is(T == bool)) 173 { 174 import std.conv : parse; 175 import std.format : format; 176 auto jsonItem = tokenizer.next; 177 if(jsonItem.token == JSONToken.True) 178 { 179 item = true; 180 } 181 else if(jsonItem.token == JSONToken.False) 182 { 183 item = false; 184 } 185 else 186 { 187 import std.format; 188 throw new Exception(format("Parsing bool: expected %s or %s , but got %s", JSONToken.True, JSONToken.False, jsonItem.token)); 189 } 190 } 191 192 private void deserializeImpl(T, JT)(ref JT tokenizer, ref T item, ReleasePolicy) if (isSomeString!T) 193 { 194 // Use phobos `to`, we want to duplicate the string if necessary. 195 import std.conv : to; 196 import std.format : format; 197 198 auto jsonItem = tokenizer.next; 199 jsonExpect(jsonItem, JSONToken.String, "Parsing " ~ T.stringof); 200 201 // this should not fail unless the data is non-unicode 202 // TODO: may need to copy the data if not immutable 203 item = jsonItem.data(tokenizer.chain).to!T; 204 } 205 206 private template SerializableMembers(T) 207 { 208 import std.traits; 209 import std.meta; 210 enum WithoutIgnore(string s) = !hasUDA!(__traits(getMember, T, s), ignore); 211 static if(is(T == struct)) 212 enum SerializableMembers = Filter!(WithoutIgnore, FieldNameTuple!T); 213 else 214 enum SerializableMembers = Filter!(WithoutIgnore, staticMap!(FieldNameTuple, T, BaseClassesTuple!T)); 215 } 216 217 private template AllIgnoredMembers(T) 218 { 219 import std.traits; 220 import std.meta; 221 static if(is(T == struct)) 222 enum AllIgnoredMembers = getUDAs!(T, IgnoredMembers); 223 else 224 enum AllIgnoredMembers = staticMap!(ApplyRight!(getUDAs, IgnoredMembers), T, BaseClassesTuple!T); 225 } 226 227 private void deserializeImpl(T, JT)(ref JT tokenizer, ref T item, ReleasePolicy relPol) if (is(T == struct) && __traits(hasMember, T, "fromJSON")) 228 { 229 item.fromJSON(tokenizer, relPol); 230 } 231 232 void deserializeAllMembers(T, JT)(ref JT tokenizer, ref T item, ReleasePolicy relPol) 233 { 234 // expect an object in JSON. We want to deserialize the JSON data 235 alias members = SerializableMembers!T; 236 alias ignoredMembers = AllIgnoredMembers!T; 237 238 // TODO use bit array instead and core.bitop 239 //size_t[(members.length + (size_t.sizeof * 8 - 1)) / size_t.sizeof / 8] visited; 240 bool[members.length] visited; 241 242 // any members that are optional, mark as already visited 243 static foreach(idx, m; members) 244 { 245 static if(hasUDA!(__traits(getMember, T, m), optional)) 246 visited[idx] = true; 247 static if(hasUDA!(__traits(getMember, T, m), extras)) 248 { 249 // this is the extras member, it holds any extra data that was not 250 // specified as a member. 251 static assert(is(typeof(__traits(getMember, T, m)) == JSONValue!S, S)); 252 enum extrasMember = m; 253 // initialize it for use 254 __traits(getMember, item, m).type = JSONType.Obj; 255 __traits(getMember, item, m).object = null; 256 // extras is always optional. 257 visited[idx] = true; 258 } 259 } 260 261 auto jsonItem = tokenizer.next; 262 jsonExpect(jsonItem, JSONToken.ObjectStart, "Parsing " ~ T.stringof); 263 264 // look at each string, then parse the given values 265 jsonItem = tokenizer.next(); 266 while(jsonItem.token != JSONToken.ObjectEnd) 267 { 268 if(jsonItem.token == JSONToken.Comma) 269 jsonItem = tokenizer.next(); 270 271 jsonExpect(jsonItem, JSONToken.String, "Expecting member name of " ~ T.stringof); 272 auto name = jsonItem.data(tokenizer.chain); 273 274 jsonItem = tokenizer.next(); 275 jsonExpect(jsonItem, JSONToken.Colon, "Expecting colon when parsing " ~ T.stringof); 276 OBJ_MEMBER_SWITCH: 277 switch(name) 278 { 279 static foreach(i, m; members) 280 static if(!hasUDA!(__traits(getMember, item, m), extras)) 281 {{ 282 static if(hasUDA!(__traits(getMember, item, m), alternateName)) 283 { 284 enum jsonName = getUDAs!(__traits(getMember, item, m), alternateName)[0].name; 285 } 286 else 287 enum jsonName = m; 288 case jsonName: 289 tokenizer.deserializeImpl(__traits(getMember, item, m), relPol); 290 visited[i] = true; 291 break OBJ_MEMBER_SWITCH; 292 }} 293 294 static if(ignoredMembers.length > 0) 295 { 296 static foreach(m; ignoredMembers) 297 { 298 static foreach(s; m.ignoredMembers) 299 { 300 case s: 301 } 302 } 303 // ignored members are ignored if they show up 304 tokenizer.skipItem(); 305 break OBJ_MEMBER_SWITCH; 306 } 307 308 default: 309 static if(is(typeof(extrasMember)) && is(typeof(__traits(getMember, item, extrasMember)) == JSONValue!SType, SType)) 310 {{ 311 // any extras should be put in here 312 import std.conv : to; 313 JSONValue!SType newItem; 314 tokenizer.deserializeImpl(newItem, relPol); 315 __traits(getMember, item, extrasMember).object[name.to!(immutable(SType))] = newItem; 316 break; 317 }} 318 else 319 { 320 import std.format : format; 321 throw new Exception(format("No member named '%s' in type `%s`", name, T.stringof)); 322 } 323 } 324 // shut up compiler 325 static if(members.length > 0 || ignoredMembers.length > 0) 326 { 327 if(relPol == ReleasePolicy.afterMembers) 328 tokenizer.releaseParsed(); 329 jsonItem = tokenizer.next(); 330 } 331 } 332 // ensure all members visited 333 static if(members.length) 334 { 335 import std.algorithm : canFind, map, filter; 336 if(visited[].canFind(false)) 337 { 338 // this is a bit ugly, but gives a nicer message. 339 static immutable marr = [members]; 340 import std.format; 341 import std.range : enumerate; 342 throw new Exception(format("The following members of `%s` were not specified: `%-(%s` `%)`", T.stringof, visited[].enumerate.filter!(a => !a[1]).map!(a => marr[a[0]]))); 343 } 344 } 345 } 346 347 private void deserializeImpl(T, JT)(ref JT tokenizer, ref T item, ReleasePolicy relPol) if (is(T == struct) && !isInstanceOf!(JSONValue, T) && !isInstanceOf!(Nullable, T) && !__traits(hasMember, T, "fromJSON")) 348 { 349 // check to see if any member is defined as the representation 350 import std.traits; 351 alias representers = getSymbolsByUDA!(T, serializeAs); 352 static if(representers.length > 0) 353 { 354 static assert(representers.length == 1, "Only one field can be used to represent an object"); 355 deserializeImpl(tokenizer, __traits(getMember, item, __traits(identifier, representers[0])), relPol); 356 } 357 else 358 { 359 deserializeAllMembers(tokenizer, item, relPol); 360 } 361 } 362 363 private void deserializeImpl(T, JT)(ref JT tokenizer, ref T item, ReleasePolicy relPol) if (isInstanceOf!(JSONValue, T)) 364 { 365 item = tokenizer.parseJSON!(typeof(T.str))(relPol); 366 } 367 368 // if type is Nullable, first check for JSONToken.Null, and if not, try and 369 // parse real item. 370 private void deserializeImpl(T, JT)(ref JT tokenizer, ref T item, ReleasePolicy relPol) if (isInstanceOf!(Nullable, T)) 371 { 372 if(tokenizer.peek == JSONToken.Null) 373 { 374 item.nullify; 375 // skip the null value 376 cast(void)tokenizer.next; 377 } 378 else 379 { 380 typeof(item.get()) result; 381 deserializeImpl(tokenizer, result, relPol); 382 item = result; 383 } 384 } 385 386 // deserialize a class or interface. The class must either provide a static 387 // function that returns the deserialized type, or have been registered with 388 // the JSON serialization system. 389 private void deserializeImpl(T, JT)(ref JT tokenizer, ref T item, ReleasePolicy relPol) if ((is(T == class) || is(T == interface))) 390 { 391 // NOTE: checking to see if it's callable doesn't help, because there could 392 // be a bug, and in that case, it tries the other branch. 393 static if(__traits(hasMember, T, "fromJSON")) 394 // && is(typeof(item = T.fromJSON(tokenizer, relPol)))) 395 { 396 item = T.fromJSON(tokenizer, relPol); 397 } 398 else 399 { 400 auto t = new T(); 401 deserializeAllMembers(tokenizer, t, relPol); 402 item = t; 403 } 404 } 405 406 // Given a JSON tokenizer, deserialize the given type from the JSON data. 407 T deserialize(T, JT)(ref JT tokenizer, ReleasePolicy relPol = ReleasePolicy.afterMembers) if (isInstanceOf!(JSONTokenizer, JT)) 408 { 409 T result; 410 deserializeImpl(tokenizer, result, relPol); 411 return result; 412 } 413 414 T deserialize(T, Chain)(auto ref Chain c) if (isIopipe!Chain) 415 { 416 enum shouldReplaceEscapes = is(typeof(chain.window[0] = chain.window[1])); 417 auto tokenizer = c.jsonTokenizer!(shouldReplaceEscapes); 418 return tokenizer.deserialize!T(ReleasePolicy.afterMembers); 419 } 420 421 void deserialize(T, JT)(ref JT tokenizer, ref T item, ReleasePolicy relPol = ReleasePolicy.afterMembers) if (isInstanceOf!(JSONTokenizer, JT)) 422 { 423 deserializeImpl(tokenizer, item, relPol); 424 } 425 426 void deserialize(T, Chain)(auto ref Chain c, ref T item) if (isIopipe!Chain) 427 { 428 enum shouldReplaceEscapes = is(typeof(chain.window[0] = chain.window[1])); 429 auto tokenizer = c.jsonTokenizer!(shouldReplaceEscapes); 430 return tokenizer.deserialize(item, ReleasePolicy.afterMembers); 431 } 432 433 // TODO: this really is pure, but there is a cycle in the DOM parser so 434 // compiler doesn't infer it. 435 /*pure*/ unittest 436 { 437 static struct S 438 { 439 int x; 440 string y; 441 double d; 442 bool b; 443 } 444 445 static class C 446 { 447 int x; 448 string y; 449 } 450 451 static class D : C 452 { 453 double d; 454 bool b; 455 } 456 457 auto json = q"{ 458 { 459 "x" : 5, 460 "y" : "foo", 461 "d" : 8.5, 462 "b" : true 463 }}"; 464 465 auto s = json.deserialize!S; 466 assert(s.x == 5); 467 assert(s.y == "foo"); 468 assert(s.d == 8.5); 469 assert(s.b); 470 471 auto c = json.deserialize!D; 472 assert(s.x == 5); 473 assert(s.y == "foo"); 474 assert(s.d == 8.5); 475 assert(s.b); 476 477 // test arrays and sub-objects 478 static struct S2 479 { 480 int[4] arr1; 481 int[] arr2; 482 S[] arr3; 483 } 484 auto json2= q"{ 485 { 486 "arr1" : [5,6,7,8], 487 "arr2" : [1,2,3,4,5,6,7,8], 488 "arr3" : [ 489 { 490 "x" : 5, 491 "y" : "foo", 492 "d" : 8.5, 493 "b" : true 494 }, 495 { 496 "x" : 7, 497 "y" : "bar", 498 "d" : 10.0, 499 "b" : false 500 }] 501 }}"; 502 auto s2 = json2.deserialize!S2; 503 504 assert(s2.arr1 == [5,6,7,8]); 505 assert(s2.arr2 == [1,2,3,4,5,6,7,8]); 506 assert(s2.arr3 == [S(5, "foo", 8.5, true), S(7, "bar", 10.0, false)]); 507 508 static struct S3 509 { 510 Nullable!int x; 511 Nullable!int y; 512 JSONValue!string json; 513 } 514 515 auto json3 = q"{ 516 { 517 "x" : 5, 518 "y" : null, 519 "json" : { 520 "arr1" : [5,6,7,8], 521 "arr2" : [1,null,true,"hi"], 522 "arr3" : [ 523 { 524 "x" : 5, 525 "y" : "foo", 526 "d" : 8.5, 527 "b" : true 528 }, 529 { 530 "x" : 7, 531 "y" : "bar", 532 "d" : 10.0, 533 "b" : false 534 }] 535 } 536 } 537 }"; 538 539 auto s3 = json3.deserialize!S3; 540 assert(!s3.x.isNull); 541 assert(s3.x == 5); 542 assert(s3.y.isNull); 543 assert(s3.json.type == JSONType.Obj); 544 assert(s3.json.object.length == 3); 545 auto jv = s3.json.object["arr1"]; 546 assert(jv.type == JSONType.Array); 547 assert(jv.array.length == 4); 548 assert(jv.array[0].type == JSONType.Integer); 549 assert(jv.array[1].type == JSONType.Integer); 550 assert(jv.array[2].type == JSONType.Integer); 551 assert(jv.array[3].type == JSONType.Integer); 552 assert(jv.array[0].integer == 5); 553 assert(jv.array[1].integer == 6); 554 assert(jv.array[2].integer == 7); 555 assert(jv.array[3].integer == 8); 556 jv = s3.json.object["arr2"]; // array of different types 557 assert(jv.type == JSONType.Array); 558 assert(jv.array.length == 4); 559 assert(jv.array[0].type == JSONType.Integer); 560 assert(jv.array[1].type == JSONType.Null); 561 assert(jv.array[2].type == JSONType.Bool); 562 assert(jv.array[3].type == JSONType.String); 563 assert(jv.array[0].integer == 1); 564 //jv.array[1] no value for "Null" type 565 assert(jv.array[2].boolean); 566 assert(jv.array[3].str == "hi"); 567 jv = s3.json.object["arr3"]; 568 assert(jv.type == JSONType.Array); 569 assert(jv.array.length == 2); 570 foreach(v; jv.array) 571 { 572 assert(v.type == JSONType.Obj); 573 assert(v.object.length == 4); 574 assert("x" in v.object); 575 assert("y" in v.object); 576 assert("d" in v.object); 577 assert("b" in v.object); 578 } 579 580 // ensure extras works 581 static struct S4 582 { 583 int x; 584 @extras JSONValue!string extraData; 585 } 586 587 auto s4 = json.deserialize!S4; 588 assert(s4.x == 5); 589 assert(s4.extraData.type == JSONType.Obj); 590 assert(s4.extraData.object.length == 3); 591 assert(s4.extraData.object["y"].type == JSONType.String); 592 assert(s4.extraData.object["y"].str == "foo"); 593 assert(s4.extraData.object["d"].type == JSONType.Floating); 594 assert(s4.extraData.object["d"].floating == 8.5); 595 assert(s4.extraData.object["b"].type == JSONType.Bool); 596 assert(s4.extraData.object["b"].boolean == true); 597 } 598 599 600 // test attributes and empty struct 601 unittest 602 { 603 static struct S 604 { 605 int x; 606 @ignore bool foo; 607 } 608 auto s = deserialize!S(`{"x": 5}`); 609 assert(s.x == 5); 610 611 static struct Y 612 { 613 } 614 615 auto y = deserialize!Y(`{}`); 616 617 static struct T 618 { 619 @serializeAs string s; 620 int x; 621 } 622 623 T[] arr = deserialize!(T[])(`["hi", "there"]`); 624 assert(arr.length == 2); 625 assert(arr[0].s == "hi"); 626 assert(arr[1].s == "there"); 627 } 628 629 // test serializing of class hierarchy 630 unittest 631 { 632 @ignoredMembers("type") 633 static class C 634 { 635 int x; 636 static C fromJSON(JT)(ref JT tokenizer, ReleasePolicy relPol) 637 { 638 C doDeserialize(T)() 639 { 640 tokenizer.rewind(); 641 tokenizer.endCache(); 642 auto item = new T; 643 tokenizer.deserializeAllMembers(item, relPol); 644 return item; 645 } 646 647 tokenizer.startCache(); 648 if(tokenizer.parseTo("type")) 649 { 650 int type; 651 tokenizer.deserialize(type); 652 switch(type) 653 { 654 case 0: 655 // it's a C 656 return doDeserialize!C; 657 case 1: 658 return doDeserialize!D; 659 case 2: 660 return doDeserialize!E; 661 default: 662 assert(false, "Unknown type"); 663 } 664 } 665 assert(false, "Couldn't find type"); 666 } 667 } 668 669 static class D : C 670 { 671 string s; 672 } 673 674 static class E : C 675 { 676 double y; 677 } 678 679 auto msg1 = `[{"type" : 0, "x" : 1}, {"type" : 1, "x" : 2, "s" : "hi"}, {"type" : 2, "x" : 3, "y": 5.6}]`; 680 681 auto cArr = msg1.deserialize!(C[]); 682 assert(cArr.length == 3); 683 } 684 685 void serializeImpl(T, Char)(scope void delegate(const(Char)[]) w, ref T val) if (__traits(isStaticArray, T)) 686 { 687 auto arr = val; 688 serializeImpl(w, val[]); 689 } 690 691 void serializeImpl(T, Char)(scope void delegate(const(Char)[]) w, ref T val) if (is(T == enum)) 692 { 693 // enums are special, serialize based on the name. Unless there's a UDA 694 // saying to serialize as the base type. 695 static if(hasUDA!(T, enumBaseType)) 696 { 697 serializeImpl(w, *cast(OriginalType!T*)&val); 698 } 699 else 700 { 701 import std.conv; 702 auto enumName = val.to!string; 703 serializeImpl(w, enumName); 704 } 705 } 706 707 void serializeImpl(T, Char)(scope void delegate(const(Char)[]) w, ref T val) if (isDynamicArray!T && !isSomeString!T && !is(T == enum)) 708 { 709 // open brace 710 w("["); 711 bool first = true; 712 foreach(ref item; val) 713 { 714 if(first) 715 first = false; 716 else 717 w(", "); 718 serializeImpl(w, item); 719 } 720 w("]"); 721 } 722 723 void serializeImpl(T, Char)(scope void delegate(const(Char)[]) w, ref T val) if (isSomeString!T) 724 { 725 w(`"`); 726 put(w, val); 727 w(`"`); 728 } 729 730 void serializeAllMembers(T, Char)(scope void delegate(const(Char)[]) w, auto ref T val) 731 { 732 // serialize as an object 733 bool first = true; 734 static foreach(n; SerializableMembers!T) 735 { 736 if(first) 737 first = false; 738 else 739 w(", "); 740 w(`"`); 741 w(n); 742 w(`" : `); 743 serializeImpl(w, __traits(getMember, val, n)); 744 } 745 } 746 747 void serializeImpl(T, Char)(scope void delegate(const(Char)[]) w, ref T val) if (is(T == struct)) 748 { 749 static if(isInstanceOf!(Nullable, T)) 750 { 751 if(val.isNull) 752 w("null"); 753 else 754 serializeImpl(w, val.get); 755 } 756 else static if(isInstanceOf!(JSONValue, T)) 757 { 758 with(JSONType) final switch(val.type) 759 { 760 case Integer: 761 serializeImpl(w, val.integer); 762 break; 763 case Floating: 764 serializeImpl(w, val.floating); 765 break; 766 case String: 767 serializeImpl(w, val.str); 768 break; 769 case Array: 770 serializeImpl(w, val.array); 771 break; 772 case Obj: 773 // serialize as if it was an object 774 w("{"); 775 { 776 bool first = true; 777 foreach(k, ref v; val.object) 778 { 779 if(first) 780 first = false; 781 else 782 w(", "); 783 w(`"`); 784 w(n); 785 w(`" : `); 786 serializeImpl(w, v); 787 } 788 } 789 w("}"); 790 break; 791 case Null: 792 w("null"); 793 break; 794 case Bool: 795 w(val.boolean ? "true" : "false"); 796 break; 797 } 798 } 799 else static if(__traits(hasMember, T, "toJSON")) 800 { 801 val.toJSON(w); 802 } 803 else static if(getSymbolsByUDA!(T, serializeAs).length > 0) 804 { 805 alias representers = getSymbolsByUDA!(T, serializeAs); 806 // serialize as the single item 807 static assert(representers.length == 1, "Only one field can be used to represent an object"); 808 serializeImpl(w, __traits(getMember, val, __traits(identifier, representers[0]))); 809 } 810 else static if(isInputRange!T) 811 { 812 // open brace 813 w("["); 814 bool first = true; 815 foreach(ref item; val) 816 { 817 if(first) 818 first = false; 819 else 820 w(", "); 821 serializeImpl(w, item); 822 } 823 w("]"); 824 } 825 else 826 { 827 w("{"); 828 serializeAllMembers(w, val); 829 w("}"); 830 } 831 } 832 833 void serializeImpl(T, Char)(scope void delegate(const(Char)[]) w, T val) if (is(T == class) || is(T == interface)) 834 { 835 // If the class defines a method toJSON, then use that. Otherwise, we will 836 // just serialize the data as we can. 837 static if(__traits(hasMember, T, "toJSON")) 838 { 839 val.toJSON(w); 840 } 841 else 842 { 843 w("{"); 844 serializeAllMembers(w, val); 845 w("}"); 846 } 847 } 848 849 void serializeImpl(T, Char)(scope void delegate(const(Char)[]) w, ref T val) if (!is(T == enum) && isNumeric!T) 850 { 851 import std.format; 852 formattedWrite(w, "%s", val); 853 } 854 855 void serializeImpl(Char)(scope void delegate(const(Char)[]) w, bool val) 856 { 857 w(val ? "true" : "false"); 858 } 859 860 // serialize an item to an iopipe. 861 // The behavior flag specifies whether the json serializer should release data 862 // in the iopipe as it writes, or if it should keep it in the buffer (no 863 // releases are called). 864 // 865 // Use case for releasing is for an output pipe that will be written to a file 866 // for instance as it's released. Use case for not releasing is when you are 867 // writing to a string. 868 // 869 // Returns: number of elements written in the output iopipe. If 870 // release-on-write is specified, none of the data will remain in the immediate 871 // iopipe buffer. 872 // If no-release is specified, then the return value indicates the number of 873 // elements that are in the buffer. If offset is specified, then that is where 874 // the data will begin to be written. 875 // 876 // if T does not evaluate to an array or object type, then it wraps it in a 877 // single-element array to make the result a valid JSON string. 878 // 879 size_t serialize(ReleaseOnWrite relOnWrite = ReleaseOnWrite.yes, Chain, T)(auto ref Chain chain, auto ref T val, size_t offset = 0) 880 if (isIopipe!Chain && isSomeChar!(ElementType!(WindowType!Chain))) 881 { 882 size_t result = 0; 883 alias Char = ElementEncodingType!(WindowType!Chain); 884 void w(const(Char)[] data) 885 { 886 auto nWritten = chain.writeBuf!relOnWrite(data, offset); 887 result += nWritten; 888 static if(relOnWrite) 889 offset = 0; 890 else 891 offset += nWritten; 892 } 893 894 static if(isInstanceOf!(JSONValue, T)) 895 { 896 // json value. Check to see if it's an object or array. If not, write the 897 bool needClosingBrace = 898 !(val.type == JSONType.Obj || val.type == JSONType.Array); 899 if(needClosingBrace) 900 w("["); 901 } 902 else static if(isInstanceOf!(Nullable, T)) 903 { 904 if(val.isNull) 905 { 906 w("[null]"); 907 return result; 908 } 909 else 910 return chain.serialize!relOnWrite(val, offset); 911 } 912 else static if(is(T == struct) || isArray!T || is(T == class) || is(T == interface)) 913 { 914 enum needClosingBrace = false; 915 } 916 else 917 { 918 enum needClosingBrace = true; 919 w("["); 920 } 921 922 // serialize the item, recursively 923 serializeImpl(&w, val); 924 925 if(needClosingBrace) 926 w("]"); 927 return result; 928 } 929 930 // convenience, using normal serialization to write to a string. 931 string serialize(T)(auto ref T val) 932 { 933 import std.exception; 934 auto outBuf = bufd!char; 935 auto dataSize = outBuf.serialize!(ReleaseOnWrite.no)(val); 936 auto result = outBuf.window[0 .. dataSize]; 937 result.assumeSafeAppend; 938 return result.assumeUnique; 939 } 940 941 unittest 942 { 943 auto str1 = serialize(1); 944 assert(str1 == "[1]"); 945 int item1; 946 auto strpipe = str1; 947 strpipe.deserialize(*(cast(int[1]*)&item1)); 948 assert(item1 == 1); 949 auto str2 = serialize([1,2,3,4]); 950 assert(str2 == "[1, 2, 3, 4]"); 951 int[4] item2; 952 strpipe = str2; 953 strpipe.deserialize(item2); 954 assert(item2[] == [1, 2, 3, 4]); 955 assert(str2 == "[1, 2, 3, 4]"); 956 static struct S 957 { 958 int x; 959 float y; 960 string s; 961 bool b; 962 } 963 964 assert(serialize(S(1, 2.5, "hi", true)) == `{"x" : 1, "y" : 2.5, "s" : "hi", "b" : true}`); 965 966 // serialize nested arrays and objects 967 auto str3 = serialize([S(1, 3.0, "foo", false), S(2, 8.5, "bar", true)]); 968 assert(str3 == `[{"x" : 1, "y" : 3, "s" : "foo", "b" : false}, {"x" : 2, "y" : 8.5, "s" : "bar", "b" : true}]`, str3); 969 auto arr = str3.deserialize!(S[]); 970 assert(arr.length == 2); 971 assert(arr[0].s == "foo"); 972 assert(arr[1].b); 973 assert(arr[1].x == 2); 974 } 975 976 unittest 977 { 978 static struct S 979 { 980 int x; 981 @ignore bool y; 982 } 983 984 auto s = S(1, true); 985 auto str = s.serialize; 986 assert(str == `{"x" : 1}`, str); 987 988 static struct T 989 { 990 @serializeAs string s; 991 int x; 992 } 993 994 static struct U 995 { 996 T t; 997 } 998 auto u = U(T("hello", 1)); 999 auto str2 = u.serialize; 1000 assert(str2 == `{"t" : "hello"}`, str2); 1001 } 1002 1003 unittest 1004 { 1005 // serialization of classes 1006 static class C 1007 { 1008 int x; 1009 } 1010 1011 static class D : C 1012 { 1013 string s; 1014 void toJSON(scope void delegate(const(char)[]) w) 1015 { 1016 w("{"); 1017 serializeAllMembers(w, this); 1018 w("}"); 1019 } 1020 } 1021 1022 static class E : D 1023 { 1024 double d; 1025 override void toJSON(scope void delegate(const(char)[]) w) 1026 { 1027 w("{"); 1028 serializeAllMembers(w, this); 1029 w("}"); 1030 } 1031 } 1032 1033 auto c = new C; 1034 c.x = 1; 1035 auto cstr = c.serialize; 1036 assert(cstr == `{"x" : 1}`, cstr); 1037 1038 auto d = new D; 1039 d.x = 2; 1040 d.s = "str"; 1041 auto dstr = d.serialize; 1042 assert(dstr == `{"s" : "str", "x" : 2}`, dstr); 1043 1044 auto e = new E; 1045 e.x = 3; 1046 e.s = "foo"; 1047 e.d = 1.5; 1048 auto estr = e.serialize; 1049 assert(estr == `{"d" : 1.5, "s" : "foo", "x" : 3}`, estr); 1050 d = e; 1051 assert(d.serialize == estr); 1052 } 1053 1054 unittest 1055 { 1056 // test serializing enums 1057 enum X { a, b, c } 1058 static struct S { X x; } 1059 auto s = S(X.b); 1060 auto sstr = s.serialize; 1061 assert(sstr == `{"x" : "b"}`); 1062 auto s2 = sstr.deserialize!S; 1063 assert(s2.x == X.b); 1064 }