oracle: add max nesting depth to JSONPath filter

This commit is contained in:
Evgeniy Stratonikov 2021-04-30 10:35:33 +03:00
parent 1c30d8c395
commit 6890688b8f
3 changed files with 85 additions and 6 deletions

View file

@ -31,7 +31,6 @@ func TestFilter(t *testing.T) {
}{
{"[]", "$.Name"},
{`["Acme Co"]`, "$.Manufacturers[0].Name"},
{`["Acme Co"]`, "$..Manufacturers[0].Name"},
{`[50]`, "$.Manufacturers[0].Products[0].Price"},
{`["Elbow Grease"]`, "$.Manufacturers[1].Products[0].Name"},
{`[{"Name":"Elbow Grease","Price":99.95}]`, "$.Manufacturers[1].Products[0]"},

View file

@ -13,8 +13,9 @@ type (
// pathParser combines JSONPath and a position to start parsing from.
pathParser struct {
s string
i int
s string
i int
depth int
}
)
@ -32,10 +33,15 @@ const (
pathNumber
)
const maxNestingDepth = 6
// Get returns substructures of value selected by path.
// The result is always non-nil unless path is invalid.
func Get(path string, value interface{}) ([]interface{}, bool) {
p := pathParser{s: path}
p := pathParser{
depth: maxNestingDepth,
s: path,
}
typ, _ := p.nextToken()
if typ != pathRoot {
@ -177,6 +183,11 @@ func (p *pathParser) processDot(objs []interface{}) ([]interface{}, bool) {
// descend descends 1 level down.
// It flattens arrays and returns map values for maps.
func (p *pathParser) descend(objs []interface{}) ([]interface{}, bool) {
if p.depth <= 0 {
return nil, false
}
p.depth--
var values []interface{}
for i := range objs {
switch obj := objs[i].(type) {
@ -209,7 +220,7 @@ func (p *pathParser) descendRecursive(objs []interface{}) ([]interface{}, bool)
var values []interface{}
for len(objs) > 0 {
newObjs, _ := p.descendByIdent(objs, val)
newObjs, _ := p.descendByIdentAux(objs, false, val)
values = append(values, newObjs...)
objs, _ = p.descend(objs)
}
@ -219,6 +230,17 @@ func (p *pathParser) descendRecursive(objs []interface{}) ([]interface{}, bool)
// descendByIdent performs map's field access by name.
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{}
for i := range objs {
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.
func (p *pathParser) descendByIndex(objs []interface{}, indices ...int) ([]interface{}, bool) {
if p.depth <= 0 {
return nil, false
}
p.depth--
var values []interface{}
for i := range objs {
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.
func (p *pathParser) descendByRange(objs []interface{}, start, end int) ([]interface{}, bool) {
if p.depth <= 0 {
return nil, false
}
p.depth--
var values []interface{}
for i := range objs {
arr, ok := objs[i].([]interface{})

View file

@ -98,7 +98,6 @@ func TestDescendByIdent(t *testing.T) {
{"$.*.name", `["small","big"]`},
{"$..store.name", `["big"]`},
{"$.store..name", `["big","ppp","sub1","sub2"]`},
{"$..sub[*].name", `["sub1","sub2"]`},
{"$..sub.name", `[]`},
{"$..sub..name", `["sub1","sub2"]`},
}
@ -107,6 +106,28 @@ func TestDescendByIdent(t *testing.T) {
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) {
@ -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) {
js := `[[1,2],{"1":"4"},[5,6]]`
p := pathTestCase{"$[:][1]", "[2,6]"}
@ -227,4 +270,9 @@ func TestCSharpCompat(t *testing.T) {
tc.testUnmarshalGet(t, js)
})
}
t.Run("bad cases", func(t *testing.T) {
_, ok := unmarshalGet(t, js, `$..book[*].author"`)
require.False(t, ok)
})
}