The OpenD Programming Language

1 /++
2 $(H4 High level JSON serialization API)
3 
4 Macros:
5 IONREF = $(REF_ALTTEXT $(TT $2), $2, mir, ion, $1)$(NBSP)
6 +/
7 module mir.ser.json;
8 
9 public import mir.serde;
10 import mir.ion.exception: IonException, IonMirException;
11 
12 private static immutable jsonAnnotationExceptionMsg = "JSON can store exactly one annotation.";
13 version(D_Exceptions) private static immutable jsonAnnotationException = new IonException(jsonAnnotationExceptionMsg);
14 
15 /++
16 JSON serialization back-end
17 +/
18 struct JsonSerializer(string sep, Appender)
19 {
20     import mir.bignum.decimal: Decimal;
21     import mir.bignum.integer: BigInt;
22     import mir.ion.type_code;
23     import mir.lob;
24     import mir.timestamp;
25     import std.traits: isNumeric;
26 
27     private enum hasDIP1008 = __traits(compiles, ()@nogc {throw new Exception("");});
28 
29     /++
30     JSON string buffer
31     +/
32     Appender* appender;
33 
34     /// Mutable value used to choose format specidied or user-defined serialization specializations
35     int serdeTarget = SerdeTarget.json;
36     private bool _annotation;
37     private size_t state;
38 
39     static if(sep.length)
40     {
41         private size_t deep;
42 
43         private void putSpace() scope
44         {
45             for(auto k = deep; k; k--)
46             {
47                 static if(sep.length == 1)
48                 {
49                     appender.put(sep[0]);
50                 }
51                 else
52                 {
53                     appender.put(sep);
54                 }
55             }
56         }
57     }
58 
59 scope:
60 
61     private void pushState(size_t state)
62     {
63         this.state = state;
64     }
65 
66     private size_t popState()
67     {
68         auto ret = state;
69         state = 0;
70         return ret;
71     }
72 
73     private void incState()
74     {
75         if(state++)
76         {
77             static if(sep.length)
78             {
79                 appender.put(",\n");
80             }
81             else
82             {
83                 appender.put(',');
84             }
85         }
86         else
87         {
88             static if(sep.length)
89             {
90                 appender.put('\n');
91             }
92         }
93     }
94 
95     private void putEscapedKey(scope const char[] key)
96     {
97         incState;
98         static if(sep.length)
99         {
100             putSpace;
101         }
102         appender.put('\"');
103         appender.put(key);
104         static if(sep.length)
105         {
106             appender.put(`": `);
107         }
108         else
109         {
110             appender.put(`":`);
111         }
112     }
113 
114     ///
115     size_t stringBegin()
116     {
117         appender.put('\"');
118         return 0;
119     }
120 
121     /++
122     Puts string part. The implementation allows to split string unicode points.
123     +/
124     void putStringPart(scope const(char)[] value)
125     {
126         import mir.format: printEscaped, EscapeFormat;
127         printEscaped!(char, EscapeFormat.json)(appender, value);
128     }
129 
130     ///
131     void stringEnd(size_t)
132     {
133         appender.put('\"');
134     }
135 
136     ///
137     size_t structBegin(size_t length = size_t.max)
138     {
139         static if(sep.length)
140         {
141             deep++;
142         }
143         appender.put('{');
144         return popState;
145     }
146 
147     ///
148     void structEnd(size_t state)
149     {
150         static if(sep.length)
151         {
152             deep--;
153             if (this.state)
154             {
155                 appender.put('\n');
156                 putSpace;
157             }
158         }
159         appender.put('}');
160         pushState(state);
161     }
162 
163     ///
164     size_t listBegin(size_t length = size_t.max)
165     {
166         static if(sep.length)
167         {
168             deep++;
169         }
170         appender.put('[');
171         return popState;
172     }
173 
174     ///
175     void listEnd(size_t state)
176     {
177         static if(sep.length)
178         {
179             deep--;
180             if (this.state)
181             {
182                 appender.put('\n');
183                 putSpace;
184             }
185         }
186         appender.put(']');
187         pushState(state);
188     }
189 
190     ///
191     alias sexpBegin = listBegin;
192 
193     ///
194     alias sexpEnd = listEnd;
195 
196     ///
197     void putSymbol(scope const char[] symbol)
198     {
199         putValue(symbol);
200     }
201 
202     ///
203     void putAnnotation(scope const(char)[] annotation)
204     {
205         import mir.exception: toMutable;
206         if (_annotation)
207         {
208             static if (hasDIP1008)
209                 throw new IonMirException(jsonAnnotationExceptionMsg, " The second annotation is '", annotation, "'");
210             else
211                 throw jsonAnnotationException.toMutable;
212         }
213         _annotation = true;
214         putKey(annotation);
215     }
216 
217     ///
218     auto annotationsEnd(size_t state)
219     {
220         _annotation = false;
221         return state;
222     }
223 
224     ///
225     alias annotationWrapperBegin = structBegin;
226 
227     ///
228     void annotationWrapperEnd(size_t annotationsState, size_t state)
229     {
230         return structEnd(state);
231     }
232 
233     ///
234     void nextTopLevelValue()
235     {
236         appender.put('\n');
237     }
238 
239     ///
240     void putCompiletimeKey(string key)()
241     {
242         import mir.algorithm.iteration: any;
243         static if (key.any!(c => c == '"' || c == '\\' || c < ' '))
244             putKey(key);
245         else
246             putEscapedKey(key);
247     }
248 
249     ///
250     void putKey(scope const char[] key)
251     {
252         import mir.format: printEscaped, EscapeFormat;
253 
254         incState;
255         static if(sep.length)
256         {
257             putSpace;
258         }
259         appender.put('\"');
260         printEscaped!(char, EscapeFormat.json)(appender, key);
261         static if(sep.length)
262         {
263             appender.put(`": `);
264         }
265         else
266         {
267             appender.put(`":`);
268         }
269     }
270 
271     ///
272     void putValue(Num)(const Num value)
273         if (isNumeric!Num && !is(Num == enum))
274     {
275         import mir.format: print;
276         import mir.internal.utility: isFloatingPoint;
277 
278         static if (isFloatingPoint!Num)
279         {
280             import mir.math.common: fabs;
281 
282             if (value.fabs < value.infinity)
283                 print(appender, value);
284             else if (value == Num.infinity)
285                 appender.put(`"+inf"`);
286             else if (value == -Num.infinity)
287                 appender.put(`"-inf"`);
288             else
289                 appender.put(`"nan"`);
290         }
291         else
292             print(appender, value);
293     }
294 
295     ///
296     void putValue(size_t size)(auto ref const BigInt!size num)
297     {
298         num.toString(appender);
299     }
300 
301     ///
302     void putValue(size_t size)(auto ref const Decimal!size num)
303     {
304         num.toString(appender);
305     }
306 
307     ///
308     void putValue(typeof(null))
309     {
310         appender.put("null");
311     }
312 
313     /// ditto 
314     void putNull(IonTypeCode code)
315     {
316         appender.put(code.nullStringJsonAlternative);
317     }
318 
319     ///
320     void putValue(bool b)
321     {
322         appender.put(b ? "true" : "false");
323     }
324 
325     ///
326     void putValue(scope const char[] value)
327     {
328         auto state = stringBegin;
329         putStringPart(value);
330         stringEnd(state);
331     }
332 
333     ///
334     void putValue(scope Clob value)
335     {
336         import mir.format: printEscaped, EscapeFormat;
337 
338         static if(sep.length)
339             appender.put(`{{ "`);
340         else
341             appender.put(`{{"`);
342 
343         printEscaped!(char, EscapeFormat.ionClob)(appender, value.data);
344 
345         static if(sep.length)
346             appender.put(`" }}`);
347         else
348             appender.put(`"}}`);
349     }
350 
351     ///
352     void putValue(scope Blob value)
353     {
354         import mir.base64 : encodeBase64;
355         static if(sep.length)
356             appender.put("{{ ");
357         else
358             appender.put("{{");
359 
360         encodeBase64(value.data, appender);
361 
362         static if(sep.length)
363             appender.put(" }}");
364         else
365             appender.put("}}");
366     }
367 
368     ///
369     void putValue(Timestamp value)
370     {
371         appender.put('\"');
372         value.toISOExtString(appender);
373         appender.put('\"');
374     }
375 
376     ///
377     void elemBegin()
378     {
379         incState;
380         static if(sep.length)
381         {
382             putSpace;
383         }
384     }
385 
386     ///
387     alias sexpElemBegin = elemBegin;
388 }
389 
390 /++
391 JSON serialization function.
392 +/
393 alias serializeJson = serializeJsonPretty!"";
394 
395 ///
396 version(mir_ion_test)
397 unittest
398 {
399     struct S
400     {
401         string foo;
402         uint bar;
403     }
404 
405     assert(serializeJson(S("str", 4)) == `{"foo":"str","bar":4}`);
406 }
407 
408 version(mir_ion_test)
409 unittest
410 {
411     import mir.ser.json: serializeJson;
412     import mir.format: stringBuf;
413     import mir.small_string;
414 
415     SmallString!8 smll = SmallString!8("ciaociao");
416     auto buffer = stringBuf;
417 
418     serializeJson(buffer, smll);
419     assert(buffer.data == `"ciaociao"`);
420 }
421 
422 ///
423 version(mir_ion_test)
424 unittest
425 {
426     import mir.serde: serdeIgnoreDefault;
427 
428     static struct Decor
429     {
430         int candles; // 0
431         float fluff = float.infinity; // inf 
432     }
433     
434     static struct Cake
435     {
436         @serdeIgnoreDefault
437         string name = "Chocolate Cake";
438         int slices = 8;
439         float flavor = 1;
440         @serdeIgnoreDefault
441         Decor dec = Decor(20); // { 20, inf }
442     }
443     
444     assert(Cake("Normal Cake").serializeJson == `{"name":"Normal Cake","slices":8,"flavor":1.0}`);
445     auto cake = Cake.init;
446     cake.dec = Decor.init;
447     assert(cake.serializeJson == `{"slices":8,"flavor":1.0,"dec":{"candles":0,"fluff":"+inf"}}`);
448     assert(cake.dec.serializeJson == `{"candles":0,"fluff":"+inf"}`);
449     
450     static struct A
451     {
452         @serdeIgnoreDefault
453         string str = "Banana";
454         int i = 1;
455     }
456     assert(A.init.serializeJson == `{"i":1}`);
457     
458     static struct S
459     {
460         @serdeIgnoreDefault
461         A a;
462     }
463     assert(S.init.serializeJson == `{}`);
464     assert(S(A("Berry")).serializeJson == `{"a":{"str":"Berry","i":1}}`);
465     
466     static struct D
467     {
468         S s;
469     }
470     assert(D.init.serializeJson == `{"s":{}}`);
471     assert(D(S(A("Berry"))).serializeJson == `{"s":{"a":{"str":"Berry","i":1}}}`);
472     assert(D(S(A(null, 0))).serializeJson == `{"s":{"a":{"str":"","i":0}}}`);
473     
474     static struct F
475     {
476         D d;
477     }
478     assert(F.init.serializeJson == `{"d":{"s":{}}}`);
479 }
480 
481 ///
482 version(mir_ion_test)
483 unittest
484 {
485     import mir.serde: serdeIgnoreIn;
486 
487     static struct S
488     {
489         @serdeIgnoreIn
490         string s;
491     }
492     // assert(`{"s":"d"}`.deserializeJson!S.s == null, `{"s":"d"}`.deserializeJson!S.s);
493     assert(S("d").serializeJson == `{"s":"d"}`);
494 }
495 
496 ///
497 version(mir_ion_test)
498 unittest
499 {
500     import mir.deser.json;
501 
502     static struct S
503     {
504         @serdeIgnoreOut
505         string s;
506     }
507     assert(`{"s":"d"}`.deserializeJson!S.s == "d");
508     assert(S("d").serializeJson == `{}`);
509 }
510 
511 ///
512 version(mir_ion_test)
513 unittest
514 {
515     import mir.serde: serdeIgnoreOutIf;
516 
517     static struct S
518     {
519         @serdeIgnoreOutIf!`a < 0`
520         int a;
521     }
522 
523     assert(serializeJson(S(3)) == `{"a":3}`, serializeJson(S(3)));
524     assert(serializeJson(S(-3)) == `{}`);
525 }
526 
527 ///
528 version(mir_ion_test)
529 unittest
530 {
531     import mir.rc.array;
532     auto ar = rcarray!int(1, 2, 4);
533     assert(ar.serializeJson == "[1,2,4]");
534 }
535 
536 ///
537 version(mir_ion_test)
538 unittest
539 {
540     import mir.deser.json;
541     import std.range;
542     import std.algorithm;
543     import std.conv;
544     import mir.test;
545 
546     static struct S
547     {
548         @serdeTransformIn!"a += 2"
549         @serdeTransformOut!(a =>"str".repeat.take(a).joiner("_").to!string)
550         int a;
551     }
552 
553     auto s = deserializeJson!S(`{"a":3}`);
554     s.a.should == 5;
555     assert(serializeJson(s) == `{"a":"str_str_str_str_str"}`);
556 }
557 
558 /++
559 JSON serialization for custom outputt range.
560 +/
561 version(mir_ion_test)
562 @safe pure nothrow @nogc
563 unittest
564 {
565     import mir.format: stringBuf;
566     auto buffer = stringBuf;
567     static struct S { int a; }
568     serializeJson(buffer, S(4));
569     assert(buffer.data == `{"a":4}`);
570 }
571 
572 /++
573 JSON serialization function with pretty formatting and custom output range.
574 +/
575 template serializeJsonPretty(string sep = "\t")
576 {
577     import mir.primitives: isOutputRange;
578     ///
579     void serializeJsonPretty(Appender, V)(scope ref Appender appender, scope auto ref V value, int serdeTarget = SerdeTarget.json)
580         if (isOutputRange!(Appender, const(char)[]) && isOutputRange!(Appender, char))
581     {
582         import mir.ser: serializeValue;
583         auto serializer = jsonSerializer!sep((()@trusted => &appender)(), serdeTarget);
584         serializeValue(serializer, value);
585     }
586 
587     /++
588     JSON serialization function with pretty formatting.
589     +/
590     string serializeJsonPretty(V)(scope auto ref const V value, int serdeTarget = SerdeTarget.json)
591     {
592         import std.array: appender;
593         import mir.functional: forward;
594 
595         auto app = appender!(char[]);
596         serializeJsonPretty(app, value, serdeTarget);
597         return (()@trusted => cast(string) app.data)();
598     }
599 }
600 
601 ///
602 version(mir_ion_test)
603 unittest
604 {
605     static struct S { int a; }
606     assert(S(4).serializeJsonPretty!"    " == "{\n    \"a\": 4\n}");
607 }
608 
609 ///
610 version(mir_ion_test)
611 @safe pure nothrow @nogc
612 unittest
613 {
614     import mir.format: stringBuf;
615     auto buffer = stringBuf;
616     static struct S { int a; }
617     serializeJsonPretty!"    "(buffer, S(4));
618     assert(buffer.data == "{\n    \"a\": 4\n}");
619 }
620 
621 /++
622 Creates JSON serialization back-end.
623 Use `sep` equal to `"\t"` or `"    "` for pretty formatting.
624 +/
625 template jsonSerializer(string sep = "")
626 {
627     ///
628     auto jsonSerializer(Appender)(return Appender* appender, int serdeTarget = SerdeTarget.json)
629     {
630         return JsonSerializer!(sep, Appender)(appender, serdeTarget);
631     }
632 }
633 
634 ///
635 @safe pure nothrow @nogc unittest
636 {
637     import mir.format: stringBuf;
638     import mir.bignum.integer;
639 
640     auto buffer = stringBuf;
641     auto ser = jsonSerializer((()@trusted=>&buffer)(), 3);
642     auto state0 = ser.structBegin;
643 
644         ser.putKey("null");
645         ser.putValue(null);
646 
647         ser.putKey("array");
648         auto state1 = ser.listBegin();
649             ser.elemBegin; ser.putValue(null);
650             ser.elemBegin; ser.putValue(123);
651             ser.elemBegin; ser.putValue(12300000.123);
652             ser.elemBegin; ser.putValue("\t");
653             ser.elemBegin; ser.putValue("\r");
654             ser.elemBegin; ser.putValue("\n");
655             ser.elemBegin; ser.putValue(BigInt!2(1234567890));
656         ser.listEnd(state1);
657 
658     ser.structEnd(state0);
659 
660     assert(buffer.data == `{"null":null,"array":[null,123,1.2300000123e+7,"\t","\r","\n",1234567890]}`);
661 }
662 
663 ///
664 version(mir_ion_test)
665 unittest
666 {
667     import std.array;
668     import mir.bignum.integer;
669 
670     auto app = appender!string;
671     auto ser = jsonSerializer!"    "(&app);
672     auto state0 = ser.structBegin;
673 
674         ser.putKey("null");
675         ser.putValue(null);
676 
677         ser.putKey("array");
678         auto state1 = ser.listBegin();
679             ser.elemBegin; ser.putValue(null);
680             ser.elemBegin; ser.putValue(123);
681             ser.elemBegin; ser.putValue(12300000.123);
682             ser.elemBegin; ser.putValue("\t");
683             ser.elemBegin; ser.putValue("\r");
684             ser.elemBegin; ser.putValue("\n");
685             ser.elemBegin; ser.putValue(BigInt!2("1234567890"));
686         ser.listEnd(state1);
687 
688     ser.structEnd(state0);
689 
690     assert(app.data ==
691 `{
692     "null": null,
693     "array": [
694         null,
695         123,
696         1.2300000123e+7,
697         "\t",
698         "\r",
699         "\n",
700         1234567890
701     ]
702 }`, app.data);
703 }