1 /**********************************************************\ 2 | | 3 | hprose | 4 | | 5 | Official WebSite: http://www.hprose.com/ | 6 | http://www.hprose.org/ | 7 | | 8 \**********************************************************/ 9 10 /**********************************************************\ 11 * * 12 * hprose/io/writer.d * 13 * * 14 * hprose writer library for D. * 15 * * 16 * LastModified: Aug 3, 2016 * 17 * Author: Ma Bingyao <andot@hprose.com> * 18 * * 19 \**********************************************************/ 20 21 module hprose.io.writer; 22 23 @trusted: 24 25 import hprose.io.bytes; 26 import hprose.io.classmanager; 27 import hprose.io.common; 28 import hprose.io.tags; 29 import std.bigint; 30 import std.container; 31 import std.datetime; 32 import std.json; 33 import std.math; 34 import std.stdio; 35 import std.string; 36 import std.traits; 37 import std.typecons; 38 import std.typetuple; 39 import std.utf; 40 import std.uuid; 41 import std.variant; 42 43 private { 44 alias void* any; 45 46 interface WriterRefer { 47 void set(in any value); 48 bool write(in any value); 49 void reset(); 50 } 51 52 final class FakeWriterRefer : WriterRefer { 53 void set(in any value) {} 54 bool write(in any value) { return false; } 55 void reset() {} 56 } 57 58 final class RealWriterRefer : WriterRefer { 59 private int[any] _references; 60 private int _refcount = 0; 61 private BytesIO _bytes; 62 this(BytesIO bytes) { 63 _bytes = bytes; 64 } 65 void set(in any value) { 66 if (value !is null) { 67 _references[value] = _refcount; 68 } 69 ++_refcount; 70 } 71 bool write(in any value) { 72 if (value is null) return false; 73 int i = _references.get(value, -1); 74 if (i < 0) return false; 75 _bytes.write(TagRef).write(i).write(TagSemicolon); 76 return true; 77 } 78 void reset() { 79 _references = null; 80 _refcount = 0; 81 } 82 } 83 } 84 85 unittest { 86 class Test { 87 int a; 88 } 89 BytesIO bytes = new BytesIO(); 90 WriterRefer wr = new RealWriterRefer(bytes); 91 string s = "hello"; 92 assert(wr.write(cast(any)s) == false); 93 wr.set(cast(any)s); 94 assert(wr.write(cast(any)s) == true); 95 int[] a = [1,2,3,4,5,6]; 96 assert(wr.write(cast(any)a) == false); 97 wr.set(cast(any)a); 98 assert(wr.write(cast(any)a) == true); 99 int[string] m = ["hello":1, "world":2]; 100 assert(wr.write(cast(any)m) == false); 101 wr.set(cast(any)m); 102 assert(wr.write(cast(any)m) == true); 103 Test t = new Test(); 104 assert(wr.write(cast(any)t) == false); 105 wr.set(cast(any)t); 106 assert(wr.write(cast(any)t) == true); 107 wr.reset(); 108 assert(wr.write(cast(any)s) == false); 109 assert(wr.write(cast(any)a) == false); 110 assert(wr.write(cast(any)m) == false); 111 assert(wr.write(cast(any)t) == false); 112 } 113 114 class Writer { 115 private { 116 BytesIO _bytes; 117 WriterRefer _refer; 118 int[string] _classref; 119 int _crcount = 0; 120 121 pure string hnsecsToString(long hnsecs) { 122 if (hnsecs == 0) return ""; 123 if (hnsecs % 10000 == 0) { 124 return TagPoint ~ format("%03d", hnsecs / 10000); 125 } 126 else if (hnsecs % 10 == 0) { 127 return TagPoint ~ format("%06d", hnsecs / 10); 128 } 129 else { 130 return TagPoint ~ format("%09d", hnsecs * 100); 131 } 132 } 133 134 int writeClass(T)(string name) if (is(T == struct) || is(T == class)) { 135 _bytes.write(TagClass).write(name.length).write(TagQuote).write(name).write(TagQuote); 136 enum fieldList = getSerializableFields!T; 137 enum count = fieldList.length; 138 if (count > 0) _bytes.write(count); 139 _bytes.write(TagOpenbrace); 140 foreach(f; fieldList) { 141 writeString(f); 142 } 143 _bytes.write(TagClosebrace); 144 int index = _crcount++; 145 _classref[name] = index; 146 return index; 147 } 148 } 149 150 @property BytesIO stream() { return _bytes; } 151 152 this(BytesIO bytes, bool simple = false) { 153 _bytes = bytes; 154 if (simple) { 155 _refer = new FakeWriterRefer(); 156 } 157 else { 158 _refer = new RealWriterRefer(_bytes); 159 } 160 } 161 void reset() { 162 _refer.reset(); 163 _classref = null; 164 _crcount = 0; 165 } 166 void serialize(T)(ref T value) if (isStaticArray!T) { 167 serialize(value[0 .. $]); 168 } 169 void serialize(T)(T value) if (isSerializable!T) { 170 alias Unqual!T U; 171 static if (is(U == typeof(null))) { 172 writeNull(); 173 } 174 else static if (is(U == enum)) { 175 serialize!(OriginalType!T)(cast(OriginalType!T)value); 176 } 177 else static if (isIntegral!U) { 178 static if (is(U == byte) || 179 is(U == ubyte) || 180 is(U == short) || 181 is(U == ushort) || 182 is(U == int)) { 183 writeInteger(cast(int)value); 184 } 185 else static if (is(U == long)) { 186 if (value > int.max || value < int.min) { 187 writeLong(value); 188 } 189 else { 190 writeInteger(cast(int)value); 191 } 192 } 193 else { 194 if (value > int.max) { 195 writeLong(value); 196 } 197 else { 198 writeInteger(cast(int)value); 199 } 200 } 201 } 202 else static if (is(U : BigInt)) { 203 writeLong(value); 204 } 205 else static if (isFloatingPoint!U) { 206 writeDouble(value); 207 } 208 else static if (isBoolean!U) { 209 writeBool(value); 210 } 211 else static if (isSomeChar!U) { 212 static if (is(T == dchar)) { 213 if (value > value.init) { 214 char[4] buf; 215 writeString(toUTF8(buf, value)); 216 } 217 else { 218 writeUTF8Char(cast(wchar)value); 219 } 220 } 221 else { 222 writeUTF8Char(cast(wchar)value); 223 } 224 } 225 else static if (isStaticArray!U) { 226 serialize(value[0 .. $]); 227 } 228 else static if (is(U == struct)) { 229 static if (isInstanceOf!(Nullable, U)) { 230 if (value.isNull()) { 231 writeNull(); 232 } 233 else { 234 serialize(value.get()); 235 } 236 } 237 else static if (is(U == DateTime)) { 238 writeDateTime(value); 239 } 240 else static if (is(U == Date)) { 241 writeDate(value); 242 } 243 else static if (is(U == TimeOfDay)) { 244 writeTime(value); 245 } 246 else static if (is(U == SysTime)) { 247 writeSysTime(value); 248 } 249 else static if (is(U == UUID)) { 250 writeUUID(value); 251 } 252 else static if (is(U == JSONValue)) { 253 final switch (value.type()) { 254 case JSON_TYPE.STRING: serialize(value.str); break; 255 case JSON_TYPE.INTEGER: serialize(value.integer); break; 256 case JSON_TYPE.UINTEGER: serialize(value.uinteger); break; 257 case JSON_TYPE.FLOAT: serialize(value.floating); break; 258 case JSON_TYPE.OBJECT: serialize(value.object); break; 259 case JSON_TYPE.ARRAY: serialize(value.array); break; 260 case JSON_TYPE.TRUE: serialize(true); break; 261 case JSON_TYPE.FALSE: serialize(false); break; 262 case JSON_TYPE.NULL: serialize(null); break; 263 } 264 } 265 else static if (is(U == Variant)) { 266 Variant v = value; 267 alias TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong, 268 float, double, real, bool, char, wchar, dchar, 269 BigInt, DateTime, Date, TimeOfDay, SysTime, 270 UUID, Variant, JSONValue) valueTypeTuple; 271 alias TypeTuple!(byte, ubyte, short, ushort, int, uint, long, ulong, 272 float, double, real, bool, char, wchar, dchar, 273 BigInt, DateTime, Date, TimeOfDay, SysTime, 274 UUID, Variant) keyTypeTuple; 275 TypeInfo type = value.type(); 276 if (type is typeid(null)) { 277 return writeNull(); 278 } 279 foreach(V; valueTypeTuple) { 280 if (type is typeid(V)) { 281 return serialize(v.get!(V)); 282 } 283 else if (type is typeid(V[])) { 284 return serialize(v.get!(V[])); 285 } 286 else if (type is typeid(const(V)[])) { 287 return serialize(v.get!(const(V)[])); 288 } 289 else if (type is typeid(immutable(V)[])) { 290 return serialize(v.get!(immutable(V)[])); 291 } 292 foreach(K; keyTypeTuple) { 293 if (type is typeid(V[K])) { 294 return serialize(v.get!(V[K])); 295 } 296 } 297 } 298 throw new Exception("Not support to serialize this data."); 299 } 300 else static if (isIterable!U) { 301 writeArrayWithRef(value); 302 } 303 else { 304 writeObject(value); 305 } 306 } 307 else static if (is(U == class) || 308 isSomeString!U || 309 isDynamicArray!U || 310 isAssociativeArray!U) { 311 if (value is null) { 312 writeNull(); 313 } 314 else static if (isSomeString!U) { 315 if (value.length == 0) { 316 writeEmpty(); 317 } 318 else { 319 writeStringWithRef(value); 320 } 321 } 322 else static if (isDynamicArray!U) { 323 static if (is(Unqual!(ForeachType!U) == ubyte) || 324 is(Unqual!(ForeachType!U) == byte)) { 325 writeBytesWithRef(value); 326 } 327 else { 328 writeArrayWithRef(value); 329 } 330 } 331 else static if (isAssociativeArray!U) { 332 writeAssociativeArrayWithRef(value); 333 } 334 else static if (isIterable!U) { 335 writeArrayWithRef(value); 336 } 337 else { 338 writeObjectWithRef(value); 339 } 340 } 341 else { 342 throw new Exception("Not support to serialize this data."); 343 } 344 } 345 void writeNull() { 346 _bytes.write(TagNull); 347 } 348 void writeInteger(int value) { 349 if (value >= 0 && value <= 9) { 350 _bytes.write(value); 351 } 352 else { 353 _bytes.write(TagInteger).write(value).write(TagSemicolon); 354 } 355 } 356 void writeLong(T)(in T value) if (is(T == uint) || is(T == long) || is(T == ulong)) { 357 _bytes.write(TagLong).write(value).write(TagSemicolon); 358 } 359 void writeLong(BigInt value) { 360 _bytes.write(TagLong).write(value.toDecimalString()).write(TagSemicolon); 361 } 362 void writeDouble(T)(in T value) if (isFloatingPoint!T) { 363 if (isNaN(value)) { 364 _bytes.write(TagNaN); 365 } 366 else if (isInfinity(value)) { 367 _bytes.write(TagInfinity).write(value > 0 ? TagPos : TagNeg); 368 } 369 else { 370 _bytes.write(TagDouble).write(value).write(TagSemicolon); 371 } 372 } 373 void writeBool(bool value) { 374 _bytes.write(value ? TagTrue : TagFalse); 375 } 376 void writeUTF8Char(wchar value) { 377 _bytes.write(TagUTF8Char).write(toUTF8([value])); 378 } 379 void writeDateTime(DateTime value) { 380 if (!value.isAD) { 381 throw new Exception("Years BC is not supported in hprose."); 382 } 383 if (value.year > 9999) { 384 throw new Exception("Year after 9999 is not supported in hprose."); 385 } 386 _refer.set(null); 387 _bytes.write(TagDate).write(value.toISOString()).write(TagSemicolon); 388 } 389 void writeDate(Date value) { 390 if (!value.isAD) { 391 throw new Exception("Years BC is not supported in hprose."); 392 } 393 if (value.year > 9999) { 394 throw new Exception("Year after 9999 is not supported in hprose."); 395 } 396 _refer.set(null); 397 _bytes.write(TagDate).write(value.toISOString()).write(TagSemicolon); 398 } 399 void writeTime(TimeOfDay value) { 400 _refer.set(null); 401 _bytes.write(TagTime).write(value.toISOString()).write(TagSemicolon); 402 } 403 void writeSysTime(SysTime value) { 404 if (!value.isAD) { 405 throw new Exception("Years BC is not supported in hprose."); 406 } 407 if (value.year > 9999) { 408 throw new Exception("Year after 9999 is not supported in hprose."); 409 } 410 const auto tzType = typeid(value.timezone); 411 const bool isLocalTime = tzType is typeid(LocalTime); 412 if (isLocalTime || tzType is typeid(UTC)) { 413 _refer.set(null); 414 const char tag = isLocalTime ? TagSemicolon : TagUTC; 415 long hnsecs = value.fracSecs.split!("hnsecs").hnsecs; 416 Date date = Date(value.year, value.month, value.day); 417 TimeOfDay time = TimeOfDay(value.hour, value.minute, value.second); 418 if (time == TimeOfDay(0, 0, 0) && hnsecs == 0) { 419 _bytes.write(TagDate) 420 .write(date.toISOString()) 421 .write(tag); 422 } 423 else if (date == Date(1970, 1, 1)) { 424 _bytes.write(TagTime) 425 .write(time.toISOString()) 426 .write(hnsecsToString(hnsecs)) 427 .write(tag); 428 } 429 else { 430 _bytes.write(TagDate) 431 .write(date.toISOString()) 432 .write(TagTime) 433 .write(time.toISOString()) 434 .write(hnsecsToString(hnsecs)) 435 .write(tag); 436 } 437 } 438 else { 439 writeSysTime(value.toUTC()); 440 } 441 } 442 void writeUUID(UUID value) { 443 _refer.set(null); 444 _bytes.write(TagGuid).write(TagOpenbrace).write(value.toString()).write(TagClosebrace); 445 } 446 void writeEmpty() { 447 _bytes.write(TagEmpty); 448 } 449 void writeString(T)(T value) if (isSomeString!T) { 450 _refer.set(cast(any)value); 451 _bytes.write(TagString); 452 auto len = codeLength!wchar(value); 453 if (len > 0) _bytes.write(len); 454 static if (is(T : const char[])) { 455 _bytes.write(TagQuote).write(value).write(TagQuote); 456 } 457 else { 458 _bytes.write(TagQuote).write(toUTF8(value)).write(TagQuote); 459 } 460 } 461 void writeStringWithRef(T)(T value) if (isSomeString!T) { 462 if (!_refer.write(cast(any)value)) writeString(value); 463 } 464 void writeBytes(T)(T value) 465 if (isDynamicArray!T && 466 (is(Unqual!(ForeachType!T) == ubyte) || 467 is(Unqual!(ForeachType!T) == byte))) { 468 _refer.set(cast(any)value); 469 _bytes.write(TagBytes); 470 auto len = value.length; 471 if (len > 0) _bytes.write(len); 472 _bytes.write(TagQuote).write(value).write(TagQuote); 473 } 474 void writeBytesWithRef(T)(T value) 475 if (isDynamicArray!T && 476 (is(Unqual!(ForeachType!T) == ubyte) || 477 is(Unqual!(ForeachType!T) == byte))) { 478 if (!_refer.write(cast(any)value)) writeBytes(value); 479 } 480 void writeArray(T)(T value) if ((isIterable!T || isDynamicArray!T) && isSerializable!T) { 481 static if (isDynamicArray!T && 482 is(Unqual!(ForeachType!T) == ubyte) || 483 is(Unqual!(ForeachType!T) == byte)) { 484 writeBytes(value); 485 } 486 else static if (isSomeString!T) { 487 writeString(value); 488 } 489 else { 490 static if (is(T == struct)) { 491 _refer.set(null); 492 } 493 else { 494 _refer.set(cast(any)value); 495 } 496 _bytes.write(TagList); 497 static if (isDynamicArray!T || 498 __traits(hasMember, T, "length") && 499 is(typeof(__traits(getMember, T.init, "length")) == size_t)) { 500 auto len = value.length; 501 } 502 else { 503 auto len = 0; 504 foreach(ref e; value) { ++len; } 505 } 506 if (len > 0) _bytes.write(len); 507 _bytes.write(TagOpenbrace); 508 foreach(ref e; value) serialize(e); 509 _bytes.write(TagClosebrace); 510 } 511 } 512 alias writeArray writeList; 513 void writeArrayWithRef(T)(T value) if ((isIterable!T || isDynamicArray!T) && isSerializable!T) { 514 static if (is(T == struct)) { 515 writeArray(value); 516 } 517 else { 518 if (!_refer.write(cast(any)value)) writeArray(value); 519 } 520 } 521 alias writeArrayWithRef writeListWithRef; 522 void writeAssociativeArray(T)(T value) if (isAssociativeArray!T && isSerializable!T) { 523 _refer.set(cast(any)value); 524 _bytes.write(TagMap); 525 auto len = value.length; 526 if (len > 0) _bytes.write(len); 527 _bytes.write(TagOpenbrace); 528 foreach(k, ref v; value) { 529 serialize(k); 530 serialize(v); 531 } 532 _bytes.write(TagClosebrace); 533 } 534 alias writeAssociativeArray writeMap; 535 void writeAssociativeArrayWithRef(T)(T value) if (isAssociativeArray!T && isSerializable!T) { 536 if (!_refer.write(cast(any)value)) writeAssociativeArray(value); 537 } 538 alias writeAssociativeArrayWithRef writeMapWithRef; 539 private void serializeFields(alias fieldList, T)(T value) { 540 static if (fieldList.length > 0) { 541 enum f = fieldList[0]; 542 serialize(__traits(getMember, value, f)); 543 static if (fieldList.length > 1) { 544 serializeFields!(tuple(fieldList[1..$]), T)(value); 545 } 546 } 547 } 548 void writeObject(T)(T value) if (is(T == struct) || is(T == class)) { 549 string name = ClassManager.getAlias!T; 550 int index = _classref.get(name, writeClass!T(name)); 551 static if (is(T == struct)) { 552 _refer.set(null); 553 } 554 else { 555 _refer.set(cast(any)value); 556 } 557 _bytes.write(TagObject).write(index).write(TagOpenbrace); 558 serializeFields!(getSerializableFields!T, T)(value); 559 _bytes.write(TagClosebrace); 560 } 561 void writeObjectWithRef(T)(T value) if (is(T == struct) || is(T == class)) { 562 static if (is(T == struct)) { 563 writeObject(value); 564 } 565 else { 566 if (!_refer.write(cast(any)value)) writeObject(value); 567 } 568 } 569 void writeTuple(T...)(T args) { 570 _refer.set(null); 571 _bytes.write(TagList); 572 auto len = args.length; 573 if (len > 0) _bytes.write(len); 574 _bytes.write(TagOpenbrace); 575 foreach(ref e; args) serialize(e); 576 _bytes.write(TagClosebrace); 577 } 578 } 579 580 private { 581 class MyClass { 582 const int a; 583 static const byte b; 584 private int c = 3; 585 @property { 586 int x() const { return c; } 587 int x(int value) { return c = value; } 588 } 589 this() { this.a = 1; } 590 this(int a) { this.a = a; } 591 void hello() {} 592 }; 593 594 struct MyStruct { 595 static const byte b; 596 int c = 3; 597 void hello() {} 598 } 599 } 600 601 unittest { 602 BytesIO bytes = new BytesIO(); 603 Writer rw = new Writer(bytes); 604 Writer fw = new Writer(bytes, true); 605 MyClass mc = null; 606 int[] nia = null; 607 int[string] nias = null; 608 fw.serialize(null); 609 fw.serialize(mc); 610 fw.serialize(nia); 611 fw.serialize(nias); 612 fw.serialize(1); 613 fw.serialize(12345); 614 int i = -123456789; 615 fw.serialize(i); 616 assert(bytes.toString() == "nnnn1i12345;i-123456789;"); 617 618 bytes.init(""); 619 rw.serialize(BigInt(1234567890987654321L)); 620 assert(bytes.toString() == "l1234567890987654321;"); 621 622 bytes.init(""); 623 rw.serialize(3.14159265358979323846f); 624 rw.serialize(3.14159265358979323846); 625 rw.serialize(float.nan); 626 rw.serialize(double.nan); 627 rw.serialize(float.infinity); 628 rw.serialize(-real.infinity); 629 assert(bytes.toString() == "d3.14159;d3.141592653589793;NNI+I-"); 630 631 bytes.init(""); 632 rw.serialize(true); 633 rw.serialize(false); 634 assert(bytes.toString() == "tf"); 635 636 bytes.init(""); 637 rw.serialize(Date(1980, 12, 01)); 638 assert(bytes.toString() == "D19801201;"); 639 640 bytes.init(""); 641 rw.serialize(DateTime(1980, 12, 01, 17, 48, 54)); 642 assert(bytes.toString() == "D19801201T174854;"); 643 644 bytes.init(""); 645 rw.serialize(DateTime(1980, 12, 01)); 646 assert(bytes.toString() == "D19801201T000000;"); 647 648 bytes.init(""); 649 rw.serialize(TimeOfDay(17, 48, 54)); 650 assert(bytes.toString() == "T174854;"); 651 652 bytes.init(""); 653 rw.serialize(SysTime(DateTime(1980, 12, 01, 17, 48, 54))); 654 assert(bytes.toString() == "D19801201T174854;"); 655 656 bytes.init(""); 657 rw.serialize(SysTime(DateTime(1980, 12, 01, 17, 48, 54), UTC())); 658 assert(bytes.toString() == "D19801201T174854Z"); 659 660 bytes.init(""); 661 rw.serialize(SysTime(DateTime(1980, 12, 01, 17, 48, 54), std.datetime.usecs(802_400))); 662 assert(bytes.toString() == "D19801201T174854.802400;"); 663 664 bytes.init(""); 665 rw.serialize(SysTime(DateTime(1980, 12, 01, 17, 48, 54), std.datetime.usecs(802_4), UTC())); 666 assert(bytes.toString() == "D19801201T174854.008024Z"); 667 668 bytes.init(""); 669 rw.serialize(UUID()); 670 assert(bytes.toString() == "g{00000000-0000-0000-0000-000000000000}"); 671 672 bytes.init(""); 673 rw.serialize(dnsNamespace); 674 assert(bytes.toString() == "g{" ~ dnsNamespace.toString() ~ "}"); 675 676 bytes.init(""); 677 rw.reset(); 678 rw.serialize(""); 679 rw.serialize('我'); 680 rw.serialize("hello"); 681 rw.serialize("hello"); 682 assert(bytes.toString() == "eu我s5\"hello\"r0;"); 683 684 ubyte[8] ba1 = [0,1,2,3,4,5,0x90,0xff]; 685 ubyte[8] ba2 = [0,1,2,3,4,5,0x90,0xff]; 686 bytes.init(""); 687 rw.reset(); 688 rw.serialize(ba1); 689 rw.serialize(ba1); 690 rw.serialize(ba2); 691 assert(bytes.toString() == "b8\"\x00\x01\x02\x03\x04\x05\x90\xff\"r0;b8\"\x00\x01\x02\x03\x04\x05\x90\xff\""); 692 int[5] ia = [0,1,2,3,4]; 693 int[] ida = [0,1,2,3,4]; 694 695 bytes.init(""); 696 rw.reset(); 697 rw.serialize(ia); 698 rw.serialize(ida); 699 rw.serialize(ia); 700 assert(bytes.toString() == "a5{01234}a5{01234}r0;"); 701 string[string] ssa = ["Hello": "World", "Hi": "World"]; 702 703 bytes.init(""); 704 rw.reset(); 705 rw.serialize(ssa); 706 rw.serialize(ssa); 707 assert(bytes.toString() == "m2{s5\"Hello\"s5\"World\"s2\"Hi\"r2;}r0;"); 708 709 bytes.init(""); 710 rw.reset(); 711 rw.serialize(MyStruct()); 712 rw.serialize(new MyClass()); 713 assert(bytes.toString() == "c8\"MyStruct\"1{s1\"c\"}o0{3}c7\"MyClass\"1{s1\"x\"}o1{3}"); 714 715 SList!(int) slist = SList!(int)([1,2,3,4,5,6,7]); 716 Array!(int) array = Array!(int)([1,2,3,4,5,6,7]); 717 bytes.init(""); 718 rw.reset(); 719 rw.serialize(slist); 720 rw.serialize(array); 721 rw.serialize(array); 722 assert(bytes.toString() == "a7{1234567}a7{1234567}a7{1234567}"); 723 724 const char[] a = ['\xe4','\xbd','\xa0','\xe5','\xa5','\xbd']; 725 const Variant vi = a; 726 const JSONValue jv = 12; 727 bytes.init(""); 728 rw.reset(); 729 rw.serialize(vi); 730 rw.serialize(jv); 731 assert(bytes.toString() == "s2\"你好\"i12;"); 732 733 Nullable!int ni = 10; 734 bytes.init(""); 735 rw.reset(); 736 rw.serialize(ni); 737 ni.nullify(); 738 rw.serialize(ni); 739 assert(bytes.toString() == "i10;n"); 740 741 bytes.init(""); 742 rw.reset(); 743 rw.writeTuple(1,"Hello", "Hello"); 744 assert(bytes.toString() == "a3{1s5\"Hello\"r1;}"); 745 746 enum Color { 747 Red, Blue, Green 748 } 749 bytes.init(""); 750 rw.reset(); 751 rw.writeTuple(Color.Red, Color.Blue, Color.Green); 752 rw.serialize(tuple(Color.Red, Color.Blue, Color.Green)); 753 rw.writeArray(tuple(Color.Red, Color.Blue, Color.Green)); 754 assert(bytes.toString() == "a3{012}a3{012}a3{012}"); 755 }