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