forked from TrueCloudLab/neoneo-go
oracle: add max nesting depth to JSONPath filter
This commit is contained in:
parent
1c30d8c395
commit
6890688b8f
3 changed files with 85 additions and 6 deletions
|
@ -31,7 +31,6 @@ func TestFilter(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{"[]", "$.Name"},
|
{"[]", "$.Name"},
|
||||||
{`["Acme Co"]`, "$.Manufacturers[0].Name"},
|
{`["Acme Co"]`, "$.Manufacturers[0].Name"},
|
||||||
{`["Acme Co"]`, "$..Manufacturers[0].Name"},
|
|
||||||
{`[50]`, "$.Manufacturers[0].Products[0].Price"},
|
{`[50]`, "$.Manufacturers[0].Products[0].Price"},
|
||||||
{`["Elbow Grease"]`, "$.Manufacturers[1].Products[0].Name"},
|
{`["Elbow Grease"]`, "$.Manufacturers[1].Products[0].Name"},
|
||||||
{`[{"Name":"Elbow Grease","Price":99.95}]`, "$.Manufacturers[1].Products[0]"},
|
{`[{"Name":"Elbow Grease","Price":99.95}]`, "$.Manufacturers[1].Products[0]"},
|
||||||
|
|
|
@ -15,6 +15,7 @@ type (
|
||||||
pathParser struct {
|
pathParser struct {
|
||||||
s string
|
s string
|
||||||
i int
|
i int
|
||||||
|
depth int
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,10 +33,15 @@ const (
|
||||||
pathNumber
|
pathNumber
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const maxNestingDepth = 6
|
||||||
|
|
||||||
// Get returns substructures of value selected by path.
|
// Get returns substructures of value selected by path.
|
||||||
// The result is always non-nil unless path is invalid.
|
// The result is always non-nil unless path is invalid.
|
||||||
func Get(path string, value interface{}) ([]interface{}, bool) {
|
func Get(path string, value interface{}) ([]interface{}, bool) {
|
||||||
p := pathParser{s: path}
|
p := pathParser{
|
||||||
|
depth: maxNestingDepth,
|
||||||
|
s: path,
|
||||||
|
}
|
||||||
|
|
||||||
typ, _ := p.nextToken()
|
typ, _ := p.nextToken()
|
||||||
if typ != pathRoot {
|
if typ != pathRoot {
|
||||||
|
@ -177,6 +183,11 @@ func (p *pathParser) processDot(objs []interface{}) ([]interface{}, bool) {
|
||||||
// descend descends 1 level down.
|
// descend descends 1 level down.
|
||||||
// It flattens arrays and returns map values for maps.
|
// It flattens arrays and returns map values for maps.
|
||||||
func (p *pathParser) descend(objs []interface{}) ([]interface{}, bool) {
|
func (p *pathParser) descend(objs []interface{}) ([]interface{}, bool) {
|
||||||
|
if p.depth <= 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
p.depth--
|
||||||
|
|
||||||
var values []interface{}
|
var values []interface{}
|
||||||
for i := range objs {
|
for i := range objs {
|
||||||
switch obj := objs[i].(type) {
|
switch obj := objs[i].(type) {
|
||||||
|
@ -209,7 +220,7 @@ func (p *pathParser) descendRecursive(objs []interface{}) ([]interface{}, bool)
|
||||||
var values []interface{}
|
var values []interface{}
|
||||||
|
|
||||||
for len(objs) > 0 {
|
for len(objs) > 0 {
|
||||||
newObjs, _ := p.descendByIdent(objs, val)
|
newObjs, _ := p.descendByIdentAux(objs, false, val)
|
||||||
values = append(values, newObjs...)
|
values = append(values, newObjs...)
|
||||||
objs, _ = p.descend(objs)
|
objs, _ = p.descend(objs)
|
||||||
}
|
}
|
||||||
|
@ -219,6 +230,17 @@ func (p *pathParser) descendRecursive(objs []interface{}) ([]interface{}, bool)
|
||||||
|
|
||||||
// descendByIdent performs map's field access by name.
|
// descendByIdent performs map's field access by name.
|
||||||
func (p *pathParser) descendByIdent(objs []interface{}, names ...string) ([]interface{}, bool) {
|
func (p *pathParser) descendByIdent(objs []interface{}, names ...string) ([]interface{}, bool) {
|
||||||
|
return p.descendByIdentAux(objs, true, names...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pathParser) descendByIdentAux(objs []interface{}, checkDepth bool, names ...string) ([]interface{}, bool) {
|
||||||
|
if checkDepth {
|
||||||
|
if p.depth <= 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
p.depth--
|
||||||
|
}
|
||||||
|
|
||||||
var values []interface{}
|
var values []interface{}
|
||||||
for i := range objs {
|
for i := range objs {
|
||||||
obj, ok := objs[i].(map[string]interface{})
|
obj, ok := objs[i].(map[string]interface{})
|
||||||
|
@ -237,6 +259,11 @@ func (p *pathParser) descendByIdent(objs []interface{}, names ...string) ([]inte
|
||||||
|
|
||||||
// descendByIndex performs array access by index.
|
// descendByIndex performs array access by index.
|
||||||
func (p *pathParser) descendByIndex(objs []interface{}, indices ...int) ([]interface{}, bool) {
|
func (p *pathParser) descendByIndex(objs []interface{}, indices ...int) ([]interface{}, bool) {
|
||||||
|
if p.depth <= 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
p.depth--
|
||||||
|
|
||||||
var values []interface{}
|
var values []interface{}
|
||||||
for i := range objs {
|
for i := range objs {
|
||||||
obj, ok := objs[i].([]interface{})
|
obj, ok := objs[i].([]interface{})
|
||||||
|
@ -382,6 +409,11 @@ func (p *pathParser) processSlice(objs []interface{}, start int) ([]interface{},
|
||||||
|
|
||||||
// descendByRange is similar to descend but skips maps and returns sub-slices for arrays.
|
// descendByRange is similar to descend but skips maps and returns sub-slices for arrays.
|
||||||
func (p *pathParser) descendByRange(objs []interface{}, start, end int) ([]interface{}, bool) {
|
func (p *pathParser) descendByRange(objs []interface{}, start, end int) ([]interface{}, bool) {
|
||||||
|
if p.depth <= 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
p.depth--
|
||||||
|
|
||||||
var values []interface{}
|
var values []interface{}
|
||||||
for i := range objs {
|
for i := range objs {
|
||||||
arr, ok := objs[i].([]interface{})
|
arr, ok := objs[i].([]interface{})
|
||||||
|
|
|
@ -98,7 +98,6 @@ func TestDescendByIdent(t *testing.T) {
|
||||||
{"$.*.name", `["small","big"]`},
|
{"$.*.name", `["small","big"]`},
|
||||||
{"$..store.name", `["big"]`},
|
{"$..store.name", `["big"]`},
|
||||||
{"$.store..name", `["big","ppp","sub1","sub2"]`},
|
{"$.store..name", `["big","ppp","sub1","sub2"]`},
|
||||||
{"$..sub[*].name", `["sub1","sub2"]`},
|
|
||||||
{"$..sub.name", `[]`},
|
{"$..sub.name", `[]`},
|
||||||
{"$..sub..name", `["sub1","sub2"]`},
|
{"$..sub..name", `["sub1","sub2"]`},
|
||||||
}
|
}
|
||||||
|
@ -107,6 +106,28 @@ func TestDescendByIdent(t *testing.T) {
|
||||||
tc.testUnmarshalGet(t, js)
|
tc.testUnmarshalGet(t, js)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Run("big depth", func(t *testing.T) {
|
||||||
|
js := `{"a":{"b":{"c":{"d":{"e":{"f":{"g":1}}}}}}}`
|
||||||
|
t.Run("single field", func(t *testing.T) {
|
||||||
|
t.Run("max", func(t *testing.T) {
|
||||||
|
p := pathTestCase{"$.a.b.c.d.e.f", `[{"g":1}]`}
|
||||||
|
p.testUnmarshalGet(t, js)
|
||||||
|
})
|
||||||
|
|
||||||
|
_, ok := unmarshalGet(t, js, "$.a.b.c.d.e.f.g")
|
||||||
|
require.False(t, ok)
|
||||||
|
})
|
||||||
|
t.Run("wildcard", func(t *testing.T) {
|
||||||
|
t.Run("max", func(t *testing.T) {
|
||||||
|
p := pathTestCase{"$.*.*.*.*.*.*", `[{"g":1}]`}
|
||||||
|
p.testUnmarshalGet(t, js)
|
||||||
|
})
|
||||||
|
|
||||||
|
_, ok := unmarshalGet(t, js, "$.*.*.*.*.*.*.*")
|
||||||
|
require.False(t, ok)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDescendByIndex(t *testing.T) {
|
func TestDescendByIndex(t *testing.T) {
|
||||||
|
@ -131,6 +152,28 @@ func TestDescendByIndex(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Run("big depth", func(t *testing.T) {
|
||||||
|
js := `[[[[[[[1]]]]]]]`
|
||||||
|
t.Run("single index", func(t *testing.T) {
|
||||||
|
t.Run("max", func(t *testing.T) {
|
||||||
|
p := pathTestCase{"$[0][0][0][0][0][0]", "[[1]]"}
|
||||||
|
p.testUnmarshalGet(t, js)
|
||||||
|
})
|
||||||
|
|
||||||
|
_, ok := unmarshalGet(t, js, "$[0][0][0][0][0][0][0]")
|
||||||
|
require.False(t, ok)
|
||||||
|
})
|
||||||
|
t.Run("slice", func(t *testing.T) {
|
||||||
|
t.Run("max", func(t *testing.T) {
|
||||||
|
p := pathTestCase{"$[0:][0:][0:][0:][0:][0:]", "[[1]]"}
|
||||||
|
p.testUnmarshalGet(t, js)
|
||||||
|
})
|
||||||
|
|
||||||
|
_, ok := unmarshalGet(t, js, "$[0:][0:][0:][0:][0:][0:][0:]")
|
||||||
|
require.False(t, ok)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("$[:][1], skip wrong types", func(t *testing.T) {
|
t.Run("$[:][1], skip wrong types", func(t *testing.T) {
|
||||||
js := `[[1,2],{"1":"4"},[5,6]]`
|
js := `[[1,2],{"1":"4"},[5,6]]`
|
||||||
p := pathTestCase{"$[:][1]", "[2,6]"}
|
p := pathTestCase{"$[:][1]", "[2,6]"}
|
||||||
|
@ -227,4 +270,9 @@ func TestCSharpCompat(t *testing.T) {
|
||||||
tc.testUnmarshalGet(t, js)
|
tc.testUnmarshalGet(t, js)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Run("bad cases", func(t *testing.T) {
|
||||||
|
_, ok := unmarshalGet(t, js, `$..book[*].author"`)
|
||||||
|
require.False(t, ok)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue