1 /*******************************************************************************
2 
3     Iterating JSON parser
4 
5     Extends Tango's JsonParser by iteration and token classification facilities.
6 
7     Includes methods to extract the values of named entities.
8 
9     Usage example:
10         See unittests following this class.
11 
12     Copyright:
13         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
14         All rights reserved.
15 
16     License:
17         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
18         Alternatively, this file may be distributed under the terms of the Tango
19         3-Clause BSD License (see LICENSE_BSD.txt for details).
20 
21 *******************************************************************************/
22 
23 module ocean.text.json.JsonParserIter;
24 
25 
26 import ocean.meta.types.Qualifiers;
27 
28 import ocean.text.json.JsonParser;
29 
30 import ocean.core.Enforce;
31 
32 import ocean.meta.traits.Basic;
33 
34 import Integer = ocean.text.convert.Integer_tango;
35 
36 import Float = ocean.text.convert.Float;
37 
38 version (unittest)
39 {
40     import ocean.core.Test;
41 }
42 
43 
44 /******************************************************************************/
45 
46 class JsonParserIter(bool AllowNaN = false) : JsonParser!(char, AllowNaN)
47 {
48     /**************************************************************************
49 
50         Import the Token enum into this namespace
51 
52      **************************************************************************/
53 
54     public alias typeof (super).Token Token;
55 
56     /**************************************************************************
57 
58         TokenClass enum
59         "Other" is for tokens that stand for themselves
60 
61      **************************************************************************/
62 
63     public enum TokenClass
64     {
65         Other = 0,
66         ValueType,
67         Container,
68     }
69 
70     /**************************************************************************
71 
72         Token type descriptions
73 
74      **************************************************************************/
75 
76     public static istring[Token.max + 1] type_description = [
77         Token.Empty:        "Empty",
78         Token.Name:         "Name",
79         Token.String:       "String",
80         Token.Number:       "Number",
81         Token.BeginObject:  "BeginObject",
82         Token.EndObject:    "EndObject",
83         Token.BeginArray:   "BeginArray",
84         Token.EndArray:     "EndArray",
85         Token.True:         "True",
86         Token.False:        "False",
87         Token.Null:         "Null",
88         Token.NaN:          "NaN",
89         Token.Infinity:     "Inf",
90         Token.NegInfinity:  "-Inf"
91     ];
92 
93 
94     /**************************************************************************
95 
96         Token to TokenClass association
97 
98      **************************************************************************/
99 
100     public static immutable TokenClass[Token.max + 1] token_classes =
101     [
102         Token.Empty:       TokenClass.Other,
103         Token.Name:        TokenClass.Other,
104         Token.String:      TokenClass.ValueType,
105         Token.Number:      TokenClass.ValueType,
106         Token.True:        TokenClass.ValueType,
107         Token.False:       TokenClass.ValueType,
108         Token.Null:        TokenClass.ValueType,
109         Token.NaN:         TokenClass.ValueType,
110         Token.Infinity:    TokenClass.ValueType,
111         Token.NegInfinity: TokenClass.ValueType,
112         Token.BeginObject: TokenClass.Container,
113         Token.BeginArray:  TokenClass.Container,
114         Token.EndObject:   TokenClass.Container,
115         Token.EndArray:    TokenClass.Container
116     ];
117 
118     /**************************************************************************
119 
120         Token nesting difference values
121 
122      **************************************************************************/
123 
124     public static immutable int[Token.max + 1] nestings =
125     [
126         Token.BeginObject: +1,
127         Token.BeginArray:  +1,
128         Token.EndObject:   -1,
129         Token.EndArray:    -1
130     ];
131 
132     /**************************************************************************
133 
134         Returns the nesting level difference caused by the current token.
135 
136         Returns:
137             +1 if the current token is BeginObject or BeginArray,
138             -1 if the current token is EndObject or EndArray,
139              0 otherwise
140 
141      **************************************************************************/
142 
143     public int nesting ( )
144     {
145         return this.nestings[super.type];
146     }
147 
148     /**************************************************************************
149 
150         Returns:
151             the token class to which the current token (super.type()) belongs to
152 
153      **************************************************************************/
154 
155     public TokenClass token_class ( )
156     {
157         return this.token_classes[super.type];
158     }
159 
160     /**************************************************************************
161 
162         Steps to the next token in the current JSON content.
163 
164         Returns:
165             type of next token or Token.Empty if there is no next one
166 
167      **************************************************************************/
168 
169     public Token nextType ( )
170     {
171         return super.next()? super.type : Token.Empty;
172     }
173 
174     /**************************************************************************
175 
176         Resets the instance and sets the input content (convenience wrapper for
177         super.reset()).
178 
179         Params:
180             content = new JSON input content to parse
181 
182         Returns:
183             this instance
184 
185      **************************************************************************/
186 
187     public typeof (this) opCall ( cstring content )
188     {
189         super.reset(content);
190 
191         return this;
192     }
193 
194     /**************************************************************************
195 
196         'foreach' iteration over type/value pairs in the current content
197 
198      **************************************************************************/
199 
200     public int opApply ( scope int delegate ( ref Token type, ref cstring value ) dg )
201     {
202         int result = 0;
203 
204         do
205         {
206             Token  type  = super.type;
207             cstring value = super.value;
208 
209             result = dg(type, value);
210         }
211         while (!result && super.next());
212 
213         return result;
214     }
215 
216 
217     /**************************************************************************
218 
219         'foreach' iteration over type/name/value triples in the current content.
220 
221         For unnamed members name will be null.
222 
223      **************************************************************************/
224 
225     public int opApply ( scope int delegate ( ref Token type, ref cstring name,
226         ref cstring value ) dg )
227     {
228         int result = 0;
229 
230         cstring name = null;
231 
232         do
233         {
234             Token type = super.type;
235 
236             auto value = super.value;
237 
238             if (type == Token.Name)
239             {
240                 name = value;
241             }
242             else
243             {
244                 result = dg(type, name, value);
245                 name = null;
246             }
247         }
248         while (!result && super.next());
249 
250         return result;
251     }
252 
253     /**************************************************************************
254 
255         Skips the current member so that the next member is reached by a next()
256         call or in the next 'foreach' iteration cycle.
257         That is,
258             - if the current token denotes an object or array beginning,
259               to the corresponding object/array end token,
260             - if the current token is a name, steps over the name,
261             - if the current member is a value, does nothing.
262 
263         Returns:
264             0 on success or, if the contend ends before the skip destination
265             was reached,
266             - the object nesting level if an object was skipped,
267             - the array nesting level if an array was skipped,
268             - 1 if a name was skipped and the contend ends just after that name.
269 
270      **************************************************************************/
271 
272     public uint skip ( )
273     {
274         Token start_type, end_type;
275 
276         switch (start_type = super.type)
277         {
278             case Token.BeginObject:
279                 end_type = Token.EndObject;
280                 break;
281 
282             case Token.BeginArray:
283                 end_type = Token.EndArray;
284                 break;
285 
286             case Token.Name:
287                 return !super.next();
288                                                                                 // fall through
289             default:
290                 return 0;
291         }
292 
293         uint nesting = 1;
294 
295         for (bool more = super.next(); more; more = super.next())
296         {
297             Token type = super.type;
298 
299             nesting += type == start_type;
300             nesting -= type == end_type;
301 
302             if (!nesting) break;
303         }
304 
305         return nesting;
306     }
307 
308     /**************************************************************************
309 
310         Iterates over the json string looking for the named object and
311         halting iteration if it is found.
312 
313         Note that the search takes place from the current iteration position,
314         and all iterations are cumulative. The iteration position is reset using
315         the 'reset' method (in super).
316 
317         Params:
318             name = name to search for
319 
320         Returns:
321             true if named object found
322 
323      **************************************************************************/
324 
325     public bool nextNamedObject ( cstring name )
326     {
327         bool in_object;
328         do
329         {
330             if ( in_object )
331             {
332                 if ( super.value == name )
333                 {
334                     return true;
335                 }
336                 else
337                 {
338                     in_object = false;
339                 }
340             }
341             else
342             {
343                 if ( super.type == Token.BeginObject )
344                 {
345                     in_object = true;
346                 }
347             }
348         }
349         while ( super.next );
350 
351         return false;
352     }
353 
354 
355     /**************************************************************************
356 
357         Iterates over the json string looking for the named element and
358         returning the value of the following element.
359 
360         Note that the search takes place from the current iteration position,
361         and all iterations are cumulative. The iteration position is reset using
362         the 'reset' method (in super).
363 
364         Params:
365             name = name to search for
366             found = output value, set to true if named value was found
367 
368         Returns:
369             value of element after the named element
370 
371      **************************************************************************/
372 
373     public cstring nextNamed ( cstring name, out bool found )
374     {
375         return this.nextNamedValue(name, found, ( Token token ) { return true; });
376     }
377 
378 
379     /**************************************************************************
380 
381         Iterates over the json string looking for the named element and
382         returning the value of the following element if it is a boolean. If the
383         value is not boolean the search continues.
384 
385         Note that the search takes place from the current iteration position,
386         and all iterations are cumulative. The iteration position is reset using
387         the 'reset' method (in super).
388 
389         Params:
390             name = name to search for
391             found = output value, set to true if named value was found
392 
393         Returns:
394             boolean value of element after the named element
395 
396      **************************************************************************/
397 
398     public bool nextNamedBool ( cstring name, out bool found )
399     {
400         return this.nextNamedValue(name, found, ( Token token ) { return token == Token.True || token == Token.False; }) == "true";
401     }
402 
403 
404     /**************************************************************************
405 
406         Iterates over the json string looking for the named element and
407         returning the value of the following element if it is a string. If the
408         value is not a string the search continues.
409 
410         Note that the search takes place from the current iteration position,
411         and all iterations are cumulative. The iteration position is reset using
412         the 'reset' method (in super).
413 
414         Params:
415             name = name to search for
416             found = output value, set to true if named value was found
417 
418         Returns:
419             value of element after the named element
420 
421      **************************************************************************/
422 
423     public cstring nextNamedString ( cstring name, out bool found )
424     {
425         return this.nextNamedValue(name, found, ( Token token ) { return token == Token.String; });
426     }
427 
428 
429     /**************************************************************************
430 
431         Iterates over the json string looking for the named element and
432         returning the value of the following element if it is a number. If the
433         value is not a number the search continues.
434 
435         Note that the search takes place from the current iteration position,
436         and all iterations are cumulative. The iteration position is reset using
437         the 'reset' method (in super).
438 
439         Params:
440             T = numerical type to return
441             name = name to search for
442             found = output value, set to true if named value was found
443 
444         Returns:
445             numerical value of element after the named element
446 
447         Throws:
448             if the value is not valid number
449 
450      **************************************************************************/
451 
452     public T nextNamedNumber ( T ) ( cstring name, out bool found )
453     {
454         T ret;
455         auto str = this.nextNamedValue(name, found,
456             ( Token token )
457             {
458                 return token == Token.Number || token == Token.NaN ||
459                        token == Token.Infinity || token == Token.NegInfinity;
460             }
461         );
462 
463         if ( found )
464         {
465             static if ( isRealType!(T) )
466             {
467                 ret = Float.toFloat(str);
468             }
469             else static if ( isSignedIntegerType!(T) )
470             {
471                 ret = Integer.toLong(str);
472             }
473             else static if ( isUnsignedIntegerType!(T) )
474             {
475                 auto tmp = Integer.toUlong(str);
476                 enforce(tmp <= T.max && tmp >= T.min,
477                         "Value returned from toULong is out of bound for type "
478                         ~ T.stringof);
479                 ret = cast(T) tmp;
480             }
481             else
482             {
483                 static assert(false, typeof(this).stringof ~ ".nextNamedNumber - template type must be numerical, not " ~ T.stringof);
484             }
485         }
486 
487         return ret;
488     }
489 
490     /**************************************************************************
491 
492         Iterates over the json string looking for the named element and
493         returning the value of the following element if its type matches the
494         requirements of the passed delegate.
495 
496         Note that the search takes place from the current iteration position,
497         and all iterations are cumulative. The iteration position is reset using
498         the 'reset' method (in super).
499 
500         Params:
501             name = name to search for
502             found = output value, set to true if named value was found
503             type_match_dg = delegate which receives the type of the element
504                 following a correctly named value, and decides whether this is
505                 the value to be returned
506 
507         Returns:
508             value of element after the named element
509 
510      **************************************************************************/
511 
512     private cstring nextNamedValue ( cstring name, out bool found,
513         scope bool delegate ( Token ) type_match_dg )
514     {
515         bool got_name;
516         foreach ( type, value; this )
517         {
518             if ( got_name )
519             {
520                 if ( type_match_dg(type) )
521                 {
522                     found = true;
523                     return value;
524                 }
525                 else
526                 {
527                     got_name = false;
528                 }
529             }
530 
531             if ( type == Token.Name && value == name )
532             {
533                 got_name = true;
534             }
535         }
536 
537         return "";
538     }
539 }
540 
541 ///
542 unittest
543 {
544     alias JsonParserIter!(true) JsonParserIterWithNan;
545     alias JsonParserIter!(false) JsonParserIterNoNan;
546 
547     bool found;
548     istring json = `{ "object": { "cost": 12.34, "sub": { "cost": 42 } } }`;
549 
550     scope parser = new JsonParserIterNoNan();
551     parser.reset(json);
552 
553     auto val = parser.nextNamed("cost", found);
554     test(found, "Boolean flag should be set to true");
555     test!("==")(val, "12.34"[]);
556 
557     found = false;
558     auto uval = parser.nextNamedNumber!(uint)("cost", found);
559     test(found, "Boolean flag should be set to true");
560     test!("==")(uval, 42);
561 }