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