forked from mirror/gjson
Added Subselectors
It now possible to select multiple independent paths and join their results into a single JSON document. For example, given the following JSON { "info": { "friends": [ {"first": "Dale", "last": "Murphy", "age": 44}, {"first": "Roger", "last": "Craig", "age": 68}, {"first": "Jane", "last": "Murphy", "age": 47} ] } } The path `[info.friends.0.first,info.friends.1.last]` returns ["Dale","Craig"] Or path `{info.friends.0.first,info.friends.1.last}` returns {"first":"Dale","last":"Craig"} You can also rename Object members such as `{"alt1":info.friends.0.first,"alt2":info.friends.1.last}` returns {"alt1":"Dale","alt2":"Craig"} Finally you can combine this with any GJSON component `info.friends.[0.first,1.age]` returns ["Dale",68] This feature was request by @errashe in issue #113.
This commit is contained in:
parent
001444ea45
commit
3b5bf6bb5e
174
gjson.go
174
gjson.go
|
@ -865,10 +865,12 @@ func parseObjectPath(path string) (r objectPathResult) {
|
|||
return
|
||||
}
|
||||
if path[i] == '.' {
|
||||
// peek at the next byte and see if it's a '@' modifier.
|
||||
// peek at the next byte and see if it's a '@', '[', or '{'.
|
||||
r.part = path[:i]
|
||||
if !DisableModifiers &&
|
||||
i < len(path)-1 && path[i+1] == '@' {
|
||||
i < len(path)-1 &&
|
||||
(path[i+1] == '@' ||
|
||||
path[i+1] == '[' || path[i+1] == '{') {
|
||||
r.pipe = path[i+1:]
|
||||
r.piped = true
|
||||
} else {
|
||||
|
@ -1552,6 +1554,110 @@ func ForEachLine(json string, iterator func(line Result) bool) {
|
|||
}
|
||||
}
|
||||
|
||||
type subSelector struct {
|
||||
name string
|
||||
path string
|
||||
}
|
||||
|
||||
// parseSubSelectors returns the subselectors belonging to a '[path1,path2]' or
|
||||
// '{"field1":path1,"field2":path2}' type subSelection. It's expected that the
|
||||
// first character in path is either '[' or '{', and has already been checked
|
||||
// prior to calling this function.
|
||||
func parseSubSelectors(path string) (sels []subSelector, out string, ok bool) {
|
||||
depth := 1
|
||||
colon := 0
|
||||
start := 1
|
||||
i := 1
|
||||
pushSel := func() {
|
||||
var sel subSelector
|
||||
if colon == 0 {
|
||||
sel.path = path[start:i]
|
||||
} else {
|
||||
sel.name = path[start:colon]
|
||||
sel.path = path[colon+1 : i]
|
||||
}
|
||||
sels = append(sels, sel)
|
||||
colon = 0
|
||||
start = i + 1
|
||||
}
|
||||
for ; i < len(path); i++ {
|
||||
switch path[i] {
|
||||
case '\\':
|
||||
i++
|
||||
case ':':
|
||||
if depth == 1 {
|
||||
colon = i
|
||||
}
|
||||
case ',':
|
||||
if depth == 1 {
|
||||
pushSel()
|
||||
}
|
||||
case '"':
|
||||
i++
|
||||
loop:
|
||||
for ; i < len(path); i++ {
|
||||
switch path[i] {
|
||||
case '\\':
|
||||
i++
|
||||
case '"':
|
||||
break loop
|
||||
}
|
||||
}
|
||||
case '[', '(', '{':
|
||||
depth++
|
||||
case ']', ')', '}':
|
||||
depth--
|
||||
if depth == 0 {
|
||||
pushSel()
|
||||
path = path[i+1:]
|
||||
return sels, path, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// nameOfLast returns the name of the last component
|
||||
func nameOfLast(path string) string {
|
||||
for i := len(path) - 1; i >= 0; i-- {
|
||||
if path[i] == '|' || path[i] == '.' {
|
||||
if i > 0 {
|
||||
if path[i-1] == '\\' {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return path[i+1:]
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func isSimpleName(component string) bool {
|
||||
for i := 0; i < len(component); i++ {
|
||||
if component[i] < ' ' {
|
||||
return false
|
||||
}
|
||||
switch component[i] {
|
||||
case '[', ']', '{', '}', '(', ')', '#', '|':
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func appendJSONString(dst []byte, s string) []byte {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] < ' ' || s[i] == '\\' || s[i] == '"' || s[i] > 126 {
|
||||
d, _ := json.Marshal(s)
|
||||
return append(dst, string(d)...)
|
||||
}
|
||||
}
|
||||
dst = append(dst, '"')
|
||||
dst = append(dst, s...)
|
||||
dst = append(dst, '"')
|
||||
return dst
|
||||
}
|
||||
|
||||
type parseContext struct {
|
||||
json string
|
||||
value Result
|
||||
|
@ -1595,8 +1701,9 @@ type parseContext struct {
|
|||
// If you are consuming JSON from an unpredictable source then you may want to
|
||||
// use the Valid function first.
|
||||
func Get(json, path string) Result {
|
||||
if len(path) > 1 {
|
||||
if !DisableModifiers {
|
||||
if len(path) > 1 && path[0] == '@' {
|
||||
if path[0] == '@' {
|
||||
// possible modifier
|
||||
var ok bool
|
||||
var rjson string
|
||||
|
@ -1611,6 +1718,67 @@ func Get(json, path string) Result {
|
|||
}
|
||||
}
|
||||
}
|
||||
if path[0] == '[' || path[0] == '{' {
|
||||
// using a subselector path
|
||||
kind := path[0]
|
||||
var ok bool
|
||||
var subs []subSelector
|
||||
subs, path, ok = parseSubSelectors(path)
|
||||
if ok {
|
||||
if len(path) == 0 || (path[0] == '|' || path[0] == '.') {
|
||||
var b []byte
|
||||
b = append(b, kind)
|
||||
var i int
|
||||
for _, sub := range subs {
|
||||
res := Get(json, sub.path)
|
||||
if res.Exists() {
|
||||
if i > 0 {
|
||||
b = append(b, ',')
|
||||
}
|
||||
if kind == '{' {
|
||||
if len(sub.name) > 0 {
|
||||
if sub.name[0] == '"' && Valid(sub.name) {
|
||||
b = append(b, sub.name...)
|
||||
} else {
|
||||
b = appendJSONString(b, sub.name)
|
||||
}
|
||||
} else {
|
||||
last := nameOfLast(sub.path)
|
||||
if isSimpleName(last) {
|
||||
b = appendJSONString(b, last)
|
||||
} else {
|
||||
b = appendJSONString(b, "_")
|
||||
}
|
||||
}
|
||||
b = append(b, ':')
|
||||
}
|
||||
var raw string
|
||||
if len(res.Raw) == 0 {
|
||||
raw = res.String()
|
||||
if len(raw) == 0 {
|
||||
raw = "null"
|
||||
}
|
||||
} else {
|
||||
raw = res.Raw
|
||||
}
|
||||
b = append(b, raw...)
|
||||
i++
|
||||
}
|
||||
}
|
||||
b = append(b, kind+2)
|
||||
var res Result
|
||||
res.Raw = string(b)
|
||||
res.Type = JSON
|
||||
if len(path) > 0 {
|
||||
res = res.Get(path[1:])
|
||||
}
|
||||
res.Index = 0
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var i int
|
||||
var c = &parseContext{json: json}
|
||||
if len(path) >= 2 && path[0] == '.' && path[1] == '.' {
|
||||
|
|
|
@ -1862,3 +1862,52 @@ func TestParenQueries(t *testing.T) {
|
|||
assert(t, Get(json, "friends.#(a>10)#|#").Int() == 3)
|
||||
assert(t, Get(json, "friends.#(a>40)#|#").Int() == 0)
|
||||
}
|
||||
|
||||
func TestSubSelectors(t *testing.T) {
|
||||
json := `{
|
||||
"info": {
|
||||
"friends": [
|
||||
{
|
||||
"first": "Dale", "last": "Murphy", "kind": "Person",
|
||||
"cust1": true,
|
||||
"extra": [10,20,30],
|
||||
"details": {
|
||||
"city": "Tempe",
|
||||
"state": "Arizona"
|
||||
}
|
||||
},
|
||||
{
|
||||
"first": "Roger", "last": "Craig", "kind": "Person",
|
||||
"cust2": false,
|
||||
"extra": [40,50,60],
|
||||
"details": {
|
||||
"city": "Phoenix",
|
||||
"state": "Arizona"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
assert(t, Get(json, "[]").String() == "[]")
|
||||
assert(t, Get(json, "{}").String() == "{}")
|
||||
res := Get(json, `{`+
|
||||
`abc:info.friends.0.first,`+
|
||||
`info.friends.1.last,`+
|
||||
`"a`+"\r"+`a":info.friends.0.kind,`+
|
||||
`"abc":info.friends.1.kind,`+
|
||||
`{123:info.friends.1.cust2},`+
|
||||
`[info.friends.#[details.city="Phoenix"]#|#]`+
|
||||
`}.@pretty.@ugly`).String()
|
||||
// println(res)
|
||||
// {"abc":"Dale","last":"Craig","\"a\ra\"":"Person","_":{"123":false},"_":[1]}
|
||||
assert(t, Get(res, "abc").String() == "Dale")
|
||||
assert(t, Get(res, "last").String() == "Craig")
|
||||
assert(t, Get(res, "\"a\ra\"").String() == "Person")
|
||||
assert(t, Get(res, "@reverse.abc").String() == "Person")
|
||||
assert(t, Get(res, "_.123").String() == "false")
|
||||
assert(t, Get(res, "@reverse._.0").String() == "1")
|
||||
assert(t, Get(json, "info.friends.[0.first,1.extra.0]").String() ==
|
||||
`["Dale",40]`)
|
||||
assert(t, Get(json, "info.friends.#.[first,extra.0]").String() ==
|
||||
`[["Dale",10],["Roger",40]]`)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue