1 /**
2 * JSON encoding.
3 *
4 * License:
5 * This Source Code Form is subject to the terms of
6 * the Mozilla Public License, v. 2.0. If a copy of
7 * the MPL was not distributed with this file, You
8 * can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 * Authors:
11 * Vladimir Panteleev <ae@cy.md>
12 */
13
14 module ae.utils.json;
15
16 import std.exception;
17 import std.math : isFinite;
18 import std.string;
19 import std.traits;
20 import std.typecons;
21
22 import ae.utils.appender;
23 import ae.utils.array : isIdentical;
24 import ae.utils.exception;
25 import ae.utils.functor.primitives : functor;
26 import ae.utils.meta;
27 import ae.utils.textout;
28
29 // ************************************************************************
30
31 /// JSON serialization / deserialization options
32 struct JsonOptions
33 {
34 /// What to do with associative arrays with non-string keys
35 enum NonStringKeys
36 {
37 /// Fail compilation.
38 error,
39
40 /// Serialize keys as-is - results in non-compliant JSON.
41 asIs,
42
43 /// Serialize keys as strings.
44 /// Note that this may result in multiple levels of quoting.
45 stringify,
46 }
47 NonStringKeys nonStringKeys = NonStringKeys.error; /// ditto
48 }
49
50 // ************************************************************************
51
52 /// Basic JSON writer.
53 struct JsonWriter(Output)
54 {
55 /// You can set this to something to e.g. write to another buffer.
56 Output output;
57
58 private void putChars(S...)(S strings)
59 {
60 static if (is(typeof(output.putEx(strings))))
61 output.putEx(strings);
62 else
63 foreach (str; strings)
64 static if (is(typeof(output.put(str))))
65 output.put(str);
66 else
67 foreach (dchar c; str)
68 {
69 alias C = char; // TODO: get char type of output
70 C[4 / C.sizeof] buf = void;
71 auto size = encode(buf, c);
72 output.put(buf[0..size]);
73 }
74 }
75
76 /// Write a string literal.
77 private void putString(C)(in C[] s)
78 {
79 // TODO: escape Unicode characters?
80 // TODO: Handle U+2028 and U+2029 ( http://timelessrepo.com/json-isnt-a-javascript-subset )
81
82 output.putEx('"');
83 auto start = s.ptr, p = start, end = start+s.length;
84
85 while (p < end)
86 {
87 auto c = *p++;
88 if (c < Escapes.escaped.length && Escapes.escaped[c])
89 {
90 putChars(start[0..p-start-1], Escapes.chars[c]);
91 start = p;
92 }
93 }
94
95 putChars(start[0..p-start], '"');
96 }
97
98 /// Write a value of a simple type.
99 void putValue(T)(T v)
100 {
101 static if (is(typeof(v is null)))
102 if (v is null)
103 return output.put("null");
104 static if (is(T == typeof(null)))
105 return output.put("null");
106 else
107 static if (isSomeString!T)
108 putString(v);
109 else
110 static if (isSomeChar!(Unqual!T))
111 return putString((&v)[0..1]);
112 else
113 static if (is(Unqual!T == bool))
114 return output.put(v ? "true" : "false");
115 else
116 static if (is(Unqual!T : long))
117 return .put(output, v);
118 else
119 static if (is(Unqual!T : real))
120 if (v.isFinite)
121 return output.putFP(v);
122 else
123 return putString(v.to!string);
124 else
125 static assert(0, "Don't know how to write " ~ T.stringof);
126 }
127
128 void beginArray()
129 {
130 output.putEx('[');
131 } ///
132
133 void endArray()
134 {
135 output.putEx(']');
136 } ///
137
138 void beginObject()
139 {
140 output.putEx('{');
141 } ///
142
143 void endObject()
144 {
145 output.putEx('}');
146 } ///
147
148 void endKey()
149 {
150 output.putEx(':');
151 } ///
152
153 void putComma()
154 {
155 output.putEx(',');
156 } ///
157 }
158
159 /// JSON writer with indentation.
160 struct PrettyJsonWriter(Output, alias indent = '\t', alias newLine = '\n', alias pad = ' ')
161 {
162 JsonWriter!Output jsonWriter; /// Underlying writer.
163 alias jsonWriter this;
164
165 private bool indentPending;
166 private uint indentLevel;
167
168 private void putIndent()
169 {
170 if (indentPending)
171 {
172 foreach (n; 0..indentLevel)
173 output.putEx(indent);
174 indentPending = false;
175 }
176 }
177
178 private void putNewline()
179 {
180 if (!indentPending)
181 {
182 output.putEx(newLine);
183 indentPending = true;
184 }
185 }
186
187 void putValue(T)(T v)
188 {
189 putIndent();
190 jsonWriter.putValue(v);
191 } ///
192
193 void beginArray()
194 {
195 putIndent();
196 jsonWriter.beginArray();
197 indentLevel++;
198 putNewline();
199 } ///
200
201 void endArray()
202 {
203 indentLevel--;
204 putNewline();
205 putIndent();
206 jsonWriter.endArray();
207 } ///
208
209 void beginObject()
210 {
211 putIndent();
212 jsonWriter.beginObject();
213 indentLevel++;
214 putNewline();
215 } ///
216
217 void endObject()
218 {
219 indentLevel--;
220 putNewline();
221 putIndent();
222 jsonWriter.endObject();
223 } ///
224
225 void endKey()
226 {
227 output.putEx(pad, ':', pad);
228 } ///
229
230 void putComma()
231 {
232 jsonWriter.putComma();
233 putNewline();
234 } ///
235 }
236
237 /// Abstract JSON serializer based on `Writer`.
238 struct CustomJsonSerializer(Writer, JsonOptions options = JsonOptions.init)
239 {
240 Writer writer; /// Output.
241
242 /// Put a serializable value.
243 void put(T)(auto ref T v)
244 {
245 static if (__traits(hasMember, T, "toJSON"))
246 static if (is(typeof(v.toJSON())))
247 put(v.toJSON());
248 else
249 v.toJSON((&this).functor!((self, ref j) => self.put(j)));
250 else
251 static if (is(T X == Nullable!X))
252 if (v.isNull)
253 writer.putValue(null);
254 else
255 put(v.get);
256 else
257 static if (is(T == enum))
258 put(to!string(v));
259 else
260 static if (isSomeString!T || is(Unqual!T : real))
261 writer.putValue(v);
262 else
263 static if (is(T == typeof(null)))
264 writer.putValue(null);
265 else
266 static if (is(T U : U[]))
267 {
268 writer.beginArray();
269 if (v.length)
270 {
271 put(v[0]);
272 foreach (i; v[1..$])
273 {
274 writer.putComma();
275 put(i);
276 }
277 }
278 writer.endArray();
279 }
280 else
281 static if (isTuple!T)
282 {
283 // TODO: serialize as object if tuple has names
284 enum N = v.expand.length;
285 static if (N == 0)
286 return;
287 else
288 static if (N == 1)
289 put(v.expand[0]);
290 else
291 {
292 writer.beginArray();
293 foreach (n; rangeTuple!N)
294 {
295 static if (n)
296 writer.putComma();
297 put(v.expand[n]);
298 }
299 writer.endArray();
300 }
301 }
302 else
303 static if (is(typeof(T.init.keys)) && is(typeof(T.init.values)))
304 {
305 writer.beginObject();
306 bool first = true;
307 foreach (key, value; v)
308 {
309 if (!first)
310 writer.putComma();
311 else
312 first = false;
313 static if (is(typeof({string s = T.init.keys[0];})))
314 writer.putValue(key);
315 else
316 static if (options.nonStringKeys == JsonOptions.NonStringKeys.asIs)
317 put(key);
318 else
319 static if (options.nonStringKeys == JsonOptions.NonStringKeys.stringify)
320 writer.putValue(key.toJson!options);
321 else
322 static assert(false, "Cannot serialize associative array with non-string key " ~ K.stringof);
323 writer.endKey();
324 put(value);
325 }
326 writer.endObject();
327 }
328 else
329 static if (is(T==JSONFragment))
330 writer.output.put(v.json);
331 else
332 static if (is(T==struct))
333 {
334 writer.beginObject();
335 bool first = true;
336 foreach (i, ref field; v.tupleof)
337 {
338 static if (!doSkipSerialize!(T, v.tupleof[i].stringof[2..$]))
339 {
340 static if (hasAttribute!(JSONOptional, v.tupleof[i]))
341 if (isIdentical(v.tupleof[i], T.init.tupleof[i]))
342 continue;
343 if (!first)
344 writer.putComma();
345 else
346 first = false;
347 writer.putValue(getJsonName!(T, v.tupleof[i].stringof[2..$]));
348 writer.endKey();
349 put(field);
350 }
351 }
352 writer.endObject();
353 }
354 else
355 static if (is(typeof(*v)))
356 {
357 if (v)
358 put(*v);
359 else
360 writer.putValue(null);
361 }
362 else
363 static assert(0, "Can't serialize " ~ T.stringof ~ " to JSON");
364 }
365 }
366
367 /// JSON serializer with `StringBuilder` output.
368 alias JsonSerializer = CustomJsonSerializer!(JsonWriter!StringBuilder);
369
370 private struct Escapes
371 {
372 static immutable string[256] chars;
373 static immutable bool[256] escaped;
374
375 shared static this()
376 {
377 import std.string : format;
378
379 escaped[] = true;
380 foreach (c; 0..256)
381 if (c=='\\')
382 chars[c] = `\\`;
383 else
384 if (c=='\"')
385 chars[c] = `\"`;
386 else
387 if (c=='\b')
388 chars[c] = `\b`;
389 else
390 if (c=='\f')
391 chars[c] = `\f`;
392 else
393 if (c=='\n')
394 chars[c] = `\n`;
395 else
396 if (c=='\r')
397 chars[c] = `\r`;
398 else
399 if (c=='\t')
400 chars[c] = `\t`;
401 else
402 if (c<'\x20' || c == '\x7F' || c=='<' || c=='>' || c=='&')
403 chars[c] = format(`\u%04x`, c);
404 else
405 chars[c] = [cast(char)c],
406 escaped[c] = false;
407 }
408 }
409
410 // ************************************************************************
411
412 /// Serialize `T` to JSON, and return the result as a string.
413 string toJson(T)(auto ref T v)
414 {
415 JsonSerializer serializer;
416 serializer.put(v);
417 return serializer.writer.output.get();
418 }
419
420 /// ditto
421 string toJson(JsonOptions options, T)(auto ref T v)
422 {
423 CustomJsonSerializer!(JsonWriter!StringBuilder, options) serializer;
424 serializer.put(v);
425 return serializer.writer.output.get();
426 }
427
428 ///
429 debug(ae_unittest) unittest
430 {
431 struct X { int a; string b; }
432 X x = {17, "aoeu"};
433 assert(toJson(x) == `{"a":17,"b":"aoeu"}`, toJson(x));
434 int[] arr = [1,5,7];
435 assert(toJson(arr) == `[1,5,7]`);
436 assert(toJson(true) == `true`);
437
438 assert(toJson(tuple()) == ``);
439 assert(toJson(tuple(42)) == `42`);
440 assert(toJson(tuple(42, "banana")) == `[42,"banana"]`);
441 }
442
443 debug(ae_unittest) unittest
444 {
445 struct A
446 {
447 }
448
449 struct B
450 {
451 A[] a;
452 deprecated alias a this;
453 JSONFragment toJSON() const { return JSONFragment(`null`); }
454 }
455
456 B b;
457 b.toJson();
458 }
459
460 // ************************************************************************
461
462 /// Serialize `T` to a pretty (indented) JSON string.
463 string toPrettyJson(T)(T v)
464 {
465 CustomJsonSerializer!(PrettyJsonWriter!StringBuilder) serializer;
466 serializer.put(v);
467 return serializer.writer.output.get();
468 }
469
470 /// ditto
471 string toPrettyJson(JsonOptions options, T)(T v)
472 {
473 CustomJsonSerializer!(PrettyJsonWriter!StringBuilder, options) serializer;
474 serializer.put(v);
475 return serializer.writer.output.get();
476 }
477
478 ///
479 debug(ae_unittest) unittest
480 {
481 struct X { int a; string b; int[] c, d; }
482 X x = {17, "aoeu", [1, 2, 3]};
483 assert(toPrettyJson(x) ==
484 `{
485 "a" : 17,
486 "b" : "aoeu",
487 "c" : [
488 1,
489 2,
490 3
491 ],
492 "d" : [
493 ]
494 }`, toPrettyJson(x));
495 }
496
497 // ************************************************************************
498
499 import std.ascii;
500 import std.utf;
501 import std.conv;
502
503 import ae.utils.text;
504
505 private struct JsonParser(C, JsonOptions options = JsonOptions.init)
506 {
507 C[] s;
508 size_t p;
509
510 Unqual!C next()
511 {
512 enforce(p < s.length, "Out of data while parsing JSON stream");
513 return s[p++];
514 }
515
516 string readN(uint n)
517 {
518 string r;
519 for (int i=0; i<n; i++)
520 r ~= next();
521 return r;
522 }
523
524 Unqual!C peek()
525 {
526 enforce(p < s.length, "Out of data while parsing JSON stream");
527 return s[p];
528 }
529
530 @property bool eof() { return p == s.length; }
531
532 void skipWhitespace()
533 {
534 while (isWhite(peek()))
535 p++;
536 }
537
538 void expect(C c)
539 {
540 auto n = next();
541 enforce(n==c, text("Expected ", c, ", got ", n));
542 }
543
544 void read(T)(ref T value)
545 {
546 static if (is(T == typeof(null)))
547 value = readNull();
548 else
549 static if (is(T X == Nullable!X))
550 readNullable!X(value);
551 else
552 static if (is(T==enum))
553 value = readEnum!(T)();
554 else
555 static if (isSomeString!T)
556 value = readString().to!T;
557 else
558 static if (is(T==bool))
559 value = readBool();
560 else
561 static if (is(T : real))
562 value = readNumber!(T)();
563 else
564 static if (isDynamicArray!T)
565 value = readArray!(typeof(T.init[0]))();
566 else
567 static if (isStaticArray!T)
568 readStaticArray(value);
569 else
570 static if (isTuple!T)
571 readTuple!T(value);
572 else
573 static if (is(typeof(T.init.keys)) && is(typeof(T.init.values)))
574 readAA!(T)(value);
575 else
576 static if (is(T==JSONFragment))
577 {
578 auto start = p;
579 skipValue();
580 value = JSONFragment(s[start..p]);
581 }
582 else
583 static if (is(T U : U*))
584 value = readPointer!T();
585 else
586 static if (__traits(hasMember, T, "fromJSON"))
587 {
588 alias Q = Parameters!(T.fromJSON)[0];
589 Q tempValue;
590 read!Q(tempValue);
591 static if (is(typeof(value = T.fromJSON(tempValue))))
592 value = T.fromJSON(tempValue);
593 else
594 {
595 import core.lifetime : move;
596 auto convertedValue = T.fromJSON(tempValue);
597 move(convertedValue, value);
598 }
599 }
600 else
601 static if (is(T==struct))
602 readObject!(T)(value);
603 else
604 static assert(0, "Can't decode " ~ T.stringof ~ " from JSON");
605 }
606
607 void readTuple(T)(ref T value)
608 {
609 // TODO: serialize as object if tuple has names
610 enum N = T.expand.length;
611 static if (N == 0)
612 return;
613 else
614 static if (N == 1)
615 read(value.expand[0]);
616 else
617 {
618 expect('[');
619 foreach (n, ref f; value.expand)
620 {
621 static if (n)
622 expect(',');
623 read(f);
624 }
625 expect(']');
626 }
627 }
628
629 typeof(null) readNull()
630 {
631 expect('n');
632 expect('u');
633 expect('l');
634 expect('l');
635 return null;
636 }
637
638 void readNullable(T)(ref Nullable!T value)
639 {
640 skipWhitespace();
641 if (peek() == 'n')
642 {
643 readNull();
644 value = Nullable!T();
645 }
646 else
647 {
648 if (value.isNull)
649 {
650 T subvalue;
651 read!T(subvalue);
652 value = subvalue;
653 }
654 else
655 read!T(value.get());
656 }
657 }
658
659 C[] readSimpleString() /// i.e. without escapes
660 {
661 skipWhitespace();
662 expect('"');
663 auto start = p;
664 while (true)
665 {
666 auto c = next();
667 if (c=='"')
668 break;
669 else
670 if (c=='\\')
671 throw new Exception("Unexpected escaped character");
672 }
673 return s[start..p-1];
674 }
675
676 C[] readString()
677 {
678 skipWhitespace();
679 auto c = peek();
680 if (c == '"')
681 {
682 next(); // '"'
683 C[] result;
684 auto start = p;
685 while (true)
686 {
687 c = next();
688 if (c=='"')
689 break;
690 else
691 if (c=='\\')
692 {
693 result ~= s[start..p-1];
694 switch (next())
695 {
696 case '"': result ~= '"'; break;
697 case '/': result ~= '/'; break;
698 case '\\': result ~= '\\'; break;
699 case 'b': result ~= '\b'; break;
700 case 'f': result ~= '\f'; break;
701 case 'n': result ~= '\n'; break;
702 case 'r': result ~= '\r'; break;
703 case 't': result ~= '\t'; break;
704 case 'u':
705 {
706 wstring buf;
707 goto Unicode_start;
708
709 while (s[p..$].startsWith(`\u`))
710 {
711 p+=2;
712 Unicode_start:
713 buf ~= cast(wchar)fromHex!ushort(readN(4));
714 }
715 result ~= buf.to!(C[]);
716 break;
717 }
718 default: enforce(false, "Unknown escape");
719 }
720 start = p;
721 }
722 }
723 result ~= s[start..p-1];
724 return result;
725 }
726 else
727 if (isDigit(c) || c=='-') // For languages that don't distinguish numeric strings from numbers
728 {
729 static immutable bool[256] numeric =
730 [
731 '0':true,
732 '1':true,
733 '2':true,
734 '3':true,
735 '4':true,
736 '5':true,
737 '6':true,
738 '7':true,
739 '8':true,
740 '9':true,
741 '.':true,
742 '-':true,
743 '+':true,
744 'e':true,
745 'E':true,
746 ];
747
748 auto start = p;
749 while (numeric[c = peek()])
750 p++;
751 return s[start..p].dup;
752 }
753 else
754 {
755 foreach (n; "null")
756 expect(n);
757 return null;
758 }
759 }
760
761 bool readBool()
762 {
763 skipWhitespace();
764 if (peek()=='t')
765 {
766 enforce(readN(4) == "true", "Bad boolean");
767 return true;
768 }
769 else
770 if (peek()=='f')
771 {
772 enforce(readN(5) == "false", "Bad boolean");
773 return false;
774 }
775 else
776 {
777 ubyte i = readNumber!ubyte();
778 enforce(i < 2, "Bad digit for implicit number-to-bool conversion");
779 return !!i;
780 }
781 }
782
783 T readNumber(T)()
784 {
785 skipWhitespace();
786 const(C)[] n;
787 auto start = p;
788 Unqual!C c = peek();
789 if (c == '"') // TODO: implicit type conversion should be optional
790 n = readSimpleString();
791 else
792 {
793 while (c=='+' || c=='-' || (c>='0' && c<='9') || c=='e' || c=='E' || c=='.')
794 {
795 p++;
796 if (eof) break;
797 c = peek();
798 }
799 n = s[start..p];
800 }
801 static if (is(T : long))
802 return to!T(n);
803 else
804 static if (is(T : real))
805 return fpParse!T(n);
806 else
807 static assert(0, "Don't know how to parse numerical type " ~ T.stringof);
808 }
809
810 T[] readArray(T)()
811 {
812 skipWhitespace();
813 expect('[');
814 skipWhitespace();
815 T[] result;
816 if (peek()==']')
817 {
818 p++;
819 return result;
820 }
821 while(true)
822 {
823 T subvalue;
824 read!T(subvalue);
825 result ~= subvalue;
826
827 skipWhitespace();
828 if (peek()==']')
829 {
830 p++;
831 return result;
832 }
833 else
834 expect(',');
835 }
836 }
837
838 void readStaticArray(T, size_t n)(ref T[n] value)
839 {
840 skipWhitespace();
841 expect('[');
842 skipWhitespace();
843 foreach (i, ref subvalue; value)
844 {
845 if (i)
846 {
847 expect(',');
848 skipWhitespace();
849 }
850 read(subvalue);
851 skipWhitespace();
852 }
853 expect(']');
854 }
855
856 void readObject(T)(ref T v)
857 {
858 skipWhitespace();
859 expect('{');
860 skipWhitespace();
861 if (peek()=='}')
862 {
863 p++;
864 return;
865 }
866
867 while (true)
868 {
869 auto jsonField = readSimpleString();
870 mixin(exceptionContext(q{"Error with field " ~ to!string(jsonField)}));
871 skipWhitespace();
872 expect(':');
873
874 bool found;
875 foreach (i, ref field; v.tupleof)
876 {
877 enum name = getJsonName!(T, v.tupleof[i].stringof[2..$]);
878 if (name == jsonField)
879 {
880 read(field);
881 found = true;
882 break;
883 }
884 }
885
886 if (!found)
887 {
888 static if (hasAttribute!(JSONPartial, T))
889 skipValue();
890 else
891 throw new Exception(cast(string)("Unknown field " ~ jsonField));
892 }
893
894 skipWhitespace();
895 if (peek()=='}')
896 {
897 p++;
898 return;
899 }
900 else
901 expect(',');
902 }
903 }
904
905 void readAA(T)(ref T v)
906 {
907 skipWhitespace();
908 static if (is(typeof(T.init is null)))
909 if (peek() == 'n')
910 {
911 v = readNull();
912 return;
913 }
914 expect('{');
915 skipWhitespace();
916 if (peek()=='}')
917 {
918 p++;
919 return;
920 }
921 alias K = typeof(v.keys[0]);
922
923 while (true)
924 {
925 K jsonField;
926 static if (is(K == string))
927 jsonField = readString().to!K;
928 else
929 {
930 static if (options.nonStringKeys == JsonOptions.NonStringKeys.asIs)
931 read(jsonField);
932 else
933 static if (options.nonStringKeys == JsonOptions.NonStringKeys.stringify)
934 jsonField = readString().jsonParse!(K, options);
935 else
936 static assert(false, "Cannot parse associative array with non-string key " ~ K.stringof);
937 }
938
939 skipWhitespace();
940 expect(':');
941
942 // TODO: elide copy
943 typeof(v.values[0]) subvalue;
944 read(subvalue);
945 v[jsonField] = subvalue;
946
947 skipWhitespace();
948 if (peek()=='}')
949 {
950 p++;
951 return;
952 }
953 else
954 expect(',');
955 }
956 }
957
958 T readEnum(T)()
959 {
960 return to!T(readSimpleString());
961 }
962
963 T readPointer(T)()
964 {
965 skipWhitespace();
966 if (peek()=='n')
967 {
968 enforce(readN(4) == "null", "Null expected");
969 return null;
970 }
971 alias S = typeof(*T.init);
972 T v = new S;
973 read!S(*v);
974 return v;
975 }
976
977 void skipValue()
978 {
979 skipWhitespace();
980 Unqual!C c = peek();
981 switch (c)
982 {
983 case '"':
984 readString(); // TODO: Optimize
985 break;
986 case '0': .. case '9':
987 case '-':
988 while (c=='+' || c=='-' || (c>='0' && c<='9') || c=='e' || c=='E' || c=='.')
989 {
990 p++;
991 if (eof) break;
992 c = peek();
993 }
994 break;
995 case '{':
996 next();
997 skipWhitespace();
998 bool first = true;
999 while (peek() != '}')
1000 {
1001 if (first)
1002 first = false;
1003 else
1004 expect(',');
1005 skipValue(); // key
1006 skipWhitespace();
1007 expect(':');
1008 skipValue(); // value
1009 skipWhitespace();
1010 }
1011 expect('}');
1012 break;
1013 case '[':
1014 next();
1015 skipWhitespace();
1016 bool first = true;
1017 while (peek() != ']')
1018 {
1019 if (first)
1020 first = false;
1021 else
1022 expect(',');
1023 skipValue();
1024 skipWhitespace();
1025 }
1026 expect(']');
1027 break;
1028 case 't':
1029 foreach (l; "true")
1030 expect(l);
1031 break;
1032 case 'f':
1033 foreach (l; "false")
1034 expect(l);
1035 break;
1036 case 'n':
1037 foreach (l; "null")
1038 expect(l);
1039 break;
1040 default:
1041 throw new Exception(text("Can't parse: ", c));
1042 }
1043 }
1044 }
1045
1046 /// Parse the JSON in string `s` and deserialize it into an instance of `T`.
1047 template jsonParse(T, JsonOptions options = JsonOptions.init)
1048 {
1049 T jsonParse(C)(C[] s)
1050 {
1051 auto parser = JsonParser!(C, options)(s);
1052 mixin(exceptionContext(q{format("Error at position %d", parser.p)}));
1053 T result;
1054 parser.read!T(result);
1055 return result;
1056 }
1057 }
1058
1059 debug(ae_unittest) unittest
1060 {
1061 struct S { int i; S[] arr; S* p0, p1; }
1062 S s = S(42, [S(1), S(2)], null, new S(15));
1063 auto s2 = jsonParse!S(toJson(s));
1064 //assert(s == s2); // Issue 3789
1065 assert(s.i == s2.i && s.arr == s2.arr && s.p0 is s2.p0 && *s.p1 == *s2.p1);
1066 jsonParse!S(toJson(s).dup);
1067
1068 assert(jsonParse!(Tuple!())(``) == tuple());
1069 assert(jsonParse!(Tuple!int)(`42`) == tuple(42));
1070 assert(jsonParse!(Tuple!(int, string))(`[42, "banana"]`) == tuple(42, "banana"));
1071
1072 assert(jsonParse!(string[string])(`null`) is null);
1073 }
1074
1075 debug(ae_unittest) unittest
1076 {
1077 struct T { string s; wstring w; dstring d; }
1078 T t;
1079 auto s = t.toJson;
1080 assert(s == `{"s":null,"w":null,"d":null}`, s);
1081
1082 t.s = "foo";
1083 t.w = "bar"w;
1084 t.d = "baz"d;
1085 s = t.toJson;
1086 assert(s == `{"s":"foo","w":"bar","d":"baz"}`, s);
1087
1088 jsonParse!T(s);
1089 jsonParse!T(cast(char[]) s);
1090 jsonParse!T(cast(const(char)[]) s);
1091 jsonParse!T(s.to!wstring);
1092 jsonParse!T(s.to!dstring);
1093 }
1094
1095 debug(ae_unittest) unittest
1096 {
1097 jsonParse!(int[2])(`[ 1 , 2 ]`);
1098 }
1099
1100 // NaNs and infinities are serialized as strings.
1101 debug(ae_unittest) unittest
1102 {
1103 void check(double f, string s)
1104 {
1105 assert(f.toJson() == s);
1106 assert(s.jsonParse!double is f);
1107 }
1108 check(double.init, `"nan"`);
1109 check(double.infinity, `"inf"`);
1110 check(-double.infinity, `"-inf"`);
1111 }
1112
1113 /// Parse the JSON in string `s` and deserialize it into `T`.
1114 void jsonParse(T, C)(C[] s, ref T result)
1115 {
1116 auto parser = JsonParser!C(s);
1117 mixin(exceptionContext(q{format("Error at position %d", parser.p)}));
1118 parser.read!T(result);
1119 }
1120
1121 debug(ae_unittest) unittest
1122 {
1123 struct S { int a, b; }
1124 S s;
1125 s.a = 1;
1126 jsonParse(`{"b":2}`, s);
1127 assert(s == S(1, 2));
1128 }
1129
1130 // ************************************************************************
1131
1132 // TODO: migrate to UDAs
1133
1134 /**
1135 * A template that designates fields which should not be serialized to Json.
1136 *
1137 * Example:
1138 * ---
1139 * struct Point { int x, y, z; mixin NonSerialized!(x, z); }
1140 * assert(jsonParse!Point(toJson(Point(1, 2, 3))) == Point(0, 2, 0));
1141 * ---
1142 */
1143 template NonSerialized(fields...)
1144 {
1145 import ae.utils.meta : stringofArray;
1146 mixin(mixNonSerializedFields(stringofArray!fields()));
1147 }
1148
1149 private string mixNonSerializedFields(string[] fields)
1150 {
1151 string result;
1152 foreach (field; fields)
1153 result ~= "enum bool " ~ field ~ "_nonSerialized = 1;";
1154 return result;
1155 }
1156
1157 private template doSkipSerialize(T, string member)
1158 {
1159 enum bool doSkipSerialize = __traits(hasMember, T, member ~ "_nonSerialized");
1160 }
1161
1162 debug(ae_unittest) unittest
1163 {
1164 struct Point { int x, y, z; mixin NonSerialized!(x, z); }
1165 assert(jsonParse!Point(toJson(Point(1, 2, 3))) == Point(0, 2, 0));
1166 }
1167
1168 debug(ae_unittest) unittest
1169 {
1170 enum En { one, two }
1171 assert(En.one.toJson() == `"one"`);
1172 struct S { int i1, i2; S[] arr1, arr2; string[string] dic; En en; mixin NonSerialized!(i2, arr2); }
1173 S s = S(42, 5, [S(1), S(2)], [S(3), S(4)], ["apple":"fruit", "pizza":"vegetable"], En.two);
1174 auto s2 = jsonParse!S(toJson(s));
1175 assert(s.i1 == s2.i1);
1176 assert(s2.i2 is int.init);
1177 assert(s.arr1 == s2.arr1);
1178 assert(s2.arr2 is null);
1179 assert(s.dic == s2.dic, s2.dic.text);
1180 assert(s.en == En.two);
1181 }
1182
1183 debug(ae_unittest) unittest
1184 {
1185 alias B = Nullable!bool;
1186 B b;
1187
1188 b = jsonParse!B("true");
1189 assert(!b.isNull);
1190 assert(b.get == true);
1191 assert(b.toJson == "true");
1192
1193 b = jsonParse!B("false");
1194 assert(!b.isNull);
1195 assert(b.get == false);
1196 assert(b.toJson == "false");
1197
1198 b = jsonParse!B("null");
1199 assert(b.isNull);
1200 assert(b.toJson == "null");
1201
1202 struct S {}
1203 alias NS = Nullable!S;
1204 assert(NS.init.toJson == "null");
1205 }
1206
1207 debug(ae_unittest) unittest // Issue 49
1208 {
1209 immutable bool b;
1210 assert(toJson(b) == "false");
1211 }
1212
1213 debug(ae_unittest) unittest
1214 {
1215 import ae.utils.aa : OrderedMap;
1216 alias M = OrderedMap!(string, int);
1217 M m;
1218 m["one"] = 1;
1219 m["two"] = 2;
1220 auto j = (cast(const)m).toJson();
1221 assert(j == `{"one":1,"two":2}`, j);
1222 assert(j.jsonParse!M == m);
1223 }
1224
1225 debug(ae_unittest) unittest
1226 {
1227 assert(string.init.toJson.jsonParse!string is null);
1228 assert("" .toJson.jsonParse!string !is null);
1229 }
1230
1231 debug(ae_unittest) unittest
1232 {
1233 char[] s = "{}".dup;
1234 assert(s.jsonParse!(string[string]) == null);
1235 }
1236
1237 debug(ae_unittest) unittest
1238 {
1239 typeof(null) n;
1240 assert(n.toJson.jsonParse!(typeof(null)) is null);
1241 }
1242
1243 debug(ae_unittest) unittest
1244 {
1245 double f = 1.5;
1246 assert(f.toJson() == "1.5");
1247 }
1248
1249 debug(ae_unittest) unittest
1250 {
1251 dchar c = '😸';
1252 assert(c.toJson() == `"😸"`);
1253 }
1254
1255 /// `fromJSON` / `toJSON` can be added to a type to control their serialized representation.
1256 debug(ae_unittest) unittest
1257 {
1258 static struct S
1259 {
1260 string value;
1261 static S fromJSON(string value) { return S(value); }
1262 string toJSON() { return value; }
1263 }
1264 auto s = S("test");
1265 assert(s.toJson == `"test"`);
1266 assert(s.toJson.jsonParse!S == s);
1267 }
1268
1269 debug(ae_unittest) unittest
1270 {
1271 static struct S
1272 {
1273 string value;
1274 static S fromJSON(string value) { return S(value); }
1275 void toJSON(F)(F f) { f(value); }
1276 }
1277 auto s = S("test");
1278 auto p = &s;
1279 assert(p.toJson == `"test"`);
1280 assert(*p.toJson.jsonParse!(S*) == s);
1281 }
1282
1283 /// `fromJSON` / `toJSON` can also accept/return a `JSONFragment`,
1284 /// which allows full control over JSON serialization.
1285 debug(ae_unittest) unittest
1286 {
1287 static struct BigInt
1288 {
1289 string decimalDigits;
1290 static BigInt fromJSON(JSONFragment value) { return BigInt(value.json); }
1291 JSONFragment toJSON() { return JSONFragment(decimalDigits); }
1292 }
1293 auto n = BigInt("12345678901234567890");
1294 assert(n.toJson == `12345678901234567890`);
1295 assert(n.toJson.jsonParse!BigInt == n);
1296 }
1297
1298 // ************************************************************************
1299
1300 /// User-defined attribute - specify name for JSON object field.
1301 /// Useful when a JSON object may contain fields, the name of which are not valid D identifiers.
1302 struct JSONName { string name; /***/ }
1303
1304 private template getJsonName(S, string FIELD)
1305 {
1306 static if (hasAttribute!(JSONName, __traits(getMember, S, FIELD)))
1307 enum getJsonName = getAttribute!(JSONName, __traits(getMember, S, FIELD)).name;
1308 else
1309 enum getJsonName = FIELD;
1310 }
1311
1312 // ************************************************************************
1313
1314 /// User-defined attribute - only serialize this field if its value is different from its .init value.
1315 struct JSONOptional {}
1316
1317 debug(ae_unittest) unittest
1318 {
1319 static struct S { @JSONOptional bool a=true, b=false; }
1320 assert(S().toJson == `{}`, S().toJson);
1321 assert(S(false, true).toJson == `{"a":false,"b":true}`);
1322 }
1323
1324 debug(ae_unittest) unittest
1325 {
1326 static struct S { @JSONOptional float f; }
1327 assert(S().toJson == `{}`, S().toJson);
1328 }
1329
1330 debug(ae_unittest) unittest
1331 {
1332 static struct S { @JSONOptional int[1] a; }
1333 assert(S().toJson == `{}`, S().toJson);
1334 }
1335
1336 // ************************************************************************
1337
1338 /// User-defined attribute - skip unknown fields when deserializing.
1339 struct JSONPartial {}
1340
1341 debug(ae_unittest) unittest
1342 {
1343 @JSONPartial static struct S { int b; }
1344 assert(`{"a":1,"b":2,"c":3.4,"d":[5,"x"],"de":[],"e":{"k":"v"},"ee":{},"f":true,"g":false,"h":null}`.jsonParse!S == S(2));
1345 }
1346
1347 // ************************************************************************
1348
1349 /// Fragment of raw JSON.
1350 /// When serialized, the .json field is inserted into the resulting
1351 /// string verbatim, without any validation.
1352 /// When deserialized, will contain the raw JSON of one JSON object of
1353 /// any type.
1354 struct JSONFragment
1355 {
1356 string json; ///
1357 bool opCast(T)() const if (is(T==bool)) { return !!json; } ///
1358 }
1359
1360 debug(ae_unittest) unittest
1361 {
1362 JSONFragment[] arr = [JSONFragment(`1`), JSONFragment(`true`), JSONFragment(`"foo"`), JSONFragment(`[55]`)];
1363 assert(arr.toJson == `[1,true,"foo",[55]]`);
1364 assert(arr.toJson.jsonParse!(JSONFragment[]) == arr);
1365 }
1366
1367 // ************************************************************************
1368
1369 debug(ae_unittest) unittest
1370 {
1371 int[int] aa = [3: 4];
1372 {
1373 enum JsonOptions options = { nonStringKeys: JsonOptions.NonStringKeys.error };
1374 static assert(!__traits(compiles, aa.toJson!options));
1375 static assert(!__traits(compiles, "".jsonParse!(typeof(aa), options)));
1376 }
1377 {
1378 enum JsonOptions options = { nonStringKeys: JsonOptions.NonStringKeys.asIs };
1379 auto s = aa.toJson!options;
1380 assert(s == `{3:4}`);
1381 assert(s.jsonParse!(typeof(aa), options) == aa);
1382 }
1383 {
1384 enum JsonOptions options = { nonStringKeys: JsonOptions.NonStringKeys.stringify };
1385 auto s = aa.toJson!options;
1386 assert(s == `{"3":4}`);
1387 assert(s.jsonParse!(typeof(aa), options) == aa);
1388 }
1389 }