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.serialization.json;
15 
16 import std.conv;
17 import std.exception;
18 import std.format;
19 import std.string : format;
20 import std.traits;
21 import std.utf;
22 
23 import ae.utils.meta;
24 import ae.utils.text;
25 
26 import ae.utils.serialization.serialization;
27 
28 /// Serialization source which parses a JSON stream.
29 struct JsonParser(C)
30 {
31 	// TODO: some abstract input stream?
32 	struct Data
33 	{
34 		C[] s;
35 		size_t p;
36 	}
37 
38 	static template Impl(alias data)
39 	{
40 		alias Char = C;
41 
42 		C next()
43 		{
44 			enforce(data.p < data.s.length);
45 			return data.s[data.p++];
46 		}
47 
48 		void skip()
49 		{
50 			data.p++;
51 		}
52 
53 		C[] readN(size_t n)
54 		{
55 			auto end = data.p + n;
56 			enforce(end <= data.s.length);
57 			C[] result = data.s[data.p .. end];
58 			data.p = end;
59 			return result;
60 		}
61 
62 		C peek()
63 		{
64 			enforce(data.p < data.s.length);
65 			return data.s[data.p];
66 		}
67 
68 		size_t mark()
69 		{
70 			return data.p;
71 		}
72 
73 		C[] slice(size_t a, size_t b)
74 		{
75 			return data.s[a..b];
76 		}
77 
78 		@property bool eof() { return data.p == data.s.length; }
79 
80 		// *******************************************************************
81 
82 		static bool isWhite(C c)
83 		{
84 			return c == ' ' || c == '\t';
85 		}
86 
87 		void skipWhitespace()
88 		{
89 			while (isWhite(peek()))
90 				skip();
91 		}
92 
93 		void expect(C c)
94 		{
95 			auto n = next();
96 			enforce(n==c, "Expected %s, got %s".format(c, n));
97 		}
98 
99 		// *******************************************************************
100 
101 		void read(Sink)(Sink sink)
102 		{
103 			skipWhitespace();
104 			switch (peek())
105 			{
106 				case '[':
107 					skip();
108 					sink.handleArray(boundFunctorOf!readArray);
109 					break;
110 				case '"':
111 					skip();
112 					sink.handleStringFragments(boundFunctorOf!readString);
113 					break;
114 				case 't':
115 					skip();
116 					expect('r');
117 					expect('u');
118 					expect('e');
119 					sink.handleBoolean(true);
120 					break;
121 				case 'f':
122 					skip();
123 					expect('a');
124 					expect('l');
125 					expect('s');
126 					expect('e');
127 					sink.handleBoolean(false);
128 					break;
129 				case 'n':
130 					skip();
131 					expect('u');
132 					expect('l');
133 					expect('l');
134 					sink.handleNull();
135 					break;
136 				case '-':
137 				case '0':
138 					..
139 				case '9':
140 					sink.handleNumeric(readNumeric());
141 					break;
142 				case '{':
143 					skip();
144 					sink.handleObject(boundFunctorOf!readObject);
145 					break;
146 				default:
147 					throw new Exception("Unknown JSON symbol: %s".format(peek()));
148 			}
149 		}
150 
151 		void readArray(Sink)(Sink sink)
152 		{
153 			if (peek()==']')
154 			{
155 				skip();
156 				return;
157 			}
158 			while (true)
159 			{
160 				read(sink);
161 				skipWhitespace();
162 				if (peek()==']')
163 				{
164 					skip();
165 					return;
166 				}
167 				else
168 					expect(',');
169 			}
170 		}
171 
172 		void readObject(Sink)(Sink sink)
173 		{
174 			skipWhitespace();
175 			if (peek()=='}')
176 			{
177 				skip();
178 				return;
179 			}
180 
181 			while (true)
182 			{
183 				sink.handleField(boundFunctorOf!read, boundFunctorOf!readObjectValue);
184 
185 				skipWhitespace();
186 				if (peek()=='}')
187 				{
188 					skip();
189 					return;
190 				}
191 				else
192 					expect(',');
193 			}
194 		}
195 
196 		void readObjectValue(Sink)(Sink sink)
197 		{
198 			skipWhitespace();
199 			expect(':');
200 			read(sink);
201 		}
202 
203 		/// This will call sink.handleStringFragment multiple times.
204 		void readString(Sink)(Sink sink)
205 		{
206 			auto start = mark();
207 
208 			void flush()
209 			{
210 				auto end = mark();
211 				if (start != end)
212 					sink.handleStringFragment(slice(start, end));
213 			}
214 
215 			void oneConst(C c)()
216 			{
217 				static C[1] arr = [c];
218 				sink.handleStringFragment(arr[]);
219 			}
220 
221 			while (true)
222 			{
223 				C c = peek();
224 				if (c=='"')
225 				{
226 					flush();
227 					skip();
228 					return;
229 				}
230 				else
231 				if (c=='\\')
232 				{
233 					flush();
234 					skip();
235 					switch (next())
236 					{
237 						case '"':  oneConst!('"'); break;
238 						case '/':  oneConst!('/'); break;
239 						case '\\': oneConst!('\\'); break;
240 						case 'b':  oneConst!('\b'); break;
241 						case 'f':  oneConst!('\f'); break;
242 						case 'n':  oneConst!('\n'); break;
243 						case 'r':  oneConst!('\r'); break;
244 						case 't':  oneConst!('\t'); break;
245 						case 'u':
246 						{
247 							auto w = cast(wchar)fromHex!ushort(readN(4));
248 							static if (C.sizeof == 1)
249 							{
250 								char[4] buf;
251 								sink.handleStringFragment(buf[0..encode(buf, w)]);
252 							}
253 							else
254 							{
255 								Unqual!C[1] buf;
256 								buf[0] = w;
257 								sink.handleStringFragment(buf[]);
258 							}
259 							break;
260 						}
261 						default: enforce(false, "Unknown escape");
262 					}
263 					start = mark();
264 				}
265 				else
266 					skip();
267 			}
268 		}
269 
270 		C[] readNumeric()
271 		{
272 			auto p = mark();
273 
274 			static immutable bool[256] numeric =
275 			[
276 				'0':true,
277 				'1':true,
278 				'2':true,
279 				'3':true,
280 				'4':true,
281 				'5':true,
282 				'6':true,
283 				'7':true,
284 				'8':true,
285 				'9':true,
286 				'.':true,
287 				'-':true,
288 				'+':true,
289 				'e':true,
290 				'E':true,
291 			];
292 
293 			while (!eof() && numeric[peek()])
294 				skip();
295 			return slice(p, mark());
296 		}
297 	}
298 }
299 
300 struct JsonDeserializer(C)
301 {
302 	JsonParser!C.Data jsonData;
303 	alias JsonParser!C.Impl!jsonData jsonImpl;
304 	void[0] anchor;
305 	alias Deserializer!anchor deserializer;
306 
307 	this(C[] s)
308 	{
309 		jsonData.s = s;
310 	}
311 
312 	T deserialize(T)()
313 	{
314 		T t;
315 		auto sink = deserializer.makeSink(&t);
316 		jsonImpl.read(sink);
317 		return t;
318 	}
319 }
320 
321 /// Parse JSON from a string and deserialize it into the given type.
322 T jsonParse(T, C)(C[] s)
323 {
324 	auto parser = JsonDeserializer!C(s);
325 //	mixin(exceptionContext(q{format("Error at position %d", parser.p)}));
326 	return parser.deserialize!T();
327 }
328 
329 // ***************************************************************************
330 
331 struct Escapes
332 {
333 	string[256] chars;
334 	bool[256] escaped;
335 
336 	void populate()
337 	{
338 		import std.string;
339 
340 		escaped[] = true;
341 		foreach (c; 0..256)
342 			if (c=='\\')
343 				chars[c] = `\\`;
344 			else
345 			if (c=='\"')
346 				chars[c] = `\"`;
347 			else
348 			if (c=='\b')
349 				chars[c] = `\b`;
350 			else
351 			if (c=='\f')
352 				chars[c] = `\f`;
353 			else
354 			if (c=='\n')
355 				chars[c] = `\n`;
356 			else
357 			if (c=='\r')
358 				chars[c] = `\r`;
359 			else
360 			if (c=='\t')
361 				chars[c] = `\t`;
362 			else
363 			if (c<'\x20' || c == '\x7F' || c=='<' || c=='>' || c=='&')
364 				chars[c] = format(`\u%04x`, c);
365 			else
366 				chars[c] = [cast(char)c],
367 				escaped[c] = false;
368 	}
369 }
370 immutable Escapes escapes = { Escapes escapes; escapes.populate(); return escapes; }();
371 
372 /// Serialization target which writes a JSON stream.
373 struct JsonWriter
374 {
375 	static template Impl(alias source, alias output)
376 	{
377 		alias Parent = RefType!(thisOf!source);
378 
379 		static template Sink(alias output)
380 		{
381 			void handleNumeric(C)(C[] str)
382 			{
383 				output.put(str);
384 			}
385 
386 			void handleString(C)(C[] str)
387 			{
388 				output.put('"');
389 				handleStringFragment(str);
390 				output.put('"');
391 			}
392 
393 			void handleNull()
394 			{
395 				output.put("null");
396 			}
397 
398 			void handleBoolean(bool v)
399 			{
400 				output.put(v ? "true" : "false");
401 			}
402 
403 			void handleStringFragment(C)(C[] s)
404 			{
405 				auto start = s.ptr, p = start, end = start+s.length;
406 
407 				while (p < end)
408 				{
409 					auto c = *p++;
410 					if (escapes.escaped[c])
411 						output.put(start[0..p-start-1], escapes.chars[c]),
412 						start = p;
413 				}
414 
415 				output.put(start[0..p-start]);
416 			}
417 
418 			void handleArray(Reader)(Reader reader)
419 			{
420 				needComma = false;
421 				output.put('[');
422 				reader(scopeProxy!arraySink);
423 				output.put(']');
424 			}
425 
426 			void handleStringFragments(Reader)(Reader reader)
427 			{
428 				output.put('"');
429 				reader(scopeProxy!sink);
430 				output.put('"');
431 			}
432 
433 			void handleObject(Reader)(Reader reader)
434 			{
435 				needComma = false;
436 				output.put('{');
437 				reader(scopeProxy!objectSink);
438 				output.put('}');
439 			}
440 		}
441 		alias sink = Sink!output;
442 
443 		static bool needComma; // Yes, a global
444 
445 		static template ArraySink(alias output)
446 		{
447 			alias handleObject = opDispatch!"handleObject";
448 			alias handleStringFragments = opDispatch!"handleStringFragments";
449 			alias handleStringFragment = opDispatch!"handleStringFragment";
450 			alias handleBoolean = opDispatch!"handleBoolean";
451 			alias handleNull = opDispatch!"handleNull";
452 			alias handleNumeric = opDispatch!"handleNumeric";
453 			alias handleString = opDispatch!"handleString";
454 			alias handleArray = opDispatch!"handleArray";
455 
456 			template opDispatch(string name)
457 			{
458 				void opDispatch(Args...)(auto ref Args args)
459 				{
460 					if (needComma)
461 					{
462 						output.put(',');
463 						needComma = false;
464 					}
465 
466 					mixin("Sink!output." ~ name ~ "(args);");
467 					needComma = true;
468 				}
469 			}
470 		}
471 		alias arraySink = ArraySink!output;
472 
473 		static template ObjectSink(alias output)
474 		{
475 			void handleField(NameReader, ValueReader)(NameReader nameReader, ValueReader valueReader)
476 			{
477 				if (needComma)
478 				{
479 					output.put(',');
480 					needComma = false;
481 				}
482 
483 				nameReader (scopeProxy!sink);
484 				output.put(':');
485 				valueReader(scopeProxy!sink);
486 
487 				needComma = true;
488 			}
489 		}
490 		alias objectSink = ObjectSink!output;
491 
492 		auto makeSink()
493 		{
494 			auto s = scopeProxy!sink;
495 			return s;
496 		}
497 	}
498 }
499 
500 struct JsonSerializer(C)
501 {
502 	static assert(is(C == char), "TODO");
503 	import ae.utils.textout;
504 
505 	void[0] anchor;
506 	alias Serializer.Impl!anchor serializer;
507 
508 	StringBuilder sb;
509 	alias JsonWriter.Impl!(serializer, sb) writer;
510 
511 	void serialize(T)(auto ref T v)
512 	{
513 		auto sink = writer.makeSink();
514 		serializer.read(sink, v);
515 	}
516 }
517 
518 S toJson(S = string, T)(auto ref T v)
519 {
520 	JsonSerializer!(Unqual!(typeof(S.init[0]))) s;
521 	s.serialize(v);
522 	return s.sb.get();
523 }
524 
525 // ***************************************************************************
526 
527 unittest
528 {
529 	static string jsonToJson(string s)
530 	{
531 		static struct Test
532 		{
533 			alias C = char; // TODO
534 			import ae.utils.textout;
535 
536 			JsonParser!C.Data jsonData;
537 			alias JsonParser!C.Impl!jsonData jsonImpl;
538 			void[0] anchor;
539 
540 			StringBuilder sb;
541 			alias JsonWriter.Impl!(jsonImpl, sb) writer;
542 
543 			string run(string s)
544 			{
545 				jsonData.s = s.dup;
546 				auto sink = writer.makeSink();
547 				jsonImpl.read(sink);
548 				return sb.get();
549 			}
550 		}
551 
552 		Test test;
553 		return test.run(s);
554 	}
555 
556 	static T objToObj(T)(T v)
557 	{
558 		static struct Test
559 		{
560 			void[0] anchor;
561 			alias Serializer.Impl!anchor serializer;
562 			alias Deserializer!anchor deserializer;
563 
564 			T run(T v)
565 			{
566 				T r;
567 				auto sink = deserializer.makeSink(&r);
568 				serializer.read(sink, v);
569 				return r;
570 			}
571 		}
572 
573 		Test test;
574 		return test.run(v);
575 	}
576 
577 	static void check(I, O)(I input, O output, O correct, string inputDescription, string outputDescription)
578 	{
579 		assert(output == correct, "%s => %s:\nValue:    %s\nResult:   %s\nExpected: %s".format(inputDescription, outputDescription, input, output, correct));
580 	}
581 
582 	static void testSerialize(T, S)(T v, S s) { check(v, toJson   !S(v), s, T.stringof, "JSON"); }
583 	static void testParse    (T, S)(T v, S s) { check(s, jsonParse!T(s), v, "JSON", T.stringof); }
584 	static void testJson     (T, S)(T v, S s) { check(s, jsonToJson (s), s, "JSON", "JSON"); }
585 	static void testObj      (T, S)(T v, S s) { check(v, objToObj   (v), v, T.stringof, T.stringof); }
586 
587 	static void testAll(T, S)(T v, S s)
588 	{
589 		testSerialize(v, s);
590 		testParse    (v, s);
591 		testJson     (v, s);
592 		testObj      (v, s);
593 	}
594 
595 	testAll  (`Hello "world"`   , `"Hello \"world\""` );
596 	testAll  (["Hello", "world"], `["Hello","world"]` );
597 	testAll  ([true, false]     , `[true,false]`      );
598 	testAll  ([4, 2]            , `[4,2]`             );
599 	testAll  (["a":1, "b":2]    , `{"a":1,"b":2}`     );
600 	struct S { int i; string s; }
601 	testAll  (S(42, "foo")      , `{"i":42,"s":"foo"}`);
602 //	testAll  (`"test"`w         , "test"w             );
603 	testParse(S(0, null)        , `{"s":null}`        );
604 
605 	testAll  (4                 , `4`                 );
606 	testAll  (4.5               , `4.5`               );
607 
608 	testAll  ((int[]).init      ,  `null`             );
609 
610 	struct All { All[] arr; All[string] aa; }
611 
612 	testAll  ((All[]).init      ,  `null`             );
613 
614 //	assert(toJson(tuple()) == ``);
615 //	assert(toJson(tuple(42)) == `42`);
616 //	assert(toJson(tuple(42, "banana")) == `[42,"banana"]`);
617 }