Added new static value character

You can use the '!' character to define static json as a path
component.

For example,

  {name.last,"foo":!"bar"} => {name.last,"foo":"bar"}

see #249
This commit is contained in:
tidwall 2021-11-10 09:18:18 -07:00
parent 2c9fd2476a
commit 6b6af2ad5e
2 changed files with 79 additions and 17 deletions

View File

@ -1807,7 +1807,7 @@ func isSimpleName(component string) bool {
return false return false
} }
switch component[i] { switch component[i] {
case '[', ']', '{', '}', '(', ')', '#', '|': case '[', ']', '{', '}', '(', ')', '#', '|', '!':
return false return false
} }
} }
@ -1871,23 +1871,25 @@ type parseContext struct {
// use the Valid function first. // use the Valid function first.
func Get(json, path string) Result { func Get(json, path string) Result {
if len(path) > 1 { if len(path) > 1 {
if !DisableModifiers { if (path[0] == '@' && !DisableModifiers) || path[0] == '!' {
if path[0] == '@' { // possible modifier
// possible modifier var ok bool
var ok bool var npath string
var npath string var rjson string
var rjson string if path[0] == '@' && !DisableModifiers {
npath, rjson, ok = execModifier(json, path) npath, rjson, ok = execModifier(json, path)
if ok { } else if path[0] == '!' {
path = npath npath, rjson, ok = execStatic(json, path)
if len(path) > 0 && (path[0] == '|' || path[0] == '.') { }
res := Get(rjson, path[1:]) if ok {
res.Index = 0 path = npath
res.Indexes = nil if len(path) > 0 && (path[0] == '|' || path[0] == '.') {
return res res := Get(rjson, path[1:])
} res.Index = 0
return Parse(rjson) res.Indexes = nil
return res
} }
return Parse(rjson)
} }
} }
if path[0] == '[' || path[0] == '{' { if path[0] == '[' || path[0] == '{' {
@ -2556,8 +2558,40 @@ func safeInt(f float64) (n int64, ok bool) {
return int64(f), true return int64(f), true
} }
// execStatic parses the path to find a static value.
// The input expects that the path already starts with a '!'
func execStatic(json, path string) (pathOut, res string, ok bool) {
name := path[1:]
if len(name) > 0 {
switch name[0] {
case '{', '[', '"', '+', '-', '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9':
_, res = parseSquash(name, 0)
pathOut = name[len(res):]
return pathOut, res, true
}
}
for i := 1; i < len(path); i++ {
if path[i] == '|' {
pathOut = path[i:]
name = path[1:i]
break
}
if path[i] == '.' {
pathOut = path[i:]
name = path[1:i]
break
}
}
switch strings.ToLower(name) {
case "true", "false", "null", "nan", "inf":
return pathOut, name, true
}
return pathOut, res, false
}
// execModifier parses the path to find a matching modifier function. // execModifier parses the path to find a matching modifier function.
// then input expects that the path already starts with a '@' // The input expects that the path already starts with a '@'
func execModifier(json, path string) (pathOut, res string, ok bool) { func execModifier(json, path string) (pathOut, res string, ok bool) {
name := path[1:] name := path[1:]
var hasArgs bool var hasArgs bool

View File

@ -2410,3 +2410,31 @@ func TestQueryGetPath(t *testing.T) {
assert(t, arr[i].Path(readmeJSON) == fmt.Sprintf("friends.%d.first", i)) assert(t, arr[i].Path(readmeJSON) == fmt.Sprintf("friends.%d.first", i))
} }
} }
func TestStaticJSON(t *testing.T) {
json := `{
"name": {"first": "Tom", "last": "Anderson"}
}`
assert(t, Get(json,
`"bar"`).Raw ==
``)
assert(t, Get(json,
`!"bar"`).Raw ==
`"bar"`)
assert(t, Get(json,
`!{"name":{"first":"Tom"}}.{name.first}.first`).Raw ==
`"Tom"`)
assert(t, Get(json,
`{name.last,"foo":!"bar"}`).Raw ==
`{"last":"Anderson","foo":"bar"}`)
assert(t, Get(json,
`{name.last,"foo":!{"a":"b"},"that"}`).Raw ==
`{"last":"Anderson","foo":{"a":"b"}}`)
assert(t, Get(json,
`{name.last,"foo":!{"c":"d"},!"that"}`).Raw ==
`{"last":"Anderson","foo":{"c":"d"},"_":"that"}`)
assert(t, Get(json,
`[!true,!false,!null,!inf,!nan,!hello,{"name":!"andy",name.last},+inf,!["any","thing"]]`).Raw ==
`[true,false,null,inf,nan,{"name":"andy","last":"Anderson"},["any","thing"]]`,
)
}