1 /** 2 * Provides various means for parsing JSON documents. 3 * 4 * This module contains two different parser implementations. The first 5 * implementation returns a single JSON document in the form of a 6 * $(D JSONValue), while the second implementation returns a stream 7 * of nodes. The stream based parser is particularly useful for 8 * deserializing with few allocations or for processing large documents. 9 * 10 * Copyright: Copyright 2012 - 2015, Sönke Ludwig. 11 * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 12 * Authors: Sönke Ludwig 13 * Source: $(PHOBOSSRC std/data/json/parser.d) 14 */ 15 module funkwerk.stdx.data.json.parser; 16 17 /// 18 unittest 19 { 20 import std.algorithm : equal, map; 21 import std.format : format; 22 23 // Parse a JSON string to a single value 24 JSONValue value = toJSONValue(`{"name": "D", "kind": "language"}`); 25 26 // Parse a JSON string to a node stream 27 auto nodes = parseJSONStream(`{"name": "D", "kind": "language"}`); 28 with (JSONParserNodeKind) { 29 assert(nodes.map!(n => n.kind).equal( 30 [objectStart, key, literal, key, literal, objectEnd])); 31 } 32 33 // Parse a list of tokens instead of a string 34 auto tokens = lexJSON(`{"name": "D", "kind": "language"}`); 35 JSONValue value2 = toJSONValue(tokens); 36 assert(value == value2, format!"%s != %s"(value, value2)); 37 } 38 39 import std.array : appender; 40 import std.range : ElementType, isInputRange; 41 import std.traits : isIntegral, isSomeChar; 42 43 import funkwerk.stdx.data.json.lexer; 44 import funkwerk.stdx.data.json.value; 45 46 /// The default amount of nesting in the input allowed by `toJSONValue` and `parseJSONValue`. 47 enum defaultMaxDepth = 512; 48 49 /** 50 * Parses a JSON string or token range and returns the result as a 51 * `JSONValue`. 52 * 53 * The input string must be a valid JSON document. In particular, it must not 54 * contain any additional text other than whitespace after the end of the 55 * JSON document. 56 * 57 * See_also: `parseJSONValue` 58 */ 59 JSONValue toJSONValue(LexOptions options = LexOptions.init, Input)(Input input, string filename = "", int maxDepth = defaultMaxDepth) 60 if (isInputRange!Input && (isSomeChar!(ElementType!Input) || isIntegral!(ElementType!Input))) 61 { 62 auto tokens = lexJSON!options(input, filename); 63 return toJSONValue(tokens, maxDepth); 64 } 65 /// ditto 66 JSONValue toJSONValue(Input)(Input tokens, int maxDepth = defaultMaxDepth) 67 if (isJSONTokenInputRange!Input) 68 { 69 import funkwerk.stdx.data.json.foundation; 70 auto ret = parseJSONValue(tokens, maxDepth); 71 enforceJson(tokens.empty, "Unexpected characters following JSON", tokens.location); 72 return ret; 73 } 74 75 /// 76 @safe unittest 77 { 78 // parse a simple number 79 JSONValue a = toJSONValue(`1.0`); 80 assert(a == 1.0); 81 82 // parse an object 83 JSONValue b = toJSONValue(`{"a": true, "b": "test"}`); 84 auto bdoc = b.get!(JSONValue[string]); 85 assert(bdoc.length == 2); 86 assert(bdoc["a"] == true); 87 assert(bdoc["b"] == "test"); 88 89 // parse an array 90 JSONValue c = toJSONValue(`[1, 2, null]`); 91 auto cdoc = c.get!(JSONValue[]); 92 assert(cdoc.length == 3); 93 assert(cdoc[0] == 1.0); 94 assert(cdoc[1] == 2.0); 95 assert(cdoc[2] == null); 96 97 import std.conv; 98 JSONValue jv = toJSONValue(`{ "a": 1234 }`); 99 assert(jv["a"].to!int == 1234); 100 } 101 102 unittest { // issue #22 103 import std.conv; 104 JSONValue jv = toJSONValue(`{ "a": 1234 }`); 105 assert(jv["a"].to!int == 1234); 106 } 107 108 /*unittest 109 { 110 import std.bigint; 111 auto v = toJSONValue!(LexOptions.useBigInt)(`{"big": 12345678901234567890}`); 112 assert(v["big"].value == BigInt("12345678901234567890")); 113 }*/ 114 115 @safe unittest 116 { 117 import std.exception; 118 assertNotThrown(toJSONValue("{} \t\r\n")); 119 assertThrown(toJSONValue(`{} {}`)); 120 } 121 122 123 /** 124 * Consumes a single JSON value from the input range and returns the result as a 125 * `JSONValue`. 126 * 127 * The input string must start with a valid JSON document. Any characters 128 * occurring after this document will be left in the input range. Use 129 * `toJSONValue` instead if you wish to perform a normal string to `JSONValue` 130 * conversion. 131 * 132 * See_also: `toJSONValue` 133 */ 134 JSONValue parseJSONValue(LexOptions options = LexOptions.init, Input)(ref Input input, string filename = "", int maxDepth = defaultMaxDepth) 135 if (isInputRange!Input && (isSomeChar!(ElementType!Input) || isIntegral!(ElementType!Input))) 136 { 137 import funkwerk.stdx.data.json.foundation; 138 139 auto tokens = lexJSON!options(input, filename); 140 auto ret = parseJSONValue(tokens, maxDepth); 141 input = tokens.input; 142 return ret; 143 } 144 145 /// Parse an object 146 @safe unittest 147 { 148 // parse an object 149 string str = `{"a": true, "b": "test"}`; 150 JSONValue v = parseJSONValue(str); 151 assert(!str.length); // the input has been consumed 152 153 auto obj = v.get!(JSONValue[string]); 154 assert(obj.length == 2); 155 assert(obj["a"] == true); 156 assert(obj["b"] == "test"); 157 } 158 159 /// Parse multiple consecutive values 160 @safe unittest 161 { 162 string str = `1.0 2.0`; 163 JSONValue v1 = parseJSONValue(str); 164 assert(v1 == 1.0); 165 assert(str == `2.0`); 166 JSONValue v2 = parseJSONValue(str); 167 assert(v2 == 2.0); 168 assert(str == ``); 169 } 170 171 172 /** 173 * Parses a stream of JSON tokens and returns the result as a $(D JSONValue). 174 * 175 * All tokens belonging to the document will be consumed from the input range. 176 * Any tokens after the end of the first JSON document will be left in the 177 * input token range for possible later consumption. 178 */ 179 @safe JSONValue parseJSONValue(Input)(ref Input tokens, int maxDepth = defaultMaxDepth) 180 if (isJSONTokenInputRange!Input) 181 { 182 import std.array; 183 import funkwerk.stdx.data.json.foundation; 184 185 enforceJson(maxDepth > 0, "Too much nesting", tokens.location); 186 187 enforceJson(!tokens.empty, "Missing JSON value before EOF", tokens.location); 188 189 JSONValue ret; 190 191 final switch (tokens.front.kind) with (JSONTokenKind) 192 { 193 case none: assert(false); 194 case error: enforceJson(false, "Invalid token encountered", tokens.front.location); assert(false); 195 case null_: ret = JSONValue(null); break; 196 case boolean: ret = JSONValue(tokens.front.boolean); break; 197 case number: 198 final switch (tokens.front.number.type) 199 { 200 case JSONNumber.Type.double_: ret = tokens.front.number.doubleValue; break; 201 case JSONNumber.Type.long_: ret = tokens.front.number.longValue; break; 202 case JSONNumber.Type.bigInt: () @trusted { ret = WrappedBigInt(tokens.front.number.bigIntValue); } (); break; 203 } 204 break; 205 case string: ret = JSONValue(tokens.front..string); break; 206 case objectStart: 207 auto loc = tokens.front.location; 208 bool first = true; 209 JSONValue[.string] obj; 210 tokens.popFront(); 211 while (true) 212 { 213 enforceJson(!tokens.empty, "Missing closing '}'", loc); 214 if (tokens.front.kind == objectEnd) break; 215 216 if (!first) 217 { 218 enforceJson(tokens.front.kind == comma, "Expected ',' or '}'", tokens.front.location); 219 tokens.popFront(); 220 enforceJson(!tokens.empty, "Expected field name", tokens.location); 221 } 222 else first = false; 223 224 enforceJson(tokens.front.kind == string, "Expected field name string", tokens.front.location); 225 auto key = tokens.front..string; 226 tokens.popFront(); 227 enforceJson(!tokens.empty && tokens.front.kind == colon, "Expected ':'", 228 tokens.empty ? tokens.location : tokens.front.location); 229 tokens.popFront(); 230 obj[key] = parseJSONValue(tokens, maxDepth - 1); 231 } 232 ret = JSONValue(obj, loc); 233 break; 234 case arrayStart: 235 auto loc = tokens.front.location; 236 bool first = true; 237 auto array = appender!(JSONValue[]); 238 tokens.popFront(); 239 while (true) 240 { 241 enforceJson(!tokens.empty, "Missing closing ']'", loc); 242 if (tokens.front.kind == arrayEnd) break; 243 244 if (!first) 245 { 246 enforceJson(tokens.front.kind == comma, "Expected ',' or ']'", tokens.front.location); 247 tokens.popFront(); 248 } 249 else first = false; 250 251 () @trusted { array ~= parseJSONValue(tokens, maxDepth - 1); }(); 252 } 253 ret = JSONValue(array.data, loc); 254 break; 255 case objectEnd, arrayEnd, colon, comma: 256 enforceJson(false, "Expected JSON value", tokens.front.location); 257 assert(false); 258 } 259 260 tokens.popFront(); 261 return ret; 262 } 263 264 /// 265 @safe unittest 266 { 267 // lex 268 auto tokens = lexJSON(`[1, 2, 3]`); 269 270 // parse 271 auto doc = parseJSONValue(tokens); 272 273 auto arr = doc.get!(JSONValue[]); 274 assert(arr.length == 3); 275 assert(arr[0] == 1.0); 276 assert(arr[1] == 2.0); 277 assert(arr[2] == 3.0); 278 } 279 280 @safe unittest 281 { 282 import std.exception; 283 284 assertThrown(toJSONValue(`]`)); 285 assertThrown(toJSONValue(`}`)); 286 assertThrown(toJSONValue(`,`)); 287 assertThrown(toJSONValue(`:`)); 288 assertThrown(toJSONValue(`{`)); 289 assertThrown(toJSONValue(`[`)); 290 assertThrown(toJSONValue(`[1,]`)); 291 assertThrown(toJSONValue(`[1,,]`)); 292 assertThrown(toJSONValue(`[1,`)); 293 assertThrown(toJSONValue(`[1 2]`)); 294 assertThrown(toJSONValue(`{1: 1}`)); 295 assertThrown(toJSONValue(`{"a": 1,}`)); 296 assertThrown(toJSONValue(`{"a" 1}`)); 297 assertThrown(toJSONValue(`{"a": 1 "b": 2}`)); 298 } 299 300 @safe unittest 301 { 302 import std.exception; 303 304 // Test depth/nesting limitation 305 assertNotThrown(toJSONValue(`[]`, "", 1)); 306 // values inside objects/arrays count as a level of nesting 307 assertThrown(toJSONValue(`[1, 2, 3]`, "", 1)); 308 assertNotThrown(toJSONValue(`[1, 2, 3]`, "", 2)); 309 assertThrown(toJSONValue(`[[]]`, "", 1)); 310 assertNotThrown(toJSONValue(`{}`, "", 1)); 311 assertThrown(toJSONValue(`{"a": {}}`, "", 1)); 312 } 313 314 /** 315 * Parses a JSON document using a lazy parser node range. 316 * 317 * This mode parsing mode is similar to a streaming XML (StAX) parser. It can 318 * be used to parse JSON documents of unlimited size. The memory consumption 319 * grows linearly with the nesting level (about 4 bytes per level), but is 320 * independent of the number of values in the JSON document. 321 * 322 * The resulting range of nodes is guaranteed to be ordered according to the 323 * following grammar, where uppercase terminals correspond to the node kind 324 * (See $(D JSONParserNodeKind)). 325 * 326 * $(UL 327 * $(LI list → value*) 328 * $(LI value → LITERAL | array | object) 329 * $(LI array → ARRAYSTART (value)* ARRAYEND) 330 * $(LI object → OBJECTSTART (KEY value)* OBJECTEND) 331 * ) 332 */ 333 JSONParserRange!(JSONLexerRange!(Input, options)) 334 parseJSONStream(LexOptions options = LexOptions.init, Input) 335 (Input input, string filename = null) 336 if (isInputRange!Input && (isSomeChar!(ElementType!Input) || isIntegral!(ElementType!Input))) 337 { 338 return parseJSONStream(lexJSON!(options)(input, filename)); 339 } 340 /// ditto 341 JSONParserRange!Input parseJSONStream(Input)(Input tokens) 342 if (isJSONTokenInputRange!Input) 343 { 344 return JSONParserRange!Input(tokens); 345 } 346 347 /// 348 @safe unittest 349 { 350 import std.algorithm; 351 352 auto rng1 = parseJSONStream(`{ "a": 1, "b": [null] }`); 353 with (JSONParserNodeKind) 354 { 355 assert(rng1.map!(n => n.kind).equal( 356 [objectStart, key, literal, key, arrayStart, literal, arrayEnd, 357 objectEnd])); 358 } 359 360 auto rng2 = parseJSONStream(`1 {"a": 2} null`); 361 with (JSONParserNodeKind) 362 { 363 assert(rng2.map!(n => n.kind).equal( 364 [literal, objectStart, key, literal, objectEnd, literal])); 365 } 366 } 367 368 @safe unittest 369 { 370 auto rng = parseJSONStream(`{"a": 1, "b": [null, true], "c": {"d": {}}}`); 371 with (JSONParserNodeKind) 372 { 373 rng.popFront(); 374 assert(rng.front.kind == key && rng.front.key == "a"); rng.popFront(); 375 assert(rng.front.kind == literal && rng.front.literal.number == 1.0); rng.popFront(); 376 assert(rng.front.kind == key && rng.front.key == "b"); rng.popFront(); 377 assert(rng.front.kind == arrayStart); rng.popFront(); 378 assert(rng.front.kind == literal && rng.front.literal.kind == JSONTokenKind.null_); rng.popFront(); 379 assert(rng.front.kind == literal && rng.front.literal.boolean == true); rng.popFront(); 380 assert(rng.front.kind == arrayEnd); rng.popFront(); 381 assert(rng.front.kind == key && rng.front.key == "c"); rng.popFront(); 382 assert(rng.front.kind == objectStart); rng.popFront(); 383 assert(rng.front.kind == key && rng.front.key == "d"); rng.popFront(); 384 assert(rng.front.kind == objectStart); rng.popFront(); 385 assert(rng.front.kind == objectEnd); rng.popFront(); 386 assert(rng.front.kind == objectEnd); rng.popFront(); 387 assert(rng.front.kind == objectEnd); rng.popFront(); 388 assert(rng.empty); 389 } 390 } 391 392 @safe unittest 393 { 394 auto rng = parseJSONStream(`[]`); 395 with (JSONParserNodeKind) 396 { 397 import std.algorithm; 398 assert(rng.map!(n => n.kind).equal([arrayStart, arrayEnd])); 399 } 400 } 401 402 @safe unittest 403 { 404 import std.array; 405 import std.exception; 406 407 assertThrown(parseJSONStream(`]`).array); 408 assertThrown(parseJSONStream(`}`).array); 409 assertThrown(parseJSONStream(`,`).array); 410 assertThrown(parseJSONStream(`:`).array); 411 assertThrown(parseJSONStream(`{`).array); 412 assertThrown(parseJSONStream(`[`).array); 413 assertThrown(parseJSONStream(`[1,]`).array); 414 assertThrown(parseJSONStream(`[1,,]`).array); 415 assertThrown(parseJSONStream(`[1,`).array); 416 assertThrown(parseJSONStream(`[1 2]`).array); 417 assertThrown(parseJSONStream(`{1: 1}`).array); 418 assertThrown(parseJSONStream(`{"a": 1,}`).array); 419 assertThrown(parseJSONStream(`{"a" 1}`).array); 420 assertThrown(parseJSONStream(`{"a": 1 "b": 2}`).array); 421 assertThrown(parseJSONStream(`{"a": 1, "b": [null, true], "c": {"d": {}}}}`).array); 422 } 423 424 // Not possible to test anymore with the new String customization scheme 425 /*@safe unittest { // test for @nogc interface 426 static struct MyAppender 427 { 428 @nogc: 429 void put(string s) { } 430 void put(dchar ch) {} 431 void put(char ch) {} 432 @property string data() { return null; } 433 } 434 static MyAppender createAppender() @nogc { return MyAppender.init; } 435 436 static struct EmptyStream 437 { 438 @nogc: 439 @property bool empty() { return true; } 440 @property dchar front() { return ' '; } 441 void popFront() { assert(false); } 442 @property EmptyStream save() { return this; } 443 } 444 445 void test(T)() 446 { 447 T t; 448 auto str = parseJSONStream!(LexOptions.noThrow, createAppender)(t); 449 while (!str.empty) { 450 auto f = str.front; 451 str.popFront(); 452 } 453 } 454 // just instantiate, don't run 455 auto t1 = &test!string; 456 auto t2 = &test!wstring; 457 auto t3 = &test!dstring; 458 auto t4 = &test!EmptyStream; 459 }*/ 460 461 462 /** 463 * Lazy input range of JSON parser nodes. 464 * 465 * See $(D parseJSONStream) for more information. 466 */ 467 struct JSONParserRange(Input) 468 if (isJSONTokenInputRange!Input) 469 { 470 import funkwerk.stdx.data.json.foundation; 471 472 private { 473 Input _input; 474 JSONTokenKind[] _containerStack; 475 size_t _containerStackFill = 0; 476 // see doc/why-we-dont-need-save.md 477 JSONTokenKind _currentKind; 478 JSONParserNodeKind _prevKind; 479 JSONParserNode _node; 480 } 481 482 /** 483 * Constructs a new parser range. 484 */ 485 this(Input input) 486 { 487 _input = input; 488 } 489 490 // For debugging. 491 string toString() 492 { 493 import std.format : format; 494 495 return format!"JSONParserRange(%s, %s, %s, %s)"( 496 _containerStack[0 .. _containerStackFill], _currentKind, _prevKind, _node); 497 } 498 499 /** 500 * NOT a replacement for save()! Just here to convert from const to mutable! 501 */ 502 JSONParserRange dup() const 503 { 504 auto result = JSONParserRange(this._input); 505 506 result._containerStack = this._containerStack.dup; 507 result._containerStackFill = this._containerStackFill; 508 result._currentKind = this._currentKind; 509 result._prevKind = this._prevKind; 510 result._node = this._node; 511 return result; 512 } 513 514 /** 515 * Determines of the range has been exhausted. 516 */ 517 @property bool empty() { return _containerStackFill == 0 && _input.empty && _node.kind == JSONParserNodeKind.none; } 518 519 /** 520 * Returns the current node from the stream. 521 */ 522 @property ref const(JSONParserNode) front() 523 { 524 ensureFrontValid(); 525 return _node; 526 } 527 528 /** 529 * Skips to the next node in the stream. 530 */ 531 void popFront() 532 { 533 assert(!empty); 534 ensureFrontValid(); 535 _prevKind = _node.kind; 536 _node.kind = JSONParserNodeKind.none; 537 } 538 539 private void ensureFrontValid() 540 { 541 if (_node.kind == JSONParserNodeKind.none) 542 { 543 readNext(); 544 assert(_node.kind != JSONParserNodeKind.none); 545 } 546 } 547 548 private void readNext() 549 { 550 if (_containerStackFill) 551 { 552 if (_currentKind == JSONTokenKind.objectStart) 553 readNextInObject(); 554 else readNextInArray(); 555 } 556 else readNextValue(); 557 } 558 559 private void readNextInObject() @trusted 560 { 561 enforceJson(!_input.empty, "Missing closing '}'", _input.location); 562 switch (_prevKind) 563 { 564 default: assert(false); 565 case JSONParserNodeKind.objectStart: 566 if (_input.front.kind == JSONTokenKind.objectEnd) 567 { 568 _node.kind = JSONParserNodeKind.objectEnd; 569 popContainer; 570 } 571 else 572 { 573 enforceJson(_input.front.kind == JSONTokenKind..string, 574 "Expected field name", _input.front.location); 575 _node.key = _input.front..string; 576 } 577 _input.popFront(); 578 break; 579 case JSONParserNodeKind.key: 580 enforceJson(_input.front.kind == JSONTokenKind.colon, 581 "Expected ':'", _input.front.location); 582 _input.popFront(); 583 readNextValue(); 584 break; 585 case JSONParserNodeKind.literal, JSONParserNodeKind.objectEnd, JSONParserNodeKind.arrayEnd: 586 if (_input.front.kind == JSONTokenKind.objectEnd) 587 { 588 _node.kind = JSONParserNodeKind.objectEnd; 589 popContainer; 590 } 591 else 592 { 593 enforceJson(_input.front.kind == JSONTokenKind.comma, 594 "Expected ',' or '}'", _input.front.location); 595 _input.popFront(); 596 enforceJson(!_input.empty && _input.front.kind == JSONTokenKind..string, 597 "Expected field name", _input.front.location); 598 _node.key = _input.front..string; 599 } 600 _input.popFront(); 601 break; 602 } 603 } 604 605 private void readNextInArray() 606 { 607 enforceJson(!_input.empty, "Missing closing ']'", _input.location); 608 switch (_prevKind) 609 { 610 default: assert(false); 611 case JSONParserNodeKind.arrayStart: 612 if (_input.front.kind == JSONTokenKind.arrayEnd) 613 { 614 _node.kind = JSONParserNodeKind.arrayEnd; 615 popContainer; 616 _input.popFront(); 617 } 618 else 619 { 620 readNextValue(); 621 } 622 break; 623 case JSONParserNodeKind.literal, JSONParserNodeKind.objectEnd, JSONParserNodeKind.arrayEnd: 624 if (_input.front.kind == JSONTokenKind.arrayEnd) 625 { 626 _node.kind = JSONParserNodeKind.arrayEnd; 627 popContainer; 628 _input.popFront(); 629 } 630 else 631 { 632 enforceJson(_input.front.kind == JSONTokenKind.comma, 633 "Expected ',' or ']'", _input.front.location); 634 _input.popFront(); 635 enforceJson(!_input.empty, "Missing closing ']'", _input.location); 636 readNextValue(); 637 } 638 break; 639 } 640 } 641 642 private void readNextValue() 643 { 644 switch (_input.front.kind) 645 { 646 default: 647 throw new JSONException("Expected JSON value", _input.location); 648 case JSONTokenKind.none: assert(false); 649 case JSONTokenKind.null_, JSONTokenKind.boolean, 650 JSONTokenKind.number, JSONTokenKind..string: 651 _node.literal = _input.front; 652 _input.popFront(); 653 break; 654 case JSONTokenKind.objectStart: 655 _node.kind = JSONParserNodeKind.objectStart; 656 pushContainer(JSONTokenKind.objectStart); 657 _input.popFront(); 658 break; 659 case JSONTokenKind.arrayStart: 660 _node.kind = JSONParserNodeKind.arrayStart; 661 pushContainer(JSONTokenKind.arrayStart); 662 _input.popFront(); 663 break; 664 } 665 } 666 667 private void popContainer() 668 { 669 _containerStackFill--; 670 if (_containerStackFill) 671 _currentKind = _containerStack[_containerStackFill - 1]; 672 } 673 674 private void pushContainer(JSONTokenKind kind) 675 { 676 import std.algorithm : max; 677 if (_containerStackFill) 678 _containerStack[_containerStackFill - 1] = _currentKind; 679 if (_containerStackFill >= _containerStack.length) 680 _containerStack.length = max(32, _containerStack.length*3/2); 681 _containerStack[_containerStackFill++] = kind; 682 _currentKind = kind; 683 } 684 } 685 686 687 /** 688 * Represents a single node of a JSON parse tree. 689 * 690 * See $(D parseJSONStream) and $(D JSONParserRange) more information. 691 */ 692 struct JSONParserNode 693 { 694 @safe: 695 import std.algorithm : among; 696 import funkwerk.stdx.data.json.foundation : Location; 697 698 private alias Kind = JSONParserNodeKind; // compatibility alias 699 700 private 701 { 702 Kind _kind = Kind.none; 703 union 704 { 705 string _key; 706 JSONToken _literal; 707 } 708 } 709 710 /** 711 * The kind of this node. 712 */ 713 @property Kind kind() const nothrow { return _kind; } 714 /// ditto 715 @property Kind kind(Kind value) nothrow 716 in (!value.among(Kind.key, Kind.literal)) 717 { return _kind = value; } 718 719 /** 720 * The key identifier for $(D Kind.key) nodes. 721 * 722 * Setting the key will automatically switch the node kind. 723 */ 724 @property string key() const @trusted nothrow 725 { 726 assert(_kind == Kind.key); 727 return _key; 728 } 729 /// ditto 730 @property string key(string value) nothrow 731 { 732 _kind = Kind.key; 733 return () @trusted { return _key = value; } (); 734 } 735 736 /** 737 * The literal token for $(D Kind.literal) nodes. 738 * 739 * Setting the literal will automatically switch the node kind. 740 */ 741 @property ref inout(JSONToken) literal() inout @trusted nothrow 742 { 743 assert(_kind == Kind.literal); 744 return _literal; 745 } 746 /// ditto 747 @property ref JSONToken literal(JSONToken literal) return nothrow 748 { 749 _kind = Kind.literal; 750 return *() @trusted { return &(_literal = literal); } (); 751 } 752 753 @property Location location() 754 const @trusted nothrow { 755 if (_kind == Kind.literal) return _literal.location; 756 return Location.init; 757 } 758 759 /** 760 * Enables equality comparisons. 761 * 762 * Note that the location is considered part of the token and thus is 763 * included in the comparison. 764 */ 765 bool opEquals(in ref JSONParserNode other) 766 const nothrow 767 { 768 if (this.kind != other.kind) return false; 769 770 switch (this.kind) 771 { 772 default: return true; 773 case Kind.literal: return this.literal == other.literal; 774 case Kind.key: return this.key == other.key; 775 } 776 } 777 /// ditto 778 bool opEquals(JSONParserNode other) const nothrow { return opEquals(other); } 779 780 unittest 781 { 782 JSONToken t1, t2, t3; 783 t1..string = "test"; 784 t2..string = "test".idup; 785 t3..string = "other"; 786 787 JSONParserNode n1, n2; 788 n2.literal = t1; assert(n1 != n2); 789 n1.literal = t1; assert(n1 == n2); 790 n1.literal = t3; assert(n1 != n2); 791 n1.literal = t2; assert(n1 == n2); 792 n1.kind = Kind.objectStart; assert(n1 != n2); 793 n1.key = "test"; assert(n1 != n2); 794 n2.key = "other"; assert(n1 != n2); 795 n2.key = "test".idup; assert(n1 == n2); 796 } 797 798 /** 799 * Enables usage of $(D JSONToken) as an associative array key. 800 */ 801 size_t toHash() const nothrow @trusted 802 { 803 hash_t ret = 723125331 + cast(int)_kind * 3962627; 804 805 switch (_kind) 806 { 807 default: return ret; 808 case Kind.literal: return ret + _literal.toHash(); 809 case Kind.key: return ret + typeid(.string).getHash(&_key); 810 } 811 } 812 813 /** 814 * Converts the node to a string representation. 815 * 816 * Note that this representation is NOT the JSON representation, but rather 817 * a representation suitable for printing out a node. 818 */ 819 string toString() const 820 { 821 import std.string; 822 switch (this.kind) 823 { 824 default: return format("%s", this.kind); 825 case Kind.key: return format("[key \"%s\"]", this.key); 826 case Kind.literal: return literal.toString(); 827 } 828 } 829 } 830 831 832 /** 833 * Identifies the kind of a parser node. 834 */ 835 enum JSONParserNodeKind 836 { 837 none, /// Used internally, never occurs in a node stream 838 key, /// An object key 839 literal, /// A literal value ($(D null), $(D boolean), $(D number) or $(D string)) 840 objectStart, /// The start of an object value 841 objectEnd, /// The end of an object value 842 arrayStart, /// The start of an array value 843 arrayEnd, /// The end of an array value 844 } 845 846 847 /// Tests if a given type is an input range of $(D JSONToken). 848 enum isJSONTokenInputRange(R) = isInputRange!R && is(typeof(R.init.front) : JSONToken); 849 850 static assert(isJSONTokenInputRange!(JSONLexerRange!string)); 851 852 /// Tests if a given type is an input range of $(D JSONParserNode). 853 enum isJSONParserNodeInputRange(R) = isInputRange!R && is(typeof(R.init.front) : JSONParserNode); 854 855 static assert(isJSONParserNodeInputRange!(JSONParserRange!(JSONLexerRange!string))); 856 857 // Workaround for https://issues.dlang.org/show_bug.cgi?id=14425 858 private alias Workaround_14425 = JSONParserRange!(JSONLexerRange!string); 859 860 /** 861 * Skips a single JSON value in a parser stream. 862 * 863 * The value pointed to by `nodes.front` will be skipped. All JSON types will 864 * be skipped, which means in particular that arrays and objects will be 865 * skipped recursively. 866 * 867 * Params: 868 * nodes = An input range of JSON parser nodes 869 */ 870 void skipValue(R)(ref R nodes) if (isJSONParserNodeInputRange!R) 871 { 872 import funkwerk.stdx.data.json.foundation; 873 enforceJson(!nodes.empty, "Unexpected end of input", nodes.front.literal.location); 874 875 auto k = nodes.front.kind; 876 nodes.popFront(); 877 878 with (JSONParserNodeKind) { 879 if (k != arrayStart && k != objectStart) return; 880 881 int depth = 1; 882 while (!nodes.empty) { 883 k = nodes.front.kind; 884 nodes.popFront(); 885 if (k == arrayStart || k == objectStart) depth++; 886 else if (k == arrayEnd || k == objectEnd) { 887 if (--depth == 0) break; 888 } 889 } 890 } 891 } 892 893 /// 894 @safe unittest 895 { 896 auto j = parseJSONStream(q{ 897 [ 898 [1, 2, 3], 899 "foo" 900 ] 901 }); 902 903 assert(j.front.kind == JSONParserNodeKind.arrayStart); 904 j.popFront(); 905 906 // skips the whole [1, 2, 3] array 907 j.skipValue(); 908 909 string value = j.readString; 910 assert(value == "foo"); 911 912 assert(j.front.kind == JSONParserNodeKind.arrayEnd); 913 j.popFront(); 914 915 assert(j.empty); 916 } 917 918 919 /** 920 * Skips all entries in an object until a certain key is reached. 921 * 922 * The node range must either point to the start of an object 923 * (`JSONParserNodeKind.objectStart`), or to a key within an object 924 * (`JSONParserNodeKind.key`). 925 * 926 * Params: 927 * nodes = An input range of JSON parser nodes 928 * key = Name of the key to find 929 * 930 * Returns: 931 * `true` is returned if and only if the specified key has been found. 932 * 933 * Params: 934 * nodes = An input range of JSON parser nodes 935 */ 936 bool skipToKey(R)(ref R nodes, string key) if (isJSONParserNodeInputRange!R) 937 { 938 import std.algorithm/*.comparison*/ : among; 939 import funkwerk.stdx.data.json.foundation; 940 941 enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 942 enforceJson(nodes.front.kind.among!(JSONParserNodeKind.objectStart, JSONParserNodeKind.key) > 0, 943 "Expected object or object key", nodes.front.location); 944 945 if (nodes.front.kind == JSONParserNodeKind.objectStart) 946 nodes.popFront(); 947 948 while (true) { 949 auto k = nodes.front.kind; 950 if (k == JSONParserNodeKind.objectEnd) { 951 nodes.popFront(); 952 return false; 953 } 954 955 assert(k == JSONParserNodeKind.key); 956 if (nodes.front.key == key) { 957 nodes.popFront(); 958 return true; 959 } 960 961 nodes.popFront(); 962 963 nodes.skipValue(); 964 } 965 } 966 967 /// 968 @safe unittest 969 { 970 auto j = parseJSONStream(q{ 971 { 972 "foo": 2, 973 "bar": 3, 974 "baz": false, 975 "qux": "str" 976 } 977 }); 978 979 j.skipToKey("bar"); 980 double v1 = j.readDouble; 981 assert(v1 == 3); 982 983 j.skipToKey("qux"); 984 string v2 = j.readString; 985 assert(v2 == "str"); 986 987 assert(j.front.kind == JSONParserNodeKind.objectEnd); 988 j.popFront(); 989 990 assert(j.empty); 991 } 992 993 994 /** 995 * Reads an array and issues a callback for each entry. 996 * 997 * Params: 998 * nodes = An input range of JSON parser nodes 999 * del = The callback to invoke for each array entry 1000 */ 1001 void readArray(R)(ref R nodes, scope void delegate() @safe del) if (isJSONParserNodeInputRange!R) 1002 { 1003 import funkwerk.stdx.data.json.foundation; 1004 enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 1005 enforceJson(nodes.front.kind == JSONParserNodeKind.arrayStart, 1006 "Expected array", nodes.front.location); 1007 nodes.popFront(); 1008 1009 while (true) { 1010 auto k = nodes.front.kind; 1011 if (k == JSONParserNodeKind.arrayEnd) { 1012 nodes.popFront(); 1013 return; 1014 } 1015 del(); 1016 } 1017 } 1018 1019 /// 1020 @safe unittest 1021 { 1022 auto j = parseJSONStream(q{ 1023 [ 1024 "foo", 1025 "bar" 1026 ] 1027 }); 1028 1029 size_t i = 0; 1030 j.readArray({ 1031 auto value = j.readString(); 1032 switch (i++) { 1033 default: assert(false); 1034 case 0: assert(value == "foo"); break; 1035 case 1: assert(value == "bar"); break; 1036 } 1037 }); 1038 1039 assert(j.empty); 1040 } 1041 1042 /** 1043 * Reads an object and issues a callback for each field. 1044 * 1045 * Params: 1046 * nodes = An input range of JSON parser nodes 1047 * del = The callback to invoke for each object field 1048 */ 1049 void readObject(R)(ref R nodes, scope void delegate(string key) @safe del) if (isJSONParserNodeInputRange!R) 1050 { 1051 import funkwerk.stdx.data.json.foundation; 1052 enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 1053 enforceJson(nodes.front.kind == JSONParserNodeKind.objectStart, 1054 "Expected object", nodes.front.literal.location); 1055 nodes.popFront(); 1056 1057 while (true) { 1058 auto k = nodes.front.kind; 1059 if (k == JSONParserNodeKind.objectEnd) { 1060 nodes.popFront(); 1061 return; 1062 } 1063 auto key = nodes.front.key; 1064 nodes.popFront(); 1065 del(key); 1066 } 1067 } 1068 1069 /// 1070 @safe unittest 1071 { 1072 auto j = parseJSONStream(q{ 1073 { 1074 "foo": 1, 1075 "bar": 2 1076 } 1077 }); 1078 1079 j.readObject((key) { 1080 auto value = j.readDouble; 1081 switch (key) { 1082 default: assert(false); 1083 case "foo": assert(value == 1); break; 1084 case "bar": assert(value == 2); break; 1085 } 1086 }); 1087 1088 assert(j.empty); 1089 } 1090 1091 1092 /** 1093 * Reads a single double value. 1094 * 1095 * Params: 1096 * nodes = An input range of JSON parser nodes 1097 * 1098 * Throws: Throws a `JSONException` is the node range is empty or `nodes.front` is not a number. 1099 */ 1100 double readDouble(R)(ref R nodes) if (isJSONParserNodeInputRange!R) 1101 { 1102 import funkwerk.stdx.data.json.foundation; 1103 enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 1104 enforceJson(nodes.front.kind == JSONParserNodeKind.literal 1105 && nodes.front.literal.kind == JSONTokenKind.number, 1106 "Expected numeric value", nodes.front.literal.location); 1107 double ret = nodes.front.literal.number; 1108 nodes.popFront(); 1109 return ret; 1110 } 1111 1112 /// 1113 @safe unittest 1114 { 1115 auto j = parseJSONStream(`1.0`); 1116 double value = j.readDouble; 1117 assert(value == 1.0); 1118 assert(j.empty); 1119 } 1120 1121 1122 /** 1123 * Reads a single double value. 1124 * 1125 * Params: 1126 * nodes = An input range of JSON parser nodes 1127 * 1128 * Throws: Throws a `JSONException` is the node range is empty or `nodes.front` is not a string. 1129 */ 1130 string readString(R)(ref R nodes) if (isJSONParserNodeInputRange!R) 1131 { 1132 import funkwerk.stdx.data.json.foundation; 1133 enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 1134 enforceJson(nodes.front.kind == JSONParserNodeKind.literal 1135 && nodes.front.literal.kind == JSONTokenKind..string, 1136 "Expected string value", nodes.front.literal.location); 1137 string ret = nodes.front.literal..string; 1138 nodes.popFront(); 1139 return ret; 1140 } 1141 1142 /// 1143 @safe unittest 1144 { 1145 auto j = parseJSONStream(`"foo"`); 1146 string value = j.readString; 1147 assert(value == "foo"); 1148 assert(j.empty); 1149 } 1150 1151 1152 /** 1153 * Reads a single double value. 1154 * 1155 * Params: 1156 * nodes = An input range of JSON parser nodes 1157 * 1158 * Throws: Throws a `JSONException` is the node range is empty or `nodes.front` is not a boolean. 1159 */ 1160 bool readBool(R)(ref R nodes) if (isJSONParserNodeInputRange!R) 1161 { 1162 import funkwerk.stdx.data.json.foundation; 1163 enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 1164 enforceJson(nodes.front.kind == JSONParserNodeKind.literal 1165 && nodes.front.literal.kind == JSONTokenKind.boolean, 1166 "Expected boolean value", nodes.front.literal.location); 1167 bool ret = nodes.front.literal.boolean; 1168 nodes.popFront(); 1169 return ret; 1170 } 1171 1172 /// 1173 @safe unittest 1174 { 1175 auto j = parseJSONStream(`true`); 1176 bool value = j.readBool; 1177 assert(value == true); 1178 assert(j.empty); 1179 }