Skip to content

Commit

Permalink
#61 Fix filter when property does not exist (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
spyzhov authored Mar 14, 2023
1 parent 9c5214f commit a2fe491
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 113 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v2.5.2
uses: golangci/golangci-lint-action@v3.4.0
with:
version: v1.50.1
version: v1.51.2
args: -v

test:
strategy:
matrix:
go-version: [ '1.11.x', '1.12.x', '1.13.x', '1.14.x', '1.15.x', '1.16.x', '1.17.x', '1.18.x', '1.19.x' ]
go-version: [ '1.11.x', '1.12.x', '1.13.x', '1.14.x', '1.15.x', '1.16.x', '1.17.x', '1.18.x', '1.19.x', '1.20.x' ]
os: [ ubuntu-latest, macos-latest ]
runs-on: ${{ matrix.os }}
steps:
Expand All @@ -47,10 +47,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Go 1.19.x on ubuntu-latest
- name: Setup Go 1.20.x on ubuntu-latest
uses: actions/setup-go@v3
with:
go-version: '1.19.x'
go-version: '1.20.x'
- name: Setup Dependencies
run: |
go get golang.org/x/tools/cmd/cover
Expand Down
4 changes: 4 additions & 0 deletions buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ func (b *buffer) rpn() (result rpn, err error) {
current = string(b.data[start:b.index])
result = append(result, current)
if err != nil {
//nolint:ineffassign
err = nil
} else {
b.index--
Expand Down Expand Up @@ -549,6 +550,7 @@ func (b *buffer) tokenize() (result tokens, err error) {
err = b.numeric(true)
if err != nil && err != io.EOF {
if c == dot {
//nolint:ineffassign
err = nil
result = append(result, ".")
b.index = start
Expand Down Expand Up @@ -582,6 +584,7 @@ func (b *buffer) tokenize() (result tokens, err error) {
current = string(b.data[start:b.index])
result = append(result, current)
if err != nil {
//nolint:ineffassign
err = nil
} else {
b.index--
Expand Down Expand Up @@ -612,6 +615,7 @@ func (b *buffer) tokenize() (result tokens, err error) {
if start == b.index {
err = b.step()
if err != nil {
//nolint:ineffassign
err = nil
current = strings.ToLower(string(b.data[start : b.index+1]))
} else {
Expand Down
212 changes: 104 additions & 108 deletions jsonpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,148 +15,146 @@ import (
//
// JSONPath expressions can use the dot–notation
//
// $.store.book[0].title
// $.store.book[0].title
//
// or the bracket–notation
//
// $['store']['book'][0]['title']
// $['store']['book'][0]['title']
//
// for input pathes. Internal or output pathes will always be converted to the more general bracket–notation.
//
// JSONPath allows the wildcard symbol * for member names and array indices. It borrows the descendant operator '..' from E4X and the array slice syntax proposal [start:end:step] from ECMASCRIPT 4.
//
// Expressions of the underlying scripting language (<expr>) can be used as an alternative to explicit names or indices as in
//
// $.store.book[(@.length-1)].title
// $.store.book[(@.length-1)].title
//
// using the symbol '@' for the current object. Filter expressions are supported via the syntax ?(<boolean expr>) as in
//
// $.store.book[?(@.price < 10)].title
// $.store.book[?(@.price < 10)].title
//
// Here is a complete overview and a side by side comparison of the JSONPath syntax elements with its XPath counterparts.
//
// $ the root object/element
// @ the current object/element
// . or [] child operator
// .. recursive descent. JSONPath borrows this syntax from E4X.
// * wildcard. All objects/elements regardless their names.
// [] subscript operator. XPath uses it to iterate over element collections and for predicates. In Javascript and JSON it is the native array operator.
// [,] Union operator in XPath results in a combination of node sets. JSONPath allows alternate names or array indices as a set.
// [start:end:step] array slice operator borrowed from ES4.
// ?() applies a filter (script) expression.
// () script expression, using the underlying script engine.
// $ the root object/element
// @ the current object/element
// . or [] child operator
// .. recursive descent. JSONPath borrows this syntax from E4X.
// * wildcard. All objects/elements regardless their names.
// [] subscript operator. XPath uses it to iterate over element collections and for predicates. In Javascript and JSON it is the native array operator.
// [,] Union operator in XPath results in a combination of node sets. JSONPath allows alternate names or array indices as a set.
// [start:end:step] array slice operator borrowed from ES4.
// ?() applies a filter (script) expression.
// () script expression, using the underlying script engine.
//
// # JSONPath Script engine
//
// JSONPath Script engine
//
// Predefined constant
// # Predefined constant
//
// Package has several predefined constants. You are free to add new one with AddConstant
//
// e math.E float64
// pi math.Pi float64
// phi math.Phi float64
// e math.E float64
// pi math.Pi float64
// phi math.Phi float64
//
// sqrt2 math.Sqrt2 float64
// sqrte math.SqrtE float64
// sqrtpi math.SqrtPi float64
// sqrtphi math.SqrtPhi float64
// sqrt2 math.Sqrt2 float64
// sqrte math.SqrtE float64
// sqrtpi math.SqrtPi float64
// sqrtphi math.SqrtPhi float64
//
// ln2 math.Ln2 float64
// log2e math.Log2E float64
// ln10 math.Ln10 float64
// log10e math.Log10E float64
// ln2 math.Ln2 float64
// log2e math.Log2E float64
// ln10 math.Ln10 float64
// log10e math.Log10E float64
//
// true true bool
// false false bool
// null nil interface{}
// true true bool
// false false bool
// null nil interface{}
//
// Supported operations
// # Supported operations
//
// Package has several predefined operators. You are free to add new one with AddOperator
//
// Operator precedence: https://golang.org/ref/spec#Operator_precedence
//
// Precedence Operator
// 6 **
// 5 * / % << >> & &^
// 4 + - | ^
// 3 == != < <= > >= =~
// 2 &&
// 1 ||
// Precedence Operator
// 6 **
// 5 * / % << >> & &^
// 4 + - | ^
// 3 == != < <= > >= =~
// 2 &&
// 1 ||
//
// Arithmetic operators: https://golang.org/ref/spec#Arithmetic_operators
//
// ** power integers, floats
// + sum integers, floats, strings
// - difference integers, floats
// * product integers, floats
// / quotient integers, floats
// % remainder integers
// ** power integers, floats
// + sum integers, floats, strings
// - difference integers, floats
// * product integers, floats
// / quotient integers, floats
// % remainder integers
//
// & bitwise AND integers
// | bitwise OR integers
// ^ bitwise XOR integers
// &^ bit clear (AND NOT) integers
// & bitwise AND integers
// | bitwise OR integers
// ^ bitwise XOR integers
// &^ bit clear (AND NOT) integers
//
// << left shift integer << unsigned integer
// >> right shift integer >> unsigned integer
// << left shift integer << unsigned integer
// >> right shift integer >> unsigned integer
//
// == equals any
// != not equals any
// < less any
// <= less or equals any
// > larger any
// >= larger or equals any
// =~ equals regex string strings
// == equals any
// != not equals any
// < less any
// <= less or equals any
// > larger any
// >= larger or equals any
// =~ equals regex string strings
//
// Supported functions
// # Supported functions
//
// Package has several predefined functions. You are free to add new one with AddFunction
//
// abs math.Abs integers, floats
// acos math.Acos integers, floats
// acosh math.Acosh integers, floats
// asin math.Asin integers, floats
// asinh math.Asinh integers, floats
// atan math.Atan integers, floats
// atanh math.Atanh integers, floats
// avg Average array of integers or floats
// cbrt math.Cbrt integers, floats
// ceil math.Ceil integers, floats
// cos math.Cos integers, floats
// cosh math.Cosh integers, floats
// erf math.Erf integers, floats
// erfc math.Erfc integers, floats
// erfcinv math.Erfcinv integers, floats
// erfinv math.Erfinv integers, floats
// exp math.Exp integers, floats
// exp2 math.Exp2 integers, floats
// expm1 math.Expm1 integers, floats
// factorial N! unsigned integer
// floor math.Floor integers, floats
// gamma math.Gamma integers, floats
// j0 math.J0 integers, floats
// j1 math.J1 integers, floats
// length len array
// log math.Log integers, floats
// log10 math.Log10 integers, floats
// log1p math.Log1p integers, floats
// log2 math.Log2 integers, floats
// logb math.Logb integers, floats
// not not any
// pow10 math.Pow10 integer
// round math.Round integers, floats
// roundtoeven math.RoundToEven integers, floats
// sin math.Sin integers, floats
// sinh math.Sinh integers, floats
// sqrt math.Sqrt integers, floats
// tan math.Tan integers, floats
// tanh math.Tanh integers, floats
// trunc math.Trunc integers, floats
// y0 math.Y0 integers, floats
// y1 math.Y1 integers, floats
//
// abs math.Abs integers, floats
// acos math.Acos integers, floats
// acosh math.Acosh integers, floats
// asin math.Asin integers, floats
// asinh math.Asinh integers, floats
// atan math.Atan integers, floats
// atanh math.Atanh integers, floats
// avg Average array of integers or floats
// cbrt math.Cbrt integers, floats
// ceil math.Ceil integers, floats
// cos math.Cos integers, floats
// cosh math.Cosh integers, floats
// erf math.Erf integers, floats
// erfc math.Erfc integers, floats
// erfcinv math.Erfcinv integers, floats
// erfinv math.Erfinv integers, floats
// exp math.Exp integers, floats
// exp2 math.Exp2 integers, floats
// expm1 math.Expm1 integers, floats
// factorial N! unsigned integer
// floor math.Floor integers, floats
// gamma math.Gamma integers, floats
// j0 math.J0 integers, floats
// j1 math.J1 integers, floats
// length len array
// log math.Log integers, floats
// log10 math.Log10 integers, floats
// log1p math.Log1p integers, floats
// log2 math.Log2 integers, floats
// logb math.Logb integers, floats
// not not any
// pow10 math.Pow10 integer
// round math.Round integers, floats
// roundtoeven math.RoundToEven integers, floats
// sin math.Sin integers, floats
// sinh math.Sinh integers, floats
// sqrt math.Sqrt integers, floats
// tan math.Tan integers, floats
// tanh math.Tanh integers, floats
// trunc math.Trunc integers, floats
// y0 math.Y0 integers, floats
// y1 math.Y1 integers, floats
func JSONPath(data []byte, path string) (result []*Node, err error) {
commands, err := ParseJSONPath(path)
if err != nil {
Expand Down Expand Up @@ -197,9 +195,8 @@ func recursiveChildren(node *Node) (result []*Node) {
// ParseJSONPath will parse current path and return all commands tobe run.
// Example:
//
// result, _ := ParseJSONPath("$.store.book[?(@.price < 10)].title")
// result == []string{"$", "store", "book", "?(@.price < 10)", "title"}
//
// result, _ := ParseJSONPath("$.store.book[?(@.price < 10)].title")
// result == []string{"$", "store", "book", "?(@.price < 10)", "title"}
func ParseJSONPath(path string) (result []string, err error) {
buf := newBuffer([]byte(path))
result = make([]string, 0)
Expand Down Expand Up @@ -227,6 +224,7 @@ func ParseJSONPath(path string) (result []string, err error) {
start = buf.index
c, err = buf.next()
if err == io.EOF {
//nolint:ineffassign
err = nil
break
}
Expand Down Expand Up @@ -312,7 +310,6 @@ func ParseJSONPath(path string) (result []string, err error) {
//
// commands := []string{"$", "store", "book", "?(@.price < 10)", "title"}
// result, _ := ApplyJSONPath(node, commands)
//
func ApplyJSONPath(node *Node, commands []string) (result []*Node, err error) {
if node == nil {
return nil, nil
Expand Down Expand Up @@ -636,8 +633,7 @@ func eval(node *Node, expression rpn, cmd string) (result *Node, err error) {
} else if len(slice) == 1 {
stack = append(stack, slice[0])
} else { // no data found
// stack = append(stack, NullNode(""))
return NullNode(""), nil
stack = append(stack, NullNode(""))
}
} else if constant, ok := constants[strings.ToLower(exp)]; ok {
stack = append(stack, constant)
Expand Down
25 changes: 25 additions & 0 deletions jsonpath_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1307,6 +1307,12 @@ func TestJSONPath_comparison_consensus(t *testing.T) {
document: `["first", "second", "third"]`,
consensus: `["first", "second"]`,
},
{
name: `unexpected_key`,
selector: `$[?(@.a||@.b)]`,
document: `[{"a":0,"b":0},{"a":1},{"b":2},{"a":0,"b":1},{"a":2,"b":0}]`,
consensus: `[{"a":1},{"b":2},{"a":0,"b":1},{"a":2,"b":0}]`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down Expand Up @@ -1510,3 +1516,22 @@ func ExampleApplyJSONPath() {
// Output:
// [4][3][2][1][0][9][8][7][6][5]
}

// TestJSONPath_issue_61 is a test for https://github.com/spyzhov/ajson/issues/61
func TestJSONPath_issue_61(t *testing.T) {
data := `{"test":[{"a":1},{"b":2}]}`
root, err := Unmarshal([]byte(data))
if err != nil {
t.Errorf("Unmarshal() unexpected error = %v", err)
return
}
nodes, err := root.JSONPath(`$..[?( @.a==1 || @.b==2 )]`)
if err != nil {
t.Errorf("JSONPath() error = %v. got = %v", err, nodes)
return
}
if len(nodes) != 2 {
t.Errorf("JSONPath() unexpected result got = %v", nodes)
return
}
}

0 comments on commit a2fe491

Please sign in to comment.