From 3653274788d360d2ef9e4d731d2e14bc00607268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsa=20P=C3=A9ter?= Date: Mon, 3 Sep 2018 19:56:29 +0200 Subject: [PATCH 1/8] Fixes a problem when filtering a root array --- jsonpath.go | 8 +++-- jsonpath_test.go | 83 ++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 83 insertions(+), 8 deletions(-) diff --git a/jsonpath.go b/jsonpath.go index 00dc6fd..e58679a 100644 --- a/jsonpath.go +++ b/jsonpath.go @@ -359,8 +359,12 @@ func get_key(obj interface{}, key string) (interface{}, error) { res := []interface{}{} for i := 0; i < reflect.ValueOf(obj).Len(); i++ { tmp, _ := get_idx(obj, i) - if v, err := get_key(tmp, key); err == nil { - res = append(res, v) + if key == "" { + res = append(res, tmp) + } else { + if v, err := get_key(tmp, key); err == nil { + res = append(res, v) + } } } return res, nil diff --git a/jsonpath_test.go b/jsonpath_test.go index 90f05b7..c59064b 100644 --- a/jsonpath_test.go +++ b/jsonpath_test.go @@ -96,7 +96,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) @@ -453,7 +453,10 @@ func Test_jsonpath_get_key(t *testing.T) { }, } res, err = get_key(obj4, "a") - fmt.Println(err, res) + if res_v, ok := res.([]interface{}); ok != true || len(res_v) != 2 || res_v[0] != 1 || res_v[1] != 2 { + fmt.Println(err, res) + t.Errorf("[]map[string]interface{} support failed") + } } func Test_jsonpath_get_idx(t *testing.T) { @@ -1179,13 +1182,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 +1235,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 @@ -1243,3 +1246,71 @@ func Test_jsonpath_rootnode_is_nested_array_range(t *testing.T) { // t.Fatal("idx: 0, should be 3.1, got: %v", ares[1]) //} } + +func Test_root_array(t *testing.T) { + var ( + err error + books = []map[string]interface{}{ + map[string]interface{}{ + "category": "reference", + "meta": map[string]interface{}{ + "language": "en", + "release_year": 1984, + "available_for_order": false, + }, + "author": "Nigel Rees", + "title": "Sayings of the Century", + "price": 8.95, + }, + map[string]interface{}{ + "category": "fiction", + "meta": map[string]interface{}{ + "language": "en", + "release_year": 2012, + "availabe_for_order": true, + }, + "author": "Evelyn Waugh", + "title": "Sword of Honour", + "price": 12.99, + }, + map[string]interface{}{ + "category": "fiction", + "meta": nil, + "author": "Herman Melville", + "title": "Moby Dick", + "isbn": "0-553-21311-3", + "price": 8.99, + }, + } + ) + + res, err := JsonPathLookup(books, "$[?(@.meta.language == 'en')]") + if res_v, ok := res.([]interface{}); err != nil || ok == false || len(res_v) != 2 { + fmt.Println(res, err) + t.Error("root array support is broken") + } + + res2, err := JsonPathLookup(books, "$[?(@.meta)]") + if res_v, ok := res2.([]interface{}); err != nil || ok == false || len(res_v) != 2 { + fmt.Println(res2, err) + t.Error("root array support broken") + } + + res3, err := JsonPathLookup(books, "$[-1]") + if res_v, ok := res3.(map[string]interface{}); err != nil || ok == false || len(res_v) != 6 || res_v["meta"] != nil { + fmt.Println(res3, err) + t.Error("root array support broken") + } + + res4, err := JsonPathLookup(books, "$[*]") + if res_v, ok := res4.([]map[string]interface{}); err != nil || ok == false || len(res_v) != 3 { + fmt.Println(res4, err) + t.Error("root array support broken") + } + + res5, err := JsonPathLookup(books, "$[?(@.meta.language == 'en')].meta.release_year") + if res_v, ok := res5.([]interface{}); err != nil || ok == false || len(res_v) != 2 { + fmt.Println(res5, err) + t.Error("root array support is broken") + } +} From daa186f8f5b5006dc8052086bd6037fefc86c63f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsa=20P=C3=A9ter?= Date: Mon, 3 Sep 2018 21:50:37 +0200 Subject: [PATCH 2/8] Support expressions on the right side of equations with array roots. --- jsonpath.go | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/jsonpath.go b/jsonpath.go index e58679a..fe33542 100644 --- a/jsonpath.go +++ b/jsonpath.go @@ -144,6 +144,7 @@ func tokenize(query string) ([]string, error) { // token_start := false // token_end := false token := "" + open := 0 // fmt.Println("-------------------------------------------------- start") for idx, x := range query { @@ -171,14 +172,22 @@ func tokenize(query string) ([]string, error) { if strings.Contains(token, "[") { // fmt.Println(" contains [ ") if x == ']' && !strings.HasSuffix(token, "\\]") { - if token[0] == '.' { - tokens = append(tokens, token[1:]) - } else { - tokens = append(tokens, token[:]) + open-- + + if open == 0 { + if token[0] == '.' { + tokens = append(tokens, token[1:]) + } else { + tokens = append(tokens, token[:]) + } + token = "" } - token = "" continue } + + if x == '[' && !strings.HasSuffix(token, "\\[") { + open++ + } } else { // fmt.Println(" doesn't contains [ ") if x == '.' { @@ -667,7 +676,7 @@ func eval_filter(obj, root interface{}, lp, op, rp string) (res bool, err error) var rp_v interface{} if strings.HasPrefix(rp, "@.") { rp_v, err = filter_get_from_explicit_path(obj, rp) - } else if strings.HasPrefix(rp, "$.") { + } else if strings.HasPrefix(rp, "$.") || strings.HasPrefix(rp, "$[") { rp_v, err = filter_get_from_explicit_path(root, rp) } else { rp_v = rp From 73e6d28be4a6278c1d3323b00dd743c4092ab531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsa=20P=C3=A9ter?= Date: Mon, 3 Sep 2018 22:04:38 +0200 Subject: [PATCH 3/8] Add struct support --- jsonpath.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/jsonpath.go b/jsonpath.go index 00dc6fd..db57d7d 100644 --- a/jsonpath.go +++ b/jsonpath.go @@ -335,7 +335,8 @@ func get_key(obj interface{}, key string) (interface{}, error) { if reflect.TypeOf(obj) == nil { return nil, ErrGetFromNullObj } - switch reflect.TypeOf(obj).Kind() { + value := reflect.ValueOf(obj) + switch value.Kind() { case reflect.Map: // if obj came from stdlib json, its highly likely to be a map[string]interface{} // in which case we can save having to iterate the map keys to work out if the @@ -347,23 +348,70 @@ func get_key(obj interface{}, key string) (interface{}, error) { } return val, nil } - for _, kv := range reflect.ValueOf(obj).MapKeys() { + for _, kv := range value.MapKeys() { //fmt.Println(kv.String()) if kv.String() == key { - return reflect.ValueOf(obj).MapIndex(kv).Interface(), nil + return value.MapIndex(kv).Interface(), nil } } return nil, fmt.Errorf("key error: %s not found in object", key) case reflect.Slice: // slice we should get from all objects in it. res := []interface{}{} - for i := 0; i < reflect.ValueOf(obj).Len(); i++ { + for i := 0; i < value.Len(); i++ { tmp, _ := get_idx(obj, i) if v, err := get_key(tmp, key); err == nil { res = append(res, v) } } return res, nil + case reflect.Ptr: + // Unwrap pointer + realValue := value.Elem() + + if !realValue.IsValid() { + return nil, fmt.Errorf("null pointer") + } + + return get_key(realValue.Interface(), key) + case reflect.Interface: + // Unwrap interface value + realValue := value.Elem() + + return get_key(realValue.Interface(), key) + case reflect.Struct: + for i := 0; i < value.NumField(); i++ { + valueField := value.Field(i) + structField := value.Type().Field(i) + + // Embeded struct + if valueField.Kind() == reflect.Struct && structField.Anonymous { + v, _ := get_key(valueField.Interface(), key) + if v != nil { + return v, nil + } + } else { + if structField.Name == key { + return valueField.Interface(), nil + } + + if tag, ok := structField.Tag.Lookup("json"); ok { + values := strings.Split(tag, ",") + for _, tagValue := range values { + // In the following cases json tag names should not be checked: + // ",omitempty", "-", "-," + if (tagValue == "" && len(values) == 2) || tagValue == "-" { + break + } + if tagValue != "omitempty" && tagValue == key { + return valueField.Interface(), nil + } + } + } + } + } + + return nil, fmt.Errorf("key error: %s not found in struct", key) default: return nil, fmt.Errorf("object is not map") } From 520a965cfcae2932225ceba40741a8d4d60a4bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsa=20P=C3=A9ter?= Date: Mon, 3 Sep 2018 23:14:18 +0200 Subject: [PATCH 4/8] Use StructTag.Get() method to not change go version requirements. --- jsonpath.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsonpath.go b/jsonpath.go index db57d7d..3a9a095 100644 --- a/jsonpath.go +++ b/jsonpath.go @@ -395,7 +395,7 @@ func get_key(obj interface{}, key string) (interface{}, error) { return valueField.Interface(), nil } - if tag, ok := structField.Tag.Lookup("json"); ok { + if tag := structField.Tag.Get("json"); tag != "" { values := strings.Split(tag, ",") for _, tagValue := range values { // In the following cases json tag names should not be checked: From 416356484c4dc9d40b942e41e012712f2af85024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsa=20P=C3=A9ter?= Date: Tue, 4 Sep 2018 09:34:36 +0200 Subject: [PATCH 5/8] Add test cases to test struct support --- jsonpath_test.go | 209 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 201 insertions(+), 8 deletions(-) diff --git a/jsonpath_test.go b/jsonpath_test.go index 90f05b7..44585ee 100644 --- a/jsonpath_test.go +++ b/jsonpath_test.go @@ -10,7 +10,46 @@ import ( "testing" ) -var json_data interface{} +type Data struct { + Store *Store `json:"store"` + Expensive float64 `json:"expensive"` +} + +type Store struct { + Book []Goods `json:"book"` + Bicycle []Goods `json:"bicycle"` +} + +type Goods interface { + GetPrice() float64 +} + +type Book struct { + Category string `json:"category,omitempty"` + Author string `json:"author"` + Title string `json:"title"` + Price float64 `json:"price"` + ISBN string `json:"isbn"` + Tags []string `json:"-"` +} + +func (b *Book) GetPrice() float64 { + return b.Price +} + +type Bicycle struct { + Color string `json:"colour"` + Price float64 +} + +func (b *Bicycle) GetPrice() float64 { + return b.Price +} + +var ( + json_data interface{} + structData *Data +) func init() { data := ` @@ -53,6 +92,51 @@ func init() { } ` json.Unmarshal([]byte(data), &json_data) + + structData = &Data{ + Store: &Store{ + Book: []Goods{ + &Book{ + Category: "reference", + Author: "Nigel Rees", + Title: "Sayings of the Century", + Price: 8.95, + }, + &Book{ + Category: "fiction", + Author: "Evelyn Waugh", + Title: "Sword of Honour", + Price: 12.99, + Tags: []string{"fiction", "best-seller", "best-deal"}, + }, + &Book{ + Category: "fiction", + Author: "Herman Melville", + Title: "Moby Dick", + ISBN: "0-553-21311-3", + Price: 8.99, + }, + &Book{ + Category: "fiction", + Author: "J. R. R. Tolkien", + Title: "The Lord of the Rings", + ISBN: "0-395-19395-8", + Price: 22.99, + }, + }, + Bicycle: []Goods{ + &Bicycle{ + Color: "red", + Price: 19.95, + }, + &Bicycle{ + Color: "brown", + Price: 9.99, + }, + }, + }, + Expensive: 10, + } } func Test_jsonpath_JsonPathLookup_1(t *testing.T) { @@ -65,7 +149,7 @@ func Test_jsonpath_JsonPathLookup_1(t *testing.T) { // 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") + t.Errorf("$.store.book[0].price should be 8.95, received: %v", res) } // nagtive single index @@ -96,7 +180,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) @@ -114,6 +198,65 @@ func Test_jsonpath_JsonPathLookup_1(t *testing.T) { } } +func Test_jsonpath_JsonPathLookup_structs_1(t *testing.T) { + // key from root + res, _ := JsonPathLookup(structData, "$.expensive") + if res_v, ok := res.(float64); ok != true || res_v != 10.0 { + t.Errorf("expensive should be 10") + } + + // single index + res, _ = JsonPathLookup(structData, "$.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, received: %v", res) + } + + // nagtive single index + res, _ = JsonPathLookup(structData, "$.store.book[-1].isbn") + if res_v, ok := res.(string); ok != true || res_v != "0-395-19395-8" { + t.Errorf("$.store.book[-1].isbn should be \"0-395-19395-8\", received: %v", res) + } + + // multiple index + res, err := JsonPathLookup(structData, "$.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) + } + + // multiple index + res, err = JsonPathLookup(structData, "$.store.book[0,1].title") + t.Log(err, res) + if res_v, ok := res.([]interface{}); ok != true { + if res_v[0].(string) != "Sayings of the Century" || res_v[1].(string) != "Sword of Honour" { + t.Errorf("title are wrong: %v", res) + } + } + + // full array + res, err = JsonPathLookup(structData, "$.store.book[0:].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 || 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(structData, "$.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) + } + + // range + res, err = JsonPathLookup(structData, "$.store.book[0:1].title") + t.Log(err, res) + if res_v, ok := res.([]interface{}); ok != true { + if res_v[0].(string) != "Sayings of the Century" || res_v[1].(string) != "Sword of Honour" { + t.Errorf("title are wrong: %v", res) + } + } +} + func Test_jsonpath_JsonPathLookup_filter(t *testing.T) { res, err := JsonPathLookup(json_data, "$.store.book[?(@.isbn)].isbn") t.Log(err, res) @@ -124,7 +267,7 @@ func Test_jsonpath_JsonPathLookup_filter(t *testing.T) { } } - res, err = JsonPathLookup(json_data, "$.store.book[?(@.price > 10)].title") + res, err = JsonPathLookup(json_data, "$.store.book[?(@.price > 10)].Title") t.Log(err, res) if res_v, ok := res.([]interface{}); ok != true { if res_v[0].(string) != "Sword of Honour" || res_v[1].(string) != "The Lord of the Rings" { @@ -141,6 +284,17 @@ func Test_jsonpath_JsonPathLookup_filter(t *testing.T) { t.Log(err, res) } +func Test_jsonpath_JsonPathLookup_struct_filter(t *testing.T) { + res, err := JsonPathLookup(structData, "$.store.book[?(@.isbn)].ISBN") + t.Log(err, res) + + if res_v, ok := res.([]interface{}); ok != true { + if res_v[0].(string) != "0-553-21311-3" || res_v[1].(string) != "0-395-19395-8" { + t.Errorf("error: %v", res) + } + } +} + func Test_jsonpath_authors_of_all_books(t *testing.T) { query := "store.book[*].author" expected := []string{ @@ -456,6 +610,45 @@ func Test_jsonpath_get_key(t *testing.T) { fmt.Println(err, res) } +func Test_jsonpath_get_key_struct(t *testing.T) { + res, err := get_key(structData, "Store") + fmt.Println(err, res) + if err != nil { + t.Errorf("failed to get struct key: %v", err) + return + } + if res_v, ok := res.(*Store); !ok || len(res_v.Bicycle) != 2 || len(res_v.Book) != 4 { + t.Error("get field of struct failed") + return + } + + res, err = get_key(structData, "hah") + if err == nil { + t.Error("failed to raise missing key error") + return + } + + res, err = get_key(structData, "store") + if err != nil { + t.Errorf("failed to get struct key: %v", err) + return + } + if res_v, ok := res.(*Store); !ok || len(res_v.Bicycle) != 2 || len(res_v.Book) != 4 { + t.Error("get field of struct by json tag name failed") + return + } + + res, err = get_key(structData.Store.Book[0], "Category") + if err != nil { + t.Errorf("failed to get field of struct masked by interface: %v", err) + return + } + if res.(string) != "reference" { + t.Errorf("not expected value returned: %v", res) + return + } +} + func Test_jsonpath_get_idx(t *testing.T) { obj := []interface{}{1, 2, 3, 4} res, err := get_idx(obj, 0) @@ -1179,13 +1372,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 +1425,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 From e5a3e0f09ba1534dbf0138a4d8f2edf8e902752c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsa=20P=C3=A9ter?= Date: Tue, 4 Sep 2018 11:10:15 +0200 Subject: [PATCH 6/8] Fixes a problem when root is referenced in filter expression (#19) --- jsonpath.go | 8 ++++++-- jsonpath_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/jsonpath.go b/jsonpath.go index 00dc6fd..abbbe67 100644 --- a/jsonpath.go +++ b/jsonpath.go @@ -68,7 +68,11 @@ func (c *Compiled) String() string { } func (c *Compiled) Lookup(obj interface{}) (interface{}, error) { - var err error + var ( + err error + root = obj + ) + for _, s := range c.steps { // "key", "idx" switch s.op { @@ -128,7 +132,7 @@ func (c *Compiled) Lookup(obj interface{}) (interface{}, error) { if err != nil { return nil, err } - obj, err = get_filtered(obj, obj, s.args.(string)) + obj, err = get_filtered(obj, root, s.args.(string)) if err != nil { return nil, err } diff --git a/jsonpath_test.go b/jsonpath_test.go index 90f05b7..9c94d9b 100644 --- a/jsonpath_test.go +++ b/jsonpath_test.go @@ -134,11 +134,47 @@ func Test_jsonpath_JsonPathLookup_filter(t *testing.T) { res, err = JsonPathLookup(json_data, "$.store.book[?(@.price > 10)]") t.Log(err, res) + if res_v, ok := res.([]interface{}); !ok { + t.Errorf("expected: []interface{}, received: %v", res) + } else { + if len(res_v) != 2 { + t.Errorf("length of result should be 2, but actual length is %d: %v", len(res_v), res_v) + } else { + prices := []interface{}{res_v[0].(map[string]interface{})["price"], res_v[1].(map[string]interface{})["price"]} + if prices[0] != 12.99 || prices[1] != 22.99 { + t.Errorf("expected book prices: [12.99, 22.99] but received: %v, result: %v", prices, res_v) + } + } + } res, err = JsonPathLookup(json_data, "$.store.book[?(@.price > $.expensive)].price") t.Log(err, res) + if res_v, ok := res.([]interface{}); ok != true { + t.Errorf("expected: []interface{}, received: %v", res) + } else { + if len(res_v) != 2 { + t.Errorf("length of result should be 2, but actual length is %d: %v", len(res_v), res_v) + } else { + if res_v[0].(float64) != 12.99 || res_v[1].(float64) != 22.99 { + t.Errorf("expected result: [12.99, 22.99] but received: %v", res_v) + } + } + } + res, err = JsonPathLookup(json_data, "$.store.book[?(@.price < $.expensive)].price") t.Log(err, res) + if res_v, ok := res.([]interface{}); ok != true { + t.Errorf("expected: []Goods, received: %v", res) + } else { + if len(res_v) != 2 { + t.Errorf("length of result should be 2, but actual length is %d: %v", len(res_v), res_v) + } else { + prices := []float64{res_v[0].(float64), res_v[1].(float64)} + if prices[0] != 8.95 || prices[1] != 8.99 { + t.Errorf("expected result: [8.95, 8.99] but received: %v", prices) + } + } + } } func Test_jsonpath_authors_of_all_books(t *testing.T) { From b7fc22c85fa023e3541e0ff82f442206542af28d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsa=20P=C3=A9ter?= Date: Tue, 4 Sep 2018 11:20:29 +0200 Subject: [PATCH 7/8] Add fixed filter tests of struct support --- jsonpath_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/jsonpath_test.go b/jsonpath_test.go index 52adbc5..7301047 100644 --- a/jsonpath_test.go +++ b/jsonpath_test.go @@ -329,6 +329,58 @@ func Test_jsonpath_JsonPathLookup_struct_filter(t *testing.T) { t.Errorf("error: %v", res) } } + + res, err = JsonPathLookup(structData, "$.store.book[?(@.price > 10)].title") + t.Log(err, res) + if res_v, ok := res.([]interface{}); ok != true { + if res_v[0].(string) != "Sword of Honour" || res_v[1].(string) != "The Lord of the Rings" { + t.Errorf("error: %v", res) + } + } + + res, err = JsonPathLookup(structData, "$.store.book[?(@.price > 10)]") + t.Log(err, res) + if res_v, ok := res.([]interface{}); ok != true { + t.Errorf("expected: []interface{}, received: %v", res) + } else { + if len(res_v) != 2 { + t.Errorf("length of result should be 2, but actual length is %d: %v", len(res_v), res_v) + } else { + prices := []*Book{res_v[0].(*Book), res_v[1].(*Book)} + if prices[0].Price != 12.99 || prices[1].Price != 22.99 { + t.Errorf("expected book prices: [12.99, 22.99] but received: %v, result: %v", prices, res_v) + } + } + } + + res, err = JsonPathLookup(structData, "$.store.book[?(@.price > $.expensive)].price") + t.Log(err, res) + if res_v, ok := res.([]interface{}); ok != true { + t.Errorf("expected: []interface{}, received: %v", res) + } else { + if len(res_v) != 2 { + t.Errorf("length of result should be 2, but actual length is %d: %v", len(res_v), res_v) + } else { + if res_v[0].(float64) != 12.99 || res_v[1].(float64) != 22.99 { + t.Errorf("expected result: [12.99, 22.99] but received: %v", res_v) + } + } + } + + res, err = JsonPathLookup(structData, "$.store.book[?(@.price < $.expensive)].price") + t.Log(err, res) + if res_v, ok := res.([]interface{}); ok != true { + t.Errorf("expected: []Goods, received: %v", res) + } else { + if len(res_v) != 2 { + t.Errorf("length of result should be 2, but actual length is %d: %v", len(res_v), res_v) + } else { + prices := []float64{res_v[0].(float64), res_v[1].(float64)} + if prices[0] != 8.95 || prices[1] != 8.99 { + t.Errorf("expected result: [8.95, 8.99] but received: %v", prices) + } + } + } } func Test_jsonpath_authors_of_all_books(t *testing.T) { From 636935afab0e9018cb8f7b24b112d26276fcad98 Mon Sep 17 00:00:00 2001 From: "Y.ASAI" Date: Wed, 28 Oct 2020 10:43:59 -0700 Subject: [PATCH 8/8] fix panic in a filter expression without parentheses Before this fix, it had a panic in a filter expression without parentheses. --- jsonpath.go | 8 ++++--- jsonpath_test.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/jsonpath.go b/jsonpath.go index 97d0f9e..0a55029 100644 --- a/jsonpath.go +++ b/jsonpath.go @@ -251,12 +251,14 @@ 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, "?") { + if strings.HasPrefix(tail, "?") { // filter ------------------------------------------------- op = "filter" - if strings.HasPrefix(tail, "?(") && strings.HasSuffix(tail, ")") { - args = strings.Trim(tail[2:len(tail)-1], " ") + if !strings.HasPrefix(tail, "?(") || !strings.HasSuffix(tail, ")") { + err = fmt.Errorf("filter expression needs to be enclosed in parentheses: %v", tail) + return } + args = strings.Trim(tail[2:len(tail)-1], " ") return } else if strings.Contains(tail, ":") { // range ---------------------------------------------- diff --git a/jsonpath_test.go b/jsonpath_test.go index f6ecbc3..2fe85c9 100644 --- a/jsonpath_test.go +++ b/jsonpath_test.go @@ -318,6 +318,37 @@ func Test_jsonpath_JsonPathLookup_filter(t *testing.T) { } } } + + res, err = JsonPathLookup(json_data, "$.store.book[?@.isbn)]") + t.Log(err, res) + if _, ok := res.([]interface{}); ok == true { + t.Errorf("expected: error, received: %v", res) + } + + res, err = JsonPathLookup(json_data, "$.store.book[?(@.isbn]") + t.Log(err, res) + if _, ok := res.([]interface{}); ok == true { + t.Errorf("expected: error, received: %v", res) + } + + res, err = JsonPathLookup(json_data, "$.store.book[?@.isbn]") + t.Log(err, res) + if _, ok := res.([]interface{}); ok == true { + t.Errorf("expected: error, received: %v", res) + } + + res, err = JsonPathLookup(json_data, "$.store.book[? (@.isbn)]") + t.Log(err, res) + if _, ok := res.([]interface{}); ok == true { + t.Errorf("expected: error, received: %v", res) + } + + res, err = JsonPathLookup(json_data, "$.store.book[ ?(@.isbn)]") + t.Log(err, res) + if _, ok := res.([]interface{}); ok == true { + t.Errorf("expected: error, received: %v", res) + } + } func Test_jsonpath_JsonPathLookup_struct_filter(t *testing.T) { @@ -381,6 +412,37 @@ func Test_jsonpath_JsonPathLookup_struct_filter(t *testing.T) { } } } + + res, err = JsonPathLookup(structData, "$.store.book[?@.isbn)]") + t.Log(err, res) + if _, ok := res.([]interface{}); ok == true { + t.Errorf("expected: error, received: %v", res) + } + + res, err = JsonPathLookup(structData, "$.store.book[?(@.isbn]") + t.Log(err, res) + if _, ok := res.([]interface{}); ok == true { + t.Errorf("expected: error, received: %v", res) + } + + res, err = JsonPathLookup(structData, "$.store.book[?@.isbn]") + t.Log(err, res) + if _, ok := res.([]interface{}); ok == true { + t.Errorf("expected: error, received: %v", res) + } + + res, err = JsonPathLookup(structData, "$.store.book[? (@.isbn)]") + t.Log(err, res) + if _, ok := res.([]interface{}); ok == true { + t.Errorf("expected: error, received: %v", res) + } + + res, err = JsonPathLookup(structData, "$.store.book[ ?(@.isbn)]") + t.Log(err, res) + if _, ok := res.([]interface{}); ok == true { + t.Errorf("expected: error, received: %v", res) + } + } func Test_jsonpath_authors_of_all_books(t *testing.T) {