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