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.sd.json.encode;
15 
16 import std.traits;
17 
18 import ae.utils.appender : putEx;
19 
20 private struct Escapes
21 {
22 	string[256] chars;
23 	bool[256] escaped;
24 
25 	void populate()
26 	{
27 		import std.format : format;
28 
29 		escaped[] = true;
30 		foreach (c; 0..256)
31 			if (c=='\\')
32 				chars[c] = `\\`;
33 			else
34 			if (c=='\"')
35 				chars[c] = `\"`;
36 			else
37 			if (c=='\b')
38 				chars[c] = `\b`;
39 			else
40 			if (c=='\f')
41 				chars[c] = `\f`;
42 			else
43 			if (c=='\n')
44 				chars[c] = `\n`;
45 			else
46 			if (c=='\r')
47 				chars[c] = `\r`;
48 			else
49 			if (c=='\t')
50 				chars[c] = `\t`;
51 			else
52 			if (c<'\x20' || c == '\x7F' || c=='<' || c=='>' || c=='&')
53 				chars[c] = format(`\u%04x`, c);
54 			else
55 				chars[c] = [cast(char)c],
56 				escaped[c] = false;
57 	}
58 }
59 immutable Escapes escapes = { Escapes escapes; escapes.populate(); return escapes; }();
60 
61 /// Serialization target which writes a JSON stream.
62 /// `Output` should be a cheaply copyable reference to an output sink.
63 struct JSONEncoder(Output)
64 {
65 	Output output;
66 
67 	enum canPut(C) = is(typeof({ C c = void; output.put(c); }));
68 
69 	/// Accepts characters and emits them verbatim (array context)
70 	private static struct VerbatimHandler
71 	{
72 		Output output;
73 
74 		enum canHandleSlice(C) = canPut!(C[]);
75 		void handleSlice(C)(C[] slice)
76 		if (canHandleSlice!C)
77 		{
78 			output.put(slice);
79 		}
80 
81 		struct ElementHandler
82 		{
83 			Output output;
84 
85 			enum bool canHandleValue(C) = canPut!C;
86 			void handleValue(C)(C value)
87 			if (canHandleValue!C)
88 			{
89 				output.put(value);
90 			}
91 		}
92 
93 		void handleElement(Reader)(Reader reader)
94 		{
95 			reader.read(ElementHandler(output));
96 		}
97 
98 		void handleEnd() {}
99 	}
100 
101 	enum canHandleValue(T) = is(T == bool) || is(T == typeof(null));
102 	void handleValue(T)(T value)
103 	if (canHandleValue!T)
104 	{
105 		static if (is(T == bool))
106 			output.put(value ? "true" : "false");
107 		else
108 		static if (is(T == typeof(null)))
109 			output.put("null");
110 		else
111 			static assert(false);
112 	}
113 
114 	// Type hinting is used to signal strings (arrays of char)
115 
116 	enum canHandleTypeHint(T) = isSomeString!T;
117 	void handleTypeHint(T, Reader)(Reader reader)
118 	if (isSomeString!T)
119 	{
120 		static void escapedPut(C)(Output output, C[] s)
121 		{
122 			auto start = s.ptr, p = start, end = start+s.length;
123 
124 			while (p < end)
125 			{
126 				auto c = *p++;
127 				if (c < escapes.escaped.length && escapes.escaped[c])
128 					output.putEx(start[0..p-start-1], escapes.chars[c]),
129 					start = p;
130 			}
131 
132 			output.put(start[0..p-start]);
133 		}
134 
135 		static struct StringContentHandler
136 		{
137 			Output output;
138 
139 			enum canHandleSlice(C) = canPut!(C[]);
140 			void handleSlice(C)(C[] slice)
141 			if (canHandleSlice!C)
142 			{
143 				escapedPut(output, slice);
144 			}
145 
146 			struct ElementHandler
147 			{
148 				Output output;
149 
150 				enum bool canHandleValue(C) = canPut!C;
151 				void handleValue(C)(C value)
152 				if (canHandleValue!C)
153 				{
154 					escapedPut(output, (&value)[0 .. 1]);
155 				}
156 			}
157 
158 			void handleElement(Reader)(Reader reader)
159 			{
160 				reader.read(ElementHandler(output));
161 			}
162 
163 			void handleEnd() {}
164 		}
165 
166 		static struct StringHandler
167 		{
168 			Output output;
169 
170 			void handleArray(Reader)(Reader reader)
171 			{
172 				output.put('"');
173 				reader.read(StringContentHandler(output));
174 				output.put('"');
175 			}
176 		}
177 
178 		reader.read(StringHandler(output));
179 	}
180 
181 	void handleNumeric(Reader)(Reader reader)
182 	{
183 		reader.read(VerbatimHandler(output));
184 	}
185 
186 	void handleArray(Reader)(Reader reader)
187 	{
188 		static struct PairHandler
189 		{
190 			Output output;
191 
192 			void handlePairKey(Reader)(Reader reader)
193 			{
194 				reader.read(JSONEncoder(output));
195 				output.put(':');
196 			}
197 
198 			void handlePairValue(Reader)(Reader reader)
199 			{
200 				reader.read(JSONEncoder(output));
201 			}
202 
203 			void handleEnd() {}
204 		}
205 
206 		static struct ArrayHandler
207 		{
208 			Output output;
209 			bool first = true;
210 
211 			void handleElement(Reader)(Reader reader)
212 			{
213 				if (first)
214 					first = false;
215 				else
216 					output.put(',');
217 				reader.read(JSONEncoder(output));
218 			}
219 
220 			void handleEnd() {}
221 		}
222 
223 		output.put('[');
224 		reader.read(ArrayHandler(output));
225 		output.put(']');
226 	}
227 
228 	void handleMap(Reader)(Reader reader)
229 	{
230 		static struct PairHandler
231 		{
232 			Output output;
233 
234 			void handlePairKey(Reader)(Reader reader)
235 			{
236 				reader.read(JSONEncoder(output));
237 				output.put(':');
238 			}
239 
240 			void handlePairValue(Reader)(Reader reader)
241 			{
242 				reader.read(JSONEncoder(output));
243 			}
244 
245 			void handleEnd() {}
246 		}
247 
248 		static struct MapHandler
249 		{
250 			Output output;
251 			bool first = true;
252 
253 			void handlePair(Reader)(Reader reader)
254 			{
255 				if (first)
256 					first = false;
257 				else
258 					output.put(',');
259 				reader.read(PairHandler(output));
260 			}
261 
262 			void handleEnd() {}
263 		}
264 
265 		output.put('{');
266 		reader.read(MapHandler(output));
267 		output.put('}');
268 	}
269 }
270 
271 S toJSON(S = string, Source)(Source source)
272 {
273 	import std.array : Appender;
274 	alias Sink = Appender!S;
275 	Sink sink;
276 	JSONEncoder!(Sink*) encoder;
277 	encoder.output = &sink;
278 	source.read(&encoder);
279 	return sink.data;
280 }
281 
282 unittest
283 {
284 	import ae.utils.sd.json.decoder : decodeJSON;
285 
286 	void test(string s)
287 	{
288 		auto r = s.decodeJSON().toJSON();
289 		assert(r == s, s ~ " != " ~ r);
290 	}
291 
292 	test(`42`);
293 	test(`"Hello"`);
294 	test(`{}`);
295 	test(`{"str":"Hello","i":42}`);
296 	test(`"Hello\nworld"`);
297 	test(`[]`);
298 	test(`[true,false,null]`);
299 }
300 
301 unittest
302 {
303 	// static string jsonToJson(string s)
304 	// {
305 	// 	static struct Test
306 	// 	{
307 	// 		alias C = char; // TODO
308 	// 		import ae.utils.textout;
309 
310 	// 		JsonParser!C.Data jsonData;
311 	// 		alias JsonParser!C.Impl!jsonData jsonImpl;
312 	// 		void[0] anchor;
313 
314 	// 		StringBuilder sb;
315 	// 		alias JsonWriter.Impl!(jsonImpl, sb) writer;
316 
317 	// 		string run(string s)
318 	// 		{
319 	// 			jsonData.s = s.dup;
320 	// 			auto sink = writer.makeSink();
321 	// 			jsonImpl.read(sink);
322 	// 			return sb.get();
323 	// 		}
324 	// 	}
325 
326 	// 	Test test;
327 	// 	return test.run(s);
328 	// }
329 
330 	// static T objToObj(T)(T v)
331 	// {
332 	// 	static struct Test
333 	// 	{
334 	// 		void[0] anchor;
335 	// 		alias Serializer.Impl!anchor serializer;
336 	// 		alias Deserializer!anchor deserializer;
337 
338 	// 		T run(T v)
339 	// 		{
340 	// 			T r;
341 	// 			auto sink = deserializer.makeSink(&r);
342 	// 			serializer.read(sink, v);
343 	// 			return r;
344 	// 		}
345 	// 	}
346 
347 	// 	Test test;
348 	// 	return test.run(v);
349 	// }
350 
351 	static void check(I, O)(I input, O output, O correct, string inputDescription, string outputDescription)
352 	{
353 		import std.format : format;
354 		assert(output == correct,
355 			("%s => %s:\n" ~
356 			"Value:    %s\n" ~
357 			"Result:   %s\n" ~
358 			"Expected: %s")
359 			.format(
360 				inputDescription,
361 				outputDescription,
362 				input,
363 				output,
364 				correct,
365 			)
366 		);
367 	}
368 
369 	import ae.utils.sd.json.decoder : decodeJSON;
370 	import ae.utils.sd.serialization.serializer : serialize;
371 	import ae.utils.sd.serialization.deserializer : deserializeNew;
372 
373 	static void testJson     (T, S)(T v, S s) { check(s, s.decodeJSON.toJSON!S()        , s, "JSON"    , "JSON"    ); }
374 	static void testParse    (T, S)(T v, S s) { check(s, s.decodeJSON.deserializeNew!T(), v, "JSON"    , T.stringof); }
375 	static void testSerialize(T, S)(T v, S s) { check(v, v.serialize .toJSON!S()        , s, T.stringof, "JSON"    ); }
376 	static void testObj      (T, S)(T v, S s) { check(v, v.serialize .deserializeNew!T(), v, T.stringof, T.stringof); }
377 
378 	static void testAll(T, S)(T v, S s)
379 	{
380 		testJson     (v, s);
381 		testParse    (v, s);
382 		testSerialize(v, s);
383 		testObj      (v, s);
384 	}
385 
386 	testAll  (`Hello "world"`   , `"Hello \"world\""` );
387 	testAll  (["Hello", "world"], `["Hello","world"]` );
388 	testAll  ([true, false]     , `[true,false]`      );
389 	testAll  ([4, 2]            , `[4,2]`             );
390 	testAll  (["a":1, "b":2]    , `{"b":2,"a":1}`     );
391 	struct S { int i; string s; }
392 	testAll  (S(42, "foo")      , `{"i":42,"s":"foo"}`);
393 //	testAll  (`"test"`w         , "test"w             );
394 	testParse(S(0, null)        , `{"s":null}`        );
395 
396 	testAll  (4                 , `4`                 );
397 	testAll  (4.5               , `4.5`               );
398 	testAll  (4.1               , `4.1`               );
399 
400 	testAll  ((int[]).init      ,  `null`             );
401 
402 	struct RA { RA[] arr; }
403 	testAll  ((RA[]).init      ,  `null`             );
404 
405 	struct RM { RM[string] aa; }
406 	testAll  ((RM).init        ,  `{"aa":null}`      ); // https://issues.dlang.org/show_bug.cgi?id=21419
407 	testAll  ((RM[]).init      ,  `null`             );
408 }