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 }