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 }