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, String)) 334 parseJSONStream(LexOptions options = LexOptions.init, String = string, Input) 335 (Input input, string filename = null) 336 if (isInputRange!Input && (isSomeChar!(ElementType!Input) || isIntegral!(ElementType!Input))) 337 { 338 return parseJSONStream(lexJSON!(options, String)(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 alias String = typeof(Input.front).String; 473 474 private { 475 Input _input; 476 JSONTokenKind[] _containerStack; 477 size_t _containerStackFill = 0; 478 JSONParserNodeKind _prevKind; 479 JSONParserNode!String _node; 480 } 481 482 /** 483 * Constructs a new parser range. 484 */ 485 this(Input input) 486 { 487 _input = input; 488 } 489 490 /** 491 * Determines of the range has been exhausted. 492 */ 493 @property bool empty() { return _containerStackFill == 0 && _input.empty && _node.kind == JSONParserNodeKind.none; } 494 495 /** 496 * Returns the current node from the stream. 497 */ 498 @property ref const(JSONParserNode!String) front() 499 { 500 ensureFrontValid(); 501 return _node; 502 } 503 504 /** 505 * Skips to the next node in the stream. 506 */ 507 void popFront() 508 { 509 assert(!empty); 510 ensureFrontValid(); 511 _prevKind = _node.kind; 512 _node.kind = JSONParserNodeKind.none; 513 } 514 515 private void ensureFrontValid() 516 { 517 if (_node.kind == JSONParserNodeKind.none) 518 { 519 readNext(); 520 assert(_node.kind != JSONParserNodeKind.none); 521 } 522 } 523 524 private void readNext() 525 { 526 if (_containerStackFill) 527 { 528 if (_containerStack[_containerStackFill-1] == JSONTokenKind.objectStart) 529 readNextInObject(); 530 else readNextInArray(); 531 } 532 else readNextValue(); 533 } 534 535 private void readNextInObject() @trusted 536 { 537 enforceJson(!_input.empty, "Missing closing '}'", _input.location); 538 switch (_prevKind) 539 { 540 default: assert(false); 541 case JSONParserNodeKind.objectStart: 542 if (_input.front.kind == JSONTokenKind.objectEnd) 543 { 544 _node.kind = JSONParserNodeKind.objectEnd; 545 _containerStackFill--; 546 } 547 else 548 { 549 enforceJson(_input.front.kind == JSONTokenKind..string, 550 "Expected field name", _input.front.location); 551 _node.key = _input.front..string; 552 } 553 _input.popFront(); 554 break; 555 case JSONParserNodeKind.key: 556 enforceJson(_input.front.kind == JSONTokenKind.colon, 557 "Expected ':'", _input.front.location); 558 _input.popFront(); 559 readNextValue(); 560 break; 561 case JSONParserNodeKind.literal, JSONParserNodeKind.objectEnd, JSONParserNodeKind.arrayEnd: 562 if (_input.front.kind == JSONTokenKind.objectEnd) 563 { 564 _node.kind = JSONParserNodeKind.objectEnd; 565 _containerStackFill--; 566 } 567 else 568 { 569 enforceJson(_input.front.kind == JSONTokenKind.comma, 570 "Expected ',' or '}'", _input.front.location); 571 _input.popFront(); 572 enforceJson(!_input.empty && _input.front.kind == JSONTokenKind..string, 573 "Expected field name", _input.front.location); 574 _node.key = _input.front..string; 575 } 576 _input.popFront(); 577 break; 578 } 579 } 580 581 private void readNextInArray() 582 { 583 enforceJson(!_input.empty, "Missing closing ']'", _input.location); 584 switch (_prevKind) 585 { 586 default: assert(false); 587 case JSONParserNodeKind.arrayStart: 588 if (_input.front.kind == JSONTokenKind.arrayEnd) 589 { 590 _node.kind = JSONParserNodeKind.arrayEnd; 591 _containerStackFill--; 592 _input.popFront(); 593 } 594 else 595 { 596 readNextValue(); 597 } 598 break; 599 case JSONParserNodeKind.literal, JSONParserNodeKind.objectEnd, JSONParserNodeKind.arrayEnd: 600 if (_input.front.kind == JSONTokenKind.arrayEnd) 601 { 602 _node.kind = JSONParserNodeKind.arrayEnd; 603 _containerStackFill--; 604 _input.popFront(); 605 } 606 else 607 { 608 enforceJson(_input.front.kind == JSONTokenKind.comma, 609 "Expected ',' or ']'", _input.front.location); 610 _input.popFront(); 611 enforceJson(!_input.empty, "Missing closing ']'", _input.location); 612 readNextValue(); 613 } 614 break; 615 } 616 } 617 618 private void readNextValue() 619 { 620 switch (_input.front.kind) 621 { 622 default: 623 throw new JSONException("Expected JSON value", _input.location); 624 case JSONTokenKind.none: assert(false); 625 case JSONTokenKind.null_, JSONTokenKind.boolean, 626 JSONTokenKind.number, JSONTokenKind..string: 627 _node.literal = _input.front; 628 _input.popFront(); 629 break; 630 case JSONTokenKind.objectStart: 631 _node.kind = JSONParserNodeKind.objectStart; 632 pushContainer(JSONTokenKind.objectStart); 633 _input.popFront(); 634 break; 635 case JSONTokenKind.arrayStart: 636 _node.kind = JSONParserNodeKind.arrayStart; 637 pushContainer(JSONTokenKind.arrayStart); 638 _input.popFront(); 639 break; 640 } 641 } 642 643 private void pushContainer(JSONTokenKind kind) 644 { 645 import std.algorithm/*.comparison*/ : max; 646 if (_containerStackFill >= _containerStack.length) 647 _containerStack.length = max(32, _containerStack.length*3/2); 648 _containerStack[_containerStackFill++] = kind; 649 } 650 } 651 652 653 /** 654 * Represents a single node of a JSON parse tree. 655 * 656 * See $(D parseJSONStream) and $(D JSONParserRange) more information. 657 */ 658 struct JSONParserNode(String) 659 { 660 @safe: 661 import std.algorithm/*.comparison*/ : among; 662 import funkwerk.stdx.data.json.foundation : Location; 663 664 private alias Kind = JSONParserNodeKind; // compatibility alias 665 666 private 667 { 668 Kind _kind = Kind.none; 669 union 670 { 671 String _key; 672 JSONToken!String _literal; 673 } 674 } 675 676 /** 677 * The kind of this node. 678 */ 679 @property Kind kind() const nothrow { return _kind; } 680 /// ditto 681 @property Kind kind(Kind value) nothrow 682 in { assert(!value.among(Kind.key, Kind.literal)); } 683 body { return _kind = value; } 684 685 /** 686 * The key identifier for $(D Kind.key) nodes. 687 * 688 * Setting the key will automatically switch the node kind. 689 */ 690 @property String key() const @trusted nothrow 691 { 692 assert(_kind == Kind.key); 693 return _key; 694 } 695 /// ditto 696 @property String key(String value) nothrow 697 { 698 _kind = Kind.key; 699 return () @trusted { return _key = value; } (); 700 } 701 702 /** 703 * The literal token for $(D Kind.literal) nodes. 704 * 705 * Setting the literal will automatically switch the node kind. 706 */ 707 @property ref inout(JSONToken!String) literal() inout @trusted nothrow 708 { 709 assert(_kind == Kind.literal); 710 return _literal; 711 } 712 /// ditto 713 @property ref JSONToken!String literal(JSONToken!String literal) return nothrow 714 { 715 _kind = Kind.literal; 716 return *() @trusted { return &(_literal = literal); } (); 717 } 718 719 @property Location location() 720 const @trusted nothrow { 721 if (_kind == Kind.literal) return _literal.location; 722 return Location.init; 723 } 724 725 /** 726 * Enables equality comparisons. 727 * 728 * Note that the location is considered part of the token and thus is 729 * included in the comparison. 730 */ 731 bool opEquals(in ref JSONParserNode other) 732 const nothrow 733 { 734 if (this.kind != other.kind) return false; 735 736 switch (this.kind) 737 { 738 default: return true; 739 case Kind.literal: return this.literal == other.literal; 740 case Kind.key: return this.key == other.key; 741 } 742 } 743 /// ditto 744 bool opEquals(JSONParserNode other) const nothrow { return opEquals(other); } 745 746 unittest 747 { 748 JSONToken!string t1, t2, t3; 749 t1..string = "test"; 750 t2..string = "test".idup; 751 t3..string = "other"; 752 753 JSONParserNode!string n1, n2; 754 n2.literal = t1; assert(n1 != n2); 755 n1.literal = t1; assert(n1 == n2); 756 n1.literal = t3; assert(n1 != n2); 757 n1.literal = t2; assert(n1 == n2); 758 n1.kind = Kind.objectStart; assert(n1 != n2); 759 n1.key = "test"; assert(n1 != n2); 760 n2.key = "other"; assert(n1 != n2); 761 n2.key = "test".idup; assert(n1 == n2); 762 } 763 764 /** 765 * Enables usage of $(D JSONToken) as an associative array key. 766 */ 767 size_t toHash() const nothrow @trusted 768 { 769 hash_t ret = 723125331 + cast(int)_kind * 3962627; 770 771 switch (_kind) 772 { 773 default: return ret; 774 case Kind.literal: return ret + _literal.toHash(); 775 case Kind.key: return ret + typeid(.string).getHash(&_key); 776 } 777 } 778 779 /** 780 * Converts the node to a string representation. 781 * 782 * Note that this representation is NOT the JSON representation, but rather 783 * a representation suitable for printing out a node. 784 */ 785 string toString() const 786 { 787 import std.string; 788 switch (this.kind) 789 { 790 default: return format("%s", this.kind); 791 case Kind.key: return format("[key \"%s\"]", this.key); 792 case Kind.literal: return literal.toString(); 793 } 794 } 795 } 796 797 798 /** 799 * Identifies the kind of a parser node. 800 */ 801 enum JSONParserNodeKind 802 { 803 none, /// Used internally, never occurs in a node stream 804 key, /// An object key 805 literal, /// A literal value ($(D null), $(D boolean), $(D number) or $(D string)) 806 objectStart, /// The start of an object value 807 objectEnd, /// The end of an object value 808 arrayStart, /// The start of an array value 809 arrayEnd, /// The end of an array value 810 } 811 812 813 /// Tests if a given type is an input range of $(D JSONToken). 814 enum isJSONTokenInputRange(R) = isInputRange!R && is(typeof(R.init.front) : JSONToken!String, String); 815 816 static assert(isJSONTokenInputRange!(JSONLexerRange!string)); 817 818 /// Tests if a given type is an input range of $(D JSONParserNode). 819 enum isJSONParserNodeInputRange(R) = isInputRange!R && is(typeof(R.init.front) : JSONParserNode!String, String); 820 821 static assert(isJSONParserNodeInputRange!(JSONParserRange!(JSONLexerRange!string))); 822 823 // Workaround for https://issues.dlang.org/show_bug.cgi?id=14425 824 private alias Workaround_14425 = JSONParserRange!(JSONLexerRange!string); 825 826 827 /** 828 * Skips a single JSON value in a parser stream. 829 * 830 * The value pointed to by `nodes.front` will be skipped. All JSON types will 831 * be skipped, which means in particular that arrays and objects will be 832 * skipped recursively. 833 * 834 * Params: 835 * nodes = An input range of JSON parser nodes 836 */ 837 void skipValue(R)(ref R nodes) if (isJSONParserNodeInputRange!R) 838 { 839 import funkwerk.stdx.data.json.foundation; 840 enforceJson(!nodes.empty, "Unexpected end of input", nodes.front.literal.location); 841 842 auto k = nodes.front.kind; 843 nodes.popFront(); 844 845 with (JSONParserNodeKind) { 846 if (k != arrayStart && k != objectStart) return; 847 848 int depth = 1; 849 while (!nodes.empty) { 850 k = nodes.front.kind; 851 nodes.popFront(); 852 if (k == arrayStart || k == objectStart) depth++; 853 else if (k == arrayEnd || k == objectEnd) { 854 if (--depth == 0) break; 855 } 856 } 857 } 858 } 859 860 /// 861 @safe unittest 862 { 863 auto j = parseJSONStream(q{ 864 [ 865 [1, 2, 3], 866 "foo" 867 ] 868 }); 869 870 assert(j.front.kind == JSONParserNodeKind.arrayStart); 871 j.popFront(); 872 873 // skips the whole [1, 2, 3] array 874 j.skipValue(); 875 876 string value = j.readString; 877 assert(value == "foo"); 878 879 assert(j.front.kind == JSONParserNodeKind.arrayEnd); 880 j.popFront(); 881 882 assert(j.empty); 883 } 884 885 886 /** 887 * Skips all entries in an object until a certain key is reached. 888 * 889 * The node range must either point to the start of an object 890 * (`JSONParserNodeKind.objectStart`), or to a key within an object 891 * (`JSONParserNodeKind.key`). 892 * 893 * Params: 894 * nodes = An input range of JSON parser nodes 895 * key = Name of the key to find 896 * 897 * Returns: 898 * `true` is returned if and only if the specified key has been found. 899 * 900 * Params: 901 * nodes = An input range of JSON parser nodes 902 */ 903 bool skipToKey(R)(ref R nodes, string key) if (isJSONParserNodeInputRange!R) 904 { 905 import std.algorithm/*.comparison*/ : among; 906 import funkwerk.stdx.data.json.foundation; 907 908 enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 909 enforceJson(nodes.front.kind.among!(JSONParserNodeKind.objectStart, JSONParserNodeKind.key) > 0, 910 "Expected object or object key", nodes.front.location); 911 912 if (nodes.front.kind == JSONParserNodeKind.objectStart) 913 nodes.popFront(); 914 915 while (true) { 916 auto k = nodes.front.kind; 917 if (k == JSONParserNodeKind.objectEnd) { 918 nodes.popFront(); 919 return false; 920 } 921 922 assert(k == JSONParserNodeKind.key); 923 if (nodes.front.key == key) { 924 nodes.popFront(); 925 return true; 926 } 927 928 nodes.popFront(); 929 930 nodes.skipValue(); 931 } 932 } 933 934 /// 935 @safe unittest 936 { 937 auto j = parseJSONStream(q{ 938 { 939 "foo": 2, 940 "bar": 3, 941 "baz": false, 942 "qux": "str" 943 } 944 }); 945 946 j.skipToKey("bar"); 947 double v1 = j.readDouble; 948 assert(v1 == 3); 949 950 j.skipToKey("qux"); 951 string v2 = j.readString; 952 assert(v2 == "str"); 953 954 assert(j.front.kind == JSONParserNodeKind.objectEnd); 955 j.popFront(); 956 957 assert(j.empty); 958 } 959 960 961 /** 962 * Reads an array and issues a callback for each entry. 963 * 964 * Params: 965 * nodes = An input range of JSON parser nodes 966 * del = The callback to invoke for each array entry 967 */ 968 void readArray(R)(ref R nodes, scope void delegate() @safe del) if (isJSONParserNodeInputRange!R) 969 { 970 import funkwerk.stdx.data.json.foundation; 971 enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 972 enforceJson(nodes.front.kind == JSONParserNodeKind.arrayStart, 973 "Expected array", nodes.front.location); 974 nodes.popFront(); 975 976 while (true) { 977 auto k = nodes.front.kind; 978 if (k == JSONParserNodeKind.arrayEnd) { 979 nodes.popFront(); 980 return; 981 } 982 del(); 983 } 984 } 985 986 /// 987 @safe unittest 988 { 989 auto j = parseJSONStream(q{ 990 [ 991 "foo", 992 "bar" 993 ] 994 }); 995 996 size_t i = 0; 997 j.readArray({ 998 auto value = j.readString(); 999 switch (i++) { 1000 default: assert(false); 1001 case 0: assert(value == "foo"); break; 1002 case 1: assert(value == "bar"); break; 1003 } 1004 }); 1005 1006 assert(j.empty); 1007 } 1008 1009 /** Reads an array and returns a lazy range of parser node ranges. 1010 * 1011 * The given parser node range must point to a node of kind 1012 * `JSONParserNodeKind.arrayStart`. Each of the returned sub ranges 1013 * corresponds to the contents of a single array entry. 1014 * 1015 * Params: 1016 * nodes = An input range of JSON parser nodes 1017 * 1018 * Throws: 1019 * A `JSONException` is thrown if the input range does not point to the 1020 * start of an array. 1021 */ 1022 auto readArray(R)(ref R nodes) @system if (isJSONParserNodeInputRange!R) 1023 { 1024 static struct VR { 1025 R* nodes; 1026 size_t depth = 0; 1027 1028 @property bool empty() { return !nodes || nodes.empty; } 1029 1030 @property ref const(typeof(nodes.front)) front() { return nodes.front; } 1031 1032 void popFront() 1033 { 1034 switch (nodes.front.kind) with (JSONParserNodeKind) 1035 { 1036 default: break; 1037 case objectStart, arrayStart: depth++; break; 1038 case objectEnd, arrayEnd: depth--; break; 1039 } 1040 1041 nodes.popFront(); 1042 1043 if (depth == 0) nodes = null; 1044 } 1045 } 1046 1047 static struct ARR { 1048 R* nodes; 1049 VR value; 1050 1051 @property bool empty() { return !nodes || nodes.empty; } 1052 1053 @property ref inout(VR) front() inout { return value; } 1054 1055 void popFront() 1056 { 1057 while (!value.empty) value.popFront(); 1058 if (nodes.front.kind == JSONParserNodeKind.arrayEnd) { 1059 nodes.popFront(); 1060 nodes = null; 1061 } else { 1062 value = VR(nodes); 1063 } 1064 } 1065 } 1066 1067 import funkwerk.stdx.data.json.foundation; 1068 1069 enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 1070 enforceJson(nodes.front.kind == JSONParserNodeKind.arrayStart, 1071 "Expected array", nodes.front.location); 1072 nodes.popFront(); 1073 1074 ARR ret; 1075 1076 if (nodes.front.kind != JSONParserNodeKind.arrayEnd) { 1077 ret.nodes = &nodes; 1078 ret.value = VR(&nodes); 1079 } else nodes.popFront(); 1080 1081 return ret; 1082 } 1083 1084 /// 1085 unittest { 1086 auto j = parseJSONStream(q{ 1087 [ 1088 "foo", 1089 "bar" 1090 ] 1091 }); 1092 1093 size_t i = 0; 1094 foreach (ref entry; j.readArray) { 1095 auto value = entry.readString; 1096 assert(entry.empty); 1097 switch (i++) { 1098 default: assert(false); 1099 case 0: assert(value == "foo"); break; 1100 case 1: assert(value == "bar"); break; 1101 } 1102 } 1103 assert(i == 2); 1104 } 1105 1106 1107 /** 1108 * Reads an object and issues a callback for each field. 1109 * 1110 * Params: 1111 * nodes = An input range of JSON parser nodes 1112 * del = The callback to invoke for each object field 1113 */ 1114 void readObject(R)(ref R nodes, scope void delegate(string key) @safe del) if (isJSONParserNodeInputRange!R) 1115 { 1116 import funkwerk.stdx.data.json.foundation; 1117 enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 1118 enforceJson(nodes.front.kind == JSONParserNodeKind.objectStart, 1119 "Expected object", nodes.front.literal.location); 1120 nodes.popFront(); 1121 1122 while (true) { 1123 auto k = nodes.front.kind; 1124 if (k == JSONParserNodeKind.objectEnd) { 1125 nodes.popFront(); 1126 return; 1127 } 1128 auto key = nodes.front.key; 1129 nodes.popFront(); 1130 del(key); 1131 } 1132 } 1133 1134 /// 1135 @safe unittest 1136 { 1137 auto j = parseJSONStream(q{ 1138 { 1139 "foo": 1, 1140 "bar": 2 1141 } 1142 }); 1143 1144 j.readObject((key) { 1145 auto value = j.readDouble; 1146 switch (key) { 1147 default: assert(false); 1148 case "foo": assert(value == 1); break; 1149 case "bar": assert(value == 2); break; 1150 } 1151 }); 1152 1153 assert(j.empty); 1154 } 1155 1156 1157 /** 1158 * Reads a single double value. 1159 * 1160 * Params: 1161 * nodes = An input range of JSON parser nodes 1162 * 1163 * Throws: Throws a `JSONException` is the node range is empty or `nodes.front` is not a number. 1164 */ 1165 double readDouble(R)(ref R nodes) if (isJSONParserNodeInputRange!R) 1166 { 1167 import funkwerk.stdx.data.json.foundation; 1168 enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 1169 enforceJson(nodes.front.kind == JSONParserNodeKind.literal 1170 && nodes.front.literal.kind == JSONTokenKind.number, 1171 "Expected numeric value", nodes.front.literal.location); 1172 double ret = nodes.front.literal.number; 1173 nodes.popFront(); 1174 return ret; 1175 } 1176 1177 /// 1178 @safe unittest 1179 { 1180 auto j = parseJSONStream(`1.0`); 1181 double value = j.readDouble; 1182 assert(value == 1.0); 1183 assert(j.empty); 1184 } 1185 1186 1187 /** 1188 * Reads a single double value. 1189 * 1190 * Params: 1191 * nodes = An input range of JSON parser nodes 1192 * 1193 * Throws: Throws a `JSONException` is the node range is empty or `nodes.front` is not a string. 1194 */ 1195 string readString(R)(ref R nodes) if (isJSONParserNodeInputRange!R) 1196 { 1197 import funkwerk.stdx.data.json.foundation; 1198 enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 1199 enforceJson(nodes.front.kind == JSONParserNodeKind.literal 1200 && nodes.front.literal.kind == JSONTokenKind..string, 1201 "Expected string value", nodes.front.literal.location); 1202 string ret = nodes.front.literal..string; 1203 nodes.popFront(); 1204 return ret; 1205 } 1206 1207 /// 1208 @safe unittest 1209 { 1210 auto j = parseJSONStream(`"foo"`); 1211 string value = j.readString; 1212 assert(value == "foo"); 1213 assert(j.empty); 1214 } 1215 1216 1217 /** 1218 * Reads a single double value. 1219 * 1220 * Params: 1221 * nodes = An input range of JSON parser nodes 1222 * 1223 * Throws: Throws a `JSONException` is the node range is empty or `nodes.front` is not a boolean. 1224 */ 1225 bool readBool(R)(ref R nodes) if (isJSONParserNodeInputRange!R) 1226 { 1227 import funkwerk.stdx.data.json.foundation; 1228 enforceJson(!nodes.empty, "Unexpected end of input", Location.init); 1229 enforceJson(nodes.front.kind == JSONParserNodeKind.literal 1230 && nodes.front.literal.kind == JSONTokenKind.boolean, 1231 "Expected boolean value", nodes.front.literal.location); 1232 bool ret = nodes.front.literal.boolean; 1233 nodes.popFront(); 1234 return ret; 1235 } 1236 1237 /// 1238 @safe unittest 1239 { 1240 auto j = parseJSONStream(`true`); 1241 bool value = j.readBool; 1242 assert(value == true); 1243 assert(j.empty); 1244 }