diff --git a/LICENSE b/LICENSE index 530afca..50ad671 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 oliver +Copyright (c) 2021, 2015; DoltHub Authors, oliver Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..488830e --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module github.com/dolthub/jsonpath + +go 1.15 + +require ( + github.com/pkg/errors v0.9.1 // indirect + github.com/stretchr/testify v1.7.0 + gopkg.in/src-d/go-errors.v1 v1.0.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1ea467e --- /dev/null +++ b/go.sum @@ -0,0 +1,15 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc= +gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/jsonpath.go b/jsonpath.go index 00dc6fd..dc2b279 100644 --- a/jsonpath.go +++ b/jsonpath.go @@ -1,3 +1,9 @@ +// Copyright 2015, 2021; oliver, DoltHub Authors +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + package jsonpath import ( @@ -7,11 +13,16 @@ import ( "go/types" "reflect" "regexp" + "sort" "strconv" "strings" + + errKind "gopkg.in/src-d/go-errors.v1" ) var ErrGetFromNullObj = errors.New("get attribute from null object") +var ErrKeyError = errKind.NewKind("key error: %s not found in object") + func JsonPathLookup(obj interface{}, jpath string) (interface{}, error) { c, err := Compile(jpath) @@ -45,6 +56,9 @@ func Compile(jpath string) (*Compiled, error) { if err != nil { return nil, err } + if len(tokens) == 0 { + return nil, fmt.Errorf("empty path") + } if tokens[0] != "@" && tokens[0] != "$" { return nil, fmt.Errorf("$ or @ should in front of path") } @@ -70,7 +84,7 @@ func (c *Compiled) String() string { func (c *Compiled) Lookup(obj interface{}) (interface{}, error) { var err error for _, s := range c.steps { - // "key", "idx" + // "key", "idx", "range", "filter", "scan" switch s.op { case "key": obj, err = get_key(obj, s.key) @@ -132,8 +146,20 @@ func (c *Compiled) Lookup(obj interface{}) (interface{}, error) { if err != nil { return nil, err } + case "scan": + obj, err = get_scan(obj) + if err != nil { + return nil, err + } + if obj == nil { + continue + } + // empty scan is NULL + if len(obj.([]interface{})) == 0 { + obj = nil + } default: - return nil, fmt.Errorf("expression don't support in filter") + return nil, fmt.Errorf("unsupported jsonpath operation: %s", s.op) } } return obj, nil @@ -144,9 +170,27 @@ func tokenize(query string) ([]string, error) { // token_start := false // token_end := false token := "" + quoteChar := rune(0) // fmt.Println("-------------------------------------------------- start") for idx, x := range query { + if quoteChar != 0 { + if x == quoteChar { + quoteChar = 0 + } else { + token += string(x) + } + + continue + } else if x == '"' { + if token == "." { + token = "" + } + + quoteChar = x + continue + } + token += string(x) // //fmt.Printf("idx: %d, x: %s, token: %s, tokens: %v\n", idx, string(x), token, tokens) if idx == 0 { @@ -193,6 +237,11 @@ func tokenize(query string) ([]string, error) { } } } + + if quoteChar != 0 { + token = string(quoteChar) + token + } + if len(token) > 0 { if token[0] == '.' { token = token[1:] @@ -325,7 +374,7 @@ func filter_get_from_explicit_path(obj interface{}, path string) (interface{}, e return nil, err } default: - return nil, fmt.Errorf("expression don't support in filter") + return nil, fmt.Errorf("unsupported jsonpath operation %s in filter", op) } } return xobj, nil @@ -343,7 +392,7 @@ func get_key(obj interface{}, key string) (interface{}, error) { if jsonMap, ok := obj.(map[string]interface{}); ok { val, exists := jsonMap[key] if !exists { - return nil, fmt.Errorf("key error: %s not found in object", key) + return nil, ErrKeyError.New(key) } return val, nil } @@ -353,7 +402,7 @@ func get_key(obj interface{}, key string) (interface{}, error) { return reflect.ValueOf(obj).MapIndex(kv).Interface(), nil } } - return nil, fmt.Errorf("key error: %s not found in object", key) + return nil, ErrKeyError.New(key) case reflect.Slice: // slice we should get from all objects in it. res := []interface{}{} @@ -521,6 +570,59 @@ func get_filtered(obj, root interface{}, filter string) ([]interface{}, error) { return res, nil } +func get_scan(obj interface{}) (interface{}, error) { + if reflect.TypeOf(obj) == nil { + return nil, nil + } + switch reflect.TypeOf(obj).Kind() { + case reflect.Map: + // iterate over keys in sorted by length, then alphabetically + var res []interface{} + if jsonMap, ok := obj.(map[string]interface{}); ok { + var sortedKeys []string + for k := range jsonMap { + sortedKeys = append(sortedKeys, k) + } + sort.Slice(sortedKeys, func(i, j int) bool { + if len(sortedKeys[i]) != len(sortedKeys[j]) { + return len(sortedKeys[i]) < len(sortedKeys[j]) + } + return sortedKeys[i] < sortedKeys[j] + }) + for _, k := range sortedKeys { + res = append(res, jsonMap[k]) + } + return res, nil + } + keys := reflect.ValueOf(obj).MapKeys() + sort.Slice(keys, func(i, j int) bool { + ki, kj := keys[i].String(), keys[j].String() + if len(ki) != len(kj) { + return len(ki) < len(kj) + } + return ki < kj + }) + for _, k := range keys { + res = append(res, reflect.ValueOf(obj).MapIndex(k).Interface()) + } + return res, nil + case reflect.Slice: + // slice we should get from all objects in it. + var res []interface{} + for i := 0; i < reflect.ValueOf(obj).Len(); i++ { + tmp := reflect.ValueOf(obj).Index(i).Interface() + newObj, err := get_scan(tmp) + if err != nil { + return nil, err + } + res = append(res, newObj.([]interface{})...) + } + return res, nil + default: + return nil, fmt.Errorf("object is not scannable: %v", reflect.TypeOf(obj).Kind()) + } +} + // @.isbn => @.isbn, exists, nil // @.price < 10 => @.price, <, 10 // @.price <= $.expensive => @.price, <=, $.expensive diff --git a/jsonpath_test.go b/jsonpath_test.go index 90f05b7..f7c4a9a 100644 --- a/jsonpath_test.go +++ b/jsonpath_test.go @@ -1,3 +1,9 @@ +// Copyright 2015, 2021; oliver, DoltHub Authors +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + package jsonpath import ( @@ -8,6 +14,8 @@ import ( "reflect" "regexp" "testing" + + "github.com/stretchr/testify/assert" ) var json_data interface{} @@ -56,8 +64,14 @@ func init() { } func Test_jsonpath_JsonPathLookup_1(t *testing.T) { + // empty string + res, err := JsonPathLookup(json_data, "") + if err == nil { + t.Errorf("expected error from empty jsonpath") + } + // key from root - res, _ := JsonPathLookup(json_data, "$.expensive") + res, _ = JsonPathLookup(json_data, "$.expensive") if res_v, ok := res.(float64); ok != true || res_v != 10.0 { t.Errorf("expensive should be 10") } @@ -68,6 +82,12 @@ func Test_jsonpath_JsonPathLookup_1(t *testing.T) { t.Errorf("$.store.book[0].price should be 8.95") } + // quoted - single index + res, _ = JsonPathLookup(json_data, `$."store"."book"[0]."price"`) + if res_v, ok := res.(float64); ok != true || res_v != 8.95 { + t.Errorf(`$."store"."book"[0]."price" should be 8.95`) + } + // nagtive single index res, _ = JsonPathLookup(json_data, "$.store.book[-1].isbn") if res_v, ok := res.(string); ok != true || res_v != "0-395-19395-8" { @@ -75,7 +95,7 @@ func Test_jsonpath_JsonPathLookup_1(t *testing.T) { } // multiple index - res, err := JsonPathLookup(json_data, "$.store.book[0,1].price") + res, err = JsonPathLookup(json_data, "$.store.book[0,1].price") t.Log(err, res) if res_v, ok := res.([]interface{}); ok != true || res_v[0].(float64) != 8.95 || res_v[1].(float64) != 12.99 { t.Errorf("exp: [8.95, 12.99], got: %v", res) @@ -153,90 +173,42 @@ func Test_jsonpath_authors_of_all_books(t *testing.T) { t.Log(res, expected) } -var token_cases = []map[string]interface{}{ - map[string]interface{}{ - "query": "$..author", - "tokens": []string{"$", "*", "author"}, - }, - map[string]interface{}{ - "query": "$.store.*", - "tokens": []string{"$", "store", "*"}, - }, - map[string]interface{}{ - "query": "$.store..price", - "tokens": []string{"$", "store", "*", "price"}, - }, - map[string]interface{}{ - "query": "$.store.book[*].author", - "tokens": []string{"$", "store", "book[*]", "author"}, - }, - map[string]interface{}{ - "query": "$..book[2]", - "tokens": []string{"$", "*", "book[2]"}, - }, - map[string]interface{}{ - "query": "$..book[(@.length-1)]", - "tokens": []string{"$", "*", "book[(@.length-1)]"}, - }, - map[string]interface{}{ - "query": "$..book[0,1]", - "tokens": []string{"$", "*", "book[0,1]"}, - }, - map[string]interface{}{ - "query": "$..book[:2]", - "tokens": []string{"$", "*", "book[:2]"}, - }, - map[string]interface{}{ - "query": "$..book[?(@.isbn)]", - "tokens": []string{"$", "*", "book[?(@.isbn)]"}, - }, - map[string]interface{}{ - "query": "$.store.book[?(@.price < 10)]", - "tokens": []string{"$", "store", "book[?(@.price < 10)]"}, - }, - map[string]interface{}{ - "query": "$..book[?(@.price <= $.expensive)]", - "tokens": []string{"$", "*", "book[?(@.price <= $.expensive)]"}, - }, - map[string]interface{}{ - "query": "$..book[?(@.author =~ /.*REES/i)]", - "tokens": []string{"$", "*", "book[?(@.author =~ /.*REES/i)]"}, - }, - map[string]interface{}{ - "query": "$..book[?(@.author =~ /.*REES\\]/i)]", - "tokens": []string{"$", "*", "book[?(@.author =~ /.*REES\\]/i)]"}, - }, - map[string]interface{}{ - "query": "$..*", - "tokens": []string{"$", "*"}, - }, - map[string]interface{}{ - "query": "$....author", - "tokens": []string{"$", "*", "author"}, - }, +var token_cases = []struct { + query string + expected []string +}{ + {"$..author", []string{"$", "*", "author"}}, + {"$.store.*", []string{"$", "store", "*"}}, + {"$.store..price", []string{"$", "store", "*", "price"}}, + {"$.store.book[*].author", []string{"$", "store", "book[*]", "author"}}, + {"$..book[2]", []string{"$", "*", "book[2]"}}, + {"$..book[(@.length-1)]", []string{"$", "*", "book[(@.length-1)]"}}, + {"$..book[0,1]", []string{"$", "*", "book[0,1]"}}, + {"$..book[:2]", []string{"$", "*", "book[:2]"}}, + {"$..book[?(@.isbn)]", []string{"$", "*", "book[?(@.isbn)]"}}, + {"$.store.book[?(@.price < 10)]", []string{"$", "store", "book[?(@.price < 10)]"}}, + {"$..book[?(@.price <= $.expensive)]", []string{"$", "*", "book[?(@.price <= $.expensive)]"}}, + {"$..book[?(@.author =~ /.*REES/i)]", []string{"$", "*", "book[?(@.author =~ /.*REES/i)]"}}, + {"$..book[?(@.author =~ /.*REES\\]/i)]", []string{"$", "*", "book[?(@.author =~ /.*REES\\]/i)]"}}, + {"$..*", []string{"$", "*"}}, + {"$....author", []string{"$", "*", "author"}}, + {`$."col"`, []string{"$", "col"}}, + {`$."col.with.dots"."sub.with.dots"`, []string{"$", "col.with.dots", "sub.with.dots"}}, + {`$."unterminated`, []string{"$", `"unterminated`}}, + {`$."col with spaces"."sub with spaces"`, []string{"$", "col with spaces", "sub with spaces"}}, } func Test_jsonpath_tokenize(t *testing.T) { - for idx, tcase := range token_cases { - t.Logf("idx[%d], tcase: %v", idx, tcase) - query := tcase["query"].(string) - expected_tokens := tcase["tokens"].([]string) - tokens, err := tokenize(query) - t.Log(err, tokens, expected_tokens) - if len(tokens) != len(expected_tokens) { - t.Errorf("different length: (got)%v, (expected)%v", len(tokens), len(expected_tokens)) - continue - } - for i := 0; i < len(expected_tokens); i++ { - if tokens[i] != expected_tokens[i] { - t.Errorf("not expected: [%d], (got)%v != (expected)%v", i, tokens[i], expected_tokens[i]) - } - } + for _, tcase := range token_cases { + t.Run(tcase.query, func(t *testing.T) { + tokens, err := tokenize(tcase.query) + assert.NoError(t, err) + assert.Equal(t, tcase.expected, tokens) + }) } } var parse_token_cases = []map[string]interface{}{ - map[string]interface{}{ "token": "$", "op": "root", @@ -371,7 +343,7 @@ func Test_jsonpath_parse_token(t *testing.T) { if op == "range" { if args_v, ok := args.([2]interface{}); ok == true { - fmt.Println(args_v) + t.Logf("%v", args_v) exp_from := exp_args.([2]interface{})[0] exp_to := exp_args.([2]interface{})[1] if args_v[0] != exp_from { @@ -390,7 +362,7 @@ func Test_jsonpath_parse_token(t *testing.T) { if op == "filter" { if args_v, ok := args.(string); ok == true { - fmt.Println(args_v) + t.Logf(args_v) if exp_args.(string) != args_v { t.Errorf("len(args) not expected: (got)%v != (exp)%v", len(args_v), len(exp_args.([]string))) return @@ -408,7 +380,7 @@ func Test_jsonpath_get_key(t *testing.T) { "key": 1, } res, err := get_key(obj, "key") - fmt.Println(err, res) + t.Logf("err: %v, res: %v", err, res) if err != nil { t.Errorf("failed to get key: %v", err) return @@ -419,7 +391,7 @@ func Test_jsonpath_get_key(t *testing.T) { } res, err = get_key(obj, "hah") - fmt.Println(err, res) + t.Logf("err: %v, res: %v", err, res) if err == nil { t.Errorf("key error not raised") return @@ -431,7 +403,7 @@ func Test_jsonpath_get_key(t *testing.T) { obj2 := 1 res, err = get_key(obj2, "key") - fmt.Println(err, res) + t.Logf("err: %v, res: %v", err, res) if err == nil { t.Errorf("object is not map error not raised") @@ -440,7 +412,7 @@ func Test_jsonpath_get_key(t *testing.T) { obj3 := map[string]string{"key": "hah"} res, err = get_key(obj3, "key") if res_v, ok := res.(string); ok != true || res_v != "hah" { - fmt.Println(err, res) + t.Logf("err: %v, res: %v", err, res) t.Errorf("map[string]string support failed") } @@ -453,13 +425,13 @@ func Test_jsonpath_get_key(t *testing.T) { }, } res, err = get_key(obj4, "a") - fmt.Println(err, res) + t.Logf("err: %v, res: %v", err, res) } func Test_jsonpath_get_idx(t *testing.T) { obj := []interface{}{1, 2, 3, 4} res, err := get_idx(obj, 0) - fmt.Println(err, res) + t.Logf("err: %v, res: %v", err, res) if err != nil { t.Errorf("failed to get_idx(obj,0): %v", err) return @@ -469,19 +441,19 @@ func Test_jsonpath_get_idx(t *testing.T) { } res, err = get_idx(obj, 2) - fmt.Println(err, res) + t.Logf("err: %v, res: %v", err, res) if v, ok := res.(int); ok != true || v != 3 { t.Errorf("failed to get int 3") } res, err = get_idx(obj, 4) - fmt.Println(err, res) + t.Logf("err: %v, res: %v", err, res) if err == nil { t.Errorf("index out of range error not raised") return } res, err = get_idx(obj, -1) - fmt.Println(err, res) + t.Logf("err: %v, res: %v", err, res) if err != nil { t.Errorf("failed to get_idx(obj, -1): %v", err) return @@ -491,13 +463,13 @@ func Test_jsonpath_get_idx(t *testing.T) { } res, err = get_idx(obj, -4) - fmt.Println(err, res) + t.Logf("err: %v, res: %v", err, res) if v, ok := res.(int); ok != true || v != 1 { t.Errorf("failed to get int 1") } res, err = get_idx(obj, -5) - fmt.Println(err, res) + t.Logf("err: %v, res: %v", err, res) if err == nil { t.Errorf("index out of range error not raised") return @@ -512,7 +484,7 @@ func Test_jsonpath_get_idx(t *testing.T) { obj2 := []int{1, 2, 3, 4} res, err = get_idx(obj2, 0) - fmt.Println(err, res) + t.Logf("err: %v, res: %v", err, res) if err != nil { t.Errorf("failed to get_idx(obj2,0): %v", err) return @@ -526,7 +498,7 @@ func Test_jsonpath_get_range(t *testing.T) { obj := []int{1, 2, 3, 4, 5} res, err := get_range(obj, 0, 2) - fmt.Println(err, res) + t.Logf("err: %v, res: %v", err, res) if err != nil { t.Errorf("failed to get_range: %v", err) } @@ -536,11 +508,11 @@ func Test_jsonpath_get_range(t *testing.T) { obj1 := []interface{}{1, 2, 3, 4, 5} res, err = get_range(obj1, 3, -1) - fmt.Println(err, res) + t.Logf("err: %v, res: %v", err, res) if err != nil { t.Errorf("failed to get_range: %v", err) } - fmt.Println(res.([]interface{})) + t.Logf("%v", res.([]interface{})) if res.([]interface{})[0] != 4 || res.([]interface{})[1] != 5 { t.Errorf("failed get_range: %v, expect: [4,5]", res) } @@ -565,16 +537,96 @@ func Test_jsonpath_get_range(t *testing.T) { obj2 := 2 res, err = get_range(obj2, 0, 1) - fmt.Println(err, res) + t.Logf("err: %v, res: %v", err, res) if err == nil { t.Errorf("object is Slice error not raised") } } +func Test_jsonpath_get_scan(t *testing.T) { + obj := map[string]interface{}{ + "key": 1, + } + res, err := get_scan(obj) + if err != nil { + t.Errorf("failed to scan: %v", err) + return + } + if res.([]interface{})[0] != 1 { + t.Errorf("scanned value is not 1: %v", res) + return + } + + obj2 := 1 + res, err = get_scan(obj2) + if err == nil || err.Error() != "object is not scannable: int" { + t.Errorf("object is not scannable error not raised") + return + } + + obj3 := map[string]string{"key1": "hah1", "key2": "hah2", "key3": "hah3"} + res, err = get_scan(obj3) + if err != nil { + t.Errorf("failed to scan: %v", err) + return + } + res_v, ok := res.([]interface{}) + if !ok { + t.Errorf("scanned result is not a slice") + } + if len(res_v) != 3 { + t.Errorf("scanned result is of wrong length") + } + if v, ok := res_v[0].(string); !ok || v != "hah1" { + t.Errorf("scanned result contains unexpected value: %v", v) + } + if v, ok := res_v[1].(string); !ok || v != "hah2" { + t.Errorf("scanned result contains unexpected value: %v", v) + } + if v, ok := res_v[2].(string); !ok || v != "hah3" { + t.Errorf("scanned result contains unexpected value: %v", v) + } + + obj4 := map[string]interface{}{ + "key1" : "abc", + "key2" : 123, + "key3" : map[string]interface{}{ + "a": 1, + "b": 2, + "c": 3, + }, + "key4" : []interface{}{1,2,3}, + "key5" : nil, + } + res, err = get_scan(obj4) + res_v, ok = res.([]interface{}) + if !ok { + t.Errorf("scanned result is not a slice") + } + if len(res_v) != 5 { + t.Errorf("scanned result is of wrong length") + } + if v, ok := res_v[0].(string); !ok || v != "abc" { + t.Errorf("scanned result contains unexpected value: %v", v) + } + if v, ok := res_v[1].(int); !ok || v != 123 { + t.Errorf("scanned result contains unexpected value: %v", v) + } + if v, ok := res_v[2].(map[string]interface{}); !ok || v["a"].(int) != 1 || v["b"].(int) != 2 || v["c"].(int) != 3 { + t.Errorf("scanned result contains unexpected value: %v", v) + } + if v, ok := res_v[3].([]interface{}); !ok || v[0].(int) != 1 || v[1].(int) != 2 || v[2].(int) != 3 { + t.Errorf("scanned result contains unexpected value: %v", v) + } + if res_v[4] != nil { + t.Errorf("scanned result contains unexpected value: %v", res_v[4]) + } +} + func Test_jsonpath_types_eval(t *testing.T) { fset := token.NewFileSet() res, err := types.Eval(fset, nil, 0, "1 < 2") - fmt.Println(err, res, res.Type, res.Value, res.IsValue()) + t.Logf("err: %v, res: %v, res.Type: %v, res.Value: %v, res.IsValue: %v", err, res, res.Type, res.Value, res.IsValue()) } var tcase_parse_filter = []map[string]interface{}{ @@ -781,7 +833,7 @@ var tcase_eval_filter = []map[string]interface{}{ func Test_jsonpath_eval_filter(t *testing.T) { for idx, tcase := range tcase_eval_filter[1:] { - fmt.Println("------------------------------") + t.Logf("------------------------------") obj := tcase["obj"].(map[string]interface{}) root := tcase["root"].(map[string]interface{}) lp := tcase["lp"].(string) @@ -1108,7 +1160,7 @@ var tcases_reg_op = []struct { func TestRegOp(t *testing.T) { for idx, tcase := range tcases_reg_op { - fmt.Println("idx: ", idx, "tcase: ", tcase) + t.Logf("idx: %v, tcase: %v", idx, tcase) res, err := regFilterCompile(tcase.Line) if tcase.Err == true { if err == nil { @@ -1179,13 +1231,13 @@ func Test_jsonpath_rootnode_is_array_range(t *testing.T) { t.Logf("idx: %v, v: %v", idx, v) } if len(ares) != 2 { - t.Fatal("len is not 2. got: %v", len(ares)) + t.Fatalf("len is not 2. got: %v", len(ares)) } if ares[0].(float64) != 12.34 { - t.Fatal("idx: 0, should be 12.34. got: %v", ares[0]) + t.Fatalf("idx: 0, should be 12.34. got: %v", ares[0]) } if ares[1].(float64) != 13.34 { - t.Fatal("idx: 0, should be 12.34. got: %v", ares[1]) + t.Fatalf("idx: 0, should be 12.34. got: %v", ares[1]) } } @@ -1232,7 +1284,7 @@ func Test_jsonpath_rootnode_is_nested_array_range(t *testing.T) { t.Logf("idx: %v, v: %v", idx, v) } if len(ares) != 2 { - t.Fatal("len is not 2. got: %v", len(ares)) + t.Fatalf("len is not 2. got: %v", len(ares)) } //FIXME: `$[:1].[0].test` got wrong result diff --git a/readme.md b/readme.md index a8ee2db..bfdcbdc 100644 --- a/readme.md +++ b/readme.md @@ -9,7 +9,7 @@ but also with some minor differences. this library is till bleeding edge, so use it at your own risk. :D -**Golang Version Required**: 1.5+ +**Golang Version Required**: 1.15+ Get Started ------------ @@ -111,4 +111,4 @@ example json path syntax. | $.store.book[:].price | [8.9.5, 12.99, 8.9.9, 22.99] | | $.store.book[?(@.author =~ /(?i).*REES/)].author | "Nigel Rees" | -> Note: golang support regular expression flags in form of `(?imsU)pattern` \ No newline at end of file +> Note: golang support regular expression flags in form of `(?imsU)pattern`