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