1 /*************************************************************************************************** 2 * 3 * A fast JSON parser implementing RFC 7159. 4 * 5 * The most prominent change compared to the initial revision is the allowance of all data types as 6 * root values, not just objects and arrays. 7 * 8 * Usage_Hints: 9 * $(UL 10 * $(LI This parser only supports UTF-8 without BOM.) 11 * $(LI When a JSON object has duplicate keys, the last one in the set will determine the value 12 * of associative-array entries or struct fields.) 13 * $(LI `BigInt` and large number parsing are not implemented currently, but all integral types 14 * as well as minimal exact representations of many `double` values are supported.) 15 * ) 16 * 17 * Authors: 18 * $(LINK2 mailto:Marco.Leise@gmx.de, Marco Leise) 19 * 20 * Copyright: 21 * © 2017-2023 $(LINK2 mailto:Marco.Leise@gmx.de, Marco Leise), $(LINK2 mailto:etienne@cimons.com, Etienne Cimon) 22 * 23 * License: 24 * $(LINK2 https://mit-license.org/, The MIT License (MIT)) 25 * 26 **************************************************************************************************/ 27 module fast.json; 28 29 //import core.stdc.string; 30 import std.range; 31 import std.traits; 32 import memutils.ct : isTuple; 33 34 import fast.buffer; 35 import fast.cstring; 36 import fast.internal.sysdef; 37 import fast.parsing; 38 import fast.format; 39 import fast.internal.helpers : logError, logInfo; 40 41 nothrow: 42 @safe: 43 44 /******************************************************************************* 45 * 46 * Loads a JSON string and validates the used parts. This includes a UTF-8 47 * validation on strings. 48 * 49 * Params: 50 * text = The string to load. 51 * 52 * Returns: 53 * A `Json` struct. 54 * 55 **************************************/ 56 auto parseJSON(ALLOC, uint vl = trustedSource, T: 57 const(char)[])(T text) nothrow 58 { 59 return Json!(ALLOC, vl, false)(text); 60 } 61 62 /******************************************************************************* 63 * 64 * Load a JSON string that is considered 100% correct. No checks will be 65 * performed, not even if you try to read a number as a string. 66 * 67 * Params: 68 * text = The string to load. 69 * 70 * Returns: 71 * A `Json` struct. 72 * 73 **************************************/ 74 auto parseTrustedJSON(ALLOC, T: 75 const(char)[])(T text) nothrow 76 { 77 return Json!(ALLOC, trustedSource, false)(text); 78 } 79 80 /******************************************************************************* 81 * 82 * Validates a JSON string. 83 * 84 * Params: 85 * text = The string to load. 86 * 87 * Returns: 88 * true if verification failed 89 * 90 **************************************/ 91 bool validateJSON(ALLOC, T: 92 const(char)[])(T text) 93 { 94 auto json = Json!(ALLOC, validateAll, true)(text); 95 json.skipValue(); 96 return json.hasError(); 97 } 98 99 /// JSON data types returned by `peek`. 100 enum DataType : ubyte 101 { 102 string, 103 number, 104 object, 105 array, 106 boolean, 107 null_ 108 } 109 110 /// Validation strength of JSON parser 111 enum 112 { 113 trustedSource, /// Assume 100% correct JSON and speed up parsing. 114 validateUsed, /// Ignore errors in skipped portions. 115 validateAll, /// Do a complete validation of the JSON data. 116 } 117 118 /// A UDA used to remap enum members or struct field names to JSON strings. 119 struct JsonMapping 120 { 121 string[string] map; 122 } 123 /// A UDA for including fields in serialization 124 struct serialize 125 { 126 } 127 128 /// JSON parser state returned by the `state` property. 129 struct JsonParserState 130 { 131 const(char)* text; 132 size_t textlen; 133 size_t nesting; 134 } 135 136 /******************************************************************************* 137 * 138 * This is a forward JSON parser for picking off items of interest on the go. 139 * It neither produces a node structure, nor does it produce events. Instead you 140 * can peek at the value type that lies ahead and/or directly consume a JSON 141 * value from the parser. Objects and arrays can be iterated over via `foreach`, 142 * while you can also directly ask for one or multiple keys of an object. 143 * 144 * Prams: 145 * vl = Validation level. Any of `trustedSource`, `validateUsed` or 146 * `validateAll`. 147 * validateUtf8 = If validation is enabled, this also checks UTF-8 encoding 148 * of JSON strings. 149 * 150 **************************************/ 151 struct Json(ALLOC, uint vl = validateUsed, bool validateUtf8 = vl > trustedSource) 152 if ((vl > trustedSource || !validateUtf8)) 153 { 154 nothrow: 155 @trusted: 156 private: 157 158 enum isTrusted = vl == trustedSource; 159 enum skipAllInter = false; 160 enum isValidating = vl >= validateUsed; 161 enum isValidateAll = vl == validateAll; 162 163 const(char*) m_start = void; 164 const(char)* m_text = void; 165 size_t m_text_len = 0; 166 size_t m_nesting = 0; 167 ALLOC m_alloc; 168 char[] m_buffer; 169 bool m_isString = false; 170 bool m_error = false; 171 172 public: 173 @property bool hasError() 174 { 175 return m_error; 176 } 177 178 @disable this(); 179 @disable this(this); 180 181 /******************************************************************************* 182 * 183 * Constructor taking a `string` for fast slicing. 184 * 185 * JSON strings without escape sequences can be returned as slices. 186 * 187 * Params: 188 * text = The JSON text to parse. 189 * simdPrep = Set this to `No.simdPrep` to indicate that `text` is already 190 * suffixed by 16 zero bytes as required for SIMD processing. 191 * 192 **************************************/ 193 nothrow 194 this(string text, Flag!"simdPrep" simdPrep = Yes.simdPrep) 195 { 196 //import core.memory; 197 m_isString = true; 198 this(cast(const(char)[]) text, simdPrep); 199 } 200 201 ~this() 202 { 203 if (m_buffer.length > 0) 204 m_alloc.deallocate(m_buffer); 205 } 206 207 /******************************************************************************* 208 * 209 * Constructor taking a `const char[]`. 210 * 211 * JSON strings allocate on the GC heap when returned. 212 * 213 * Params: 214 * text = The JSON text to parse. 215 * simdPrep = Set this to `No.simdPrep` to indicate that `text` is already 216 * suffixed by 16 zero bytes as required for SIMD processing. 217 * 218 **************************************/ 219 pure nothrow 220 this(const(char)[] text, Flag!"simdPrep" simdPrep = Yes.simdPrep) 221 { 222 /*if (simdPrep) 223 { 224 // We need to append 16 zero bytes for SSE to work, and if that reallocates the char[] 225 // we can declare it unique/immutable and don't need to allocate when returning JSON strings. 226 auto oldPtr = text.ptr; 227 text ~= "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; 228 m_isString |= oldPtr !is text.ptr; 229 }*/ 230 m_start = m_text = text.ptr; 231 m_text_len = text.length; 232 skipWhitespace!false(); 233 } 234 235 /+ 236 ╔══════════════════════════════════════════════════════════════════════════════ 237 ║ ⚑ String 238 ╚══════════════════════════════════════════════════════════════════════════════ 239 +/ 240 241 /******************************************************************************* 242 * 243 * Reads a string off the JSON text. 244 * 245 * Params: 246 * allowNull = Allow `null` as a valid option for the string. 247 * 248 * Returns: 249 * A GC managed string. 250 * 251 **************************************/ 252 string read(T)(bool allowNull = true) if (is(T == string)) 253 { 254 if (!allowNull || peek == DataType..string) 255 { 256 auto borrowed = borrowString(); 257 return cast(string) borrowed; 258 } 259 return readNull(); 260 } 261 262 /******************************************************************************* 263 * 264 * Reads an enumeration off the JSON text. 265 * 266 **************************************/ 267 T read(T)() if (is(T == enum)) 268 { 269 enum mapping = buildRemapTable!T; 270 auto oldPos = m_text; 271 auto text = borrowString(); 272 foreach (m; mapping) 273 if (text.length == m.json.length && memcmp(text.ptr, m.json.ptr, m.json.length) == 0) 274 return m.d; 275 m_text = oldPos; 276 static if (isValidating) 277 { 278 handleError(format!"Could not find enum member `%s` in `%s`"(text, T.stringof)); 279 } 280 assert(0); 281 } 282 283 /******************************************************************************* 284 * 285 * Reads a string off the JSON text with limited lifetime. 286 * 287 * The reference to this slice is not guaranteed to be valid after the JSON 288 * parser has been destroyed or another object key or string value has been 289 * parsed. So make a copy before you continue parsing. 290 * 291 * Returns: 292 * If the string had no escape sequences in it, the returned array is a 293 * slice of the JSON text buffer, otherwise temporary copy. 294 * 295 **************************************/ 296 const(char)[] borrowString() 297 { 298 expect('"', "at start of string"); 299 auto escFreeStart = m_text; 300 301 if (scanString!validateUtf8()) 302 { 303 // Fast path here is to return a slice of the JSON if it doesn't contain escapes. 304 size_t length = m_text - escFreeStart; 305 skipOnePlusWhitespace!skipAllInter(); 306 return escFreeStart[0 .. length]; 307 } 308 else 309 { 310 // Otherwise we copy to a separate memory area managed by this parser instance. 311 size_t len = 0; 312 bool eos = false; 313 char* mem_base = m_buffer.ptr; 314 size_t mem_start_offset = m_buffer.length; 315 goto CopyToBuffer; 316 do 317 { 318 do 319 { 320 if (m_text - m_start > m_text_len + 1) 321 return null; 322 m_buffer = cast(char[]) m_alloc.reallocate(m_buffer, m_buffer.length + 4, false); 323 mem_base = m_buffer.ptr + mem_start_offset; 324 uint decoded = decodeEscape(mem_base + len); 325 len += decoded; 326 } 327 while (*m_text == '\\'); 328 329 escFreeStart = m_text; 330 eos = scanString!validateUtf8(); 331 CopyToBuffer: 332 size_t escFreeLength = m_text - escFreeStart; 333 334 if (escFreeLength > 0) 335 { 336 337 m_buffer = cast(char[]) m_alloc.reallocate(m_buffer, m_buffer.length + escFreeLength, false); 338 339 mem_base = m_buffer.ptr + mem_start_offset; 340 import ldc.intrinsics; 341 342 llvm_memcpy(mem_base + len, escFreeStart, escFreeLength); 343 len += escFreeLength; 344 345 } 346 } 347 while (!eos); 348 skipOnePlusWhitespace!skipAllInter(); 349 return mem_base[mem_start_offset .. mem_start_offset + len]; 350 } 351 } 352 353 private bool scanString(bool validate)() 354 { 355 static if (validate) 356 { 357 import core.bitop; 358 359 while (true) 360 { 361 // Stop for control-characters, \, " and anything non-ASCII. 362 m_text.seekToRanges!"\0\x1F\"\"\\\\\x7F\xFF"; 363 364 // Handle printable ASCII range 365 if (*m_text == '"') 366 return true; 367 if (*m_text == '\\') 368 return false; 369 370 // Anything else better be UTF-8 371 uint u = *cast(uint*) m_text; 372 version (LittleEndian) 373 u = bswap(u); 374 375 // Filter overlong ASCII and missing follow byte. 376 if ( 377 (u & 0b111_00000_11_000000_00000000_00000000) == 0b110_00000_10_000000_00000000_00000000 && 378 (u > 0b110_00001_10_111111_11111111_11111111)) 379 m_text += 2; 380 // Handle overlong representation, UTF-16 surrogate pairs and missing follow bytes. 381 else if ( 382 (u & 0b1111_0000_11_000000_11_000000_00000000) == 0b1110_0000_10_000000_10_000000_00000000 && 383 (u & 0b0000_1111_00_100000_00_000000_00000000) != 0b0000_1101_00_100000_00_000000_00000000 && 384 (u > 0b1110_0000_10_011111_10_111111_11111111)) 385 m_text += 3; 386 // Handle missing follow bytes, Handle overlong representation and out of valid range (max. 0x10FFFF) 387 else if ( 388 (u & 0b11111_000_11_000000_11_000000_11_000000) == 0b11110_000_10_000000_10_000000_10_000000 && 389 (u > 0b11110_000_10_001111_10_111111_10_111111) && ( 390 u < 0b11110_100_10_010000_10_000000_10_000000)) 391 m_text += 4; 392 // Handle invalid code units. 393 else if (*m_text < ' ' || *m_text == 0x7F) 394 expectNot("is a disallowed control character in strings"); 395 else if (*m_text >= 0x80 && *m_text <= 0xBF) 396 expectNot("is a UTF-8 follow byte and cannot start a sequence"); 397 else 398 expectNot("is not a valid UTF-8 sequence start"); 399 } 400 } 401 else 402 { 403 m_text.seekToAnyOf!("\\\"\0"); 404 return *m_text == '"'; 405 } 406 } 407 408 private bool stringCompareCallback(ref immutable(char)* key, ref const(char)* str) 409 { 410 do 411 { 412 auto key4 = cast(char[4]*) key; 413 char[4] buf = *key4; 414 uint bytes = decodeEscape(buf.ptr); 415 if (buf != *key4) 416 return false; 417 key += bytes; 418 } 419 while (str[0] == '\\'); 420 return true; 421 } 422 423 private static immutable escapes = { 424 char[256] result = '\0'; 425 result['"'] = '"'; 426 result['\\'] = '\\'; 427 result['/'] = '/'; 428 result['b'] = '\b'; 429 result['f'] = '\f'; 430 result['n'] = '\n'; 431 result['r'] = '\r'; 432 result['t'] = '\t'; 433 return result; 434 }(); 435 436 private void skipEscape() 437 { 438 static if (isValidateAll) 439 { 440 if (m_text[1] != 'u') 441 { 442 // Normal escape sequence. 2 bytes removed. 443 if (!escapes[*++m_text]) 444 expectNot("in escape sequence"); 445 m_text++; 446 } 447 else 448 { 449 // UTF-16 450 m_text += 2; 451 decodeUtf16HexToCodepoint(); 452 } 453 } 454 else 455 m_text += 2; 456 } 457 458 private uint decodeEscape(scope char* dst) 459 { 460 if (m_text[1] != 'u') 461 { 462 // Normal escape sequence. 2 bytes removed. 463 dst[0] = escapes[m_text[1]]; 464 static if (isValidating) 465 if (!dst[0]) 466 handleError("Invalid escape sequence"); 467 m_text += 2; 468 return 1; 469 } 470 else 471 { 472 // UTF-16 473 m_text += 2; 474 uint cp = decodeUtf16HexToCodepoint(); 475 476 if (cp >= 0xD800 && cp <= 0xDBFF) 477 { 478 dst[0] = cast(char)(0b11110_000 | cp >> 18); 479 dst[1] = cast(char)(0b10_000000 | cp >> 12 & 0b00_111111); 480 dst[2] = cast(char)(0b10_000000 | cp >> 6 & 0b00_111111); 481 dst[3] = cast(char)(0b10_000000 | cp & 0b00_111111); 482 return 4; 483 } 484 else if (cp >= 0x800) 485 { 486 dst[0] = cast(char)(0b1110_0000 | cp >> 12); 487 dst[1] = cast(char)(0b10_000000 | cp >> 6 & 0b00_111111); 488 dst[2] = cast(char)(0b10_000000 | cp & 0b00_111111); 489 return 3; 490 } 491 else if (cp >= 0x80) 492 { 493 dst[0] = cast(char)(0b110_00000 | cp >> 6); 494 dst[1] = cast(char)(0b10_000000 | cp & 0b00_111111); 495 return 2; 496 } 497 else if (cp <= 0x20) 498 { 499 dst[0] = '?'; 500 return 1; 501 } 502 else 503 { 504 dst[0] = cast(char)(cp); 505 return 1; 506 } 507 } 508 } 509 510 private dchar decodeUtf16HexToCodepoint() 511 { 512 import fast.internal.helpers; 513 514 uint cp, hi; 515 foreach (i; staticIota!(0, 2)) 516 { 517 static if (isValidating) 518 { 519 if (auto badByte = hexDecode4(m_text, cp)) 520 { 521 m_text = badByte; 522 expectNot("is not a hex digit"); 523 } 524 } 525 else 526 { 527 cp = hexDecode4(m_text); 528 } 529 530 static if (i == 0) 531 { 532 // Is this a high surrogate (followed by a low surrogate) or not ? 533 if (cp < 0xD800 || cp > 0xDBFF) 534 break; 535 hi = cp - 0xD800 + 0x40 << 10; 536 } 537 else static if (i == 1) 538 { 539 static if (isValidating) 540 { 541 if (cp < 0xDC00 || cp > 0xDFFF) 542 handleError("The UTF-16 escape produced an invalid code point."); 543 cp -= 0xDC00; 544 } 545 cp |= hi; 546 } 547 } 548 549 static if (isValidating) 550 if (cp > 0x10FFFF || cp >= 0xD800 && cp <= 0xDFFF) 551 handleError("The UTF-16 escape produced an invalid code point."); 552 553 return cp; 554 } 555 556 private void skipString(bool skipInter)() 557 { 558 m_text++; 559 skipRestOfString!skipInter(); 560 } 561 562 private void skipRestOfString(bool skipInter)() 563 { 564 while (!scanString!isValidateAll()) 565 skipEscape(); 566 skipOnePlusWhitespace!skipInter(); 567 } 568 569 /+ 570 ╔══════════════════════════════════════════════════════════════════════════════ 571 ║ ⚑ Number 572 ╚══════════════════════════════════════════════════════════════════════════════ 573 +/ 574 575 /******************************************************************************* 576 * 577 * Reads a number off the JSON text. 578 * 579 * If you ask for an unsigned value, no minus sign will be accepted in the JSON, 580 * otherwise all features of JSON numbers will be available. In particular large 581 * integers can be given in scientific notation. 582 * 583 * Params: 584 * N = Built-in numerical type that should be returned. 585 * 586 * Returns: 587 * The parsed number. 588 * 589 * Throws: 590 * JSONException, on invalid JSON or integer overflow. 591 * 592 **************************************/ 593 N read(N)() if (isNumeric!N && !is(N == enum)) 594 { 595 N n = void; 596 static if (isUnsigned!N) 597 enum NumberOptions opt = {}; 598 else 599 enum NumberOptions opt = {minus: true}; 600 if (parseNumber!opt(m_text, n)) 601 skipWhitespace!skipAllInter(); 602 else static if (isValidating) 603 { 604 handleError(format!"Could not convert JSON number to `%s`"(N.stringof)); 605 606 } 607 return n; 608 } 609 610 private string format(string fmt, ARGS...)(ARGS args) @trusted 611 { 612 import fast.format : formattedWrite, decCharsVal; 613 614 size_t size_estimate = fmt.length + 16; 615 foreach (arg; args) 616 { 617 static if (is(typeof(arg) : char[])) 618 { 619 size_estimate += arg.length; 620 } 621 else static if (isIntegral!(typeof(arg))) 622 { 623 size_estimate += decCharsVal(arg); 624 } 625 else static if (isFloatingPoint!(typeof(arg))) 626 size_estimate += decChars!(typeof(arg)); 627 } 628 m_buffer = cast(char[]) m_alloc.reallocate(m_buffer, m_buffer.length + size_estimate, false); 629 char* buf = m_buffer.ptr + m_buffer.length - size_estimate; 630 string ret = cast(string) formattedWrite!fmt(buf, args); 631 632 return ret; 633 } 634 635 private void skipNumber(bool skipInter)() 636 { 637 static if (isValidateAll) 638 { 639 if (*m_text == '-') 640 m_text++; 641 if (*m_text == '0') 642 m_text++; 643 else 644 trySkipDigits(); 645 if (*m_text == '.') 646 { 647 m_text++; 648 trySkipDigits(); 649 } 650 if ((*m_text | 0x20) == 'e') 651 { 652 m_text++; 653 if (*m_text == '+' || *m_text == '-') 654 m_text++; 655 trySkipDigits(); 656 } 657 skipWhitespace!false(); 658 } 659 else 660 { 661 m_text.skipCharRanges!"\t\n\r\r ++-.09EEee"; 662 static if (skipInter) 663 m_text.skipAllOf!"\t\n\r ,"; 664 } 665 } 666 667 static if (isValidateAll) 668 { 669 private void trySkipDigits() 670 { 671 if (*m_text - '0' > 9) 672 expectNot("in number literal"); 673 m_text.skipAllOf!"0123456789"; 674 } 675 } 676 677 /+ 678 ╔══════════════════════════════════════════════════════════════════════════════ 679 ║ ⚑ Object 680 ╚══════════════════════════════════════════════════════════════════════════════ 681 +/ 682 683 /******************************************************************************* 684 * 685 * Reads a plain old data struct off the JSON text. 686 * 687 * Params: 688 * T = Type of struct that should be returned. 689 * 690 * Returns: 691 * A struct of type `T`. 692 * 693 **************************************/ 694 T read(T)() if (is(T == struct) && __traits(isPOD, T)) 695 { 696 nest('{', "on start of object"); 697 698 T t; 699 if (*m_text != '}') 700 while (true) 701 { 702 auto key = borrowString(); 703 static if (!skipAllInter) 704 { 705 expect(':', "between key and value"); 706 if (m_error) 707 return t; 708 skipWhitespace!false(); 709 } 710 import ldc.intrinsics; 711 712 enum mapping = buildRemapTable!T; 713 foreach (m; mapping) 714 { 715 if (key.length == m.json.length && memcmp(key.ptr, m.json.ptr, m.json.length) == 0) 716 { 717 mixin("alias keyT = typeof(T." ~ m.d ~ ");"); 718 mixin("t." ~ m.d ~ " = read!keyT;"); 719 goto Success; 720 } 721 } 722 skipValue(); 723 724 Success: 725 if (*m_text == '}') 726 break; 727 728 static if (!skipAllInter) 729 { 730 expect(',', "between key-value pairs"); 731 if (m_error) 732 return t; 733 skipWhitespace!false(); 734 } 735 } 736 737 unnest(); 738 return t; 739 } 740 741 /******************************************************************************* 742 * 743 * Reads a plain old data struct or `null` off the JSON text. 744 * 745 * Params: 746 * T = Type of struct pointer that should be returned. 747 * 748 * Returns: 749 * A pointer to a newly filled struct of type `T` on the GC heap. 750 * 751 **************************************/ 752 T read(T)() 753 if (is(PointerTarget!T == struct) && __traits(isPOD, PointerTarget!T)) 754 { 755 if (peek == DataType.null_) 756 return readNull(); 757 T tp = new PointerTarget!T; 758 *tp = read!(PointerTarget!T)(); 759 return tp; 760 } 761 762 /******************************************************************************* 763 * 764 * Reads an associative-array off a JSON text. 765 * 766 * The key type must be `string`, the value type can be any type otherwise 767 * supported by the parser. 768 * 769 * Params: 770 * T = The type of AA to return. 771 * 772 * Returns: 773 * A newly filled associative array. 774 * 775 **************************************/ 776 T read(T)() if (is(KeyType!T == string)) 777 { 778 T aa; 779 foreach (key; byKey) 780 aa[m_isString ? cast(immutable) key: key.idup] = read!(ValueType!T)(); 781 return aa; 782 } 783 784 /******************************************************************************* 785 * 786 * An alias to the `singleKey` method. Instead of `json.singleKey!"something"` 787 * you can write `json.something`. Read the notes on `singleKey`. 788 * 789 **************************************/ 790 alias opDispatch = singleKey; 791 792 /******************************************************************************* 793 * 794 * Skips all keys of an object except the first occurence with the given key 795 * name. 796 * 797 * Params: 798 * name = the key name of interest 799 * 800 * Returns: 801 * A temporary struct, a proxy to the parser, that will automatically seek to 802 * the end of the current JSON object on destruction. 803 * 804 * Throws: 805 * JSONException when the key is not found in the object or parsing errors 806 * occur. 807 * 808 * Note: 809 * Since this is an on the fly parser, you can only get one key from an 810 * object with this method. Use `keySwitch` or `foreach(key; json)` to get 811 * values from multiple keys. 812 * 813 * See_Also: 814 * keySwitch 815 * 816 **************************************/ 817 @property SingleKey singleKey(string name)() 818 { 819 nest('{', "on start of object"); 820 821 if (*m_text != '}') 822 while (true) 823 { 824 auto key = borrowString(); 825 static if (!skipAllInter) 826 { 827 expect(':', "between key and value"); 828 skipWhitespace!false(); 829 } 830 831 if (key.length == name.length && memcmp(key.ptr, name.ptr, name.length) == 0) 832 return SingleKey(this); 833 834 skipValueImpl!skipAllInter(); 835 836 if (*m_text == '}') 837 break; 838 839 static if (!skipAllInter) 840 { 841 expect(',', "between key-value pairs"); 842 skipWhitespace!false(); 843 } 844 } 845 846 unnest(); 847 static if (isValidating) 848 handleError("Key not found."); 849 assert(0); 850 } 851 852 /******************************************************************************* 853 * 854 * Selects from a set of given keys in an object and calls the corresponding 855 * delegate. The difference to `singleKey` when invoked with a single key is 856 * that `keySwitch` will not error out if the key is non-existent and may 857 * trigger the delegate multiple times, if the JSON object has duplicate keys. 858 * 859 * Params: 860 * Args = the names of the keys 861 * dlg = the delegates corresponding to the keys 862 * 863 * Throws: 864 * JSONException when the key is not found in the object or parsing errors 865 * occur. 866 * 867 **************************************/ 868 void keySwitch(Args...)(scope void delegate()[Args.length] dlg...) nothrow 869 { 870 nest('{', "on start of object"); 871 872 if (*m_text != '}') 873 while (true) 874 { 875 auto key = borrowString(); 876 static if (!skipAllInter) 877 { 878 expect(':', "between key and value"); 879 skipWhitespace!false(); 880 } 881 882 auto oldPos = m_text; 883 foreach (i, arg; Args) 884 { 885 if (key.length == arg.length && memcmp(key.ptr, arg.ptr, arg.length) == 0) 886 { 887 (cast(void delegate() nothrow) dlg[i])(); 888 goto Next; 889 } 890 } 891 skipValue(); 892 893 Next: 894 if (*m_text == '}') 895 break; 896 897 static if (!skipAllInter) 898 if (oldPos !is m_text) 899 { 900 expect(',', "after key-value pair"); 901 skipWhitespace!false(); 902 } 903 } 904 905 unnest(); 906 } 907 908 private int byKeyImpl(scope int delegate(ref const char[]) foreachBody) nothrow 909 { 910 nest('{', "at start of foreach over object"); 911 912 int result = 0; 913 if (*m_text != '}') 914 while (true) 915 { 916 auto key = borrowString(); 917 static if (!skipAllInter) 918 { 919 expect(':', "between key and value"); 920 skipWhitespace!false; 921 } 922 923 if (iterationGuts!"{}"(result, key, cast(int delegate(ref const char[]) nothrow) foreachBody, "after key-value pair")) 924 break; 925 } 926 927 unnest(); 928 return result; 929 } 930 931 /******************************************************************************* 932 * 933 * Iterate the keys of a JSON object with `foreach`. 934 * 935 * Notes: 936 * $(UL 937 * $(LI If you want to store the key, you need to duplicate it.) 938 * ) 939 * 940 * Example: 941 * --- 942 * uint id; 943 * foreach (key; json.byKey) 944 * if (key == "id") 945 * id = json.read!uint; 946 * --- 947 **************************************/ 948 @trusted @nogc pure nothrow 949 @property int delegate(scope int delegate(ref const char[]) nothrow) nothrow byKey() 950 { 951 return cast(int delegate(scope int delegate(ref const char[]) nothrow) nothrow)&byKeyImpl; 952 } 953 954 /+ 955 ╔══════════════════════════════════════════════════════════════════════════════ 956 ║ ⚑ Array handling 957 ╚══════════════════════════════════════════════════════════════════════════════ 958 +/ 959 960 /******************************************************************************* 961 * 962 * Reads a dynamic array off the JSON text. 963 * 964 **************************************/ 965 T read(T)() if (isDynamicArray!T && !isSomeString!T) 966 { 967 Appender!T app; 968 foreach (i; this) 969 app.put(read!(typeof(T.init[0]))); 970 return app.data; 971 } 972 973 /******************************************************************************* 974 * 975 * Reads a static array off the JSON text. 976 * 977 * When validation is enabled, it is an error if the JSON array has a different 978 * length lengths don't match up. Otherwise unset elements receive their initial 979 * value. 980 * 981 **************************************/ 982 T read(T)() if (isStaticArray!T) 983 { 984 T sa = void; 985 size_t cnt; 986 foreach (i; this) 987 { 988 if (i < T.length) 989 sa[i] = read!(typeof(T.init[0])); 990 cnt = i + 1; 991 } 992 static if (isValidating) 993 { 994 if (cnt != T.length) 995 { 996 handleError(format!"Static array size mismatch. Expected %d, got %d"(T.length, cnt)); 997 } 998 } 999 else 1000 { 1001 foreach (i; cnt .. T.length) 1002 sa[i] = T.init; 1003 } 1004 return sa; 1005 } 1006 1007 /******************************************************************************* 1008 * 1009 * Iterate over a JSON array via `foreach`. 1010 * 1011 **************************************/ 1012 int opApply(scope int delegate(const size_t) foreachBody) 1013 { 1014 nest('[', "at start of foreach over array"); 1015 1016 int result = 0; 1017 if (*m_text != ']') 1018 for (size_t idx = 0; true; idx++) 1019 if (iterationGuts!"[]"(result, idx, cast(int delegate(const size_t) nothrow) foreachBody, "after array element")) 1020 break; 1021 1022 unnest(); 1023 return result; 1024 } 1025 1026 /+ 1027 ╔══════════════════════════════════════════════════════════════════════════════ 1028 ║ ⚑ Boolean 1029 ╚══════════════════════════════════════════════════════════════════════════════ 1030 +/ 1031 1032 /******************************************************************************* 1033 * 1034 * Reads a boolean value off the JSON text. 1035 * 1036 **************************************/ 1037 bool read(T)() if (is(T == bool)) 1038 { 1039 return skipBoolean!(skipAllInter, isValidating)(); 1040 } 1041 1042 private bool skipBoolean(bool skipInter, bool validate = isValidateAll)() 1043 { 1044 auto isFalse = *m_text == 'f'; 1045 static if (validate) 1046 { 1047 if (*cast(char[4]*) m_text != "true" && *cast(char[4]*) m_text != "fals") 1048 handleError("`true` or `false` expected."); 1049 } 1050 m_text += isFalse ? 5 : 4; 1051 skipWhitespace!skipInter(); 1052 return !isFalse; 1053 } 1054 1055 /+ 1056 ╔══════════════════════════════════════════════════════════════════════════════ 1057 ║ ⚑ Null 1058 ╚══════════════════════════════════════════════════════════════════════════════ 1059 +/ 1060 1061 /******************************************************************************* 1062 * 1063 * Reads `null` off the JSON text. 1064 * 1065 **************************************/ 1066 typeof(null) readNull() 1067 { 1068 skipNull!(skipAllInter, isValidating)(); 1069 return null; 1070 } 1071 1072 private void skipNull(bool skipInter, bool validate = isValidateAll)() 1073 { 1074 static if (validate) 1075 if (*cast(const uint*) m_text != *cast(const uint*) "null".ptr) 1076 handleError("`null` expected."); 1077 m_text += 4; 1078 skipWhitespace!skipInter(); 1079 } 1080 1081 /+ 1082 ╔══════════════════════════════════════════════════════════════════════════════ 1083 ║ ⚑ Helpers and Error Handling 1084 ╚══════════════════════════════════════════════════════════════════════════════ 1085 +/ 1086 1087 /******************************************************************************* 1088 * 1089 * Skips the next JSON value if you are not interested. 1090 * 1091 **************************************/ 1092 void skipValue() 1093 { 1094 skipValueImpl!skipAllInter(); 1095 } 1096 1097 private void skipValueImpl(bool skipInter)() 1098 { 1099 with (DataType) switch (peek) 1100 { 1101 case string: 1102 skipString!skipInter(); 1103 break; 1104 case number: 1105 skipNumber!skipInter(); 1106 break; 1107 case object: 1108 static if (isValidateAll) 1109 { 1110 foreach (_; this.byKey) 1111 break; 1112 } 1113 else 1114 { 1115 m_text++; 1116 seekObjectEnd(); 1117 skipOnePlusWhitespace!skipInter(); 1118 } 1119 break; 1120 case array: 1121 static if (isValidateAll) 1122 { 1123 foreach (_; this) 1124 break; 1125 } 1126 else 1127 { 1128 m_text++; 1129 seekArrayEnd(); 1130 skipOnePlusWhitespace!skipInter(); 1131 } 1132 break; 1133 case boolean: 1134 skipBoolean!skipInter(); 1135 break; 1136 case null_: 1137 skipNull!skipInter(); 1138 break; 1139 default: 1140 1141 break; 1142 } 1143 } 1144 1145 /******************************************************************************* 1146 * 1147 * Returns the type of data that is up next in the JSON text. 1148 * 1149 **************************************/ 1150 @property DataType peek() 1151 { 1152 static immutable trans = { 1153 DataType[256] result = cast(DataType) ubyte.max; 1154 result['{'] = DataType.object; 1155 result['['] = DataType.array; 1156 result['-'] = DataType.number; 1157 foreach (i; '0' .. '9' + 1) 1158 result[i] = DataType.number; 1159 result['"'] = DataType..string; 1160 result['t'] = DataType.boolean; 1161 result['f'] = DataType.boolean; 1162 result['n'] = DataType.null_; 1163 return result; 1164 }(); 1165 1166 DataType vt = trans[*m_text]; 1167 static if (isValidating) 1168 if (vt == ubyte.max) 1169 expectNot("while peeking at next value type"); 1170 return vt; 1171 } 1172 1173 /******************************************************************************* 1174 * 1175 * Save or restore the parser's internal state. 1176 * 1177 * If you want to read only a certain object from the JSON, but exactly which 1178 * depends on the value of some key, this is where saving and restoring the 1179 * parser state helps. 1180 * 1181 * Before each candidate you save the parser state. Then you perform just the 1182 * minimal work to test if the candidate matches some criteria. If it does, 1183 * restore the parser state and read the elements in full. Of it doesn't, just 1184 * skip to the next. 1185 * 1186 **************************************/ 1187 @property const(JsonParserState) state() const 1188 { 1189 return JsonParserState(m_text, m_text_len, m_nesting); 1190 } 1191 1192 @property void state(const JsonParserState oldState) 1193 { 1194 m_text = oldState.text; 1195 m_text_len = oldState.textlen; 1196 m_nesting = oldState.nesting; 1197 } 1198 1199 private void nest(char c, string msg) 1200 { 1201 expect(c, msg); 1202 skipWhitespace!false(); 1203 m_nesting++; 1204 } 1205 1206 private void unnest() 1207 in 1208 { 1209 assert(m_nesting > 0); 1210 } 1211 body 1212 { 1213 if (--m_nesting == 0) 1214 { 1215 skipOnePlusWhitespace!false(); 1216 static if (isValidating) 1217 if (*m_text != '\0') // 1218 handleError("Expected end of JSON."); 1219 } 1220 else 1221 skipOnePlusWhitespace!skipAllInter(); 1222 } 1223 1224 private bool iterationGuts(char[2] braces, T, D)(ref int result, T idx, scope D dlg, 1225 string missingCommaMsg) 1226 { 1227 auto oldPos = m_text; 1228 static if (isValidateAll) 1229 { 1230 if (result) 1231 { 1232 skipValueImpl!(!isValidateAll)(); 1233 goto PastValue; 1234 } 1235 } 1236 result = dlg(idx); 1237 if (oldPos is m_text) 1238 skipValueImpl!false(); 1239 1240 PastValue: 1241 if (*m_text == braces[1]) 1242 return true; 1243 1244 static if (!isValidateAll) 1245 if (result) 1246 { 1247 seekAggregateEnd!braces(); 1248 return true; 1249 } 1250 1251 static if (!skipAllInter) 1252 if (oldPos !is m_text) 1253 { 1254 expect(',', missingCommaMsg); 1255 skipWhitespace!false(); 1256 } 1257 return false; 1258 } 1259 1260 static if (!isValidateAll) 1261 { 1262 private void seekObjectEnd() 1263 { 1264 seekAggregateEnd!"{}"(); 1265 } 1266 1267 private void seekArrayEnd() 1268 { 1269 seekAggregateEnd!"[]"(); 1270 } 1271 1272 private void seekAggregateEnd(immutable char[2] parenthesis)() 1273 { 1274 size_t nesting = 1; 1275 while (m_text - m_start < m_text_len + 1) 1276 { 1277 m_text.seekToAnyOf!(parenthesis ~ "\"\0"); 1278 final switch (*m_text) 1279 { 1280 case parenthesis[0]: 1281 m_text++; 1282 nesting++; 1283 break; 1284 case parenthesis[1]: 1285 if (--nesting == 0) 1286 return; 1287 m_text++; 1288 break; 1289 case '"': 1290 // Could skip ':' or ',' here by passing `true`, but we skip it above anyways. 1291 skipString!false(); 1292 break; 1293 case '\0': 1294 return; 1295 } 1296 } 1297 } 1298 } 1299 1300 /// This also increments the JSON read pointer. 1301 private void expect(char c, string msg) 1302 { 1303 static if (isValidating) 1304 if (*m_text != c) 1305 expectImpl(c, msg); 1306 m_text++; 1307 } 1308 1309 private void expectNot(char c, string msg) 1310 { 1311 static if (isValidating) 1312 if (*m_text == c) 1313 expectNot(msg); 1314 } 1315 1316 static if (isValidating) 1317 { 1318 @noinline 1319 private void expectNot(string msg) 1320 { 1321 string error_msg; 1322 if (isPrintable(*m_text)) 1323 error_msg = format!"Character '%s' %s."(*m_text, msg); 1324 else 1325 error_msg = format!"Byte %d %s."(*m_text, msg); 1326 handleError(error_msg); 1327 } 1328 1329 @noinline 1330 private void expectImpl(char c, string msg) 1331 { 1332 string error_msg; 1333 if (isPrintable(*m_text)) 1334 error_msg = format!"Expected '%s', but found '%s' %s."(c, *m_text, msg); 1335 else 1336 error_msg = format!"Expected '%s', but found byte %d %s."(c, *m_text, msg); 1337 handleError(error_msg); 1338 } 1339 1340 @noinline 1341 private void handleError(T)(T msg) 1342 { 1343 m_error = true; 1344 import fast.unicode; 1345 1346 size_t line; 1347 const(char)* p = m_start; 1348 const(char)* last = m_start; 1349 while (p < m_text) 1350 { 1351 last = p; 1352 p.skipToNextLine(); 1353 line++; 1354 } 1355 line += p is m_text; 1356 size_t column = last[0 .. m_text - last].countGraphemes() + 1; 1357 1358 logError(format!"%s line %d col %d"(msg, line, column)); 1359 //while (*m_text != '\0') m_text++; 1360 } 1361 } 1362 1363 @forceinline @nogc pure nothrow 1364 private void skipOnePlusWhitespace(bool skipInter)() 1365 { 1366 m_text++; 1367 skipWhitespace!skipInter(); 1368 } 1369 1370 @forceinline @nogc pure nothrow 1371 private void skipWhitespace(bool skipInter)() 1372 { 1373 static if (skipInter) 1374 m_text.skipAllOf!"\t\n\r ,:"; 1375 else 1376 m_text.skipAsciiWhitespace(); 1377 } 1378 1379 private static struct SingleKey 1380 { 1381 alias json this; 1382 1383 private Json* m_pjson; 1384 private const(char*) m_oldPos; 1385 1386 @safe @nogc pure nothrow 1387 @property ref Json json() 1388 { 1389 return *m_pjson; 1390 } 1391 1392 this(ref Json json) 1393 { 1394 m_pjson = &json; 1395 m_oldPos = json.m_text; 1396 } 1397 1398 ~this() 1399 { 1400 static if (isValidateAll) 1401 { 1402 if (*json.m_text != '}') 1403 { 1404 if (m_oldPos !is json.m_text) 1405 { 1406 json.expect(',', "after key-value pair"); 1407 json.skipWhitespace!false(); 1408 } 1409 while (true) 1410 { 1411 json.skipString!false(); 1412 json.expect(':', "between key and value"); 1413 json.skipWhitespace!false(); 1414 json.skipValueImpl!false(); 1415 1416 if (*json.m_text == '}') 1417 break; 1418 1419 json.expect(',', "after key-value pair"); 1420 json.skipWhitespace!false(); 1421 } 1422 } 1423 } 1424 else 1425 { 1426 json.seekObjectEnd(); 1427 } 1428 json.unnest(); 1429 } 1430 } 1431 1432 } 1433 1434 private template buildRemapTable(T) 1435 { 1436 import std.typetuple; 1437 import fast.internal.helpers; 1438 1439 static if (is(T == enum)) 1440 { 1441 struct Remap 1442 { 1443 T d; 1444 string json; 1445 } 1446 1447 enum members = EnumMembers!T; 1448 } 1449 else 1450 { 1451 struct Remap 1452 { 1453 string d; 1454 string json; 1455 } 1456 1457 enum members = FieldNameTuple!T; 1458 } 1459 enum mapping = getUDA!(T, JsonMapping).map; 1460 1461 template Impl(size_t a, size_t b) 1462 { 1463 static if (b - a > 1) 1464 { 1465 alias Impl = TypeTuple!(Impl!(a, (b + a) / 2), Impl!((b + a) / 2, b)); 1466 } 1467 else static if (b - a == 1) 1468 { 1469 static if (is(T == enum)) 1470 enum key = members[a].to!string; 1471 else 1472 alias key = members[a]; 1473 static if ((key in mapping) !is null) 1474 enum mapped = mapping[key]; 1475 else 1476 alias mapped = key; 1477 alias Impl = TypeTuple!(Remap(members[a], mapped)); 1478 } 1479 else 1480 alias Impl = TypeTuple!(); 1481 } 1482 1483 alias buildRemapTable = Impl!(0, members.length); 1484 } 1485 1486 size_t serializationLength(T)(T t) nothrow @trusted 1487 if (is(T == struct) && !hasMember!(T, "opSlice") && !hasMember!(T, "get") && !isTuple!T) 1488 { 1489 size_t len; 1490 import std.traits : hasUDA; 1491 1492 len++; // { 1493 static foreach (sym; T.tupleof) 1494 { 1495 { 1496 enum isPublic = __traits(getProtection, sym) == "public"; 1497 static assert(!hasUDA!(sym, serialize) || (isPublic && hasUDA!(sym, serialize)), "Protected field has @serialize UDA"); 1498 static if (isPublic && hasUDA!(sym, serialize)) 1499 { 1500 if (len > 1) 1501 len += 4; // ,"": 1502 else 1503 len += 3; // "": 1504 enum i = sym.stringof; 1505 len += i.length; 1506 1507 static if (isPointer!(typeof(sym))) 1508 len += serializationLength(*__traits(getMember, t, i)); 1509 else 1510 len += serializationLength(__traits(getMember, t, i)); 1511 1512 } 1513 } 1514 } 1515 len++; // } 1516 return len; 1517 } 1518 1519 // hashmap 1520 size_t serializationLength(T)(T t) nothrow @trusted 1521 if (is(T == struct) && hasMember!(T, "get") && hasMember!(T, "opApply") && !isTuple!T) 1522 { 1523 size_t len; 1524 len++; // { 1525 foreach (key, ref val; t) 1526 { 1527 if (len > 1) 1528 len++; // , 1529 1530 static if (isPointer!(typeof(key))) 1531 len += serializationLength(*key); 1532 else 1533 len += serializationLength(key); 1534 1535 len++; // : 1536 1537 static if (isPointer!(typeof(val))) 1538 len += serializationLength(*val); 1539 else 1540 len += serializationLength(val); 1541 1542 } 1543 1544 len++; // } 1545 return len; 1546 } 1547 1548 size_t serializationLength(T)(T t) nothrow @trusted 1549 if ((isArray!T || (is(T == struct) && hasMember!(T, "opSlice")) || isTuple!T) && !isSomeString!T) 1550 { 1551 size_t len; 1552 len++; // [ 1553 foreach (i, v; t) 1554 { 1555 if (i > 0) 1556 { 1557 len++; // , 1558 } 1559 static if (isPointer!(typeof(v))) 1560 len += serializationLength(*v); 1561 else 1562 len += serializationLength(v); 1563 } 1564 len++; // ] 1565 return len; 1566 } 1567 1568 private size_t serializationLength(T)(T t) nothrow @trusted 1569 if (isIntegral!T && !isFloatingPoint!T) 1570 { 1571 import fast.format : decCharsVal; 1572 1573 char[8] buf; 1574 char* bufptr = buf.ptr; 1575 auto ret = bufptr.formattedWrite!"%d"(decCharsVal(t)); 1576 1577 return decCharsVal(t); 1578 } 1579 1580 private bool hasEscape(char c) 1581 { 1582 if ((c <= 0x1F && c > 0xC) || (c > 0x1F && c != '"' && c != '\\')) 1583 return false; 1584 else if (c == '\t') 1585 return true; 1586 else if (c == '\b') 1587 return true; 1588 else if (c == '\n') 1589 return true; 1590 else if (c == '\r') 1591 return true; 1592 else if (c == '"') 1593 return true; 1594 else if (c == '\\') 1595 return true; 1596 else 1597 return false; // '?' 1598 } 1599 1600 private size_t serializationLength(T)(T t) nothrow @trusted if (isSomeString!T) 1601 { 1602 import fast.format : formattedWrite, indexOf; 1603 1604 size_t i; 1605 ptrdiff_t idx; 1606 T substr = t; 1607 do 1608 { 1609 idx = substr.indexOf("\"\t\r\n\\\b"); 1610 if (idx > -1) 1611 { 1612 i++; 1613 substr = substr[idx + 1 .. $]; 1614 } 1615 } 1616 while (idx > -1); 1617 return i + (cast(void[]) t).length + 2; // escapes + strlen + quotes 1618 } 1619 1620 private size_t serializationLength(T)(T t) nothrow @trusted if (isFloatingPoint!T) 1621 { 1622 import fast.format : decCharsVal; 1623 1624 return decCharsVal(t); // todo: optimize this 1625 } 1626 1627 private size_t serializationLength(T)(T t) nothrow @trusted if (isBoolean!T) 1628 { 1629 return t ? 4 : 5; // true : false 1630 } 1631 1632 char[] serializeJSON(T)(char[] buf, T t) nothrow @trusted 1633 if (is(T == struct) && !hasMember!(T, "opSlice") && !hasMember!(T, "get") && !isTuple!T) 1634 { 1635 import std.traits : hasUDA; 1636 1637 char* buf_start = buf.ptr; 1638 size_t offset; 1639 char[] written = formattedWrite!"{"(buf.ptr + offset); 1640 offset += written.length; 1641 static foreach (sym; T.tupleof) 1642 { 1643 { 1644 enum isPublic = __traits(getProtection, sym) == "public"; 1645 static assert(!hasUDA!(sym, serialize) || (isPublic && hasUDA!(sym, serialize)), "Protected field has @serialize UDA"); 1646 static if (isPublic && hasUDA!(sym, serialize)) 1647 { 1648 alias ChildType = typeof(sym); 1649 enum i = sym.stringof; 1650 if (offset > 1) 1651 written = formattedWrite!`,"%S":`(buf.ptr + offset, i); 1652 else 1653 written = formattedWrite!`"%S":`(buf.ptr + offset, i); 1654 1655 offset += written.length; 1656 static if (isPointer!(typeof(sym))) 1657 written = serializeJSON(buf[offset .. $], *__traits(getMember, t, i)); 1658 else 1659 written = serializeJSON(buf[offset .. $], __traits(getMember, t, i)); 1660 // todo: Add hashmap 1661 offset += written.length; 1662 } 1663 } 1664 } 1665 written = formattedWrite!"}"(buf.ptr + offset); 1666 offset += written.length; 1667 return buf_start[0 .. offset]; 1668 } 1669 1670 char[] serializeJSON(T)(char[] buf, T t) nothrow @trusted 1671 if (is(T == struct) && hasMember!(T, "get") && hasMember!(T, "opApply") && !isTuple!T) 1672 { 1673 char* buf_start = buf.ptr; 1674 size_t offset; 1675 char[] written = formattedWrite!"{"(buf.ptr + offset); 1676 offset += written.length; 1677 foreach (key, ref val; t) 1678 { 1679 if (offset > 1) 1680 { 1681 written = formattedWrite!`,`(buf.ptr + offset); 1682 offset += written.length; 1683 } 1684 1685 static if (isPointer!(typeof(key))) 1686 written = serializeJSON(buf[offset .. $], *key); 1687 else 1688 written = serializeJSON(buf[offset .. $], key); 1689 1690 offset += written.length; 1691 1692 written = formattedWrite!`:`(buf.ptr + offset); 1693 1694 offset += written.length; 1695 static if (isPointer!(typeof(val))) 1696 written = serializeJSON(buf[offset .. $], *val); 1697 else 1698 written = serializeJSON(buf[offset .. $], val); 1699 1700 offset += written.length; 1701 } 1702 1703 written = formattedWrite!"}"(buf.ptr + offset); 1704 offset += written.length; 1705 return buf_start[0 .. offset]; 1706 } 1707 1708 private char[] serializeJSON(T)(char[] buf, T t) nothrow @trusted 1709 if ((isArray!T || (is(T == struct) && hasMember!(T, "opSlice")) || isTuple!T) && !isSomeString!T) 1710 { 1711 char* buf_start = buf.ptr; 1712 size_t offset; 1713 char[] written = formattedWrite!"["(buf.ptr + offset, t); 1714 offset += written.length; 1715 foreach (i, v; t) 1716 { 1717 if (i > 0) 1718 { 1719 written = formattedWrite!`,`(buf.ptr + offset); 1720 offset += written.length; 1721 } 1722 static if (isPointer!(typeof(v))) 1723 written = serializeJSON(buf[offset .. $], *v); 1724 else 1725 written = serializeJSON(buf[offset .. $], v); 1726 offset += written.length; 1727 } 1728 written = formattedWrite!"]"(buf.ptr + offset, t); 1729 offset += written.length; 1730 return buf_start[0 .. offset]; 1731 } 1732 1733 private char[] serializeJSON(T)(char[] buf, T t) nothrow @trusted 1734 if (isIntegral!T && !isFloatingPoint!T) 1735 { 1736 char[] written = formattedWrite!"%d"(buf.ptr, t); 1737 return buf[0 .. written.length]; 1738 } 1739 1740 private char[] serializeJSON(T)(char[] buf, T t) nothrow @trusted 1741 if (isSomeString!T) 1742 { 1743 // escape string..? 1744 char[] written = formattedWrite!`"%S"`(buf.ptr, t); 1745 return buf[0 .. written.length]; 1746 } 1747 1748 private char[] serializeJSON(T)(char[] buf, T t) nothrow @trusted 1749 if (isFloatingPoint!T) 1750 { 1751 char[] written = formattedWrite!`%f`(buf.ptr, t); 1752 return buf[0 .. written.length]; 1753 } 1754 1755 private char[] serializeJSON(T)(char[] buf, T t) nothrow @trusted if (isBoolean!T) 1756 { 1757 char[] written = t ? formattedWrite!`true`(buf.ptr) : formattedWrite!`false`(buf.ptr); 1758 return buf[0 .. written.length]; 1759 } 1760 1761 unittest 1762 { 1763 struct Counter 1764 { 1765 size_t array, object, key, string, number, boolean, null_; 1766 } 1767 1768 void valueHandler(ref Json!validateAll.File json, ref Counter ctr) 1769 { 1770 with (DataType) final switch (json.peek) 1771 { 1772 case array: 1773 ctr.array++; 1774 foreach (_; json) 1775 valueHandler(json, ctr); 1776 break; 1777 case object: 1778 ctr.object++; 1779 foreach (key; json.byKey) 1780 { 1781 ctr.key++; 1782 valueHandler(json, ctr); 1783 } 1784 break; 1785 case string: 1786 ctr..string++; 1787 json.skipValue(); 1788 break; 1789 case number: 1790 ctr.number++; 1791 json.skipValue(); 1792 break; 1793 case boolean: 1794 ctr.boolean++; 1795 json.skipValue(); 1796 break; 1797 case null_: 1798 ctr.null_++; 1799 json.skipValue(); 1800 break; 1801 } 1802 } 1803 1804 // Tests that need to pass according to RFC 7159 1805 passFile("test/pass1.json", Counter(6, 4, 33, 21, 32, 4, 2)); 1806 passFile("test/pass2.json", Counter(19, 0, 0, 1, 0, 0, 0)); 1807 passFile("test/pass3.json", Counter(0, 2, 3, 2, 0, 0, 0)); 1808 passFile("test/fail1.json", Counter(0, 0, 0, 1, 0, 0, 0)); 1809 passFile("test/fail18.json", Counter(20, 0, 0, 1, 0, 0, 0)); 1810 1811 // Tests that need to fail 1812 foreach (i; chain(iota(2, 18), iota(19, 34))) 1813 failFile("test/fail" ~ i.to!string ~ ".json"); 1814 1815 // Deserialization 1816 struct Test 1817 { 1818 string text1; 1819 string text2; 1820 string text3; 1821 double dbl = 0; 1822 float flt = 0; 1823 ulong ul; 1824 uint ui; 1825 ushort us; 1826 ubyte ub; 1827 long lm, lp; 1828 int im, ip; 1829 short sm, sp; 1830 byte bm, bp; 1831 bool t, f; 1832 Test* tp1, tp2; 1833 int[2] sa; 1834 int[] da; 1835 Test[string] aa; 1836 SearchPolicy e; 1837 } 1838 1839 Test t1 = { 1840 text1: "abcde", 1841 text2: "", 1842 text3: null, 1843 dbl: 1.1, 1844 flt: -1.1, 1845 ul: ulong.max, 1846 ui: uint.max, 1847 us: ushort.max, 1848 ub: ubyte.max, 1849 lm: long.min, 1850 lp: long.max, 1851 im: int.min, 1852 ip: int.max, 1853 sm: short.min, 1854 sp: short.max, 1855 bm: byte.min, 1856 bp: byte.max, 1857 t: true, 1858 f: false, 1859 tp1: null, 1860 tp2: new Test("This is", "a", "test."), 1861 sa: [33, 44], 1862 da: [5, 6, 7], 1863 aa: ["hash": Test("x", "y", "z")], 1864 e: SearchPolicy.linear}; 1865 Test t2 = parseJSON(`{ 1866 "text1" : "abcde", 1867 "text2" : "", 1868 "text3" : null, 1869 "dbl" : 1.1, 1870 "flt" : -1.1, 1871 "ul" : ` 1872 ~ ulong.max.to!string ~ `, 1873 "ui" : ` 1874 ~ uint.max.to!string ~ `, 1875 "us" : ` 1876 ~ ushort.max.to!string ~ `, 1877 "ub" : ` 1878 ~ ubyte.max.to!string ~ `, 1879 "lm" : ` 1880 ~ long.min.to!string ~ `, 1881 "lp" : ` 1882 ~ long.max.to!string ~ `, 1883 "im" : ` 1884 ~ int.min.to!string ~ `, 1885 "ip" : ` 1886 ~ int.max.to!string ~ `, 1887 "sm" : ` 1888 ~ short.min.to!string ~ `, 1889 "sp" : ` 1890 ~ short.max.to!string ~ `, 1891 "bm" : ` 1892 ~ byte.min.to!string ~ `, 1893 "bp" : ` 1894 ~ byte.max.to!string ~ `, 1895 "t" : true, 1896 "f" : false, 1897 "tp1" : null, 1898 "tp2" : { "text1": "This is", "text2": "a", "text3": "test." }, 1899 "sa" : [ 33, 44 ], 1900 "da" : [ 5, 6, 7 ], 1901 "aa" : { "hash" : { "text1":"x", "text2":"y", "text3":"z" } }, 1902 "e" : "linear" 1903 }`).read!Test; 1904 1905 assert(t2.tp2 && *t1.tp2 == *t2.tp2); 1906 assert(t1.da == t2.da); 1907 assert(t1.aa == t2.aa); 1908 t2.tp2 = t1.tp2; 1909 t2.da = t1.da; 1910 t2.aa = t1.aa; 1911 assert(t1 == t2); 1912 } 1913 1914 // Test case for Issue #4 1915 unittest 1916 { 1917 auto str = `{"initiator_carrier_code":null,"a":"b"}`; 1918 auto js = parseTrustedJSON(str); 1919 foreach (key; js.byKey) 1920 { 1921 if (key == "initiator_carrier_code") 1922 { 1923 auto t = js.read!string; 1924 assert(t is null); 1925 } 1926 } 1927 } 1928 1929 // Test case for Issue #5 1930 unittest 1931 { 1932 import std.utf; 1933 1934 auto str = `{"a":"SΛNNO𐍈€한"}`; 1935 str.validate; 1936 validateJSON(str); 1937 }