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 }