Skip to content

Commit

Permalink
Merge pull request #14 from PaesslerAG/clean-up-selectors
Browse files Browse the repository at this point in the history
Clean up selectors
  • Loading branch information
generikvault authored Mar 15, 2019
2 parents 13fe51c + 634317f commit 532dc46
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 248 deletions.
4 changes: 2 additions & 2 deletions jsonpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ func Get(path string, value interface{}) (interface{}, error) {

var lang = gval.NewLanguage(
gval.Base(),
gval.PrefixExtension('$', single(getRootEvaluable).parse),
gval.PrefixExtension('@', single(getCurrentEvaluable).parse),
gval.PrefixExtension('$', parseRootPath),
gval.PrefixExtension('@', parseCurrentPath),
)

//Language is the JSONPath Language
Expand Down
66 changes: 39 additions & 27 deletions jsonpath_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,10 +288,10 @@ func TestJsonPath(t *testing.T) {
name: "mapper",
path: "$..x",
data: `{
"a" : {"x" : 1},
"b" : [{"x" : 2}, {"y" : 3}],
"x" : 4
}`,
"a" : {"x" : 1},
"b" : [{"x" : 2}, {"y" : 3}],
"x" : 4
}`,
want: arr{
1.,
2.,
Expand All @@ -303,10 +303,10 @@ func TestJsonPath(t *testing.T) {
name: "mapper union",
path: `$..["x", "a"]`,
data: `{
"a" : {"x" : 1},
"b" : [{"x" : 2}, {"y" : 3}],
"x" : 4
}`,
"a" : {"x" : 1},
"b" : [{"x" : 2}, {"y" : 3}],
"x" : 4
}`,
want: arr{
1.,
2.,
Expand Down Expand Up @@ -401,31 +401,43 @@ func TestJsonPath(t *testing.T) {
name: "mapper select script",
path: `$.abc.f..["x"](@ == "1")`,
data: `{
"abc":{
"d":[
"1",
"1"
],
"f":{
"a":{
"x":"1"
},
"b":{
"x":"1"
},
"c":{
"x":"xx"
}
}
}
}`,
"abc":{
"d":[
"1",
"1"
],
"f":{
"a":{
"x":"1"
},
"b":{
"x":"1"
},
"c":{
"x":"xx"
}
}
}
}`,
want: arr{
false,
true,
true,
},
reorder: true,
},
{
name: "float equal",
path: `$.a == 1.23`,
data: `{"a":1.23, "b":2}`,
want: true,
},
{
name: "ending star",
path: `$.welcome.message[*]`,
data: `{"welcome":{"message":["Good Morning", "Hello World!"]}}`,
want: arr{"Good Morning", "Hello World!"},
},
}
for _, tt := range tests {
tt.lang = jsonpath.Language()
Expand Down Expand Up @@ -457,7 +469,7 @@ func (tt jsonpathTest) test(t *testing.T) {
}

if err != nil {
t.Errorf("JSONPath(*) error = %v", err)
t.Errorf("JSONPath(%s) error = %v", tt.path, err)
return
}

Expand Down
129 changes: 78 additions & 51 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,41 @@ import (
"github.com/PaesslerAG/gval"
)

func (s single) parse(c context.Context, p *gval.Parser) (r gval.Evaluable, err error) {
jp := &parser{Parser: p, single: s}
err = jp.parsePath(c)
type parser struct {
*gval.Parser
path path
}

func parseRootPath(ctx context.Context, gParser *gval.Parser) (r gval.Evaluable, err error) {
p := newParser(gParser)
return p.parse(ctx)
}

func parseCurrentPath(ctx context.Context, gParser *gval.Parser) (r gval.Evaluable, err error) {
p := newParser(gParser)
p.appendPlainSelector(currentElementSelector())
return p.parse(ctx)
}

func newParser(p *gval.Parser) *parser {
return &parser{Parser: p, path: plainPath{}}
}

func (p *parser) parse(c context.Context) (r gval.Evaluable, err error) {
err = p.parsePath(c)

if err != nil {
return nil, err
}
return jp.evaluable(), nil
return p.path.evaluate, nil
}

func (jp *parser) parsePath(c context.Context) error {
switch jp.Scan() {
func (p *parser) parsePath(c context.Context) error {
switch p.Scan() {
case '.':
return jp.parseSelect(c)
return p.parseSelect(c)
case '[':
keys, seperator, err := jp.parseBracket(c)
keys, seperator, err := p.parseBracket(c)

if err != nil {
return err
Expand All @@ -36,49 +55,49 @@ func (jp *parser) parsePath(c context.Context) error {
return fmt.Errorf("range query has at least the parameter [min:max:step]")
}
keys = append(keys, []gval.Evaluable{
jp.Const(0), jp.Const(float64(math.MaxInt32)), jp.Const(1)}[len(keys):]...)
jp.newMultiStage(getRangeEvaluable(keys[0], keys[1], keys[2]))
p.Const(0), p.Const(float64(math.MaxInt32)), p.Const(1)}[len(keys):]...)
p.appendAmbiguousSelector(rangeSelector(keys[0], keys[1], keys[2]))
case '?':
if len(keys) != 1 {
return fmt.Errorf("filter needs exactly one key")
}
jp.newMultiStage(filterEvaluable(keys[0]))
p.appendAmbiguousSelector(filterSelector(keys[0]))
default:
if len(keys) == 1 {
jp.newSingleStage(getSelectEvaluable(keys[0]))
p.appendPlainSelector(directSelector(keys[0]))
} else {
jp.newMultiStage(getMultiSelectEvaluable(keys))
p.appendAmbiguousSelector(multiSelector(keys))
}
}
return jp.parsePath(c)
return p.parsePath(c)
case '(':
return jp.parseScript(c)
return p.parseScript(c)
default:
jp.Camouflage("jsonpath", '.', '[', '(')
p.Camouflage("jsonpath", '.', '[', '(')
return nil
}
}

func (jp *parser) parseSelect(c context.Context) error {
scan := jp.Scan()
func (p *parser) parseSelect(c context.Context) error {
scan := p.Scan()
switch scan {
case scanner.Ident:
jp.newSingleStage(getSelectEvaluable(jp.Const(jp.TokenText())))
return jp.parsePath(c)
p.appendPlainSelector(directSelector(p.Const(p.TokenText())))
return p.parsePath(c)
case '.':
jp.newMultiStage(mapperEvaluable)
return jp.parseMapper(c)
p.appendAmbiguousSelector(mapperSelector())
return p.parseMapper(c)
case '*':
jp.newMultiStage(starEvaluable)
return jp.parsePath(c)
p.appendAmbiguousSelector(starSelector())
return p.parsePath(c)
default:
return jp.Expected("JSON select", scanner.Ident, '.', '*')
return p.Expected("JSON select", scanner.Ident, '.', '*')
}
}

func (jp *parser) parseBracket(c context.Context) (keys []gval.Evaluable, seperator rune, err error) {
func (p *parser) parseBracket(c context.Context) (keys []gval.Evaluable, seperator rune, err error) {
for {
scan := jp.Scan()
scan := p.Scan()
skipScan := false
switch scan {
case '?':
Expand All @@ -88,11 +107,11 @@ func (jp *parser) parseBracket(c context.Context) (keys []gval.Evaluable, sepera
if len(keys) == 1 {
i = math.MaxInt32
}
keys = append(keys, jp.Const(i))
keys = append(keys, p.Const(i))
skipScan = true
case '*':
if jp.Scan() != ']' {
return nil, 0, jp.Expected("JSON bracket star", ']')
if p.Scan() != ']' {
return nil, 0, p.Expected("JSON bracket star", ']')
}
return []gval.Evaluable{}, 0, nil
case ']':
Expand All @@ -102,15 +121,15 @@ func (jp *parser) parseBracket(c context.Context) (keys []gval.Evaluable, sepera
}
fallthrough
default:
jp.Camouflage("jsonpath brackets")
key, err := jp.ParseExpression(c)
p.Camouflage("jsonpath brackets")
key, err := p.ParseExpression(c)
if err != nil {
return nil, 0, err
}
keys = append(keys, key)
}
if !skipScan {
scan = jp.Scan()
scan = p.Scan()
}
if seperator == 0 {
seperator = scan
Expand All @@ -121,24 +140,24 @@ func (jp *parser) parseBracket(c context.Context) (keys []gval.Evaluable, sepera
return
case '?':
if len(keys) != 0 {
return nil, 0, jp.Expected("JSON filter", ']')
return nil, 0, p.Expected("JSON filter", ']')
}
default:
return nil, 0, jp.Expected("JSON bracket separator", ':', ',')
return nil, 0, p.Expected("JSON bracket separator", ':', ',')
}
if seperator != scan {
return nil, 0, fmt.Errorf("mixed %v and %v in JSON bracket", seperator, scan)
}
}
}

func (jp *parser) parseMapper(c context.Context) error {
scan := jp.Scan()
func (p *parser) parseMapper(c context.Context) error {
scan := p.Scan()
switch scan {
case scanner.Ident:
jp.newSingleStage(getSelectEvaluable(jp.Const(jp.TokenText())))
p.appendPlainSelector(directSelector(p.Const(p.TokenText())))
case '[':
keys, seperator, err := jp.parseBracket(c)
keys, seperator, err := p.parseBracket(c)

if err != nil {
return err
Expand All @@ -150,28 +169,36 @@ func (jp *parser) parseMapper(c context.Context) error {
if len(keys) != 1 {
return fmt.Errorf("filter needs exactly one key")
}
jp.newMultiStage(filterEvaluable(keys[0]))
p.appendAmbiguousSelector(filterSelector(keys[0]))
default:
jp.newMultiStage(getMultiSelectEvaluable(keys))
p.appendAmbiguousSelector(multiSelector(keys))
}
case '*':
jp.newMultiStage(starEvaluable)
p.appendAmbiguousSelector(starSelector())
case '(':
return jp.parseScript(c)
return p.parseScript(c)
default:
return jp.Expected("JSON mapper", '[', scanner.Ident, '*')
return p.Expected("JSON mapper", '[', scanner.Ident, '*')
}
return jp.parsePath(c)
return p.parsePath(c)
}

func (jp *parser) parseScript(c context.Context) error {
script, err := jp.ParseExpression(c)
func (p *parser) parseScript(c context.Context) error {
script, err := p.ParseExpression(c)
if err != nil {
return err
}
if jp.Scan() != ')' {
return jp.Expected("jsnopath script", ')')
if p.Scan() != ')' {
return p.Expected("jsnopath script", ')')
}
jp.newSingleStage(newScript(script))
return jp.parsePath(c)
p.appendPlainSelector(newScript(script))
return p.parsePath(c)
}

func (p *parser) appendPlainSelector(next plainSelector) {
p.path = p.path.withPlainSelector(next)
}

func (p *parser) appendAmbiguousSelector(next ambiguousSelector) {
p.path = p.path.withAmbiguousSelector(next)
}
Loading

0 comments on commit 532dc46

Please sign in to comment.