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 }