Skip to content

Commit 530466c

Browse files
committed
add regular expression support
1 parent 54ae25d commit 530466c

3 files changed

Lines changed: 155 additions & 28 deletions

File tree

jsonpath.go

Lines changed: 89 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"go/token"
77
"go/types"
88
"reflect"
9+
"regexp"
910
"strconv"
1011
"strings"
1112
)
@@ -78,6 +79,9 @@ func JsonPathLookup(obj interface{}, jpath string) (interface{}, error) {
7879
return nil, err
7980
}
8081
obj, err = get_filtered(obj, obj, args.(string))
82+
if err != nil {
83+
return nil, err
84+
}
8185
default:
8286
return nil, fmt.Errorf("expression don't support in filter")
8387
}
@@ -365,6 +369,19 @@ func get_range(obj, frm, to interface{}) (interface{}, error) {
365369
}
366370
}
367371

372+
func regFilterCompile(rule string) (*regexp.Regexp, error) {
373+
runes := []rune(rule)
374+
if len(runes) <= 2 {
375+
return nil, errors.New("empty rule")
376+
}
377+
378+
if runes[0] != '/' || runes[len(runes)-1] != '/' {
379+
return nil, errors.New("invalid syntax. should be in `/pattern/` form")
380+
}
381+
runes = runes[1 : len(runes)-1]
382+
return regexp.Compile(string(runes))
383+
}
384+
368385
func get_filtered(obj, root interface{}, filter string) ([]interface{}, error) {
369386
lp, op, rp, err := parse_filter(filter)
370387
if err != nil {
@@ -375,26 +392,64 @@ func get_filtered(obj, root interface{}, filter string) ([]interface{}, error) {
375392

376393
switch reflect.TypeOf(obj).Kind() {
377394
case reflect.Slice:
378-
for i := 0; i < reflect.ValueOf(obj).Len(); i++ {
379-
tmp := reflect.ValueOf(obj).Index(i).Interface()
380-
ok, err := eval_filter(tmp, root, lp, op, rp)
395+
if op == "=~" {
396+
// regexp
397+
pat, err := regFilterCompile(rp)
381398
if err != nil {
382399
return nil, err
383400
}
384-
if ok == true {
385-
res = append(res, tmp)
401+
402+
for i := 0; i < reflect.ValueOf(obj).Len(); i++ {
403+
tmp := reflect.ValueOf(obj).Index(i).Interface()
404+
ok, err := eval_reg_filter(tmp, root, lp, pat)
405+
if err != nil {
406+
return nil, err
407+
}
408+
if ok == true {
409+
res = append(res, tmp)
410+
}
411+
}
412+
} else {
413+
for i := 0; i < reflect.ValueOf(obj).Len(); i++ {
414+
tmp := reflect.ValueOf(obj).Index(i).Interface()
415+
ok, err := eval_filter(tmp, root, lp, op, rp)
416+
if err != nil {
417+
return nil, err
418+
}
419+
if ok == true {
420+
res = append(res, tmp)
421+
}
386422
}
387423
}
388424
return res, nil
389425
case reflect.Map:
390-
for _, kv := range reflect.ValueOf(obj).MapKeys() {
391-
tmp := reflect.ValueOf(obj).MapIndex(kv).Interface()
392-
ok, err := eval_filter(tmp, root, lp, op, rp)
426+
if op == "=~" {
427+
// regexp
428+
pat, err := regFilterCompile(rp)
393429
if err != nil {
394430
return nil, err
395431
}
396-
if ok == true {
397-
res = append(res, tmp)
432+
433+
for _, kv := range reflect.ValueOf(obj).MapKeys() {
434+
tmp := reflect.ValueOf(obj).MapIndex(kv).Interface()
435+
ok, err := eval_reg_filter(tmp, root, lp, pat)
436+
if err != nil {
437+
return nil, err
438+
}
439+
if ok == true {
440+
res = append(res, tmp)
441+
}
442+
}
443+
} else {
444+
for _, kv := range reflect.ValueOf(obj).MapKeys() {
445+
tmp := reflect.ValueOf(obj).MapIndex(kv).Interface()
446+
ok, err := eval_filter(tmp, root, lp, op, rp)
447+
if err != nil {
448+
return nil, err
449+
}
450+
if ok == true {
451+
res = append(res, tmp)
452+
}
398453
}
399454
}
400455
default:
@@ -507,18 +562,36 @@ func parse_filter_v1(filter string) (lp string, op string, rp string, err error)
507562
return lp, op, rp, err
508563
}
509564

510-
func eval_filter(obj, root interface{}, lp, op, rp string) (res bool, err error) {
565+
func eval_reg_filter(obj, root interface{}, lp string, pat *regexp.Regexp) (res bool, err error) {
566+
if pat == nil {
567+
return false, errors.New("nil pat")
568+
}
569+
lp_v, err := get_lp_v(obj, root, lp)
570+
if err != nil {
571+
return false, err
572+
}
573+
switch v := lp_v.(type) {
574+
case string:
575+
return pat.MatchString(v), nil
576+
default:
577+
return false, errors.New("only string can match with regular expression")
578+
}
579+
}
580+
581+
func get_lp_v(obj, root interface{}, lp string) (interface{}, error) {
511582
var lp_v interface{}
512-
//fmt.Println(obj, root)
513-
//fmt.Printf("lp: %v, op: %v, rp: %v\n", lp, op, rp)
514583
if strings.HasPrefix(lp, "@.") {
515-
//fmt.Println("@. ----------------")
516-
lp_v, err = filter_get_from_explicit_path(obj, lp)
584+
return filter_get_from_explicit_path(obj, lp)
517585
} else if strings.HasPrefix(lp, "$.") {
518-
lp_v, err = filter_get_from_explicit_path(root, lp)
586+
return filter_get_from_explicit_path(root, lp)
519587
} else {
520588
lp_v = lp
521589
}
590+
return lp_v, nil
591+
}
592+
593+
func eval_filter(obj, root interface{}, lp, op, rp string) (res bool, err error) {
594+
lp_v, err := get_lp_v(obj, root, lp)
522595

523596
if op == "exists" {
524597
return lp_v != nil, nil

jsonpath_test.go

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"go/token"
77
"go/types"
88
"reflect"
9+
"regexp"
910
"testing"
1011
)
1112

@@ -780,7 +781,7 @@ var tcase_eval_filter = []map[string]interface{}{
780781
}
781782

782783
func Test_jsonpath_eval_filter(t *testing.T) {
783-
for idx, tcase := range tcase_eval_filter {
784+
for idx, tcase := range tcase_eval_filter[1:] {
784785
fmt.Println("------------------------------")
785786
obj := tcase["obj"].(map[string]interface{})
786787
root := tcase["root"].(map[string]interface{})
@@ -1053,3 +1054,55 @@ func BenchmarkJsonPathLookup_9(b *testing.B) {
10531054
JsonPathLookup(json_data, "$.store.book[?(@.author == 'Nigel Rees')].price")
10541055
}
10551056
}
1057+
1058+
func BenchmarkJsonPathLookup_10(b *testing.B) {
1059+
for i := 0; i < b.N; i++ {
1060+
JsonPathLookup(json_data, "$.store.book[?(@.author =~ /(?i).*REES/)].price")
1061+
}
1062+
}
1063+
1064+
func TestReg(t *testing.T) {
1065+
r := regexp.MustCompile(`(?U).*REES`)
1066+
t.Log(r)
1067+
t.Log(r.Match([]byte(`Nigel Rees`)))
1068+
1069+
res, err := JsonPathLookup(json_data, "$.store.book[?(@.author =~ /(?i).*REES/ )].author")
1070+
t.Log(err, res)
1071+
1072+
author := res.([]interface{})[0].(string)
1073+
t.Log(author)
1074+
if author != "Nigel Rees" {
1075+
t.Fatal("should be `Nigel Rees` but got: ", author)
1076+
}
1077+
}
1078+
1079+
var tcases_reg_op = []struct {
1080+
Line string
1081+
Exp string
1082+
Err bool
1083+
}{
1084+
{``, ``, true},
1085+
{`xxx`, ``, true},
1086+
{`/xxx`, ``, true},
1087+
{`xxx/`, ``, true},
1088+
{`'/xxx/'`, ``, true},
1089+
{`"/xxx/"`, ``, true},
1090+
{`/xxx/`, `xxx`, false},
1091+
{`/π/`, `π`, false},
1092+
}
1093+
1094+
func TestRegOp(t *testing.T) {
1095+
for idx, tcase := range tcases_reg_op {
1096+
fmt.Println("idx: ", idx, "tcase: ", tcase)
1097+
res, err := regFilterCompile(tcase.Line)
1098+
if tcase.Err == true {
1099+
if err == nil {
1100+
t.Fatal("expect err but got nil")
1101+
}
1102+
} else {
1103+
if res == nil || res.String() != tcase.Exp {
1104+
t.Fatal("different. res:", res)
1105+
}
1106+
}
1107+
}
1108+
}

readme.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,15 @@ example json path syntax.
9595

9696
| jsonpath | result|
9797
| :--------- | :-------|
98-
| $.expensive | 10|
99-
| $.store.book[0].price | 8.95|
100-
| $.store.book[-1].isbn | "0-395-19395-8"|
101-
| $.store.book[0,1].price | [8.95, 12.99] |
102-
| $.store.book[0:2].price | [8.95, 12.99, 8.99]|
103-
| $.store.book[?(@.isbn)].price | [8.99, 22.99] |
104-
| $.store.book[?(@.price > 10)].title | ["Sword of Honour", "The Lord of the Rings"]|
105-
| $.store.book[?(@.price < $.expensive)].price | [8.95, 8.99] |
106-
| $.store.book[:].price | [8.9.5, 12.99, 8.9.9, 22.99] |
107-
108-
98+
| $.expensive | 10|
99+
| $.store.book[0].price | 8.95|
100+
| $.store.book[-1].isbn | "0-395-19395-8"|
101+
| $.store.book[0,1].price | [8.95, 12.99] |
102+
| $.store.book[0:2].price | [8.95, 12.99, 8.99]|
103+
| $.store.book[?(@.isbn)].price | [8.99, 22.99] |
104+
| $.store.book[?(@.price > 10)].title | ["Sword of Honour", "The Lord of the Rings"]|
105+
| $.store.book[?(@.price < $.expensive)].price | [8.95, 8.99] |
106+
| $.store.book[:].price | [8.9.5, 12.99, 8.9.9, 22.99] |
107+
| $.store.book[?(@.author =~ /(?i).*REES/)].author | "Nigel Rees" |
108+
109+
> Note: golang support regular expression flags in form of `(?imsU)pattern`

0 commit comments

Comments
 (0)