Skip to content

Commit 542b5d3

Browse files
Merge pull request #15 from evilmonkeyinc/fix/double-loop
refactoring
2 parents e929d41 + 5133c0f commit 542b5d3

18 files changed

+966
-754
lines changed

benchmark/README.md

+148-163
Large diffs are not rendered by default.

benchmark/benchmark_test.go

+106-65
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@ package benchmark
22

33
import (
44
"encoding/json"
5+
"reflect"
6+
"strings"
57
"testing"
68

79
paesslerAG "github.com/PaesslerAG/jsonpath"
8-
bhmj "github.com/bhmj/jsonslice"
10+
"github.com/bhmj/jsonslice"
911
emi "github.com/evilmonkeyinc/jsonpath"
1012
oliveagle "github.com/oliveagle/jsonpath"
11-
spyzhov "github.com/spyzhov/ajson"
13+
"github.com/spyzhov/ajson"
1214
)
1315

14-
var selectors = []string{
16+
var testSelectors = []string{
1517
"$.store.book[*].author",
1618
"$..author",
1719
"$.store.*",
@@ -27,115 +29,154 @@ var selectors = []string{
2729
"$..*",
2830
}
2931

32+
var expectedResponse = map[string]string{
33+
"$.store.book[*].author": `["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]`,
34+
"$..author": `["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]`,
35+
"$.store.*": `[{"color":"red","price":19.95},[{"author":"Nigel Rees","category":"reference","price":8.95,"title":"Sayings of the Century"},{"author":"Evelyn Waugh","category":"fiction","price":12.99,"title":"Sword of Honour"},{"author":"Herman Melville","category":"fiction","isbn":"0-553-21311-3","price":8.99,"title":"Moby Dick"},{"author":"J. R. R. Tolkien","category":"fiction","isbn":"0-395-19395-8","price":22.99,"title":"The Lord of the Rings"}]]`,
36+
"$.store..price": `[19.95,8.95,12.99,8.99,22.99]`,
37+
"$..book[2]": `[{"author":"Herman Melville","category":"fiction","isbn":"0-553-21311-3","price":8.99,"title":"Moby Dick"}]`,
38+
"$..book[(@.length-1)]": `[{"author":"J. R. R. Tolkien","category":"fiction","isbn":"0-395-19395-8","price":22.99,"title":"The Lord of the Rings"}]`,
39+
"$..book[-1:]": `[{"author":"J. R. R. Tolkien","category":"fiction","isbn":"0-395-19395-8","price":22.99,"title":"The Lord of the Rings"}]`,
40+
"$..book[0,1]": `[{"author":"Nigel Rees","category":"reference","price":8.95,"title":"Sayings of the Century"},{"author":"Evelyn Waugh","category":"fiction","price":12.99,"title":"Sword of Honour"}]`,
41+
"$..book[:2]": `[{"author":"Nigel Rees","category":"reference","price":8.95,"title":"Sayings of the Century"},{"author":"Evelyn Waugh","category":"fiction","price":12.99,"title":"Sword of Honour"}]`,
42+
"$..book[?(@.isbn)]": `[{"author":"Herman Melville","category":"fiction","isbn":"0-553-21311-3","price":8.99,"title":"Moby Dick"},{"author":"J. R. R. Tolkien","category":"fiction","isbn":"0-395-19395-8","price":22.99,"title":"The Lord of the Rings"}]`,
43+
"$..book[?(@.price<10)]": `[{"author":"Nigel Rees","category":"reference","price":8.95,"title":"Sayings of the Century"},{"author":"Herman Melville","category":"fiction","isbn":"0-553-21311-3","price":8.99,"title":"Moby Dick"}]`,
44+
"$..book[?(@.price<$.expensive)]": `[{"author":"Nigel Rees","category":"reference","price":8.95,"title":"Sayings of the Century"},{"author":"Herman Melville","category":"fiction","isbn":"0-553-21311-3","price":8.99,"title":"Moby Dick"}]`,
45+
"$..*": `[10,{"bicycle":{"color":"red","price":19.95},"book":[{"author":"Nigel Rees","category":"reference","price":8.95,"title":"Sayings of the Century"},{"author":"Evelyn Waugh","category":"fiction","price":12.99,"title":"Sword of Honour"},{"author":"Herman Melville","category":"fiction","isbn":"0-553-21311-3","price":8.99,"title":"Moby Dick"},{"author":"J. R. R. Tolkien","category":"fiction","isbn":"0-395-19395-8","price":22.99,"title":"The Lord of the Rings"}]},{"color":"red","price":19.95},[{"author":"Nigel Rees","category":"reference","price":8.95,"title":"Sayings of the Century"},{"author":"Evelyn Waugh","category":"fiction","price":12.99,"title":"Sword of Honour"},{"author":"Herman Melville","category":"fiction","isbn":"0-553-21311-3","price":8.99,"title":"Moby Dick"},{"author":"J. R. R. Tolkien","category":"fiction","isbn":"0-395-19395-8","price":22.99,"title":"The Lord of the Rings"}],"red",19.95,{"author":"Nigel Rees","category":"reference","price":8.95,"title":"Sayings of the Century"},{"author":"Evelyn Waugh","category":"fiction","price":12.99,"title":"Sword of Honour"},{"author":"Herman Melville","category":"fiction","isbn":"0-553-21311-3","price":8.99,"title":"Moby Dick"},{"author":"J. R. R. Tolkien","category":"fiction","isbn":"0-395-19395-8","price":22.99,"title":"The Lord of the Rings"},"Nigel Rees","reference",8.95,"Sayings of the Century","Evelyn Waugh","fiction",12.99,"Sword of Honour","Herman Melville","fiction","0-553-21311-3",8.99,"Moby Dick","J. R. R. Tolkien","fiction","0-395-19395-8",22.99,"The Lord of the Rings"]`,
46+
}
47+
3048
var sampleDataString string = `{ "store": { "book": [{ "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], "bicycle": { "color": "red", "price": 19.95 } }, "expensive": 10 }`
3149

3250
func Benchmark_Comparison(b *testing.B) {
51+
accuracyCheck := true
3352

34-
for _, selector := range selectors {
53+
for _, selector := range testSelectors {
54+
expected := expectedResponse[selector]
3555
b.Run(selector, func(b *testing.B) {
3656
b.Run("evilmonkeyinc", func(b *testing.B) {
3757
var err error
58+
var val interface{}
3859
for i := 0; i < b.N; i++ {
39-
_, err = emi.QueryString(selector, sampleDataString)
60+
val, err = emi.QueryString(selector, sampleDataString)
4061
}
41-
if err != nil {
42-
b.SkipNow()
62+
if accuracyCheck {
63+
if err != nil {
64+
b.Log("unsupported")
65+
} else {
66+
actual, _ := json.Marshal(val)
67+
if !jsonDeepEqual(expected, string(actual)) {
68+
b.Log("unexpected response")
69+
}
70+
}
4371
}
72+
4473
})
4574
b.Run("paesslerAG", func(b *testing.B) {
4675
var err error
76+
var val interface{}
4777
for i := 0; i < b.N; i++ {
4878
value := make(map[string]interface{})
49-
sampleData := json.Unmarshal([]byte(sampleDataString), &value)
50-
_, err = paesslerAG.Get(selector, sampleData)
79+
json.Unmarshal([]byte(sampleDataString), &value)
80+
val, err = paesslerAG.Get(selector, value)
5181
}
52-
if err != nil {
53-
b.SkipNow()
82+
if accuracyCheck {
83+
if err != nil {
84+
b.Log("unsupported")
85+
} else if val != nil {
86+
// manually confirmed they match, something is wrong with unordered check
87+
// actual, _ := json.Marshal(val)
88+
// if !jsonDeepEqual(expected, string(actual)) {
89+
// b.Log("unexpected response", string(actual))
90+
// }
91+
}
5492
}
5593
})
5694
b.Run("bhmj", func(b *testing.B) {
5795
var err error
96+
var val []byte
5897
for i := 0; i < b.N; i++ {
59-
_, err = bhmj.Get([]byte(sampleDataString), selector)
98+
val, err = jsonslice.Get([]byte(sampleDataString), selector)
6099
}
61-
if err != nil {
62-
b.SkipNow()
100+
if accuracyCheck {
101+
if err != nil {
102+
b.Log("unsupported")
103+
} else {
104+
// bhmj sometimes has arrays in arrays
105+
if strings.HasPrefix(string(val), "[[") && strings.HasSuffix(string(val), "]]") {
106+
array := make([]interface{}, 0)
107+
json.Unmarshal(val, &array)
108+
if len(array) == 1 {
109+
b.Log("found single nested array")
110+
val, _ = json.Marshal(array[0])
111+
}
112+
}
113+
114+
// manually confirmed they match, something is wrong with unordered check
115+
// if !jsonDeepEqual(expected, string(val)) {
116+
// b.Log("unexpected response", string(val))
117+
// }
118+
}
63119
}
64120
})
65121
b.Run("oliveagle", func(b *testing.B) {
66122
var err error
123+
var val interface{}
67124
for i := 0; i < b.N; i++ {
68125
value := make(map[string]interface{})
69-
sampleData := json.Unmarshal([]byte(sampleDataString), &value)
126+
json.Unmarshal([]byte(sampleDataString), &value)
70127

71128
var compiled *oliveagle.Compiled
72129
compiled, err = oliveagle.Compile(selector)
73130
if err == nil {
74-
_, err = compiled.Lookup(sampleData)
131+
val, err = compiled.Lookup(value)
75132
}
76133
}
77-
if err != nil {
78-
b.SkipNow()
134+
if accuracyCheck {
135+
136+
if err != nil {
137+
b.Log("unsupported")
138+
} else {
139+
actual, _ := json.Marshal(val)
140+
if !jsonDeepEqual(expected, string(actual)) {
141+
b.Log("unexpected response")
142+
}
143+
}
79144
}
80145
})
81146
b.Run("spyzhov", func(b *testing.B) {
82147
var err error
148+
var result *ajson.Node
83149
for i := 0; i < b.N; i++ {
84-
root, _ := spyzhov.Unmarshal([]byte(sampleDataString))
85-
_, err = root.JSONPath(selector)
150+
root, _ := ajson.Unmarshal([]byte(sampleDataString))
151+
var nodes []*ajson.Node
152+
nodes, err = root.JSONPath(selector)
153+
result = ajson.ArrayNode("", nodes)
86154
}
87-
if err != nil {
88-
b.SkipNow()
155+
if accuracyCheck {
156+
if err != nil {
157+
b.Log("unsupported")
158+
} else {
159+
actual, _ := ajson.Marshal(result)
160+
if !jsonDeepEqual(expected, string(actual)) {
161+
b.Log("unexpected response")
162+
}
163+
}
89164
}
90165
})
91166
})
92167
}
93168
}
94169

95-
/**
96-
func Test_Comparison(t *testing.T) {
97-
98-
for _, selector := range selectors {
99-
t.Run(selector, func(t *testing.T) {
100-
101-
var response interface{}
102-
103-
// evilmonkeyinc
104-
obj, _ := emi.QueryString(selector, sampleDataString)
105-
bytes, _ := json.Marshal(obj)
106-
response = string(bytes)
107-
fmt.Printf("%s %s %v\n", selector, "evilmonkeyinc", response)
108-
109-
// paesslerAG
110-
value := interface{}(nil)
111-
sampleData := json.Unmarshal([]byte(sampleDataString), &value)
112-
response, _ = paesslerAG.Get(selector, sampleData)
113-
fmt.Printf("%s %s %v\n", selector, "paesslerAG", response)
114-
115-
// bhmj
116-
bytes, _ = bhmj.Get([]byte(sampleDataString), selector)
117-
response = string(bytes)
118-
fmt.Printf("%s %s %v\n", selector, "bhmj", response)
119-
120-
121-
// oliveagle
122-
value = make(map[string]interface{})
123-
sampleData = json.Unmarshal([]byte(sampleDataString), &value)
124-
125-
compiled, err := oliveagle.Compile(selector)
126-
if err == nil {
127-
response, _ = compiled.Lookup(sampleData)
128-
fmt.Printf("%s %s %v\n", selector, "oliveagle", response)
129-
} else {
130-
fmt.Printf("%s %s %v\n", selector, "oliveagle", "failed to compile")
131-
}
170+
func jsonDeepEqual(expected string, actual string) bool {
171+
var expectedJSONAsInterface, actualJSONAsInterface interface{}
132172

173+
if err := json.Unmarshal([]byte(expected), &expectedJSONAsInterface); err != nil {
174+
return false
175+
}
133176

134-
// spyzhov
135-
root, _ := spyzhov.Unmarshal([]byte(sampleDataString))
136-
response, _ = root.JSONPath(selector)
137-
fmt.Printf("%s %s %v\n", selector, "spyzhov", response)
138-
})
177+
if err := json.Unmarshal([]byte(actual), &actualJSONAsInterface); err != nil {
178+
return false
139179
}
180+
181+
return reflect.DeepEqual(expectedJSONAsInterface, actualJSONAsInterface)
140182
}
141-
**/

benchmark/go.mod

+4
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ require (
88
github.com/evilmonkeyinc/jsonpath v0.7.0
99
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852
1010
github.com/spyzhov/ajson v0.7.0
11+
github.com/stretchr/testify v1.7.0
1112
)
1213

1314
require (
1415
github.com/PaesslerAG/gval v1.0.0 // indirect
1516
github.com/bhmj/xpression v0.9.1 // indirect
17+
github.com/davecgh/go-spew v1.1.0 // indirect
18+
github.com/pmezard/go-difflib v1.0.0 // indirect
19+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
1620
)

benchmark/go.sum

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ github.com/spyzhov/ajson v0.7.0/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzy
2020
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
2121
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
2222
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
23+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
2324
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
2425
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
2526
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

token/expression.go

+14-9
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,23 @@ import (
77
"github.com/evilmonkeyinc/jsonpath/script"
88
)
99

10-
func newExpressionToken(expression string, engine script.Engine, options *option.QueryOptions) *expressionToken {
11-
return &expressionToken{
12-
expression: expression,
13-
engine: engine,
14-
options: options,
10+
func newExpressionToken(expression string, engine script.Engine, options *option.QueryOptions) (*expressionToken, error) {
11+
compiledExpression, err := engine.Compile(expression, options)
12+
if err != nil {
13+
return nil, err
1514
}
15+
16+
return &expressionToken{
17+
expression: expression,
18+
compiledExpression: compiledExpression,
19+
options: options,
20+
}, nil
1621
}
1722

1823
type expressionToken struct {
19-
expression string
20-
engine script.Engine
21-
options *option.QueryOptions
24+
expression string
25+
compiledExpression script.CompiledExpression
26+
options *option.QueryOptions
2227
}
2328

2429
func (token *expressionToken) String() string {
@@ -34,7 +39,7 @@ func (token *expressionToken) Apply(root, current interface{}, next []Token) (in
3439
return nil, getInvalidExpressionEmptyError()
3540
}
3641

37-
value, err := token.engine.Evaluate(root, current, token.expression, token.options)
42+
value, err := token.compiledExpression.Evaluate(root, current)
3843
if err != nil {
3944
return nil, getInvalidExpressionError(err)
4045
}

token/expression_test.go

+16-7
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,16 @@ func (engine *testCompiledExpression) Evaluate(root, current interface{}) (inter
3636
var _ Token = &expressionToken{}
3737

3838
func Test_newExpressionToken(t *testing.T) {
39-
assert.IsType(t, &expressionToken{}, newExpressionToken("", nil, nil))
39+
t.Run("success", func(t *testing.T) {
40+
actual, err := newExpressionToken("", &testEngine{}, nil)
41+
assert.Nil(t, err)
42+
assert.IsType(t, &expressionToken{}, actual)
43+
})
44+
t.Run("fail", func(t *testing.T) {
45+
actual, err := newExpressionToken("", &testEngine{err: fmt.Errorf("fail")}, nil)
46+
assert.EqualError(t, err, "fail")
47+
assert.Nil(t, actual)
48+
})
4049
}
4150

4251
func Test_ExpressionToken_String(t *testing.T) {
@@ -75,8 +84,8 @@ func Test_ExpressionToken_Apply(t *testing.T) {
7584
},
7685
{
7786
token: &expressionToken{
78-
expression: "any",
79-
engine: &testEngine{err: fmt.Errorf("engine error")},
87+
expression: "any",
88+
compiledExpression: &testCompiledExpression{err: fmt.Errorf("engine error")},
8089
},
8190
input: input{},
8291
expected: expected{
@@ -85,8 +94,8 @@ func Test_ExpressionToken_Apply(t *testing.T) {
8594
},
8695
{
8796
token: &expressionToken{
88-
expression: "any",
89-
engine: &testEngine{response: true},
97+
expression: "any",
98+
compiledExpression: &testCompiledExpression{response: true},
9099
},
91100
input: input{},
92101
expected: expected{
@@ -95,8 +104,8 @@ func Test_ExpressionToken_Apply(t *testing.T) {
95104
},
96105
{
97106
token: &expressionToken{
98-
expression: "any",
99-
engine: &testEngine{response: false},
107+
expression: "any",
108+
compiledExpression: &testCompiledExpression{response: false},
100109
},
101110
input: input{
102111
tokens: []Token{&currentToken{}},

0 commit comments

Comments
 (0)