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 }