Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix null exception and error when key is not present #1

Merged
merged 2 commits into from
Dec 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/gojekfarm/jsonpath

go 1.16
40 changes: 19 additions & 21 deletions jsonpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,12 @@ func (c *Compiled) Lookup(obj interface{}) (interface{}, error) {
return nil, err
}
}

if obj == nil {
return nil, nil
}
if len(s.args.([]int)) > 1 {
res := []interface{}{}
for _, x := range s.args.([]int) {
//fmt.Println("idx ---- ", x)
tmp, err := get_idx(obj, x)
if err != nil {
return nil, err
Expand All @@ -98,13 +99,11 @@ func (c *Compiled) Lookup(obj interface{}) (interface{}, error) {
}
obj = res
} else if len(s.args.([]int)) == 1 {
//fmt.Println("idx ----------------3")
obj, err = get_idx(obj, s.args.([]int)[0])
if err != nil {
return nil, err
}
} else {
//fmt.Println("idx ----------------4")
return nil, fmt.Errorf("cannot index on empty slice")
}
case "range":
Expand All @@ -115,7 +114,11 @@ func (c *Compiled) Lookup(obj interface{}) (interface{}, error) {
return nil, err
}
}
if argsv, ok := s.args.([2]interface{}); ok == true {
if obj == nil {
return nil, nil
}

if argsv, ok := s.args.([2]interface{}); ok {
obj, err = get_range(obj, argsv[0], argsv[1])
if err != nil {
return nil, err
Expand Down Expand Up @@ -167,9 +170,7 @@ func tokenize(query string) ([]string, error) {
token = "."
continue
} else {
// fmt.Println("else: ", string(x), token)
if strings.Contains(token, "[") {
// fmt.Println(" contains [ ")
if x == ']' && !strings.HasSuffix(token, "\\]") {
if token[0] == '.' {
tokens = append(tokens, token[1:])
Expand All @@ -180,7 +181,6 @@ func tokenize(query string) ([]string, error) {
continue
}
} else {
// fmt.Println(" doesn't contains [ ")
if x == '.' {
if token[0] == '.' {
tokens = append(tokens, token[1:len(token)-1])
Expand Down Expand Up @@ -209,8 +209,6 @@ func tokenize(query string) ([]string, error) {
}
}
}
// fmt.Println("finished tokens: ", tokens)
// fmt.Println("================================================= done ")
return tokens, nil
}

Expand All @@ -237,7 +235,6 @@ func parse_token(token string) (op string, key string, args interface{}, err err
}
tail = tail[1 : len(tail)-1]

//fmt.Println(key, tail)
if strings.Contains(tail, "?") {
// filter -------------------------------------------------
op = "filter"
Expand Down Expand Up @@ -292,8 +289,6 @@ func parse_token(token string) (op string, key string, args interface{}, err err

func filter_get_from_explicit_path(obj interface{}, path string) (interface{}, error) {
steps, err := tokenize(path)
//fmt.Println("f: steps: ", steps, err)
//fmt.Println(path, steps)
if err != nil {
return nil, err
}
Expand All @@ -302,7 +297,6 @@ func filter_get_from_explicit_path(obj interface{}, path string) (interface{}, e
}
steps = steps[1:]
xobj := obj
//fmt.Println("f: xobj", xobj)
for _, s := range steps {
op, key, args, err := parse_token(s)
// "key", "idx"
Expand Down Expand Up @@ -333,7 +327,7 @@ func filter_get_from_explicit_path(obj interface{}, path string) (interface{}, e

func get_key(obj interface{}, key string) (interface{}, error) {
if reflect.TypeOf(obj) == nil {
return nil, ErrGetFromNullObj
return nil, nil
}
switch reflect.TypeOf(obj).Kind() {
case reflect.Map:
Expand All @@ -343,17 +337,17 @@ 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, nil
}
return val, nil
}
for _, kv := range reflect.ValueOf(obj).MapKeys() {
//fmt.Println(kv.String())
// fmt.Println(kv.String())
if kv.String() == key {
return reflect.ValueOf(obj).MapIndex(kv).Interface(), nil
}
}
return nil, fmt.Errorf("key error: %s not found in object", key)
return nil, nil
case reflect.Slice:
// slice we should get from all objects in it.
res := []interface{}{}
Expand Down Expand Up @@ -395,6 +389,10 @@ func get_range(obj, frm, to interface{}) (interface{}, error) {
switch reflect.TypeOf(obj).Kind() {
case reflect.Slice:
length := reflect.ValueOf(obj).Len()
// if length of slice is 0 return nil instead of out of range error
if length == 0 {
return nil, nil
}
_frm := 0
_to := length
if frm == nil {
Expand Down Expand Up @@ -423,7 +421,7 @@ func get_range(obj, frm, to interface{}) (interface{}, error) {
if _to < 0 || _to > length {
return nil, fmt.Errorf("index [to] out of range: len: %v, to: %v", length, to)
}
//fmt.Println("_frm, _to: ", _frm, _to)
// fmt.Println("_frm, _to: ", _frm, _to)
res_v := reflect.ValueOf(obj).Slice(_frm, _to)
return res_v.Interface(), nil
default:
Expand Down Expand Up @@ -668,7 +666,7 @@ func eval_filter(obj, root interface{}, lp, op, rp string) (res bool, err error)
} else {
rp_v = rp
}
//fmt.Printf("lp_v: %v, rp_v: %v\n", lp_v, rp_v)
// fmt.Printf("lp_v: %v, rp_v: %v\n", lp_v, rp_v)
return cmp_any(lp_v, rp_v, op)
}
}
Expand Down Expand Up @@ -705,7 +703,7 @@ func cmp_any(obj1, obj2 interface{}, op string) (bool, error) {
} else {
exp = fmt.Sprintf(`"%v" %s "%v"`, obj1, op, obj2)
}
//fmt.Println("exp: ", exp)
// fmt.Println("exp: ", exp)
fset := token.NewFileSet()
res, err := types.Eval(fset, nil, 0, exp)
if err != nil {
Expand Down
54 changes: 36 additions & 18 deletions jsonpath_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ func init() {
"price": 19.95
}
},
"expensive": 10
"expensive": 10,
"null_array": null,
"empty_array": []
}
`
json.Unmarshal([]byte(data), &json_data)
Expand All @@ -62,6 +64,22 @@ func Test_jsonpath_JsonPathLookup_1(t *testing.T) {
t.Errorf("expensive should be 10")
}

res, err := JsonPathLookup(json_data, "$.nulls_array[*].key")
if err != nil || res != nil {
t.Errorf("nulls_array[*].key should be nil")
}

res, err = JsonPathLookup(json_data, "$.empty_array[*].key")
if err != nil || res != nil {
t.Errorf("empty_array[*].key should be nil")
}

// not found key
res, err = JsonPathLookup(json_data, "$.not_exist_key")
if err != nil || res != nil {
t.Errorf("not_exist_key should be nil")
}

// single index
res, _ = JsonPathLookup(json_data, "$.store.book[0].price")
if res_v, ok := res.(float64); ok != true || res_v != 8.95 {
Expand All @@ -75,7 +93,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)
Expand All @@ -96,7 +114,7 @@ func Test_jsonpath_JsonPathLookup_1(t *testing.T) {
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 {
t.Errorf("exp: [8.95, 12.99, 8.99, 22.99], got: %v", res)
}

// range
res, err = JsonPathLookup(json_data, "$.store.book[0:1].price")
t.Log(err, res)
Expand Down Expand Up @@ -420,8 +438,8 @@ func Test_jsonpath_get_key(t *testing.T) {

res, err = get_key(obj, "hah")
fmt.Println(err, res)
if err == nil {
t.Errorf("key error not raised")
if err != nil {
t.Errorf("key error should not raised")
return
}
if res != nil {
Expand Down Expand Up @@ -621,8 +639,7 @@ var tcase_parse_filter = []map[string]interface{}{
}

func Test_jsonpath_parse_filter(t *testing.T) {

//for _, tcase := range tcase_parse_filter[4:] {
// for _, tcase := range tcase_parse_filter[4:] {
for _, tcase := range tcase_parse_filter {
lp, op, rp, _ := parse_filter(tcase["filter"].(string))
t.Log(tcase)
Expand Down Expand Up @@ -683,7 +700,6 @@ var tcase_filter_get_from_explicit_path = []map[string]interface{}{
}

func Test_jsonpath_filter_get_from_explicit_path(t *testing.T) {

for idx, tcase := range tcase_filter_get_from_explicit_path {
obj := tcase["obj"]
query := tcase["query"].(string)
Expand Down Expand Up @@ -790,7 +806,6 @@ func Test_jsonpath_eval_filter(t *testing.T) {
exp := tcase["exp"].(bool)
t.Logf("idx: %v, lp: %v, op: %v, rp: %v, exp: %v", idx, lp, op, rp, exp)
got, err := eval_filter(obj, root, lp, op, rp)

if err != nil {
t.Errorf("idx: %v, failed to eval: %v", idx, err)
return
Expand All @@ -806,6 +821,7 @@ var (
ifc1 interface{} = "haha"
ifc2 interface{} = "ha ha"
)

var tcase_cmp_any = []map[string]interface{}{

map[string]interface{}{
Expand Down Expand Up @@ -849,19 +865,22 @@ var tcase_cmp_any = []map[string]interface{}{
"op": "=~",
"exp": false,
"err": "op should only be <, <=, ==, >= and >",
}, {
},
{
"obj1": ifc1,
"obj2": ifc1,
"op": "==",
"exp": true,
"err": nil,
}, {
},
{
"obj1": ifc2,
"obj2": ifc2,
"op": "==",
"exp": true,
"err": nil,
}, {
},
{
"obj1": 20,
"obj2": "100",
"op": ">",
Expand All @@ -872,7 +891,7 @@ var tcase_cmp_any = []map[string]interface{}{

func Test_jsonpath_cmp_any(t *testing.T) {
for idx, tcase := range tcase_cmp_any {
//for idx, tcase := range tcase_cmp_any[8:] {
// for idx, tcase := range tcase_cmp_any[8:] {
t.Logf("idx: %v, %v %v %v, exp: %v", idx, tcase["obj1"], tcase["op"], tcase["obj2"], tcase["exp"])
res, err := cmp_any(tcase["obj1"], tcase["obj2"], tcase["op"].(string))
exp := tcase["exp"].(bool)
Expand Down Expand Up @@ -979,7 +998,6 @@ func Test_jsonpath_num_cmp(t *testing.T) {
if len(arr) != 0 {
t.Fatal("should return [], got: ", arr)
}

}

func BenchmarkJsonPathLookupCompiled(b *testing.B) {
Expand Down Expand Up @@ -1179,13 +1197,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])
}
}

Expand Down Expand Up @@ -1232,7 +1250,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
Expand Down