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 }