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