mirror of https://github.com/tidwall/gjson.git
Allow for chaining syntax in array subselects
This commit is contained in:
parent
89b19799ff
commit
b877bd43b1
76
gjson.go
76
gjson.go
|
@ -891,6 +891,11 @@ func parseObjectPath(path string) (r objectPathResult) {
|
||||||
r.path = path[i+1:]
|
r.path = path[i+1:]
|
||||||
r.more = true
|
r.more = true
|
||||||
return
|
return
|
||||||
|
} else if path[i] == '|' {
|
||||||
|
r.part = string(epart)
|
||||||
|
r.pipe = path[i+1:]
|
||||||
|
r.piped = true
|
||||||
|
return
|
||||||
} else if path[i] == '*' || path[i] == '?' {
|
} else if path[i] == '*' || path[i] == '?' {
|
||||||
r.wild = true
|
r.wild = true
|
||||||
}
|
}
|
||||||
|
@ -1321,6 +1326,12 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
|
||||||
case ']':
|
case ']':
|
||||||
if rp.arrch && rp.part == "#" {
|
if rp.arrch && rp.part == "#" {
|
||||||
if rp.alogok {
|
if rp.alogok {
|
||||||
|
left, right, ok := splitPossiblePipe(rp.alogkey)
|
||||||
|
if ok {
|
||||||
|
rp.alogkey = left
|
||||||
|
c.pipe = right
|
||||||
|
c.piped = true
|
||||||
|
}
|
||||||
var jsons = make([]byte, 0, 64)
|
var jsons = make([]byte, 0, 64)
|
||||||
jsons = append(jsons, '[')
|
jsons = append(jsons, '[')
|
||||||
for j, k := 0, 0; j < len(alog); j++ {
|
for j, k := 0, 0; j < len(alog); j++ {
|
||||||
|
@ -1368,6 +1379,71 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
|
||||||
return i, false
|
return i, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func splitPossiblePipe(path string) (left, right string, ok bool) {
|
||||||
|
// take a quick peek for the pipe character. If found we'll split the piped
|
||||||
|
// part of the path into the c.pipe field and shorten the rp.
|
||||||
|
var possible bool
|
||||||
|
for i := 0; i < len(path); i++ {
|
||||||
|
if path[i] == '|' {
|
||||||
|
possible = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !possible {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// split the left and right side of the path with the pipe character as
|
||||||
|
// the delimiter. This is a little tricky because we'll need to basically
|
||||||
|
// parse the entire path.
|
||||||
|
|
||||||
|
for i := 0; i < len(path); i++ {
|
||||||
|
if path[i] == '\\' {
|
||||||
|
i++
|
||||||
|
} else if path[i] == '.' {
|
||||||
|
if i == len(path)-1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if path[i+1] == '#' {
|
||||||
|
i += 2
|
||||||
|
if i == len(path) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if path[i] == '[' {
|
||||||
|
// inside selector, balance brackets
|
||||||
|
i++
|
||||||
|
depth := 1
|
||||||
|
for ; i < len(path); i++ {
|
||||||
|
if path[i] == '\\' {
|
||||||
|
i++
|
||||||
|
} else if path[i] == '[' {
|
||||||
|
depth++
|
||||||
|
} else if path[i] == ']' {
|
||||||
|
depth--
|
||||||
|
if depth == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if path[i] == '"' {
|
||||||
|
// inside selector string, balance quotes
|
||||||
|
i++
|
||||||
|
for ; i < len(path); i++ {
|
||||||
|
if path[i] == '\\' {
|
||||||
|
i++
|
||||||
|
} else if path[i] == '"' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if path[i] == '|' {
|
||||||
|
return path[:i], path[i+1:], true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// ForEachLine iterates through lines of JSON as specified by the JSON Lines
|
// ForEachLine iterates through lines of JSON as specified by the JSON Lines
|
||||||
// format (http://jsonlines.org/).
|
// format (http://jsonlines.org/).
|
||||||
// Each line is returned as a GJSON Result.
|
// Each line is returned as a GJSON Result.
|
||||||
|
|
|
@ -1488,25 +1488,53 @@ func TestModifier(t *testing.T) {
|
||||||
|
|
||||||
func TestChaining(t *testing.T) {
|
func TestChaining(t *testing.T) {
|
||||||
json := `{
|
json := `{
|
||||||
"friends": [
|
"info": {
|
||||||
{"first": "Dale", "last": "Murphy", "age": 44},
|
"friends": [
|
||||||
{"first": "Roger", "last": "Craig", "age": 68},
|
{"first": "Dale", "last": "Murphy", "age": 44},
|
||||||
{"first": "Jane", "last": "Murphy", "age": 47}
|
{"first": "Roger", "last": "Craig", "age": 68},
|
||||||
]
|
{"first": "Jane", "last": "Murphy", "age": 47}
|
||||||
|
]
|
||||||
|
}
|
||||||
}`
|
}`
|
||||||
res := Get(json, "friends|0|first").String()
|
res := Get(json, "info.friends|0|first").String()
|
||||||
if res != "Dale" {
|
if res != "Dale" {
|
||||||
t.Fatalf("expected '%v', got '%v'", "Dale", res)
|
t.Fatalf("expected '%v', got '%v'", "Dale", res)
|
||||||
}
|
}
|
||||||
res = Get(json, "friends|@reverse|0|age").String()
|
res = Get(json, "info.friends|@reverse|0|age").String()
|
||||||
if res != "47" {
|
if res != "47" {
|
||||||
t.Fatalf("expected '%v', got '%v'", "47", res)
|
t.Fatalf("expected '%v', got '%v'", "47", res)
|
||||||
}
|
}
|
||||||
|
res = Get(json, "@ugly|i\\nfo|friends.0.first").String()
|
||||||
|
if res != "Dale" {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", "Dale", res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitPipe(t *testing.T) {
|
||||||
|
split := func(t *testing.T, path, el, er string, eo bool) {
|
||||||
|
t.Helper()
|
||||||
|
left, right, ok := splitPossiblePipe(path)
|
||||||
|
// fmt.Printf("%-40s [%v] [%v] [%v]\n", path, left, right, ok)
|
||||||
|
if left != el || right != er || ok != eo {
|
||||||
|
t.Fatalf("expected '%v/%v/%v', got '%v/%v/%v",
|
||||||
|
el, er, eo, left, right, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
split(t, "hello", "", "", false)
|
||||||
|
split(t, "hello.world", "", "", false)
|
||||||
|
split(t, "hello|world", "hello", "world", true)
|
||||||
|
split(t, "hello\\|world", "", "", false)
|
||||||
|
split(t, "hello.#", "", "", false)
|
||||||
|
split(t, `hello.#[a|1="asdf\"|1324"]#\|that`, "", "", false)
|
||||||
|
split(t, `hello.#[a|1="asdf\"|1324"]#|that.more|yikes`,
|
||||||
|
`hello.#[a|1="asdf\"|1324"]#`, "that.more|yikes", true)
|
||||||
|
split(t, `a.#[]#\|b`, "", "", false)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArrayEx(t *testing.T) {
|
func TestArrayEx(t *testing.T) {
|
||||||
s := `
|
json := `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"c":[
|
"c":[
|
||||||
|
@ -1518,12 +1546,20 @@ func TestArrayEx(t *testing.T) {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]`
|
]`
|
||||||
res := Get(s, "@ugly|#.c.#[a=10.11]").String()
|
res := Get(json, "@ugly|#.c.#[a=10.11]").String()
|
||||||
if res != `[{"a":10.11}]` {
|
if res != `[{"a":10.11}]` {
|
||||||
t.Fatalf("expected '%v', got '%v'", `[{"a":10.11}]`, res)
|
t.Fatalf("expected '%v', got '%v'", `[{"a":10.11}]`, res)
|
||||||
}
|
}
|
||||||
res = Get(s, "@ugly|#.c.#").String()
|
res = Get(json, "@ugly|#.c.#").String()
|
||||||
if res != `[1,1]` {
|
if res != `[1,1]` {
|
||||||
t.Fatalf("expected '%v', got '%v'", `[1,1]`, res)
|
t.Fatalf("expected '%v', got '%v'", `[1,1]`, res)
|
||||||
}
|
}
|
||||||
|
res = Get(json, "@reverse|0|c|0|a").String()
|
||||||
|
if res != "11.11" {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", "11.11", res)
|
||||||
|
}
|
||||||
|
res = Get(json, "#.c|#").String()
|
||||||
|
if res != "2" {
|
||||||
|
t.Fatalf("expected '%v', got '%v'", "2", res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue