Skip to content

Commit e8e87f2

Browse files
committed
Add @dig modifier for recursive descent searches
This commit adds the "@dig" modifier, which allows for searching for values in deep or arbitrarily nested json documents For example, using the following json: ``` { "something": { "anything": { "abcdefg": { "finally": { "important": { "secret": "password" } } } } } } ``` ``` @dig:secret -> ["password"] ``` See #130
1 parent 8d2c36f commit e8e87f2

File tree

2 files changed

+124
-20
lines changed

2 files changed

+124
-20
lines changed

gjson.go

+59-20
Original file line numberDiff line numberDiff line change
@@ -2754,6 +2754,7 @@ func execModifier(json, path string) (pathOut, res string, ok bool) {
27542754
var parsedArgs bool
27552755
switch pathOut[0] {
27562756
case '{', '[', '"':
2757+
// json arg
27572758
res := Parse(pathOut)
27582759
if res.Exists() {
27592760
args = squash(pathOut)
@@ -2762,14 +2763,20 @@ func execModifier(json, path string) (pathOut, res string, ok bool) {
27622763
}
27632764
}
27642765
if !parsedArgs {
2765-
idx := strings.IndexByte(pathOut, '|')
2766-
if idx == -1 {
2767-
args = pathOut
2768-
pathOut = ""
2769-
} else {
2770-
args = pathOut[:idx]
2771-
pathOut = pathOut[idx:]
2766+
// simple arg
2767+
i := 0
2768+
for ; i < len(pathOut); i++ {
2769+
if pathOut[i] == '|' {
2770+
break
2771+
}
2772+
switch pathOut[i] {
2773+
case '{', '[', '"', '(':
2774+
s := squash(pathOut[i:])
2775+
i += len(s) - 1
2776+
}
27722777
}
2778+
args = pathOut[:i]
2779+
pathOut = pathOut[i:]
27732780
}
27742781
}
27752782
return pathOut, fn(json, args), true
@@ -2789,19 +2796,24 @@ func unwrap(json string) string {
27892796
// DisableModifiers will disable the modifier syntax
27902797
var DisableModifiers = false
27912798

2792-
var modifiers = map[string]func(json, arg string) string{
2793-
"pretty": modPretty,
2794-
"ugly": modUgly,
2795-
"reverse": modReverse,
2796-
"this": modThis,
2797-
"flatten": modFlatten,
2798-
"join": modJoin,
2799-
"valid": modValid,
2800-
"keys": modKeys,
2801-
"values": modValues,
2802-
"tostr": modToStr,
2803-
"fromstr": modFromStr,
2804-
"group": modGroup,
2799+
var modifiers map[string]func(json, arg string) string
2800+
2801+
func init() {
2802+
modifiers = map[string]func(json, arg string) string{
2803+
"pretty": modPretty,
2804+
"ugly": modUgly,
2805+
"reverse": modReverse,
2806+
"this": modThis,
2807+
"flatten": modFlatten,
2808+
"join": modJoin,
2809+
"valid": modValid,
2810+
"keys": modKeys,
2811+
"values": modValues,
2812+
"tostr": modToStr,
2813+
"fromstr": modFromStr,
2814+
"group": modGroup,
2815+
"dig": modDig,
2816+
}
28052817
}
28062818

28072819
// AddModifier binds a custom modifier command to the GJSON syntax.
@@ -3435,3 +3447,30 @@ func escapeComp(comp string) string {
34353447
}
34363448
return comp
34373449
}
3450+
3451+
func parseRecursiveDescent(all []Result, parent Result, path string) []Result {
3452+
if res := parent.Get(path); res.Exists() {
3453+
all = append(all, res)
3454+
}
3455+
if parent.IsArray() || parent.IsObject() {
3456+
parent.ForEach(func(_, val Result) bool {
3457+
all = parseRecursiveDescent(all, val, path)
3458+
return true
3459+
})
3460+
}
3461+
return all
3462+
}
3463+
3464+
func modDig(json, arg string) string {
3465+
all := parseRecursiveDescent(nil, Parse(json), arg)
3466+
var out []byte
3467+
out = append(out, '[')
3468+
for i, res := range all {
3469+
if i > 0 {
3470+
out = append(out, ',')
3471+
}
3472+
out = append(out, res.Raw...)
3473+
}
3474+
out = append(out, ']')
3475+
return string(out)
3476+
}

gjson_test.go

+65
Original file line numberDiff line numberDiff line change
@@ -2636,3 +2636,68 @@ func TestIssue301(t *testing.T) {
26362636
assert(t, Get(json, `fav\.movie.[1]`).String() == "[]")
26372637

26382638
}
2639+
2640+
func TestModDig(t *testing.T) {
2641+
json := `
2642+
{
2643+
2644+
"group": {
2645+
"issues": [
2646+
{
2647+
"fields": {
2648+
"labels": [
2649+
"milestone_1",
2650+
"group:foo",
2651+
"plan:a",
2652+
"plan:b"
2653+
]
2654+
},
2655+
"refid": "123"
2656+
},{
2657+
"fields": {
2658+
"labels": [
2659+
"milestone_2",
2660+
"group:foo",
2661+
"plan:a",
2662+
"plan"
2663+
]
2664+
},
2665+
"refid": "456"
2666+
},[
2667+
{"extra_deep":[{
2668+
"fields": {
2669+
"labels": [
2670+
"milestone_3",
2671+
"group:foo",
2672+
"plan:a",
2673+
"plan"
2674+
]
2675+
},
2676+
"refid": "789"
2677+
}]
2678+
}]
2679+
]
2680+
}
2681+
}
2682+
`
2683+
assert(t, Get(json, "group.@dig:#(refid=123)|0.fields.labels.0").String() == "milestone_1")
2684+
assert(t, Get(json, "group.@dig:#(refid=456)|0.fields.labels.0").String() == "milestone_2")
2685+
assert(t, Get(json, "group.@dig:#(refid=789)|0.fields.labels.0").String() == "milestone_3")
2686+
json = `
2687+
{ "something": {
2688+
"anything": {
2689+
"abcdefg": {
2690+
"finally": {
2691+
"important": {
2692+
"secret": "password",
2693+
"name": "jake"
2694+
}
2695+
},
2696+
"name": "melinda"
2697+
}
2698+
}
2699+
}
2700+
}`
2701+
assert(t, Get(json, "@dig:name").String() == `["melinda","jake"]`)
2702+
assert(t, Get(json, "@dig:secret").String() == `["password"]`)
2703+
}

0 commit comments

Comments
 (0)