Skip to content
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
311 changes: 148 additions & 163 deletions benchmark/README.md

Large diffs are not rendered by default.

171 changes: 106 additions & 65 deletions benchmark/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ package benchmark

import (
"encoding/json"
"reflect"
"strings"
"testing"

paesslerAG "github.com/PaesslerAG/jsonpath"
bhmj "github.com/bhmj/jsonslice"
"github.com/bhmj/jsonslice"
emi "github.com/evilmonkeyinc/jsonpath"
oliveagle "github.com/oliveagle/jsonpath"
spyzhov "github.com/spyzhov/ajson"
"github.com/spyzhov/ajson"
)

var selectors = []string{
var testSelectors = []string{
"$.store.book[*].author",
"$..author",
"$.store.*",
Expand All @@ -27,115 +29,154 @@ var selectors = []string{
"$..*",
}

var expectedResponse = map[string]string{
"$.store.book[*].author": `["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]`,
"$..author": `["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]`,
"$.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"}]]`,
"$.store..price": `[19.95,8.95,12.99,8.99,22.99]`,
"$..book[2]": `[{"author":"Herman Melville","category":"fiction","isbn":"0-553-21311-3","price":8.99,"title":"Moby Dick"}]`,
"$..book[(@.length-1)]": `[{"author":"J. R. R. Tolkien","category":"fiction","isbn":"0-395-19395-8","price":22.99,"title":"The Lord of the Rings"}]`,
"$..book[-1:]": `[{"author":"J. R. R. Tolkien","category":"fiction","isbn":"0-395-19395-8","price":22.99,"title":"The Lord of the Rings"}]`,
"$..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"}]`,
"$..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"}]`,
"$..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"}]`,
"$..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"}]`,
"$..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"}]`,
"$..*": `[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"]`,
}

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 }`

func Benchmark_Comparison(b *testing.B) {
accuracyCheck := true

for _, selector := range selectors {
for _, selector := range testSelectors {
expected := expectedResponse[selector]
b.Run(selector, func(b *testing.B) {
b.Run("evilmonkeyinc", func(b *testing.B) {
var err error
var val interface{}
for i := 0; i < b.N; i++ {
_, err = emi.QueryString(selector, sampleDataString)
val, err = emi.QueryString(selector, sampleDataString)
}
if err != nil {
b.SkipNow()
if accuracyCheck {
if err != nil {
b.Log("unsupported")
} else {
actual, _ := json.Marshal(val)
if !jsonDeepEqual(expected, string(actual)) {
b.Log("unexpected response")
}
}
}

})
b.Run("paesslerAG", func(b *testing.B) {
var err error
var val interface{}
for i := 0; i < b.N; i++ {
value := make(map[string]interface{})
sampleData := json.Unmarshal([]byte(sampleDataString), &value)
_, err = paesslerAG.Get(selector, sampleData)
json.Unmarshal([]byte(sampleDataString), &value)
val, err = paesslerAG.Get(selector, value)
}
if err != nil {
b.SkipNow()
if accuracyCheck {
if err != nil {
b.Log("unsupported")
} else if val != nil {
// manually confirmed they match, something is wrong with unordered check
// actual, _ := json.Marshal(val)
// if !jsonDeepEqual(expected, string(actual)) {
// b.Log("unexpected response", string(actual))
// }
}
}
})
b.Run("bhmj", func(b *testing.B) {
var err error
var val []byte
for i := 0; i < b.N; i++ {
_, err = bhmj.Get([]byte(sampleDataString), selector)
val, err = jsonslice.Get([]byte(sampleDataString), selector)
}
if err != nil {
b.SkipNow()
if accuracyCheck {
if err != nil {
b.Log("unsupported")
} else {
// bhmj sometimes has arrays in arrays
if strings.HasPrefix(string(val), "[[") && strings.HasSuffix(string(val), "]]") {
array := make([]interface{}, 0)
json.Unmarshal(val, &array)
if len(array) == 1 {
b.Log("found single nested array")
val, _ = json.Marshal(array[0])
}
}

// manually confirmed they match, something is wrong with unordered check
// if !jsonDeepEqual(expected, string(val)) {
// b.Log("unexpected response", string(val))
// }
}
}
})
b.Run("oliveagle", func(b *testing.B) {
var err error
var val interface{}
for i := 0; i < b.N; i++ {
value := make(map[string]interface{})
sampleData := json.Unmarshal([]byte(sampleDataString), &value)
json.Unmarshal([]byte(sampleDataString), &value)

var compiled *oliveagle.Compiled
compiled, err = oliveagle.Compile(selector)
if err == nil {
_, err = compiled.Lookup(sampleData)
val, err = compiled.Lookup(value)
}
}
if err != nil {
b.SkipNow()
if accuracyCheck {

if err != nil {
b.Log("unsupported")
} else {
actual, _ := json.Marshal(val)
if !jsonDeepEqual(expected, string(actual)) {
b.Log("unexpected response")
}
}
}
})
b.Run("spyzhov", func(b *testing.B) {
var err error
var result *ajson.Node
for i := 0; i < b.N; i++ {
root, _ := spyzhov.Unmarshal([]byte(sampleDataString))
_, err = root.JSONPath(selector)
root, _ := ajson.Unmarshal([]byte(sampleDataString))
var nodes []*ajson.Node
nodes, err = root.JSONPath(selector)
result = ajson.ArrayNode("", nodes)
}
if err != nil {
b.SkipNow()
if accuracyCheck {
if err != nil {
b.Log("unsupported")
} else {
actual, _ := ajson.Marshal(result)
if !jsonDeepEqual(expected, string(actual)) {
b.Log("unexpected response")
}
}
}
})
})
}
}

/**
func Test_Comparison(t *testing.T) {

for _, selector := range selectors {
t.Run(selector, func(t *testing.T) {

var response interface{}

// evilmonkeyinc
obj, _ := emi.QueryString(selector, sampleDataString)
bytes, _ := json.Marshal(obj)
response = string(bytes)
fmt.Printf("%s %s %v\n", selector, "evilmonkeyinc", response)

// paesslerAG
value := interface{}(nil)
sampleData := json.Unmarshal([]byte(sampleDataString), &value)
response, _ = paesslerAG.Get(selector, sampleData)
fmt.Printf("%s %s %v\n", selector, "paesslerAG", response)

// bhmj
bytes, _ = bhmj.Get([]byte(sampleDataString), selector)
response = string(bytes)
fmt.Printf("%s %s %v\n", selector, "bhmj", response)


// oliveagle
value = make(map[string]interface{})
sampleData = json.Unmarshal([]byte(sampleDataString), &value)

compiled, err := oliveagle.Compile(selector)
if err == nil {
response, _ = compiled.Lookup(sampleData)
fmt.Printf("%s %s %v\n", selector, "oliveagle", response)
} else {
fmt.Printf("%s %s %v\n", selector, "oliveagle", "failed to compile")
}
func jsonDeepEqual(expected string, actual string) bool {
var expectedJSONAsInterface, actualJSONAsInterface interface{}

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

// spyzhov
root, _ := spyzhov.Unmarshal([]byte(sampleDataString))
response, _ = root.JSONPath(selector)
fmt.Printf("%s %s %v\n", selector, "spyzhov", response)
})
if err := json.Unmarshal([]byte(actual), &actualJSONAsInterface); err != nil {
return false
}

return reflect.DeepEqual(expectedJSONAsInterface, actualJSONAsInterface)
}
**/
4 changes: 4 additions & 0 deletions benchmark/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ require (
github.com/evilmonkeyinc/jsonpath v0.7.0
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852
github.com/spyzhov/ajson v0.7.0
github.com/stretchr/testify v1.7.0
)

require (
github.com/PaesslerAG/gval v1.0.0 // indirect
github.com/bhmj/xpression v0.9.1 // indirect
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)
1 change: 1 addition & 0 deletions benchmark/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ github.com/spyzhov/ajson v0.7.0/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzy
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
23 changes: 14 additions & 9 deletions token/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,23 @@ import (
"github.com/evilmonkeyinc/jsonpath/script"
)

func newExpressionToken(expression string, engine script.Engine, options *option.QueryOptions) *expressionToken {
return &expressionToken{
expression: expression,
engine: engine,
options: options,
func newExpressionToken(expression string, engine script.Engine, options *option.QueryOptions) (*expressionToken, error) {
compiledExpression, err := engine.Compile(expression, options)
if err != nil {
return nil, err
}

return &expressionToken{
expression: expression,
compiledExpression: compiledExpression,
options: options,
}, nil
}

type expressionToken struct {
expression string
engine script.Engine
options *option.QueryOptions
expression string
compiledExpression script.CompiledExpression
options *option.QueryOptions
}

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

value, err := token.engine.Evaluate(root, current, token.expression, token.options)
value, err := token.compiledExpression.Evaluate(root, current)
if err != nil {
return nil, getInvalidExpressionError(err)
}
Expand Down
23 changes: 16 additions & 7 deletions token/expression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,16 @@ func (engine *testCompiledExpression) Evaluate(root, current interface{}) (inter
var _ Token = &expressionToken{}

func Test_newExpressionToken(t *testing.T) {
assert.IsType(t, &expressionToken{}, newExpressionToken("", nil, nil))
t.Run("success", func(t *testing.T) {
actual, err := newExpressionToken("", &testEngine{}, nil)
assert.Nil(t, err)
assert.IsType(t, &expressionToken{}, actual)
})
t.Run("fail", func(t *testing.T) {
actual, err := newExpressionToken("", &testEngine{err: fmt.Errorf("fail")}, nil)
assert.EqualError(t, err, "fail")
assert.Nil(t, actual)
})
}

func Test_ExpressionToken_String(t *testing.T) {
Expand Down Expand Up @@ -75,8 +84,8 @@ func Test_ExpressionToken_Apply(t *testing.T) {
},
{
token: &expressionToken{
expression: "any",
engine: &testEngine{err: fmt.Errorf("engine error")},
expression: "any",
compiledExpression: &testCompiledExpression{err: fmt.Errorf("engine error")},
},
input: input{},
expected: expected{
Expand All @@ -85,8 +94,8 @@ func Test_ExpressionToken_Apply(t *testing.T) {
},
{
token: &expressionToken{
expression: "any",
engine: &testEngine{response: true},
expression: "any",
compiledExpression: &testCompiledExpression{response: true},
},
input: input{},
expected: expected{
Expand All @@ -95,8 +104,8 @@ func Test_ExpressionToken_Apply(t *testing.T) {
},
{
token: &expressionToken{
expression: "any",
engine: &testEngine{response: false},
expression: "any",
compiledExpression: &testCompiledExpression{response: false},
},
input: input{
tokens: []Token{&currentToken{}},
Expand Down
Loading