From 9a2ac7eab568b8fb95783eff5e4d7d703bbc2f01 Mon Sep 17 00:00:00 2001 From: stubents <39908122+stubents@users.noreply.github.com> Date: Tue, 21 May 2024 10:23:09 +0200 Subject: [PATCH] Adds a new Query() method that works like Path() but uses ohler/ojg to evaluate the jsonPath expression. --- array.go | 8 ++ array_test.go | 42 ++++--- boolean.go | 8 ++ boolean_test.go | 10 ++ chain.go | 2 +- go.mod | 3 +- go.sum | 2 + json.go | 41 +++++++ number.go | 8 ++ number_test.go | 10 ++ object.go | 8 ++ object_test.go | 18 +++ string.go | 8 ++ string_test.go | 10 ++ value.go | 7 ++ value_test.go | 299 +++++++++++++++++++++++++++++++++--------------- 16 files changed, 378 insertions(+), 106 deletions(-) diff --git a/array.go b/array.go index c1314d2f9..2e404696a 100644 --- a/array.go +++ b/array.go @@ -124,6 +124,14 @@ func (a *Array) Path(path string) *Value { return jsonPath(opChain, a.value, path) } +// Query is similar to Value.Query. +func (a *Array) Query(path string) *Value { + opChain := a.chain.enter("Query(%q)", path) + defer opChain.leave() + + return jsonPathOjg(opChain, a.value, path) +} + // Schema is similar to Value.Schema. func (a *Array) Schema(schema interface{}) *Array { opChain := a.chain.enter("Schema()") diff --git a/array_test.go b/array_test.go index 3a46b0519..f808469f1 100644 --- a/array_test.go +++ b/array_test.go @@ -12,6 +12,7 @@ func TestArray_FailedChain(t *testing.T) { value.chain.assert(t, failure) value.Path("$").chain.assert(t, failure) + value.Query("$").chain.assert(t, failure) value.Schema("") value.Alias("foo") @@ -239,28 +240,41 @@ func TestArray_Alias(t *testing.T) { assert.Equal(t, []string{"foo", "Filter()"}, childValue.chain.context.AliasedPath) } +var jsonPathCases = []struct { + name string + value []interface{} +}{ + { + name: "empty", + value: []interface{}{}, + }, + { + name: "not empty", + value: []interface{}{"foo", 123.0}, + }, +} + func TestArray_Path(t *testing.T) { - cases := []struct { - name string - value []interface{} - }{ - { - name: "empty", - value: []interface{}{}, - }, - { - name: "not empty", - value: []interface{}{"foo", 123.0}, - }, + for _, tc := range jsonPathCases { + t.Run(tc.name, func(t *testing.T) { + reporter := newMockReporter(t) + + value := NewArray(reporter, tc.value) + + assert.Equal(t, tc.value, value.Path("$").Raw()) + value.chain.assert(t, success) + }) } +} - for _, tc := range cases { +func TestArray_Query(t *testing.T) { + for _, tc := range jsonPathCases { t.Run(tc.name, func(t *testing.T) { reporter := newMockReporter(t) value := NewArray(reporter, tc.value) - assert.Equal(t, tc.value, value.Path("$").Raw()) + assert.Equal(t, tc.value, value.Query("$").Raw()) value.chain.assert(t, success) }) } diff --git a/boolean.go b/boolean.go index b2d0412dd..e0118020b 100644 --- a/boolean.go +++ b/boolean.go @@ -94,6 +94,14 @@ func (b *Boolean) Path(path string) *Value { return jsonPath(opChain, b.value, path) } +// Query is similar to Value.Query +func (b *Boolean) Query(path string) *Value { + opChain := b.chain.enter("Query(%q)", path) + defer opChain.leave() + + return jsonPathOjg(opChain, b.value, path) +} + // Schema is similar to Value.Schema. func (b *Boolean) Schema(schema interface{}) *Boolean { opChain := b.chain.enter("Schema()") diff --git a/boolean_test.go b/boolean_test.go index 96ee6637d..34a1ee3fd 100644 --- a/boolean_test.go +++ b/boolean_test.go @@ -14,6 +14,7 @@ func TestBoolean_FailedChain(t *testing.T) { value.chain.assert(t, failure) value.Path("$").chain.assert(t, failure) + value.Query("$").chain.assert(t, failure) value.Schema("") value.Alias("foo") @@ -133,6 +134,15 @@ func TestBoolean_Path(t *testing.T) { value.chain.assert(t, success) } +func TestBoolean_Query(t *testing.T) { + reporter := newMockReporter(t) + + value := NewBoolean(reporter, true) + + assert.Equal(t, true, value.Query("$").Raw()) + value.chain.assert(t, success) +} + func TestBoolean_Schema(t *testing.T) { reporter := newMockReporter(t) diff --git a/chain.go b/chain.go index 94fc11ee2..de23bac07 100644 --- a/chain.go +++ b/chain.go @@ -16,7 +16,7 @@ import ( // to current assertion starting from chain root // // - AssertionHandler: provides methods to handle successful and failed assertions; -// may be defined by user, but usually we just use DefaulAssertionHandler +// may be defined by user, but usually we just use DefaultAssertionHandler // // - AssertionSeverity: severity to be used for failures (fatal or non-fatal) // diff --git a/go.mod b/go.mod index 971a2baf1..a7186e3a1 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gavv/httpexpect/v2 -go 1.19 +go 1.21 require ( github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 @@ -30,6 +30,7 @@ require ( github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect github.com/klauspost/compress v1.15.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect + github.com/ohler55/ojg v1.22.0 // indirect github.com/onsi/ginkgo v1.10.1 // indirect github.com/onsi/gomega v1.7.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index c8f0501fd..387649ab7 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,8 @@ github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp9 github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/ohler55/ojg v1.22.0 h1:McZObj3cD/Zz/ojzk5Pi5VvgQcagxmT1bVKNzhE5ihI= +github.com/ohler55/ojg v1.22.0/go.mod h1:gQhDVpQLqrmnd2eqGAvJtn+NfKoYJbe/A4Sj3/Vro4o= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= diff --git a/json.go b/json.go index 47a640a41..31a7ec15d 100644 --- a/json.go +++ b/json.go @@ -3,6 +3,7 @@ package httpexpect import ( "errors" "fmt" + "github.com/ohler55/ojg/jp" "reflect" "regexp" @@ -10,6 +11,46 @@ import ( "github.com/yalp/jsonpath" ) +func jsonPathOjg(opChain *chain, value interface{}, path string) *Value { + if opChain.failed() { + return newValue(opChain, nil) + } + + expr, err := jp.ParseString(path) + if err != nil { + opChain.fail(AssertionFailure{ + Type: AssertValid, + Actual: &AssertionValue{path}, + Errors: []error{ + errors.New("expected: valid json path"), + err, + }, + }) + return newValue(opChain, nil) + } + result := expr.Get(value) + // in order to keep the results somewhat consistent with yalp's results, + // we return a single value where no wildcards or descends are involved. + // TODO: it might be more consistent if it also included filters + if len(result) == 1 && !hasWildcardsOrDescend(expr) { + return newValue(opChain, result[0]) + } + if result == nil { + return newValue(opChain, []interface{}{}) + } + return newValue(opChain, result) +} + +func hasWildcardsOrDescend(expr jp.Expr) bool { + for _, frag := range expr { + switch frag.(type) { + case jp.Wildcard, jp.Descent: + return true + } + } + return false +} + func jsonPath(opChain *chain, value interface{}, path string) *Value { if opChain.failed() { return newValue(opChain, nil) diff --git a/number.go b/number.go index d11e8f5d5..9650e1c5e 100644 --- a/number.go +++ b/number.go @@ -95,6 +95,14 @@ func (n *Number) Path(path string) *Value { return jsonPath(opChain, n.value, path) } +// Query is similar to Value.Query. +func (n *Number) Query(path string) *Value { + opChain := n.chain.enter("Query(%q)", path) + defer opChain.leave() + + return jsonPathOjg(opChain, n.value, path) +} + // Schema is similar to Value.Schema. func (n *Number) Schema(schema interface{}) *Number { opChain := n.chain.enter("Schema()") diff --git a/number_test.go b/number_test.go index 2b0545110..79843ac84 100644 --- a/number_test.go +++ b/number_test.go @@ -14,6 +14,7 @@ func TestNumber_FailedChain(t *testing.T) { value.chain.assert(t, failure) value.Path("$").chain.assert(t, failure) + value.Query("$").chain.assert(t, failure) value.Schema("") value.Alias("foo") @@ -155,6 +156,15 @@ func TestNumber_Path(t *testing.T) { value.chain.assert(t, success) } +func TestNumber_Query(t *testing.T) { + reporter := newMockReporter(t) + + value := NewNumber(reporter, 123.0) + + assert.Equal(t, 123.0, value.Query("$").Raw()) + value.chain.assert(t, success) +} + func TestNumber_Schema(t *testing.T) { reporter := newMockReporter(t) diff --git a/object.go b/object.go index ec827538d..8205ae709 100644 --- a/object.go +++ b/object.go @@ -133,6 +133,14 @@ func (o *Object) Path(path string) *Value { return jsonPath(opChain, o.value, path) } +// Query is similar to Value.Query. +func (o *Object) Query(query string) *Value { + opChain := o.chain.enter("Query(%q)", query) + defer opChain.leave() + + return jsonPathOjg(opChain, o.value, query) +} + // Schema is similar to Value.Schema. func (o *Object) Schema(schema interface{}) *Object { opChain := o.chain.enter("Schema()") diff --git a/object_test.go b/object_test.go index 2b81ae5ea..bfa8f8a3b 100644 --- a/object_test.go +++ b/object_test.go @@ -12,6 +12,7 @@ func TestObject_FailedChain(t *testing.T) { value.chain.assert(t, failure) value.Path("$").chain.assert(t, failure) + value.Query("$").chain.assert(t, failure) value.Schema("") value.Alias("foo") @@ -291,6 +292,23 @@ func TestObject_Path(t *testing.T) { value.chain.assert(t, success) } +func TestObject_Query(t *testing.T) { + reporter := newMockReporter(t) + + m := map[string]interface{}{ + "foo": 123.0, + "bar": []interface{}{"456", 789.0}, + "baz": map[string]interface{}{ + "a": "b", + }, + } + + value := NewObject(reporter, m) + + assert.Equal(t, m, value.Query("$").Raw()) + value.chain.assert(t, success) +} + func TestObject_Schema(t *testing.T) { reporter := newMockReporter(t) diff --git a/string.go b/string.go index dcc5277fe..598cd4de1 100644 --- a/string.go +++ b/string.go @@ -99,6 +99,14 @@ func (s *String) Path(path string) *Value { return jsonPath(opChain, s.value, path) } +// Query is similar to Value.Query. +func (s *String) Query(path string) *Value { + opChain := s.chain.enter("Query(%q)", path) + defer opChain.leave() + + return jsonPathOjg(opChain, s.value, path) +} + // Schema is similar to Value.Schema. func (s *String) Schema(schema interface{}) *String { opChain := s.chain.enter("Schema()") diff --git a/string_test.go b/string_test.go index d5824a454..c0dcd0e1a 100644 --- a/string_test.go +++ b/string_test.go @@ -15,6 +15,7 @@ func TestString_FailedChain(t *testing.T) { value.chain.assert(t, failure) value.Path("$").chain.assert(t, failure) + value.Query("$").chain.assert(t, failure) value.Schema("") value.Alias("foo") @@ -163,6 +164,15 @@ func TestString_Path(t *testing.T) { value.chain.assert(t, success) } +func TestString_Query(t *testing.T) { + reporter := newMockReporter(t) + + value := NewString(reporter, "foo") + + assert.Equal(t, "foo", value.Query("$").Raw()) + value.chain.assert(t, success) +} + func TestString_Schema(t *testing.T) { reporter := newMockReporter(t) diff --git a/value.go b/value.go index 801de234a..dcdd046b2 100644 --- a/value.go +++ b/value.go @@ -172,6 +172,13 @@ func (v *Value) Path(path string) *Value { return jsonPath(opChain, v.value, path) } +func (v *Value) Query(path string) *Value { + opChain := v.chain.enter("Query(%q)", path) + defer opChain.leave() + + return jsonPathOjg(opChain, v.value, path) +} + // Schema succeeds if value matches given JSON Schema. // // JSON Schema specifies a JSON-based format to define the structure of diff --git a/value_test.go b/value_test.go index 8997e48e7..58b9ed917 100644 --- a/value_test.go +++ b/value_test.go @@ -3,6 +3,7 @@ package httpexpect import ( "encoding/json" "os" + "reflect" "runtime" "testing" @@ -813,38 +814,70 @@ func TestValue_PathTypes(t *testing.T) { value := NewValue(reporter, data) assert.Equal(t, data, value.Path("$").Raw()) + assert.Equal(t, data, value.Query("$").Raw()) assert.Equal(t, data["users"], value.Path("$.users").Raw()) + assert.Equal(t, data["users"], value.Query("$.users").Raw()) assert.Equal(t, user0, value.Path("$.users[0]").Raw()) + assert.Equal(t, user0, value.Query("$.users[0]").Raw()) assert.Equal(t, "john", value.Path("$.users[0].name").Raw()) - assert.Equal(t, []interface{}{"john", "bob"}, value.Path("$.users[*].name").Raw()) - assert.Equal(t, []interface{}{"john", "bob"}, value.Path("$..name").Raw()) + assert.Equal(t, "john", value.Query("$.users[0].name").Raw()) + assert.Equal(t, []any{"john", "bob"}, value.Path("$.users[*].name").Raw()) + assert.Equal(t, []any{"john", "bob"}, value.Query("$.users[*].name").Raw()) + assert.Equal(t, []any{"john", "bob"}, value.Path("$..name").Raw()) + assert.Equal(t, []any{"john", "bob"}, value.Query("$..name").Raw()) value.chain.assert(t, success) names := value.Path("$..name").Array().Iter() names[0].String().IsEqual("john").chain.assert(t, success) names[1].String().IsEqual("bob").chain.assert(t, success) value.chain.assert(t, success) + + names = value.Query("$..name").Array().Iter() + names[0].String().IsEqual("john").chain.assert(t, success) + names[1].String().IsEqual("bob").chain.assert(t, success) + value.chain.assert(t, success) }) - t.Run("bad key", func(t *testing.T) { + t.Run("unknown key", func(t *testing.T) { reporter := newMockReporter(t) value := NewValue(reporter, data) bad := value.Path("$.bad") - assert.True(t, bad != nil) - assert.True(t, bad.Raw() == nil) + assert.NotNil(t, bad) + assert.Nil(t, bad.Raw()) value.chain.assert(t, failure) }) - t.Run("invalid query", func(t *testing.T) { + t.Run("unknown key is ignored in ojg", func(t *testing.T) { + reporter := newMockReporter(t) + + value := NewValue(reporter, data) + + bad := value.Query("$.bad") + assert.Equal(t, []interface{}{}, bad.Raw()) + value.chain.assert(t, success) + }) + + t.Run("invalid query fails in yalp", func(t *testing.T) { reporter := newMockReporter(t) value := NewValue(reporter, data) bad := value.Path("!") - assert.True(t, bad != nil) - assert.True(t, bad.Raw() == nil) + assert.NotNil(t, bad) + assert.Nil(t, bad.Raw()) + value.chain.assert(t, failure) + }) + + t.Run("invalid query fails in ojg", func(t *testing.T) { + reporter := newMockReporter(t) + + value := NewValue(reporter, data) + + bad := value.Query("!") + assert.NotNil(t, bad) + assert.Nil(t, bad.Raw()) value.chain.assert(t, failure) }) }) @@ -863,10 +896,15 @@ func TestValue_PathTypes(t *testing.T) { value := NewValue(reporter, data) assert.Equal(t, data, value.Path("$").Raw()) + assert.Equal(t, data, value.Query("$").Raw()) assert.Equal(t, user0, value.Path("$[0]").Raw()) + assert.Equal(t, user0, value.Query("$[0]").Raw()) assert.Equal(t, "john", value.Path("$[0].name").Raw()) + assert.Equal(t, "john", value.Query("$[0].name").Raw()) assert.Equal(t, []interface{}{"john", "bob"}, value.Path("$[*].name").Raw()) + assert.Equal(t, []interface{}{"john", "bob"}, value.Query("$[*].name").Raw()) assert.Equal(t, []interface{}{"john", "bob"}, value.Path("$..name").Raw()) + assert.Equal(t, []interface{}{"john", "bob"}, value.Query("$..name").Raw()) value.chain.assert(t, success) }) @@ -878,6 +916,7 @@ func TestValue_PathTypes(t *testing.T) { value := NewValue(reporter, data) assert.Equal(t, data, value.Path("$").Raw()) + assert.Equal(t, data, value.Query("$").Raw()) value.chain.assert(t, success) }) @@ -889,6 +928,7 @@ func TestValue_PathTypes(t *testing.T) { value := NewValue(reporter, data) assert.Equal(t, float64(data), value.Path("$").Raw()) + assert.Equal(t, float64(data), value.Query("$").Raw()) value.chain.assert(t, success) }) @@ -900,6 +940,7 @@ func TestValue_PathTypes(t *testing.T) { value := NewValue(reporter, data) assert.Equal(t, data, value.Path("$").Raw()) + assert.Equal(t, data, value.Query("$").Raw()) value.chain.assert(t, success) }) @@ -909,10 +950,11 @@ func TestValue_PathTypes(t *testing.T) { value := NewValue(reporter, nil) assert.Equal(t, nil, value.Path("$").Raw()) + assert.Equal(t, nil, value.Query("$").Raw()) value.chain.assert(t, success) }) - t.Run("error", func(t *testing.T) { + t.Run("error in yalp", func(t *testing.T) { data := "foo" reporter := newMockReporter(t) @@ -921,12 +963,24 @@ func TestValue_PathTypes(t *testing.T) { for _, key := range []string{"$.bad", "!"} { bad := value.Path(key) - assert.True(t, bad != nil) - assert.True(t, bad.Raw() == nil) + assert.NotNil(t, bad) + assert.Nil(t, bad.Raw()) value.chain.assert(t, failure) } }) + t.Run("error in ojg", func(t *testing.T) { + data := "foo" + reporter := newMockReporter(t) + value := NewValue(reporter, data) + + bad := value.Query("!") + + assert.NotNil(t, bad) + assert.Nil(t, bad.Raw()) + value.chain.assert(t, failure) + }) + t.Run("int float", func(t *testing.T) { data := map[string]interface{}{ "A": 123, @@ -945,6 +999,14 @@ func TestValue_PathTypes(t *testing.T) { b := value.Path(`$["B"]`) b.chain.assert(t, success) assert.Equal(t, 123.0, b.Raw()) + + a = value.Query(`$["A"]`) + a.chain.assert(t, success) + assert.Equal(t, 123.0, a.Raw()) + + b = value.Query(`$["B"]`) + b.chain.assert(t, success) + assert.Equal(t, 123.0, b.Raw()) }) } @@ -1001,113 +1063,170 @@ func TestValue_PathExpressions(t *testing.T) { }, } - runTests := func(tests map[string]interface{}) { - reporter := newMockReporter(t) + type expect struct { + value any + yalpError bool + ojgError bool + // only populated if it differs from yalp + ojgValue any + } - value := NewValue(reporter, data) - value.chain.assert(t, success) + evaluateFn := func(t *testing.T, reporter *mockReporter, + pathFunction func(string) *Value, path string, expected any, expectError bool) { + actual := pathFunction(path) + if expectError { + actual.chain.assert(t, failure) + return + } + actual.chain.assert(t, success) + + calledFn := runtime.FuncForPC(reflect.ValueOf(pathFunction).Pointer()).Name() + // as the order is arbitrary in many cases, if actual.Raw is a slice use ElementsMatch + if reflect.TypeOf(actual.Raw()).Kind() == reflect.Slice && + reflect.TypeOf(expected).Kind() == reflect.Slice { + assert.ElementsMatchf(t, expected, actual.Raw(), "%v: expected %v for %s", + calledFn, expected, path) + } else { + assert.Equalf(t, expected, actual.Raw(), "%v: Expected %v for %s", + calledFn, expected, path) + } + } + + runTests := func(tests map[string]expect) { + reporter := newMockReporter(t) for path, expected := range tests { - actual := value.Path(path) - actual.chain.assert(t, success) + value := NewValue(reporter, data) + value.chain.assert(t, success) + evaluateFn(t, reporter, value.Path, path, expected.value, expected.yalpError) - assert.Equal(t, expected, actual.Raw()) + var expectedOjg any + if expected.ojgValue != nil { + expectedOjg = expected.ojgValue + } else { + expectedOjg = expected.value + } + value = NewValue(reporter, data) + value.chain.assert(t, success) + evaluateFn(t, reporter, value.Query, path, expectedOjg, expected.ojgError) } } + t.Run("filters", func(t *testing.T) { + runTests(map[string]expect{ + "$.F.V[?(@.CC == 'hello')].CC": {value: "hello", yalpError: true}, + "$..[?(@ <= $.D.C)]": {value: []any{3.1415, 3.0, 3.14}, yalpError: true}, + "$.A[?(@ > 4)]": {value: 23.3, yalpError: true}, + "$.F..[?(@ ~= /string.?b/)]": { + value: []any{"string4b", "string5b", "string6b"}, + yalpError: true}, + }) + }) + + t.Run("ojg result transormations", func(t *testing.T) { + runTests(map[string]expect{ + "$.E.A": {value: []any{"string3"}}, + "$.E.A[*]": {value: []any{"string3"}}, + }) + }) + t.Run("pick", func(t *testing.T) { - runTests(map[string]interface{}{ - "$": data, - "$.A[0]": "string", - `$["A"][0]`: "string", - "$.A": []interface{}{"string", 23.3, 3.0, true, false, nil}, - "$.A[*]": []interface{}{"string", 23.3, 3.0, true, false, nil}, - "$.A.*": []interface{}{"string", 23.3, 3.0, true, false, nil}, - "$.A.*.a": []interface{}{}, + runTests(map[string]expect{ + "$": {value: data}, + "$.A[0]": {value: "string"}, + `$["A"][0]`: {value: "string"}, + "$.A": {value: []any{"string", 23.3, 3.0, true, false, nil}}, + "$.A[*]": {value: []any{"string", 23.3, 3.0, true, false, nil}}, + "$.A.*": {value: []any{"string", 23.3, 3.0, true, false, nil}}, + "$.A.*.a": {value: []any{}}, }) }) t.Run("slice", func(t *testing.T) { - runTests(map[string]interface{}{ - "$.A[1,4,2]": []interface{}{23.3, false, 3.0}, - `$["B","C"]`: []interface{}{"value", 3.14}, - `$["C","B"]`: []interface{}{3.14, "value"}, - "$.A[1:4]": []interface{}{23.3, 3.0, true}, - "$.A[::2]": []interface{}{"string", 3.0, false}, - "$.A[-2:]": []interface{}{false, nil}, - "$.A[:-1]": []interface{}{"string", 23.3, 3.0, true, false}, - "$.A[::-1]": []interface{}{nil, false, true, 3.0, 23.3, "string"}, - "$.F.V[4:5][0,1]": []interface{}{"string5a", "string5b"}, - "$.F.V[4:6][1]": []interface{}{"string5b", "string6b"}, - "$.F.V[4:6][0,1]": []interface{}{"string5a", "string5b", "string6a", "string6b"}, - "$.F.V[4,5][0:2]": []interface{}{"string5a", "string5b", "string6a", "string6b"}, - "$.F.V[4:6]": []interface{}{ - []interface{}{ + runTests(map[string]expect{ + "$.A[1,4,2]": {value: []any{23.3, false, 3.0}}, + `$["B","C"]`: {value: []any{"value", 3.14}}, + `$["C","B"]`: {value: []any{3.14, "value"}}, + "$.A[1:4]": {value: []any{23.3, 3.0, true}}, + "$.A[::2]": {value: []any{"string", 3.0, false}}, + "$.A[-2:]": {value: []any{false, nil}}, + "$.A[:-1]": {value: []any{"string", 23.3, 3.0, true, false}}, + "$.A[::-1]": { + value: []any{nil, false, true, 3.0, 23.3, "string"}, + ojgValue: []any{}}, + "$.F.V[4:5][0,1]": {value: []any{"string5a", "string5b"}}, + "$.F.V[4:6][1]": {value: []any{"string5b", "string6b"}}, + "$.F.V[4:6][0,1]": {value: []any{"string5a", "string5b", "string6a", "string6b"}}, + "$.F.V[4,5][0:2]": {value: []any{"string5a", "string5b", "string6a", "string6b"}}, + "$.F.V[4:6]": {value: []any{ + []any{ "string5a", "string5b", }, - []interface{}{ + []any{ "string6a", "string6b", }, - }, + }}, }) }) t.Run("quote", func(t *testing.T) { - runTests(map[string]interface{}{ - `$[A][0]`: "string", - `$["A"][0]`: "string", - `$[B,C]`: []interface{}{"value", 3.14}, - `$["B","C"]`: []interface{}{"value", 3.14}, + runTests(map[string]expect{ + `$[A][0]`: {value: "string", ojgError: true}, + `$["A"][0]`: {value: "string"}, + `$[B,C]`: {value: []any{"value", 3.14}, ojgError: true}, + `$["B","C"]`: {value: []any{"value", 3.14}}, }) }) t.Run("search", func(t *testing.T) { - runTests(map[string]interface{}{ - "$..C": []interface{}{3.14, 3.1415, 3.141592, 3.14159265}, - `$..["C"]`: []interface{}{3.14, 3.1415, 3.141592, 3.14159265}, - "$.D.V..C": []interface{}{3.141592}, - "$.D.V.*.C": []interface{}{3.141592}, - "$.D.V..*.C": []interface{}{3.141592}, - "$.D.*..C": []interface{}{3.141592}, - "$.*.V..C": []interface{}{3.141592}, - "$.*.D.V.C": []interface{}{3.14159265}, - "$.*.D..C": []interface{}{3.14159265}, - "$.*.D.V..*": []interface{}{3.14159265}, - "$..D..V..C": []interface{}{3.141592, 3.14159265}, - "$.*.*.*.C": []interface{}{3.141592, 3.14159265}, - "$..V..C": []interface{}{3.141592, 3.14159265}, - "$.D.V..*": []interface{}{ + runTests(map[string]expect{ + "$..C": {value: []any{3.14, 3.1415, 3.141592, 3.14159265}}, + `$..["C"]`: {value: []any{3.14, 3.1415, 3.141592, 3.14159265}}, + "$.D.V..C": {value: []any{3.141592}}, + "$.D.V.*.C": {value: []any{3.141592}}, + "$.D.V..*.C": {value: []any{3.141592}}, + "$.D.*..C": {value: []any{3.141592}}, + "$.*.V..C": {value: []any{3.141592}}, + "$.*.D.V.C": {value: []any{3.14159265}}, + "$.*.D..C": {value: []any{3.14159265}}, + "$.*.D.V..*": {value: []any{3.14159265}}, + "$..D..V..C": {value: []any{3.141592, 3.14159265}}, + "$.*.*.*.C": {value: []any{3.141592, 3.14159265}}, + "$..V..C": {value: []any{3.141592, 3.14159265}}, + "$.D.V..*": {value: []any{ "string2a", "string2b", map[string]interface{}{ "C": 3.141592, }, 3.141592, - }, - "$..A": []interface{}{ - []interface{}{"string", 23.3, 3.0, true, false, nil}, - []interface{}{"string3"}, - }, - "$..A..*": []interface{}{"string", 23.3, 3.0, true, false, nil, "string3"}, - "$.A..*": []interface{}{"string", 23.3, 3.0, true, false, nil}, - "$.A.*": []interface{}{"string", 23.3, 3.0, true, false, nil}, - "$..A[0,1]": []interface{}{"string", 23.3}, - "$..A[0]": []interface{}{"string", "string3"}, - "$.*.V[0]": []interface{}{"string2a", "string4a"}, - "$.*.V[1]": []interface{}{"string2b", "string4b"}, - "$.*.V[0,1]": []interface{}{"string2a", "string2b", "string4a", "string4b"}, - "$.*.V[0:2]": []interface{}{"string2a", "string2b", "string4a", "string4b"}, - "$.*.V[2].C": []interface{}{3.141592}, - "$..V[2].C": []interface{}{3.141592}, - "$..V[*].C": []interface{}{3.141592}, - "$.*.V[2].*": []interface{}{3.141592, 3.1415926535}, - "$.*.V[2:3].*": []interface{}{3.141592, 3.1415926535}, - "$.*.V[2:4].*": []interface{}{3.141592, 3.1415926535, "hello"}, - "$..V[2,3].CC": []interface{}{3.1415926535, "hello"}, - "$..V[2:4].CC": []interface{}{3.1415926535, "hello"}, - "$..V[*].*": []interface{}{ + }}, + "$..A": {value: []any{ + []any{"string", 23.3, 3.0, true, false, nil}, + []any{"string3"}, + }}, + "$..A..*": {value: []any{"string", 23.3, 3.0, true, false, nil, "string3"}}, + "$.A..*": {value: []any{"string", 23.3, 3.0, true, false, nil}}, + "$.A.*": {value: []any{"string", 23.3, 3.0, true, false, nil}}, + "$..A[0,1]": { + value: []any{"string", 23.3}, + ojgValue: []any{"string", 23.3, "string3"}}, + "$..A[0]": {value: []any{"string", "string3"}}, + "$.*.V[0]": {value: []any{"string2a", "string4a"}}, + "$.*.V[1]": {value: []any{"string2b", "string4b"}}, + "$.*.V[0,1]": {value: []any{"string2a", "string2b", "string4a", "string4b"}}, + "$.*.V[0:2]": {value: []any{"string2a", "string2b", "string4a", "string4b"}}, + "$.*.V[2].C": {value: []any{3.141592}}, + "$..V[2].C": {value: []any{3.141592}}, + "$..V[*].C": {value: []any{3.141592}}, + "$.*.V[2].*": {value: []any{3.141592, 3.1415926535}}, + "$.*.V[2:3].*": {value: []any{3.141592, 3.1415926535}}, + "$.*.V[2:4].*": {value: []any{3.141592, 3.1415926535, "hello"}}, + "$..V[2,3].CC": {value: []any{3.1415926535, "hello"}}, + "$..V[2:4].CC": {value: []any{3.1415926535, "hello"}}, + "$..V[*].*": {value: []any{ 3.141592, 3.1415926535, "hello", @@ -1115,16 +1234,16 @@ func TestValue_PathExpressions(t *testing.T) { "string5b", "string6a", "string6b", - }, - "$..[0]": []interface{}{ + }}, + "$..[0]": {value: []any{ "string", "string2a", "string3", "string4a", "string5a", "string6a", - }, - "$..ZZ": []interface{}{}, + }}, + "$..ZZ": {value: []any{}}, }) }) }