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