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 }