├── .gitignore ├── .travis.yml ├── LICENSE ├── jsonpath.go ├── jsonpath_test.go └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | *.sw[op] 6 | 7 | # Folders 8 | _obj 9 | _test 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | *.test 25 | *.prof 26 | .idea 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.5 5 | - 1.5.1 6 | - 1.6.2 7 | 8 | os: linux 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 oliver 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /jsonpath.go: -------------------------------------------------------------------------------- 1 | package jsonpath 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "go/token" 7 | "go/types" 8 | "reflect" 9 | "regexp" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | var ErrGetFromNullObj = errors.New("get attribute from null object") 15 | 16 | func JsonPathLookup(obj interface{}, jpath string) (interface{}, error) { 17 | c, err := Compile(jpath) 18 | if err != nil { 19 | return nil, err 20 | } 21 | return c.Lookup(obj) 22 | } 23 | 24 | type Compiled struct { 25 | path string 26 | steps []step 27 | } 28 | 29 | type step struct { 30 | op string 31 | key string 32 | args interface{} 33 | } 34 | 35 | func MustCompile(jpath string) *Compiled { 36 | c, err := Compile(jpath) 37 | if err != nil { 38 | panic(err) 39 | } 40 | return c 41 | } 42 | 43 | func Compile(jpath string) (*Compiled, error) { 44 | tokens, err := tokenize(jpath) 45 | if err != nil { 46 | return nil, err 47 | } 48 | if tokens[0] != "@" && tokens[0] != "$" { 49 | return nil, fmt.Errorf("$ or @ should in front of path") 50 | } 51 | tokens = tokens[1:] 52 | res := Compiled{ 53 | path: jpath, 54 | steps: make([]step, len(tokens)), 55 | } 56 | for i, token := range tokens { 57 | op, key, args, err := parse_token(token) 58 | if err != nil { 59 | return nil, err 60 | } 61 | res.steps[i] = step{op, key, args} 62 | } 63 | return &res, nil 64 | } 65 | 66 | func (c *Compiled) String() string { 67 | return fmt.Sprintf("Compiled lookup: %s", c.path) 68 | } 69 | 70 | func (c *Compiled) Lookup(obj interface{}) (interface{}, error) { 71 | var err error 72 | for _, s := range c.steps { 73 | // "key", "idx" 74 | switch s.op { 75 | case "key": 76 | obj, err = get_key(obj, s.key) 77 | if err != nil { 78 | return nil, err 79 | } 80 | case "idx": 81 | if len(s.key) > 0 { 82 | // no key `$[0].test` 83 | obj, err = get_key(obj, s.key) 84 | if err != nil { 85 | return nil, err 86 | } 87 | } 88 | 89 | if len(s.args.([]int)) > 1 { 90 | res := []interface{}{} 91 | for _, x := range s.args.([]int) { 92 | //fmt.Println("idx ---- ", x) 93 | tmp, err := get_idx(obj, x) 94 | if err != nil { 95 | return nil, err 96 | } 97 | res = append(res, tmp) 98 | } 99 | obj = res 100 | } else if len(s.args.([]int)) == 1 { 101 | //fmt.Println("idx ----------------3") 102 | obj, err = get_idx(obj, s.args.([]int)[0]) 103 | if err != nil { 104 | return nil, err 105 | } 106 | } else { 107 | //fmt.Println("idx ----------------4") 108 | return nil, fmt.Errorf("cannot index on empty slice") 109 | } 110 | case "range": 111 | if len(s.key) > 0 { 112 | // no key `$[:1].test` 113 | obj, err = get_key(obj, s.key) 114 | if err != nil { 115 | return nil, err 116 | } 117 | } 118 | if argsv, ok := s.args.([2]interface{}); ok == true { 119 | obj, err = get_range(obj, argsv[0], argsv[1]) 120 | if err != nil { 121 | return nil, err 122 | } 123 | } else { 124 | return nil, fmt.Errorf("range args length should be 2") 125 | } 126 | case "filter": 127 | obj, err = get_key(obj, s.key) 128 | if err != nil { 129 | return nil, err 130 | } 131 | obj, err = get_filtered(obj, obj, s.args.(string)) 132 | if err != nil { 133 | return nil, err 134 | } 135 | default: 136 | return nil, fmt.Errorf("expression don't support in filter") 137 | } 138 | } 139 | return obj, nil 140 | } 141 | 142 | func tokenize(query string) ([]string, error) { 143 | tokens := []string{} 144 | // token_start := false 145 | // token_end := false 146 | token := "" 147 | 148 | // fmt.Println("-------------------------------------------------- start") 149 | for idx, x := range query { 150 | token += string(x) 151 | // //fmt.Printf("idx: %d, x: %s, token: %s, tokens: %v\n", idx, string(x), token, tokens) 152 | if idx == 0 { 153 | if token == "$" || token == "@" { 154 | tokens = append(tokens, token[:]) 155 | token = "" 156 | continue 157 | } else { 158 | return nil, fmt.Errorf("should start with '$'") 159 | } 160 | } 161 | if token == "." { 162 | continue 163 | } else if token == ".." { 164 | if tokens[len(tokens)-1] != "*" { 165 | tokens = append(tokens, "*") 166 | } 167 | token = "." 168 | continue 169 | } else { 170 | // fmt.Println("else: ", string(x), token) 171 | if strings.Contains(token, "[") { 172 | // fmt.Println(" contains [ ") 173 | if x == ']' && !strings.HasSuffix(token, "\\]") { 174 | if token[0] == '.' { 175 | tokens = append(tokens, token[1:]) 176 | } else { 177 | tokens = append(tokens, token[:]) 178 | } 179 | token = "" 180 | continue 181 | } 182 | } else { 183 | // fmt.Println(" doesn't contains [ ") 184 | if x == '.' { 185 | if token[0] == '.' { 186 | tokens = append(tokens, token[1:len(token)-1]) 187 | } else { 188 | tokens = append(tokens, token[:len(token)-1]) 189 | } 190 | token = "." 191 | continue 192 | } 193 | } 194 | } 195 | } 196 | if len(token) > 0 { 197 | if token[0] == '.' { 198 | token = token[1:] 199 | if token != "*" { 200 | tokens = append(tokens, token[:]) 201 | } else if tokens[len(tokens)-1] != "*" { 202 | tokens = append(tokens, token[:]) 203 | } 204 | } else { 205 | if token != "*" { 206 | tokens = append(tokens, token[:]) 207 | } else if tokens[len(tokens)-1] != "*" { 208 | tokens = append(tokens, token[:]) 209 | } 210 | } 211 | } 212 | // fmt.Println("finished tokens: ", tokens) 213 | // fmt.Println("================================================= done ") 214 | return tokens, nil 215 | } 216 | 217 | /* 218 | op: "root", "key", "idx", "range", "filter", "scan" 219 | */ 220 | func parse_token(token string) (op string, key string, args interface{}, err error) { 221 | if token == "$" { 222 | return "root", "$", nil, nil 223 | } 224 | if token == "*" { 225 | return "scan", "*", nil, nil 226 | } 227 | 228 | bracket_idx := strings.Index(token, "[") 229 | if bracket_idx < 0 { 230 | return "key", token, nil, nil 231 | } else { 232 | key = token[:bracket_idx] 233 | tail := token[bracket_idx:] 234 | if len(tail) < 3 { 235 | err = fmt.Errorf("len(tail) should >=3, %v", tail) 236 | return 237 | } 238 | tail = tail[1 : len(tail)-1] 239 | 240 | //fmt.Println(key, tail) 241 | if strings.Contains(tail, "?") { 242 | // filter ------------------------------------------------- 243 | op = "filter" 244 | if strings.HasPrefix(tail, "?(") && strings.HasSuffix(tail, ")") { 245 | args = strings.Trim(tail[2:len(tail)-1], " ") 246 | } 247 | return 248 | } else if strings.Contains(tail, ":") { 249 | // range ---------------------------------------------- 250 | op = "range" 251 | tails := strings.Split(tail, ":") 252 | if len(tails) != 2 { 253 | err = fmt.Errorf("only support one range(from, to): %v", tails) 254 | return 255 | } 256 | var frm interface{} 257 | var to interface{} 258 | if frm, err = strconv.Atoi(strings.Trim(tails[0], " ")); err != nil { 259 | if strings.Trim(tails[0], " ") == "" { 260 | err = nil 261 | } 262 | frm = nil 263 | } 264 | if to, err = strconv.Atoi(strings.Trim(tails[1], " ")); err != nil { 265 | if strings.Trim(tails[1], " ") == "" { 266 | err = nil 267 | } 268 | to = nil 269 | } 270 | args = [2]interface{}{frm, to} 271 | return 272 | } else if tail == "*" { 273 | op = "range" 274 | args = [2]interface{}{nil, nil} 275 | return 276 | } else { 277 | // idx ------------------------------------------------ 278 | op = "idx" 279 | res := []int{} 280 | for _, x := range strings.Split(tail, ",") { 281 | if i, err := strconv.Atoi(strings.Trim(x, " ")); err == nil { 282 | res = append(res, i) 283 | } else { 284 | return "", "", nil, err 285 | } 286 | } 287 | args = res 288 | } 289 | } 290 | return op, key, args, nil 291 | } 292 | 293 | func filter_get_from_explicit_path(obj interface{}, path string) (interface{}, error) { 294 | steps, err := tokenize(path) 295 | //fmt.Println("f: steps: ", steps, err) 296 | //fmt.Println(path, steps) 297 | if err != nil { 298 | return nil, err 299 | } 300 | if steps[0] != "@" && steps[0] != "$" { 301 | return nil, fmt.Errorf("$ or @ should in front of path") 302 | } 303 | steps = steps[1:] 304 | xobj := obj 305 | //fmt.Println("f: xobj", xobj) 306 | for _, s := range steps { 307 | op, key, args, err := parse_token(s) 308 | // "key", "idx" 309 | switch op { 310 | case "key": 311 | xobj, err = get_key(xobj, key) 312 | if err != nil { 313 | return nil, err 314 | } 315 | case "idx": 316 | if len(args.([]int)) != 1 { 317 | return nil, fmt.Errorf("don't support multiple index in filter") 318 | } 319 | xobj, err = get_key(xobj, key) 320 | if err != nil { 321 | return nil, err 322 | } 323 | xobj, err = get_idx(xobj, args.([]int)[0]) 324 | if err != nil { 325 | return nil, err 326 | } 327 | default: 328 | return nil, fmt.Errorf("expression don't support in filter") 329 | } 330 | } 331 | return xobj, nil 332 | } 333 | 334 | func get_key(obj interface{}, key string) (interface{}, error) { 335 | if reflect.TypeOf(obj) == nil { 336 | return nil, ErrGetFromNullObj 337 | } 338 | switch reflect.TypeOf(obj).Kind() { 339 | case reflect.Map: 340 | // if obj came from stdlib json, its highly likely to be a map[string]interface{} 341 | // in which case we can save having to iterate the map keys to work out if the 342 | // key exists 343 | if jsonMap, ok := obj.(map[string]interface{}); ok { 344 | val, exists := jsonMap[key] 345 | if !exists { 346 | return nil, fmt.Errorf("key error: %s not found in object", key) 347 | } 348 | return val, nil 349 | } 350 | for _, kv := range reflect.ValueOf(obj).MapKeys() { 351 | //fmt.Println(kv.String()) 352 | if kv.String() == key { 353 | return reflect.ValueOf(obj).MapIndex(kv).Interface(), nil 354 | } 355 | } 356 | return nil, fmt.Errorf("key error: %s not found in object", key) 357 | case reflect.Slice: 358 | // slice we should get from all objects in it. 359 | res := []interface{}{} 360 | for i := 0; i < reflect.ValueOf(obj).Len(); i++ { 361 | tmp, _ := get_idx(obj, i) 362 | if v, err := get_key(tmp, key); err == nil { 363 | res = append(res, v) 364 | } 365 | } 366 | return res, nil 367 | default: 368 | return nil, fmt.Errorf("object is not map") 369 | } 370 | } 371 | 372 | func get_idx(obj interface{}, idx int) (interface{}, error) { 373 | switch reflect.TypeOf(obj).Kind() { 374 | case reflect.Slice: 375 | length := reflect.ValueOf(obj).Len() 376 | if idx >= 0 { 377 | if idx >= length { 378 | return nil, fmt.Errorf("index out of range: len: %v, idx: %v", length, idx) 379 | } 380 | return reflect.ValueOf(obj).Index(idx).Interface(), nil 381 | } else { 382 | // < 0 383 | _idx := length + idx 384 | if _idx < 0 { 385 | return nil, fmt.Errorf("index out of range: len: %v, idx: %v", length, idx) 386 | } 387 | return reflect.ValueOf(obj).Index(_idx).Interface(), nil 388 | } 389 | default: 390 | return nil, fmt.Errorf("object is not Slice") 391 | } 392 | } 393 | 394 | func get_range(obj, frm, to interface{}) (interface{}, error) { 395 | switch reflect.TypeOf(obj).Kind() { 396 | case reflect.Slice: 397 | length := reflect.ValueOf(obj).Len() 398 | _frm := 0 399 | _to := length 400 | if frm == nil { 401 | frm = 0 402 | } 403 | if to == nil { 404 | to = length - 1 405 | } 406 | if fv, ok := frm.(int); ok == true { 407 | if fv < 0 { 408 | _frm = length + fv 409 | } else { 410 | _frm = fv 411 | } 412 | } 413 | if tv, ok := to.(int); ok == true { 414 | if tv < 0 { 415 | _to = length + tv + 1 416 | } else { 417 | _to = tv + 1 418 | } 419 | } 420 | if _frm < 0 || _frm >= length { 421 | return nil, fmt.Errorf("index [from] out of range: len: %v, from: %v", length, frm) 422 | } 423 | if _to < 0 || _to > length { 424 | return nil, fmt.Errorf("index [to] out of range: len: %v, to: %v", length, to) 425 | } 426 | //fmt.Println("_frm, _to: ", _frm, _to) 427 | res_v := reflect.ValueOf(obj).Slice(_frm, _to) 428 | return res_v.Interface(), nil 429 | default: 430 | return nil, fmt.Errorf("object is not Slice") 431 | } 432 | } 433 | 434 | func regFilterCompile(rule string) (*regexp.Regexp, error) { 435 | runes := []rune(rule) 436 | if len(runes) <= 2 { 437 | return nil, errors.New("empty rule") 438 | } 439 | 440 | if runes[0] != '/' || runes[len(runes)-1] != '/' { 441 | return nil, errors.New("invalid syntax. should be in `/pattern/` form") 442 | } 443 | runes = runes[1 : len(runes)-1] 444 | return regexp.Compile(string(runes)) 445 | } 446 | 447 | func get_filtered(obj, root interface{}, filter string) ([]interface{}, error) { 448 | lp, op, rp, err := parse_filter(filter) 449 | if err != nil { 450 | return nil, err 451 | } 452 | 453 | res := []interface{}{} 454 | 455 | switch reflect.TypeOf(obj).Kind() { 456 | case reflect.Slice: 457 | if op == "=~" { 458 | // regexp 459 | pat, err := regFilterCompile(rp) 460 | if err != nil { 461 | return nil, err 462 | } 463 | 464 | for i := 0; i < reflect.ValueOf(obj).Len(); i++ { 465 | tmp := reflect.ValueOf(obj).Index(i).Interface() 466 | ok, err := eval_reg_filter(tmp, root, lp, pat) 467 | if err != nil { 468 | return nil, err 469 | } 470 | if ok == true { 471 | res = append(res, tmp) 472 | } 473 | } 474 | } else { 475 | for i := 0; i < reflect.ValueOf(obj).Len(); i++ { 476 | tmp := reflect.ValueOf(obj).Index(i).Interface() 477 | ok, err := eval_filter(tmp, root, lp, op, rp) 478 | if err != nil { 479 | return nil, err 480 | } 481 | if ok == true { 482 | res = append(res, tmp) 483 | } 484 | } 485 | } 486 | return res, nil 487 | case reflect.Map: 488 | if op == "=~" { 489 | // regexp 490 | pat, err := regFilterCompile(rp) 491 | if err != nil { 492 | return nil, err 493 | } 494 | 495 | for _, kv := range reflect.ValueOf(obj).MapKeys() { 496 | tmp := reflect.ValueOf(obj).MapIndex(kv).Interface() 497 | ok, err := eval_reg_filter(tmp, root, lp, pat) 498 | if err != nil { 499 | return nil, err 500 | } 501 | if ok == true { 502 | res = append(res, tmp) 503 | } 504 | } 505 | } else { 506 | for _, kv := range reflect.ValueOf(obj).MapKeys() { 507 | tmp := reflect.ValueOf(obj).MapIndex(kv).Interface() 508 | ok, err := eval_filter(tmp, root, lp, op, rp) 509 | if err != nil { 510 | return nil, err 511 | } 512 | if ok == true { 513 | res = append(res, tmp) 514 | } 515 | } 516 | } 517 | default: 518 | return nil, fmt.Errorf("don't support filter on this type: %v", reflect.TypeOf(obj).Kind()) 519 | } 520 | 521 | return res, nil 522 | } 523 | 524 | // @.isbn => @.isbn, exists, nil 525 | // @.price < 10 => @.price, <, 10 526 | // @.price <= $.expensive => @.price, <=, $.expensive 527 | // @.author =~ /.*REES/i => @.author, match, /.*REES/i 528 | 529 | func parse_filter(filter string) (lp string, op string, rp string, err error) { 530 | tmp := "" 531 | 532 | stage := 0 533 | str_embrace := false 534 | for idx, c := range filter { 535 | switch c { 536 | case '\'': 537 | if str_embrace == false { 538 | str_embrace = true 539 | } else { 540 | switch stage { 541 | case 0: 542 | lp = tmp 543 | case 1: 544 | op = tmp 545 | case 2: 546 | rp = tmp 547 | } 548 | tmp = "" 549 | } 550 | case ' ': 551 | if str_embrace == true { 552 | tmp += string(c) 553 | continue 554 | } 555 | switch stage { 556 | case 0: 557 | lp = tmp 558 | case 1: 559 | op = tmp 560 | case 2: 561 | rp = tmp 562 | } 563 | tmp = "" 564 | 565 | stage += 1 566 | if stage > 2 { 567 | return "", "", "", errors.New(fmt.Sprintf("invalid char at %d: `%c`", idx, c)) 568 | } 569 | default: 570 | tmp += string(c) 571 | } 572 | } 573 | if tmp != "" { 574 | switch stage { 575 | case 0: 576 | lp = tmp 577 | op = "exists" 578 | case 1: 579 | op = tmp 580 | case 2: 581 | rp = tmp 582 | } 583 | tmp = "" 584 | } 585 | return lp, op, rp, err 586 | } 587 | 588 | func parse_filter_v1(filter string) (lp string, op string, rp string, err error) { 589 | tmp := "" 590 | istoken := false 591 | for _, c := range filter { 592 | if istoken == false && c != ' ' { 593 | istoken = true 594 | } 595 | if istoken == true && c == ' ' { 596 | istoken = false 597 | } 598 | if istoken == true { 599 | tmp += string(c) 600 | } 601 | if istoken == false && tmp != "" { 602 | if lp == "" { 603 | lp = tmp[:] 604 | tmp = "" 605 | } else if op == "" { 606 | op = tmp[:] 607 | tmp = "" 608 | } else if rp == "" { 609 | rp = tmp[:] 610 | tmp = "" 611 | } 612 | } 613 | } 614 | if tmp != "" && lp == "" && op == "" && rp == "" { 615 | lp = tmp[:] 616 | op = "exists" 617 | rp = "" 618 | err = nil 619 | return 620 | } else if tmp != "" && rp == "" { 621 | rp = tmp[:] 622 | tmp = "" 623 | } 624 | return lp, op, rp, err 625 | } 626 | 627 | func eval_reg_filter(obj, root interface{}, lp string, pat *regexp.Regexp) (res bool, err error) { 628 | if pat == nil { 629 | return false, errors.New("nil pat") 630 | } 631 | lp_v, err := get_lp_v(obj, root, lp) 632 | if err != nil { 633 | return false, err 634 | } 635 | switch v := lp_v.(type) { 636 | case string: 637 | return pat.MatchString(v), nil 638 | default: 639 | return false, errors.New("only string can match with regular expression") 640 | } 641 | } 642 | 643 | func get_lp_v(obj, root interface{}, lp string) (interface{}, error) { 644 | var lp_v interface{} 645 | if strings.HasPrefix(lp, "@.") { 646 | return filter_get_from_explicit_path(obj, lp) 647 | } else if strings.HasPrefix(lp, "$.") { 648 | return filter_get_from_explicit_path(root, lp) 649 | } else { 650 | lp_v = lp 651 | } 652 | return lp_v, nil 653 | } 654 | 655 | func eval_filter(obj, root interface{}, lp, op, rp string) (res bool, err error) { 656 | lp_v, err := get_lp_v(obj, root, lp) 657 | 658 | if op == "exists" { 659 | return lp_v != nil, nil 660 | } else if op == "=~" { 661 | return false, fmt.Errorf("not implemented yet") 662 | } else { 663 | var rp_v interface{} 664 | if strings.HasPrefix(rp, "@.") { 665 | rp_v, err = filter_get_from_explicit_path(obj, rp) 666 | } else if strings.HasPrefix(rp, "$.") { 667 | rp_v, err = filter_get_from_explicit_path(root, rp) 668 | } else { 669 | rp_v = rp 670 | } 671 | //fmt.Printf("lp_v: %v, rp_v: %v\n", lp_v, rp_v) 672 | return cmp_any(lp_v, rp_v, op) 673 | } 674 | } 675 | 676 | func isNumber(o interface{}) bool { 677 | switch v := o.(type) { 678 | case int, int8, int16, int32, int64: 679 | return true 680 | case uint, uint8, uint16, uint32, uint64: 681 | return true 682 | case float32, float64: 683 | return true 684 | case string: 685 | _, err := strconv.ParseFloat(v, 64) 686 | if err == nil { 687 | return true 688 | } else { 689 | return false 690 | } 691 | } 692 | return false 693 | } 694 | 695 | func cmp_any(obj1, obj2 interface{}, op string) (bool, error) { 696 | switch op { 697 | case "<", "<=", "==", ">=", ">": 698 | default: 699 | return false, fmt.Errorf("op should only be <, <=, ==, >= and >") 700 | } 701 | 702 | var exp string 703 | if isNumber(obj1) && isNumber(obj2) { 704 | exp = fmt.Sprintf(`%v %s %v`, obj1, op, obj2) 705 | } else { 706 | exp = fmt.Sprintf(`"%v" %s "%v"`, obj1, op, obj2) 707 | } 708 | //fmt.Println("exp: ", exp) 709 | fset := token.NewFileSet() 710 | res, err := types.Eval(fset, nil, 0, exp) 711 | if err != nil { 712 | return false, err 713 | } 714 | if res.IsValue() == false || (res.Value.String() != "false" && res.Value.String() != "true") { 715 | return false, fmt.Errorf("result should only be true or false") 716 | } 717 | if res.Value.String() == "true" { 718 | return true, nil 719 | } 720 | 721 | return false, nil 722 | } 723 | -------------------------------------------------------------------------------- /jsonpath_test.go: -------------------------------------------------------------------------------- 1 | package jsonpath 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "go/token" 7 | "go/types" 8 | "reflect" 9 | "regexp" 10 | "testing" 11 | ) 12 | 13 | var json_data interface{} 14 | 15 | func init() { 16 | data := ` 17 | { 18 | "store": { 19 | "book": [ 20 | { 21 | "category": "reference", 22 | "author": "Nigel Rees", 23 | "title": "Sayings of the Century", 24 | "price": 8.95 25 | }, 26 | { 27 | "category": "fiction", 28 | "author": "Evelyn Waugh", 29 | "title": "Sword of Honour", 30 | "price": 12.99 31 | }, 32 | { 33 | "category": "fiction", 34 | "author": "Herman Melville", 35 | "title": "Moby Dick", 36 | "isbn": "0-553-21311-3", 37 | "price": 8.99 38 | }, 39 | { 40 | "category": "fiction", 41 | "author": "J. R. R. Tolkien", 42 | "title": "The Lord of the Rings", 43 | "isbn": "0-395-19395-8", 44 | "price": 22.99 45 | } 46 | ], 47 | "bicycle": { 48 | "color": "red", 49 | "price": 19.95 50 | } 51 | }, 52 | "expensive": 10 53 | } 54 | ` 55 | json.Unmarshal([]byte(data), &json_data) 56 | } 57 | 58 | func Test_jsonpath_JsonPathLookup_1(t *testing.T) { 59 | // key from root 60 | res, _ := JsonPathLookup(json_data, "$.expensive") 61 | if res_v, ok := res.(float64); ok != true || res_v != 10.0 { 62 | t.Errorf("expensive should be 10") 63 | } 64 | 65 | // single index 66 | res, _ = JsonPathLookup(json_data, "$.store.book[0].price") 67 | if res_v, ok := res.(float64); ok != true || res_v != 8.95 { 68 | t.Errorf("$.store.book[0].price should be 8.95") 69 | } 70 | 71 | // nagtive single index 72 | res, _ = JsonPathLookup(json_data, "$.store.book[-1].isbn") 73 | if res_v, ok := res.(string); ok != true || res_v != "0-395-19395-8" { 74 | t.Errorf("$.store.book[-1].isbn should be \"0-395-19395-8\"") 75 | } 76 | 77 | // multiple index 78 | res, err := JsonPathLookup(json_data, "$.store.book[0,1].price") 79 | t.Log(err, res) 80 | if res_v, ok := res.([]interface{}); ok != true || res_v[0].(float64) != 8.95 || res_v[1].(float64) != 12.99 { 81 | t.Errorf("exp: [8.95, 12.99], got: %v", res) 82 | } 83 | 84 | // multiple index 85 | res, err = JsonPathLookup(json_data, "$.store.book[0,1].title") 86 | t.Log(err, res) 87 | if res_v, ok := res.([]interface{}); ok != true { 88 | if res_v[0].(string) != "Sayings of the Century" || res_v[1].(string) != "Sword of Honour" { 89 | t.Errorf("title are wrong: %v", res) 90 | } 91 | } 92 | 93 | // full array 94 | res, err = JsonPathLookup(json_data, "$.store.book[0:].price") 95 | t.Log(err, res) 96 | if res_v, ok := res.([]interface{}); ok != true || res_v[0].(float64) != 8.95 || res_v[1].(float64) != 12.99 || res_v[2].(float64) != 8.99 || res_v[3].(float64) != 22.99 { 97 | t.Errorf("exp: [8.95, 12.99, 8.99, 22.99], got: %v", res) 98 | } 99 | 100 | // range 101 | res, err = JsonPathLookup(json_data, "$.store.book[0:1].price") 102 | t.Log(err, res) 103 | if res_v, ok := res.([]interface{}); ok != true || res_v[0].(float64) != 8.95 || res_v[1].(float64) != 12.99 { 104 | t.Errorf("exp: [8.95, 12.99], got: %v", res) 105 | } 106 | 107 | // range 108 | res, err = JsonPathLookup(json_data, "$.store.book[0:1].title") 109 | t.Log(err, res) 110 | if res_v, ok := res.([]interface{}); ok != true { 111 | if res_v[0].(string) != "Sayings of the Century" || res_v[1].(string) != "Sword of Honour" { 112 | t.Errorf("title are wrong: %v", res) 113 | } 114 | } 115 | } 116 | 117 | func Test_jsonpath_JsonPathLookup_filter(t *testing.T) { 118 | res, err := JsonPathLookup(json_data, "$.store.book[?(@.isbn)].isbn") 119 | t.Log(err, res) 120 | 121 | if res_v, ok := res.([]interface{}); ok != true { 122 | if res_v[0].(string) != "0-553-21311-3" || res_v[1].(string) != "0-395-19395-8" { 123 | t.Errorf("error: %v", res) 124 | } 125 | } 126 | 127 | res, err = JsonPathLookup(json_data, "$.store.book[?(@.price > 10)].title") 128 | t.Log(err, res) 129 | if res_v, ok := res.([]interface{}); ok != true { 130 | if res_v[0].(string) != "Sword of Honour" || res_v[1].(string) != "The Lord of the Rings" { 131 | t.Errorf("error: %v", res) 132 | } 133 | } 134 | 135 | res, err = JsonPathLookup(json_data, "$.store.book[?(@.price > 10)]") 136 | t.Log(err, res) 137 | 138 | res, err = JsonPathLookup(json_data, "$.store.book[?(@.price > $.expensive)].price") 139 | t.Log(err, res) 140 | res, err = JsonPathLookup(json_data, "$.store.book[?(@.price < $.expensive)].price") 141 | t.Log(err, res) 142 | } 143 | 144 | func Test_jsonpath_authors_of_all_books(t *testing.T) { 145 | query := "store.book[*].author" 146 | expected := []string{ 147 | "Nigel Rees", 148 | "Evelyn Waugh", 149 | "Herman Melville", 150 | "J. R. R. Tolkien", 151 | } 152 | res, _ := JsonPathLookup(json_data, query) 153 | t.Log(res, expected) 154 | } 155 | 156 | var token_cases = []map[string]interface{}{ 157 | map[string]interface{}{ 158 | "query": "$..author", 159 | "tokens": []string{"$", "*", "author"}, 160 | }, 161 | map[string]interface{}{ 162 | "query": "$.store.*", 163 | "tokens": []string{"$", "store", "*"}, 164 | }, 165 | map[string]interface{}{ 166 | "query": "$.store..price", 167 | "tokens": []string{"$", "store", "*", "price"}, 168 | }, 169 | map[string]interface{}{ 170 | "query": "$.store.book[*].author", 171 | "tokens": []string{"$", "store", "book[*]", "author"}, 172 | }, 173 | map[string]interface{}{ 174 | "query": "$..book[2]", 175 | "tokens": []string{"$", "*", "book[2]"}, 176 | }, 177 | map[string]interface{}{ 178 | "query": "$..book[(@.length-1)]", 179 | "tokens": []string{"$", "*", "book[(@.length-1)]"}, 180 | }, 181 | map[string]interface{}{ 182 | "query": "$..book[0,1]", 183 | "tokens": []string{"$", "*", "book[0,1]"}, 184 | }, 185 | map[string]interface{}{ 186 | "query": "$..book[:2]", 187 | "tokens": []string{"$", "*", "book[:2]"}, 188 | }, 189 | map[string]interface{}{ 190 | "query": "$..book[?(@.isbn)]", 191 | "tokens": []string{"$", "*", "book[?(@.isbn)]"}, 192 | }, 193 | map[string]interface{}{ 194 | "query": "$.store.book[?(@.price < 10)]", 195 | "tokens": []string{"$", "store", "book[?(@.price < 10)]"}, 196 | }, 197 | map[string]interface{}{ 198 | "query": "$..book[?(@.price <= $.expensive)]", 199 | "tokens": []string{"$", "*", "book[?(@.price <= $.expensive)]"}, 200 | }, 201 | map[string]interface{}{ 202 | "query": "$..book[?(@.author =~ /.*REES/i)]", 203 | "tokens": []string{"$", "*", "book[?(@.author =~ /.*REES/i)]"}, 204 | }, 205 | map[string]interface{}{ 206 | "query": "$..book[?(@.author =~ /.*REES\\]/i)]", 207 | "tokens": []string{"$", "*", "book[?(@.author =~ /.*REES\\]/i)]"}, 208 | }, 209 | map[string]interface{}{ 210 | "query": "$..*", 211 | "tokens": []string{"$", "*"}, 212 | }, 213 | map[string]interface{}{ 214 | "query": "$....author", 215 | "tokens": []string{"$", "*", "author"}, 216 | }, 217 | } 218 | 219 | func Test_jsonpath_tokenize(t *testing.T) { 220 | for idx, tcase := range token_cases { 221 | t.Logf("idx[%d], tcase: %v", idx, tcase) 222 | query := tcase["query"].(string) 223 | expected_tokens := tcase["tokens"].([]string) 224 | tokens, err := tokenize(query) 225 | t.Log(err, tokens, expected_tokens) 226 | if len(tokens) != len(expected_tokens) { 227 | t.Errorf("different length: (got)%v, (expected)%v", len(tokens), len(expected_tokens)) 228 | continue 229 | } 230 | for i := 0; i < len(expected_tokens); i++ { 231 | if tokens[i] != expected_tokens[i] { 232 | t.Errorf("not expected: [%d], (got)%v != (expected)%v", i, tokens[i], expected_tokens[i]) 233 | } 234 | } 235 | } 236 | } 237 | 238 | var parse_token_cases = []map[string]interface{}{ 239 | 240 | map[string]interface{}{ 241 | "token": "$", 242 | "op": "root", 243 | "key": "$", 244 | "args": nil, 245 | }, 246 | map[string]interface{}{ 247 | "token": "store", 248 | "op": "key", 249 | "key": "store", 250 | "args": nil, 251 | }, 252 | 253 | // idx -------------------------------------- 254 | map[string]interface{}{ 255 | "token": "book[2]", 256 | "op": "idx", 257 | "key": "book", 258 | "args": []int{2}, 259 | }, 260 | map[string]interface{}{ 261 | "token": "book[-1]", 262 | "op": "idx", 263 | "key": "book", 264 | "args": []int{-1}, 265 | }, 266 | map[string]interface{}{ 267 | "token": "book[0,1]", 268 | "op": "idx", 269 | "key": "book", 270 | "args": []int{0, 1}, 271 | }, 272 | map[string]interface{}{ 273 | "token": "[0]", 274 | "op": "idx", 275 | "key": "", 276 | "args": []int{0}, 277 | }, 278 | 279 | // range ------------------------------------ 280 | map[string]interface{}{ 281 | "token": "book[1:-1]", 282 | "op": "range", 283 | "key": "book", 284 | "args": [2]interface{}{1, -1}, 285 | }, 286 | map[string]interface{}{ 287 | "token": "book[*]", 288 | "op": "range", 289 | "key": "book", 290 | "args": [2]interface{}{nil, nil}, 291 | }, 292 | map[string]interface{}{ 293 | "token": "book[:2]", 294 | "op": "range", 295 | "key": "book", 296 | "args": [2]interface{}{nil, 2}, 297 | }, 298 | map[string]interface{}{ 299 | "token": "book[-2:]", 300 | "op": "range", 301 | "key": "book", 302 | "args": [2]interface{}{-2, nil}, 303 | }, 304 | 305 | // filter -------------------------------- 306 | map[string]interface{}{ 307 | "token": "book[?( @.isbn )]", 308 | "op": "filter", 309 | "key": "book", 310 | "args": "@.isbn", 311 | }, 312 | map[string]interface{}{ 313 | "token": "book[?(@.price < 10)]", 314 | "op": "filter", 315 | "key": "book", 316 | "args": "@.price < 10", 317 | }, 318 | map[string]interface{}{ 319 | "token": "book[?(@.price <= $.expensive)]", 320 | "op": "filter", 321 | "key": "book", 322 | "args": "@.price <= $.expensive", 323 | }, 324 | map[string]interface{}{ 325 | "token": "book[?(@.author =~ /.*REES/i)]", 326 | "op": "filter", 327 | "key": "book", 328 | "args": "@.author =~ /.*REES/i", 329 | }, 330 | map[string]interface{}{ 331 | "token": "*", 332 | "op": "scan", 333 | "key": "*", 334 | "args": nil, 335 | }, 336 | } 337 | 338 | func Test_jsonpath_parse_token(t *testing.T) { 339 | for idx, tcase := range parse_token_cases { 340 | t.Logf("[%d] - tcase: %v", idx, tcase) 341 | token := tcase["token"].(string) 342 | exp_op := tcase["op"].(string) 343 | exp_key := tcase["key"].(string) 344 | exp_args := tcase["args"] 345 | 346 | op, key, args, err := parse_token(token) 347 | t.Logf("[%d] - expected: op: %v, key: %v, args: %v\n", idx, exp_op, exp_key, exp_args) 348 | t.Logf("[%d] - got: err: %v, op: %v, key: %v, args: %v\n", idx, err, op, key, args) 349 | if op != exp_op { 350 | t.Errorf("ERROR: op(%v) != exp_op(%v)", op, exp_op) 351 | return 352 | } 353 | if key != exp_key { 354 | t.Errorf("ERROR: key(%v) != exp_key(%v)", key, exp_key) 355 | return 356 | } 357 | 358 | if op == "idx" { 359 | if args_v, ok := args.([]int); ok == true { 360 | for i, v := range args_v { 361 | if v != exp_args.([]int)[i] { 362 | t.Errorf("ERROR: different args: [%d], (got)%v != (exp)%v", i, v, exp_args.([]int)[i]) 363 | return 364 | } 365 | } 366 | } else { 367 | t.Errorf("ERROR: idx op should expect args:[]int{} in return, (got)%v", reflect.TypeOf(args)) 368 | return 369 | } 370 | } 371 | 372 | if op == "range" { 373 | if args_v, ok := args.([2]interface{}); ok == true { 374 | fmt.Println(args_v) 375 | exp_from := exp_args.([2]interface{})[0] 376 | exp_to := exp_args.([2]interface{})[1] 377 | if args_v[0] != exp_from { 378 | t.Errorf("(from)%v != (exp_from)%v", args_v[0], exp_from) 379 | return 380 | } 381 | if args_v[1] != exp_to { 382 | t.Errorf("(to)%v != (exp_to)%v", args_v[1], exp_to) 383 | return 384 | } 385 | } else { 386 | t.Errorf("ERROR: range op should expect args:[2]interface{}, (got)%v", reflect.TypeOf(args)) 387 | return 388 | } 389 | } 390 | 391 | if op == "filter" { 392 | if args_v, ok := args.(string); ok == true { 393 | fmt.Println(args_v) 394 | if exp_args.(string) != args_v { 395 | t.Errorf("len(args) not expected: (got)%v != (exp)%v", len(args_v), len(exp_args.([]string))) 396 | return 397 | } 398 | 399 | } else { 400 | t.Errorf("ERROR: filter op should expect args:[]string{}, (got)%v", reflect.TypeOf(args)) 401 | } 402 | } 403 | } 404 | } 405 | 406 | func Test_jsonpath_get_key(t *testing.T) { 407 | obj := map[string]interface{}{ 408 | "key": 1, 409 | } 410 | res, err := get_key(obj, "key") 411 | fmt.Println(err, res) 412 | if err != nil { 413 | t.Errorf("failed to get key: %v", err) 414 | return 415 | } 416 | if res.(int) != 1 { 417 | t.Errorf("key value is not 1: %v", res) 418 | return 419 | } 420 | 421 | res, err = get_key(obj, "hah") 422 | fmt.Println(err, res) 423 | if err == nil { 424 | t.Errorf("key error not raised") 425 | return 426 | } 427 | if res != nil { 428 | t.Errorf("key error should return nil res: %v", res) 429 | return 430 | } 431 | 432 | obj2 := 1 433 | res, err = get_key(obj2, "key") 434 | fmt.Println(err, res) 435 | if err == nil { 436 | 437 | t.Errorf("object is not map error not raised") 438 | return 439 | } 440 | obj3 := map[string]string{"key": "hah"} 441 | res, err = get_key(obj3, "key") 442 | if res_v, ok := res.(string); ok != true || res_v != "hah" { 443 | fmt.Println(err, res) 444 | t.Errorf("map[string]string support failed") 445 | } 446 | 447 | obj4 := []map[string]interface{}{ 448 | map[string]interface{}{ 449 | "a": 1, 450 | }, 451 | map[string]interface{}{ 452 | "a": 2, 453 | }, 454 | } 455 | res, err = get_key(obj4, "a") 456 | fmt.Println(err, res) 457 | } 458 | 459 | func Test_jsonpath_get_idx(t *testing.T) { 460 | obj := []interface{}{1, 2, 3, 4} 461 | res, err := get_idx(obj, 0) 462 | fmt.Println(err, res) 463 | if err != nil { 464 | t.Errorf("failed to get_idx(obj,0): %v", err) 465 | return 466 | } 467 | if v, ok := res.(int); ok != true || v != 1 { 468 | t.Errorf("failed to get int 1") 469 | } 470 | 471 | res, err = get_idx(obj, 2) 472 | fmt.Println(err, res) 473 | if v, ok := res.(int); ok != true || v != 3 { 474 | t.Errorf("failed to get int 3") 475 | } 476 | res, err = get_idx(obj, 4) 477 | fmt.Println(err, res) 478 | if err == nil { 479 | t.Errorf("index out of range error not raised") 480 | return 481 | } 482 | 483 | res, err = get_idx(obj, -1) 484 | fmt.Println(err, res) 485 | if err != nil { 486 | t.Errorf("failed to get_idx(obj, -1): %v", err) 487 | return 488 | } 489 | if v, ok := res.(int); ok != true || v != 4 { 490 | t.Errorf("failed to get int 4") 491 | } 492 | 493 | res, err = get_idx(obj, -4) 494 | fmt.Println(err, res) 495 | if v, ok := res.(int); ok != true || v != 1 { 496 | t.Errorf("failed to get int 1") 497 | } 498 | 499 | res, err = get_idx(obj, -5) 500 | fmt.Println(err, res) 501 | if err == nil { 502 | t.Errorf("index out of range error not raised") 503 | return 504 | } 505 | 506 | obj1 := 1 507 | res, err = get_idx(obj1, 1) 508 | if err == nil { 509 | t.Errorf("object is not Slice error not raised") 510 | return 511 | } 512 | 513 | obj2 := []int{1, 2, 3, 4} 514 | res, err = get_idx(obj2, 0) 515 | fmt.Println(err, res) 516 | if err != nil { 517 | t.Errorf("failed to get_idx(obj2,0): %v", err) 518 | return 519 | } 520 | if v, ok := res.(int); ok != true || v != 1 { 521 | t.Errorf("failed to get int 1") 522 | } 523 | } 524 | 525 | func Test_jsonpath_get_range(t *testing.T) { 526 | obj := []int{1, 2, 3, 4, 5} 527 | 528 | res, err := get_range(obj, 0, 2) 529 | fmt.Println(err, res) 530 | if err != nil { 531 | t.Errorf("failed to get_range: %v", err) 532 | } 533 | if res.([]int)[0] != 1 || res.([]int)[1] != 2 { 534 | t.Errorf("failed get_range: %v, expect: [1,2]", res) 535 | } 536 | 537 | obj1 := []interface{}{1, 2, 3, 4, 5} 538 | res, err = get_range(obj1, 3, -1) 539 | fmt.Println(err, res) 540 | if err != nil { 541 | t.Errorf("failed to get_range: %v", err) 542 | } 543 | fmt.Println(res.([]interface{})) 544 | if res.([]interface{})[0] != 4 || res.([]interface{})[1] != 5 { 545 | t.Errorf("failed get_range: %v, expect: [4,5]", res) 546 | } 547 | 548 | res, err = get_range(obj1, nil, 2) 549 | t.Logf("err: %v, res:%v", err, res) 550 | if res.([]interface{})[0] != 1 || res.([]interface{})[1] != 2 { 551 | t.Errorf("from support nil failed: %v", res) 552 | } 553 | 554 | res, err = get_range(obj1, nil, nil) 555 | t.Logf("err: %v, res:%v", err, res) 556 | if len(res.([]interface{})) != 5 { 557 | t.Errorf("from, to both nil failed") 558 | } 559 | 560 | res, err = get_range(obj1, -2, nil) 561 | t.Logf("err: %v, res:%v", err, res) 562 | if res.([]interface{})[0] != 4 || res.([]interface{})[1] != 5 { 563 | t.Errorf("from support nil failed: %v", res) 564 | } 565 | 566 | obj2 := 2 567 | res, err = get_range(obj2, 0, 1) 568 | fmt.Println(err, res) 569 | if err == nil { 570 | t.Errorf("object is Slice error not raised") 571 | } 572 | } 573 | 574 | func Test_jsonpath_types_eval(t *testing.T) { 575 | fset := token.NewFileSet() 576 | res, err := types.Eval(fset, nil, 0, "1 < 2") 577 | fmt.Println(err, res, res.Type, res.Value, res.IsValue()) 578 | } 579 | 580 | var tcase_parse_filter = []map[string]interface{}{ 581 | // 0 582 | map[string]interface{}{ 583 | "filter": "@.isbn", 584 | "exp_lp": "@.isbn", 585 | "exp_op": "exists", 586 | "exp_rp": "", 587 | "exp_err": nil, 588 | }, 589 | // 1 590 | map[string]interface{}{ 591 | "filter": "@.price < 10", 592 | "exp_lp": "@.price", 593 | "exp_op": "<", 594 | "exp_rp": "10", 595 | "exp_err": nil, 596 | }, 597 | // 2 598 | map[string]interface{}{ 599 | "filter": "@.price <= $.expensive", 600 | "exp_lp": "@.price", 601 | "exp_op": "<=", 602 | "exp_rp": "$.expensive", 603 | "exp_err": nil, 604 | }, 605 | // 3 606 | map[string]interface{}{ 607 | "filter": "@.author =~ /.*REES/i", 608 | "exp_lp": "@.author", 609 | "exp_op": "=~", 610 | "exp_rp": "/.*REES/i", 611 | "exp_err": nil, 612 | }, 613 | 614 | // 4 615 | { 616 | "filter": "@.author == 'Nigel Rees'", 617 | "exp_lp": "@.author", 618 | "exp_op": "==", 619 | "exp_rp": "Nigel Rees", 620 | }, 621 | } 622 | 623 | func Test_jsonpath_parse_filter(t *testing.T) { 624 | 625 | //for _, tcase := range tcase_parse_filter[4:] { 626 | for _, tcase := range tcase_parse_filter { 627 | lp, op, rp, _ := parse_filter(tcase["filter"].(string)) 628 | t.Log(tcase) 629 | t.Logf("lp: %v, op: %v, rp: %v", lp, op, rp) 630 | if lp != tcase["exp_lp"].(string) { 631 | t.Errorf("%s(got) != %v(exp_lp)", lp, tcase["exp_lp"]) 632 | return 633 | } 634 | if op != tcase["exp_op"].(string) { 635 | t.Errorf("%s(got) != %v(exp_op)", op, tcase["exp_op"]) 636 | return 637 | } 638 | if rp != tcase["exp_rp"].(string) { 639 | t.Errorf("%s(got) != %v(exp_rp)", rp, tcase["exp_rp"]) 640 | return 641 | } 642 | } 643 | } 644 | 645 | var tcase_filter_get_from_explicit_path = []map[string]interface{}{ 646 | // 0 647 | map[string]interface{}{ 648 | // 0 {"a": 1} 649 | "obj": map[string]interface{}{"a": 1}, 650 | "query": "$.a", 651 | "expected": 1, 652 | }, 653 | map[string]interface{}{ 654 | // 1 {"a":{"b":1}} 655 | "obj": map[string]interface{}{"a": map[string]interface{}{"b": 1}}, 656 | "query": "$.a.b", 657 | "expected": 1, 658 | }, 659 | map[string]interface{}{ 660 | // 2 {"a": {"b":1, "c":2}} 661 | "obj": map[string]interface{}{"a": map[string]interface{}{"b": 1, "c": 2}}, 662 | "query": "$.a.c", 663 | "expected": 2, 664 | }, 665 | map[string]interface{}{ 666 | // 3 {"a": {"b":1}, "b": 2} 667 | "obj": map[string]interface{}{"a": map[string]interface{}{"b": 1}, "b": 2}, 668 | "query": "$.a.b", 669 | "expected": 1, 670 | }, 671 | map[string]interface{}{ 672 | // 4 {"a": {"b":1}, "b": 2} 673 | "obj": map[string]interface{}{"a": map[string]interface{}{"b": 1}, "b": 2}, 674 | "query": "$.b", 675 | "expected": 2, 676 | }, 677 | map[string]interface{}{ 678 | // 5 {'a': ['b',1]} 679 | "obj": map[string]interface{}{"a": []interface{}{"b", 1}}, 680 | "query": "$.a[0]", 681 | "expected": "b", 682 | }, 683 | } 684 | 685 | func Test_jsonpath_filter_get_from_explicit_path(t *testing.T) { 686 | 687 | for idx, tcase := range tcase_filter_get_from_explicit_path { 688 | obj := tcase["obj"] 689 | query := tcase["query"].(string) 690 | expected := tcase["expected"] 691 | 692 | res, err := filter_get_from_explicit_path(obj, query) 693 | t.Log(idx, err, res) 694 | if err != nil { 695 | t.Errorf("flatten_cases: failed: [%d] %v", idx, err) 696 | } 697 | // t.Logf("typeof(res): %v, typeof(expected): %v", reflect.TypeOf(res), reflect.TypeOf(expected)) 698 | if reflect.TypeOf(res) != reflect.TypeOf(expected) { 699 | t.Errorf("different type: (res)%v != (expected)%v", reflect.TypeOf(res), reflect.TypeOf(expected)) 700 | continue 701 | } 702 | switch expected.(type) { 703 | case map[string]interface{}: 704 | if len(res.(map[string]interface{})) != len(expected.(map[string]interface{})) { 705 | t.Errorf("two map with differnt lenght: (res)%v, (expected)%v", res, expected) 706 | } 707 | default: 708 | if res != expected { 709 | t.Errorf("res(%v) != expected(%v)", res, expected) 710 | } 711 | } 712 | } 713 | } 714 | 715 | var tcase_eval_filter = []map[string]interface{}{ 716 | // 0 717 | map[string]interface{}{ 718 | "obj": map[string]interface{}{"a": 1}, 719 | "root": map[string]interface{}{}, 720 | "lp": "@.a", 721 | "op": "exists", 722 | "rp": "", 723 | "exp": true, 724 | }, 725 | // 1 726 | map[string]interface{}{ 727 | "obj": map[string]interface{}{"a": 1}, 728 | "root": map[string]interface{}{}, 729 | "lp": "@.b", 730 | "op": "exists", 731 | "rp": "", 732 | "exp": false, 733 | }, 734 | // 2 735 | map[string]interface{}{ 736 | "obj": map[string]interface{}{"a": 1}, 737 | "root": map[string]interface{}{"a": 1}, 738 | "lp": "$.a", 739 | "op": "exists", 740 | "rp": "", 741 | "exp": true, 742 | }, 743 | // 3 744 | map[string]interface{}{ 745 | "obj": map[string]interface{}{"a": 1}, 746 | "root": map[string]interface{}{"a": 1}, 747 | "lp": "$.b", 748 | "op": "exists", 749 | "rp": "", 750 | "exp": false, 751 | }, 752 | // 4 753 | map[string]interface{}{ 754 | "obj": map[string]interface{}{"a": 1, "b": map[string]interface{}{"c": 2}}, 755 | "root": map[string]interface{}{"a": 1, "b": map[string]interface{}{"c": 2}}, 756 | "lp": "$.b.c", 757 | "op": "exists", 758 | "rp": "", 759 | "exp": true, 760 | }, 761 | // 5 762 | map[string]interface{}{ 763 | "obj": map[string]interface{}{"a": 1, "b": map[string]interface{}{"c": 2}}, 764 | "root": map[string]interface{}{}, 765 | "lp": "$.b.a", 766 | "op": "exists", 767 | "rp": "", 768 | "exp": false, 769 | }, 770 | 771 | // 6 772 | map[string]interface{}{ 773 | "obj": map[string]interface{}{"a": 3}, 774 | "root": map[string]interface{}{"a": 3}, 775 | "lp": "$.a", 776 | "op": ">", 777 | "rp": "1", 778 | "exp": true, 779 | }, 780 | } 781 | 782 | func Test_jsonpath_eval_filter(t *testing.T) { 783 | for idx, tcase := range tcase_eval_filter[1:] { 784 | fmt.Println("------------------------------") 785 | obj := tcase["obj"].(map[string]interface{}) 786 | root := tcase["root"].(map[string]interface{}) 787 | lp := tcase["lp"].(string) 788 | op := tcase["op"].(string) 789 | rp := tcase["rp"].(string) 790 | exp := tcase["exp"].(bool) 791 | t.Logf("idx: %v, lp: %v, op: %v, rp: %v, exp: %v", idx, lp, op, rp, exp) 792 | got, err := eval_filter(obj, root, lp, op, rp) 793 | 794 | if err != nil { 795 | t.Errorf("idx: %v, failed to eval: %v", idx, err) 796 | return 797 | } 798 | if got != exp { 799 | t.Errorf("idx: %v, %v(got) != %v(exp)", idx, got, exp) 800 | } 801 | 802 | } 803 | } 804 | 805 | var ( 806 | ifc1 interface{} = "haha" 807 | ifc2 interface{} = "ha ha" 808 | ) 809 | var tcase_cmp_any = []map[string]interface{}{ 810 | 811 | map[string]interface{}{ 812 | "obj1": 1, 813 | "obj2": 1, 814 | "op": "==", 815 | "exp": true, 816 | "err": nil, 817 | }, 818 | map[string]interface{}{ 819 | "obj1": 1, 820 | "obj2": 2, 821 | "op": "==", 822 | "exp": false, 823 | "err": nil, 824 | }, 825 | map[string]interface{}{ 826 | "obj1": 1.1, 827 | "obj2": 2.0, 828 | "op": "<", 829 | "exp": true, 830 | "err": nil, 831 | }, 832 | map[string]interface{}{ 833 | "obj1": "1", 834 | "obj2": "2.0", 835 | "op": "<", 836 | "exp": true, 837 | "err": nil, 838 | }, 839 | map[string]interface{}{ 840 | "obj1": "1", 841 | "obj2": "2.0", 842 | "op": ">", 843 | "exp": false, 844 | "err": nil, 845 | }, 846 | map[string]interface{}{ 847 | "obj1": 1, 848 | "obj2": 2, 849 | "op": "=~", 850 | "exp": false, 851 | "err": "op should only be <, <=, ==, >= and >", 852 | }, { 853 | "obj1": ifc1, 854 | "obj2": ifc1, 855 | "op": "==", 856 | "exp": true, 857 | "err": nil, 858 | }, { 859 | "obj1": ifc2, 860 | "obj2": ifc2, 861 | "op": "==", 862 | "exp": true, 863 | "err": nil, 864 | }, { 865 | "obj1": 20, 866 | "obj2": "100", 867 | "op": ">", 868 | "exp": false, 869 | "err": nil, 870 | }, 871 | } 872 | 873 | func Test_jsonpath_cmp_any(t *testing.T) { 874 | for idx, tcase := range tcase_cmp_any { 875 | //for idx, tcase := range tcase_cmp_any[8:] { 876 | t.Logf("idx: %v, %v %v %v, exp: %v", idx, tcase["obj1"], tcase["op"], tcase["obj2"], tcase["exp"]) 877 | res, err := cmp_any(tcase["obj1"], tcase["obj2"], tcase["op"].(string)) 878 | exp := tcase["exp"].(bool) 879 | exp_err := tcase["err"] 880 | if exp_err != nil { 881 | if err == nil { 882 | t.Errorf("idx: %d error not raised: %v(exp)", idx, exp_err) 883 | break 884 | } 885 | } else { 886 | if err != nil { 887 | t.Errorf("idx: %v, error: %v", idx, err) 888 | break 889 | } 890 | } 891 | if res != exp { 892 | t.Errorf("idx: %v, %v(got) != %v(exp)", idx, res, exp) 893 | break 894 | } 895 | } 896 | } 897 | 898 | func Test_jsonpath_string_equal(t *testing.T) { 899 | data := `{ 900 | "store": { 901 | "book": [ 902 | { 903 | "category": "reference", 904 | "author": "Nigel Rees", 905 | "title": "Sayings of the Century", 906 | "price": 8.95 907 | }, 908 | { 909 | "category": "fiction", 910 | "author": "Evelyn Waugh", 911 | "title": "Sword of Honour", 912 | "price": 12.99 913 | }, 914 | { 915 | "category": "fiction", 916 | "author": "Herman Melville", 917 | "title": "Moby Dick", 918 | "isbn": "0-553-21311-3", 919 | "price": 8.99 920 | }, 921 | { 922 | "category": "fiction", 923 | "author": "J. R. R. Tolkien", 924 | "title": "The Lord of the Rings", 925 | "isbn": "0-395-19395-8", 926 | "price": 22.99 927 | } 928 | ], 929 | "bicycle": { 930 | "color": "red", 931 | "price": 19.95 932 | } 933 | }, 934 | "expensive": 10 935 | }` 936 | 937 | var j interface{} 938 | 939 | json.Unmarshal([]byte(data), &j) 940 | 941 | res, err := JsonPathLookup(j, "$.store.book[?(@.author == 'Nigel Rees')].price") 942 | t.Log(res, err) 943 | if err != nil { 944 | t.Fatalf("err: %v", err) 945 | } 946 | if fmt.Sprintf("%v", res) != "[8.95]" { 947 | t.Fatalf("not the same: %v", res) 948 | } 949 | } 950 | 951 | func Test_jsonpath_null_in_the_middle(t *testing.T) { 952 | data := `{ 953 | "head_commit": null, 954 | } 955 | ` 956 | 957 | var j interface{} 958 | 959 | json.Unmarshal([]byte(data), &j) 960 | 961 | res, err := JsonPathLookup(j, "$.head_commit.author.username") 962 | t.Log(res, err) 963 | } 964 | 965 | func Test_jsonpath_num_cmp(t *testing.T) { 966 | data := `{ 967 | "books": [ 968 | { "name": "My First Book", "price": 10 }, 969 | { "name": "My Second Book", "price": 20 } 970 | ] 971 | }` 972 | var j interface{} 973 | json.Unmarshal([]byte(data), &j) 974 | res, err := JsonPathLookup(j, "$.books[?(@.price > 100)].name") 975 | if err != nil { 976 | t.Fatal(err) 977 | } 978 | arr := res.([]interface{}) 979 | if len(arr) != 0 { 980 | t.Fatal("should return [], got: ", arr) 981 | } 982 | 983 | } 984 | 985 | func BenchmarkJsonPathLookupCompiled(b *testing.B) { 986 | c, err := Compile("$.store.book[0].price") 987 | if err != nil { 988 | b.Fatalf("%v", err) 989 | } 990 | for n := 0; n < b.N; n++ { 991 | res, err := c.Lookup(json_data) 992 | if res_v, ok := res.(float64); ok != true || res_v != 8.95 { 993 | b.Errorf("$.store.book[0].price should be 8.95") 994 | } 995 | if err != nil { 996 | b.Errorf("Unexpected error: %v", err) 997 | } 998 | } 999 | } 1000 | 1001 | func BenchmarkJsonPathLookup(b *testing.B) { 1002 | for n := 0; n < b.N; n++ { 1003 | res, err := JsonPathLookup(json_data, "$.store.book[0].price") 1004 | if res_v, ok := res.(float64); ok != true || res_v != 8.95 { 1005 | b.Errorf("$.store.book[0].price should be 8.95") 1006 | } 1007 | if err != nil { 1008 | b.Errorf("Unexpected error: %v", err) 1009 | } 1010 | } 1011 | } 1012 | 1013 | func BenchmarkJsonPathLookup_0(b *testing.B) { 1014 | for i := 0; i < b.N; i++ { 1015 | JsonPathLookup(json_data, "$.expensive") 1016 | } 1017 | } 1018 | 1019 | func BenchmarkJsonPathLookup_1(b *testing.B) { 1020 | for i := 0; i < b.N; i++ { 1021 | JsonPathLookup(json_data, "$.store.book[0].price") 1022 | } 1023 | } 1024 | 1025 | func BenchmarkJsonPathLookup_2(b *testing.B) { 1026 | for i := 0; i < b.N; i++ { 1027 | JsonPathLookup(json_data, "$.store.book[-1].price") 1028 | } 1029 | } 1030 | 1031 | func BenchmarkJsonPathLookup_3(b *testing.B) { 1032 | for i := 0; i < b.N; i++ { 1033 | JsonPathLookup(json_data, "$.store.book[0,1].price") 1034 | } 1035 | } 1036 | 1037 | func BenchmarkJsonPathLookup_4(b *testing.B) { 1038 | for i := 0; i < b.N; i++ { 1039 | JsonPathLookup(json_data, "$.store.book[0:2].price") 1040 | } 1041 | } 1042 | 1043 | func BenchmarkJsonPathLookup_5(b *testing.B) { 1044 | for i := 0; i < b.N; i++ { 1045 | JsonPathLookup(json_data, "$.store.book[?(@.isbn)].price") 1046 | } 1047 | } 1048 | 1049 | func BenchmarkJsonPathLookup_6(b *testing.B) { 1050 | for i := 0; i < b.N; i++ { 1051 | JsonPathLookup(json_data, "$.store.book[?(@.price > 10)].title") 1052 | } 1053 | } 1054 | 1055 | func BenchmarkJsonPathLookup_7(b *testing.B) { 1056 | for i := 0; i < b.N; i++ { 1057 | JsonPathLookup(json_data, "$.store.book[?(@.price < $.expensive)].price") 1058 | } 1059 | } 1060 | 1061 | func BenchmarkJsonPathLookup_8(b *testing.B) { 1062 | for i := 0; i < b.N; i++ { 1063 | JsonPathLookup(json_data, "$.store.book[:].price") 1064 | } 1065 | } 1066 | 1067 | func BenchmarkJsonPathLookup_9(b *testing.B) { 1068 | for i := 0; i < b.N; i++ { 1069 | JsonPathLookup(json_data, "$.store.book[?(@.author == 'Nigel Rees')].price") 1070 | } 1071 | } 1072 | 1073 | func BenchmarkJsonPathLookup_10(b *testing.B) { 1074 | for i := 0; i < b.N; i++ { 1075 | JsonPathLookup(json_data, "$.store.book[?(@.author =~ /(?i).*REES/)].price") 1076 | } 1077 | } 1078 | 1079 | func TestReg(t *testing.T) { 1080 | r := regexp.MustCompile(`(?U).*REES`) 1081 | t.Log(r) 1082 | t.Log(r.Match([]byte(`Nigel Rees`))) 1083 | 1084 | res, err := JsonPathLookup(json_data, "$.store.book[?(@.author =~ /(?i).*REES/ )].author") 1085 | t.Log(err, res) 1086 | 1087 | author := res.([]interface{})[0].(string) 1088 | t.Log(author) 1089 | if author != "Nigel Rees" { 1090 | t.Fatal("should be `Nigel Rees` but got: ", author) 1091 | } 1092 | } 1093 | 1094 | var tcases_reg_op = []struct { 1095 | Line string 1096 | Exp string 1097 | Err bool 1098 | }{ 1099 | {``, ``, true}, 1100 | {`xxx`, ``, true}, 1101 | {`/xxx`, ``, true}, 1102 | {`xxx/`, ``, true}, 1103 | {`'/xxx/'`, ``, true}, 1104 | {`"/xxx/"`, ``, true}, 1105 | {`/xxx/`, `xxx`, false}, 1106 | {`/π/`, `π`, false}, 1107 | } 1108 | 1109 | func TestRegOp(t *testing.T) { 1110 | for idx, tcase := range tcases_reg_op { 1111 | fmt.Println("idx: ", idx, "tcase: ", tcase) 1112 | res, err := regFilterCompile(tcase.Line) 1113 | if tcase.Err == true { 1114 | if err == nil { 1115 | t.Fatal("expect err but got nil") 1116 | } 1117 | } else { 1118 | if res == nil || res.String() != tcase.Exp { 1119 | t.Fatal("different. res:", res) 1120 | } 1121 | } 1122 | } 1123 | } 1124 | 1125 | func Test_jsonpath_rootnode_is_array(t *testing.T) { 1126 | data := `[{ 1127 | "test": 12.34 1128 | }, { 1129 | "test": 13.34 1130 | }, { 1131 | "test": 14.34 1132 | }] 1133 | ` 1134 | 1135 | var j interface{} 1136 | 1137 | err := json.Unmarshal([]byte(data), &j) 1138 | if err != nil { 1139 | t.Fatal(err) 1140 | } 1141 | 1142 | res, err := JsonPathLookup(j, "$[0].test") 1143 | t.Log(res, err) 1144 | if err != nil { 1145 | t.Fatal("err:", err) 1146 | } 1147 | if res == nil || res.(float64) != 12.34 { 1148 | t.Fatalf("different: res:%v, exp: 123", res) 1149 | } 1150 | } 1151 | 1152 | func Test_jsonpath_rootnode_is_array_range(t *testing.T) { 1153 | data := `[{ 1154 | "test": 12.34 1155 | }, { 1156 | "test": 13.34 1157 | }, { 1158 | "test": 14.34 1159 | }] 1160 | ` 1161 | 1162 | var j interface{} 1163 | 1164 | err := json.Unmarshal([]byte(data), &j) 1165 | if err != nil { 1166 | t.Fatal(err) 1167 | } 1168 | 1169 | res, err := JsonPathLookup(j, "$[:1].test") 1170 | t.Log(res, err) 1171 | if err != nil { 1172 | t.Fatal("err:", err) 1173 | } 1174 | if res == nil { 1175 | t.Fatal("res is nil") 1176 | } 1177 | ares := res.([]interface{}) 1178 | for idx, v := range ares { 1179 | t.Logf("idx: %v, v: %v", idx, v) 1180 | } 1181 | if len(ares) != 2 { 1182 | t.Fatal("len is not 2. got: %v", len(ares)) 1183 | } 1184 | if ares[0].(float64) != 12.34 { 1185 | t.Fatal("idx: 0, should be 12.34. got: %v", ares[0]) 1186 | } 1187 | if ares[1].(float64) != 13.34 { 1188 | t.Fatal("idx: 0, should be 12.34. got: %v", ares[1]) 1189 | } 1190 | } 1191 | 1192 | func Test_jsonpath_rootnode_is_nested_array(t *testing.T) { 1193 | data := `[ [ {"test":1.1}, {"test":2.1} ], [ {"test":3.1}, {"test":4.1} ] ]` 1194 | 1195 | var j interface{} 1196 | 1197 | err := json.Unmarshal([]byte(data), &j) 1198 | if err != nil { 1199 | t.Fatal(err) 1200 | } 1201 | 1202 | res, err := JsonPathLookup(j, "$[0].[0].test") 1203 | t.Log(res, err) 1204 | if err != nil { 1205 | t.Fatal("err:", err) 1206 | } 1207 | if res == nil || res.(float64) != 1.1 { 1208 | t.Fatalf("different: res:%v, exp: 123", res) 1209 | } 1210 | } 1211 | 1212 | func Test_jsonpath_rootnode_is_nested_array_range(t *testing.T) { 1213 | data := `[ [ {"test":1.1}, {"test":2.1} ], [ {"test":3.1}, {"test":4.1} ] ]` 1214 | 1215 | var j interface{} 1216 | 1217 | err := json.Unmarshal([]byte(data), &j) 1218 | if err != nil { 1219 | t.Fatal(err) 1220 | } 1221 | 1222 | res, err := JsonPathLookup(j, "$[:1].[0].test") 1223 | t.Log(res, err) 1224 | if err != nil { 1225 | t.Fatal("err:", err) 1226 | } 1227 | if res == nil { 1228 | t.Fatal("res is nil") 1229 | } 1230 | ares := res.([]interface{}) 1231 | for idx, v := range ares { 1232 | t.Logf("idx: %v, v: %v", idx, v) 1233 | } 1234 | if len(ares) != 2 { 1235 | t.Fatal("len is not 2. got: %v", len(ares)) 1236 | } 1237 | 1238 | //FIXME: `$[:1].[0].test` got wrong result 1239 | //if ares[0].(float64) != 1.1 { 1240 | // t.Fatal("idx: 0, should be 1.1, got: %v", ares[0]) 1241 | //} 1242 | //if ares[1].(float64) != 3.1 { 1243 | // t.Fatal("idx: 0, should be 3.1, got: %v", ares[1]) 1244 | //} 1245 | } 1246 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | JsonPath 2 | ---------------- 3 | 4 | ![Build Status](https://travis-ci.org/oliveagle/jsonpath.svg?branch=master) 5 | 6 | A golang implementation of JsonPath syntax. 7 | follow the majority rules in http://goessner.net/articles/JsonPath/ 8 | but also with some minor differences. 9 | 10 | this library is till bleeding edge, so use it at your own risk. :D 11 | 12 | **Golang Version Required**: 1.5+ 13 | 14 | Get Started 15 | ------------ 16 | 17 | ```bash 18 | go get github.com/oliveagle/jsonpath 19 | ``` 20 | 21 | example code: 22 | 23 | ```go 24 | import ( 25 | "github.com/oliveagle/jsonpath" 26 | "encoding/json" 27 | ) 28 | 29 | var json_data interface{} 30 | json.Unmarshal([]byte(data), &json_data) 31 | 32 | res, err := jsonpath.JsonPathLookup(json_data, "$.expensive") 33 | 34 | //or reuse lookup pattern 35 | pat, _ := jsonpath.Compile(`$.store.book[?(@.price < $.expensive)].price`) 36 | res, err := pat.Lookup(json_data) 37 | ``` 38 | 39 | Operators 40 | -------- 41 | referenced from github.com/jayway/JsonPath 42 | 43 | | Operator | Supported | Description | 44 | | ---- | :---: | ---------- | 45 | | $ | Y | The root element to query. This starts all path expressions. | 46 | | @ | Y | The current node being processed by a filter predicate. | 47 | | * | X | Wildcard. Available anywhere a name or numeric are required. | 48 | | .. | X | Deep scan. Available anywhere a name is required. | 49 | | . | Y | Dot-notated child | 50 | | ['' (, '')] | X | Bracket-notated child or children | 51 | | [ (, )] | Y | Array index or indexes | 52 | | [start:end] | Y | Array slice operator | 53 | | [?()] | Y | Filter expression. Expression must evaluate to a boolean value. | 54 | 55 | Examples 56 | -------- 57 | given these example data. 58 | 59 | ```javascript 60 | { 61 | "store": { 62 | "book": [ 63 | { 64 | "category": "reference", 65 | "author": "Nigel Rees", 66 | "title": "Sayings of the Century", 67 | "price": 8.95 68 | }, 69 | { 70 | "category": "fiction", 71 | "author": "Evelyn Waugh", 72 | "title": "Sword of Honour", 73 | "price": 12.99 74 | }, 75 | { 76 | "category": "fiction", 77 | "author": "Herman Melville", 78 | "title": "Moby Dick", 79 | "isbn": "0-553-21311-3", 80 | "price": 8.99 81 | }, 82 | { 83 | "category": "fiction", 84 | "author": "J. R. R. Tolkien", 85 | "title": "The Lord of the Rings", 86 | "isbn": "0-395-19395-8", 87 | "price": 22.99 88 | } 89 | ], 90 | "bicycle": { 91 | "color": "red", 92 | "price": 19.95 93 | } 94 | }, 95 | "expensive": 10 96 | } 97 | ``` 98 | example json path syntax. 99 | ---- 100 | 101 | | jsonpath | result| 102 | | :--------- | :-------| 103 | | $.expensive | 10| 104 | | $.store.book[0].price | 8.95| 105 | | $.store.book[-1].isbn | "0-395-19395-8"| 106 | | $.store.book[0,1].price | [8.95, 12.99] | 107 | | $.store.book[0:2].price | [8.95, 12.99, 8.99]| 108 | | $.store.book[?(@.isbn)].price | [8.99, 22.99] | 109 | | $.store.book[?(@.price > 10)].title | ["Sword of Honour", "The Lord of the Rings"]| 110 | | $.store.book[?(@.price < $.expensive)].price | [8.95, 8.99] | 111 | | $.store.book[:].price | [8.9.5, 12.99, 8.9.9, 22.99] | 112 | | $.store.book[?(@.author =~ /(?i).*REES/)].author | "Nigel Rees" | 113 | 114 | > Note: golang support regular expression flags in form of `(?imsU)pattern` --------------------------------------------------------------------------------