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