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