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