From 441bc46bdcc589e8b74486e33916e34b528f198b Mon Sep 17 00:00:00 2001 From: Julien Mathevet Date: Fri, 10 Nov 2017 12:22:35 +0100 Subject: [PATCH 1/4] add basic array element filtering --- parse.go | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/parse.go b/parse.go index db58647..876f609 100644 --- a/parse.go +++ b/parse.go @@ -7,6 +7,10 @@ import ( "sort" "strconv" "strings" + "fmt" + "regexp" + "go/types" + "go/token" ) type Applicator interface { @@ -134,6 +138,52 @@ func (w *WildCardSelection) Apply(v interface{}) (interface{}, error) { } } +type WildCardFilterSelection struct { + RootNode + Key string +} + +func (w *WildCardFilterSelection) Apply(v interface{}) (interface{}, error) { + arv, ok := v.([]interface{}) + if !ok { + return v, ArrayTypeError + } + var ret []interface{} + for _, val := range arv { + _, ok := val.(map[string]interface{}) + if !ok { + return v, MapTypeError + } + + re, err := regexp.Compile(`[\S]+`) + if err != nil { + return v, err + } + ops := re.FindAllString(w.Key, -1) + if len(ops) != 3 { + return v, fmt.Errorf("filter should contains 3 parts separated with blank") + } + wa, _ := Parse(strings.Replace(ops[0], "@", "$", 1)) + subv, _ := wa.Apply(val) + if subv == nil { + continue + } + isOk, _ := cmp_any(subv, ops[2], ops[1]) + //fmt.Printf("Key %s, subv %s, isOk %t \n", w.Key, subv, isOk) + if !isOk { + continue + } + rval, err := applyNext(w.NextNode, val) + + // Don't add anything that causes an error or returns nil. + if err == nil || rval != nil { + ret = append(ret, rval) + } + } + return ret, nil +} + + // DescentSelection is a filter that recursively descends applying it's NextNode and // corrlating the results. type DescentSelection struct { @@ -290,8 +340,10 @@ func getNode(s string) (node, string, error) { return &WildCardSelection{}, rs, nil case "[.": return &DescentSelection{}, rs, nil - case "[?", "[(": - return nil, rs, NotSupportedError + case "[?": + return &WildCardFilterSelection{Key: s[3 : n-1]}, rs, nil + case "[(": + return &WildCardFilterSelection{Key: s[2 : n-1]}, rs, nil default: // Assume it's a array index otherwise. i, err := strconv.Atoi(s[1:n]) if err != nil { @@ -322,3 +374,41 @@ func Parse(s string) (Applicator, error) { } return &rt, nil } + + +func cmp_any(obj1, obj2 interface{}, op string) (bool, error) { + switch op { + case "<", "<=", "==", ">=", ">", "!=": + default: + return false, fmt.Errorf("op should only be <, <=, ==, !=, >= and >") + } + //fmt.Println("cmp_any: ", obj1, obj2) + var sobj1 string + switch obj1.(type) { + case string: + sobj1 = fmt.Sprintf("\"%v\"", obj1) + default: + sobj1 = fmt.Sprintf("%v", obj1) + } + var sobj2 string + switch obj1.(type) { + case string: + sobj2 = fmt.Sprintf("\"%v\"", obj2) + default: + sobj2 = fmt.Sprintf("%v", obj2) + } + + exp := fmt.Sprintf("%v %s %v", sobj1, op, sobj2) + fset := token.NewFileSet() + res, err := types.Eval(fset, nil, 0, exp) + if err != nil { + return false, err + } + if res.IsValue() == false || (res.Value.String() != "false" && res.Value.String() != "true") { + return false, fmt.Errorf("result should only be true or false") + } + if res.Value.String() == "true" { + return true, nil + } + return false, nil +} \ No newline at end of file From 85db3c96f43f79e1518e04b0a7e27db993f3289e Mon Sep 17 00:00:00 2001 From: Julien Mathevet Date: Fri, 10 Nov 2017 15:03:42 +0100 Subject: [PATCH 2/4] fix test --- parse_test.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/parse_test.go b/parse_test.go index 210f8e8..c49370d 100644 --- a/parse_test.go +++ b/parse_test.go @@ -86,6 +86,14 @@ func isSameWildcardSelectionNode(n *WildCardSelection, m node) bool { return false } } +func isSameWildcardFilterSelectionNode(n *WildCardFilterSelection, m node) bool { + switch mv := m.(type) { + case *WildCardFilterSelection: + return isSameNode(n.NextNode, mv.NextNode) + default: + return false + } +} func isSameDescentSelectionNode(n *DescentSelection, m node) bool { switch mv := m.(type) { case *DescentSelection: @@ -110,6 +118,8 @@ func isSameNode(n, m node) bool { return isSameDescentSelectionNode(nv, m) case *WildCardSelection: return isSameWildcardSelectionNode(nv, m) + case *WildCardFilterSelection: + return isSameWildcardFilterSelectionNode(nv, m) default: return false } @@ -126,8 +136,8 @@ func TestGetNode(t *testing.T) { {t: `[10]`, n: &ArraySelection{Key: 10}}, {t: `[*]`, n: &WildCardSelection{}}, {t: `[..]`, n: &DescentSelection{}}, - {t: `[?(@.lenght())]`, n: nil, err: NotSupportedError}, - {t: `[(@.foo)]`, n: nil, err: NotSupportedError}, + {t: `[?(@.lenght())]`, n: &WildCardFilterSelection{Key: "@.lenght()"}}, + {t: `[(@.foo)]`, n: &WildCardFilterSelection{Key: "@.foo"}}, {t: `[0:10:2]`, n: nil, err: SyntaxError}, } for i, test := range testcases { From f211502f8fcd4d44db86c34efcc49171367c9fee Mon Sep 17 00:00:00 2001 From: Julien Mathevet Date: Fri, 10 Nov 2017 15:13:24 +0100 Subject: [PATCH 3/4] add filter tests --- parse_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/parse_test.go b/parse_test.go index c49370d..805b837 100644 --- a/parse_test.go +++ b/parse_test.go @@ -267,6 +267,37 @@ func TestParse(t *testing.T) { t: "..author..", expected: []interface{}{"Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"}, }, + { + t: "store.book[?(@.price < 10)]", + expected: []interface{}{ map[string]interface{}{ + "category": "reference", + "category.sub": "quotes", + "author": "Nigel Rees", + "title": "Saying of the Century", + "price": 8.95, + }, map[string]interface{}{ + "category" : "fiction", + "author" : "Herman Melville", + "title" : "Moby Dick", + "isbn" : "0-553-21311-3", + "price" : 8.99, + }, + + }, + }, + { + t: `store.book[?(@.category == reference)]`, + expected: []interface{}{ map[string]interface{}{ + "category": "reference", + "category.sub": "quotes", + "author": "Nigel Rees", + "title": "Saying of the Century", + "price": 8.95, + }, + + }, + }, + } for i, test := range testcases { a, err := Parse(test.t) From 476e972d1076f5eb8a2a4c5a0dd0df1ac8b36f90 Mon Sep 17 00:00:00 2001 From: Julien Mathevet Date: Wed, 15 Nov 2017 16:44:12 +0100 Subject: [PATCH 4/4] flatten results --- parse.go | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/parse.go b/parse.go index 876f609..d218550 100644 --- a/parse.go +++ b/parse.go @@ -118,7 +118,7 @@ func (w *WildCardSelection) Apply(v interface{}) (interface{}, error) { rval, err := applyNext(w.NextNode, tv[key]) // Don't add anything that causes an error or returns nil. if err == nil || rval != nil { - ret = append(ret, rval) + ret = flattenAppend(ret, rval) } } return ret, nil @@ -128,7 +128,7 @@ func (w *WildCardSelection) Apply(v interface{}) (interface{}, error) { rval, err := applyNext(w.NextNode, val) // Don't add anything that causes an error or returns nil. if err == nil || rval != nil { - ret = append(ret, rval) + ret = flattenAppend(ret, rval) } } return ret, nil @@ -160,19 +160,19 @@ func (w *WildCardFilterSelection) Apply(v interface{}) (interface{}, error) { return v, err } ops := re.FindAllString(w.Key, -1) - if len(ops) != 3 { - return v, fmt.Errorf("filter should contains 3 parts separated with blank") - } wa, _ := Parse(strings.Replace(ops[0], "@", "$", 1)) subv, _ := wa.Apply(val) if subv == nil { continue } - isOk, _ := cmp_any(subv, ops[2], ops[1]) - //fmt.Printf("Key %s, subv %s, isOk %t \n", w.Key, subv, isOk) - if !isOk { - continue + + if len(ops) == 3 { + isOk, _ := cmp_any(subv, ops[2], ops[1]) + if !isOk { + continue + } } + rval, err := applyNext(w.NextNode, val) // Don't add anything that causes an error or returns nil. @@ -340,10 +340,8 @@ func getNode(s string) (node, string, error) { return &WildCardSelection{}, rs, nil case "[.": return &DescentSelection{}, rs, nil - case "[?": + case "[?", "[(": return &WildCardFilterSelection{Key: s[3 : n-1]}, rs, nil - case "[(": - return &WildCardFilterSelection{Key: s[2 : n-1]}, rs, nil default: // Assume it's a array index otherwise. i, err := strconv.Atoi(s[1:n]) if err != nil { @@ -382,7 +380,6 @@ func cmp_any(obj1, obj2 interface{}, op string) (bool, error) { default: return false, fmt.Errorf("op should only be <, <=, ==, !=, >= and >") } - //fmt.Println("cmp_any: ", obj1, obj2) var sobj1 string switch obj1.(type) { case string: