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
This commit is contained in:
tidwall 2023-08-09 14:28:31 -07:00
parent 8d2c36ffa4
commit e8e87f2a00
2 changed files with 124 additions and 20 deletions

View File

@ -2754,6 +2754,7 @@ func execModifier(json, path string) (pathOut, res string, ok bool) {
var parsedArgs bool var parsedArgs bool
switch pathOut[0] { switch pathOut[0] {
case '{', '[', '"': case '{', '[', '"':
// json arg
res := Parse(pathOut) res := Parse(pathOut)
if res.Exists() { if res.Exists() {
args = squash(pathOut) args = squash(pathOut)
@ -2762,14 +2763,20 @@ func execModifier(json, path string) (pathOut, res string, ok bool) {
} }
} }
if !parsedArgs { if !parsedArgs {
idx := strings.IndexByte(pathOut, '|') // simple arg
if idx == -1 { i := 0
args = pathOut for ; i < len(pathOut); i++ {
pathOut = "" if pathOut[i] == '|' {
} else { break
args = pathOut[:idx] }
pathOut = pathOut[idx:] switch pathOut[i] {
case '{', '[', '"', '(':
s := squash(pathOut[i:])
i += len(s) - 1
}
} }
args = pathOut[:i]
pathOut = pathOut[i:]
} }
} }
return pathOut, fn(json, args), true return pathOut, fn(json, args), true
@ -2789,19 +2796,24 @@ func unwrap(json string) string {
// DisableModifiers will disable the modifier syntax // DisableModifiers will disable the modifier syntax
var DisableModifiers = false var DisableModifiers = false
var modifiers = map[string]func(json, arg string) string{ var modifiers map[string]func(json, arg string) string
"pretty": modPretty,
"ugly": modUgly, func init() {
"reverse": modReverse, modifiers = map[string]func(json, arg string) string{
"this": modThis, "pretty": modPretty,
"flatten": modFlatten, "ugly": modUgly,
"join": modJoin, "reverse": modReverse,
"valid": modValid, "this": modThis,
"keys": modKeys, "flatten": modFlatten,
"values": modValues, "join": modJoin,
"tostr": modToStr, "valid": modValid,
"fromstr": modFromStr, "keys": modKeys,
"group": modGroup, "values": modValues,
"tostr": modToStr,
"fromstr": modFromStr,
"group": modGroup,
"dig": modDig,
}
} }
// AddModifier binds a custom modifier command to the GJSON syntax. // AddModifier binds a custom modifier command to the GJSON syntax.
@ -3435,3 +3447,30 @@ func escapeComp(comp string) string {
} }
return comp return comp
} }
func parseRecursiveDescent(all []Result, parent Result, path string) []Result {
if res := parent.Get(path); res.Exists() {
all = append(all, res)
}
if parent.IsArray() || parent.IsObject() {
parent.ForEach(func(_, val Result) bool {
all = parseRecursiveDescent(all, val, path)
return true
})
}
return all
}
func modDig(json, arg string) string {
all := parseRecursiveDescent(nil, Parse(json), arg)
var out []byte
out = append(out, '[')
for i, res := range all {
if i > 0 {
out = append(out, ',')
}
out = append(out, res.Raw...)
}
out = append(out, ']')
return string(out)
}

View File

@ -2636,3 +2636,68 @@ func TestIssue301(t *testing.T) {
assert(t, Get(json, `fav\.movie.[1]`).String() == "[]") assert(t, Get(json, `fav\.movie.[1]`).String() == "[]")
} }
func TestModDig(t *testing.T) {
json := `
{
"group": {
"issues": [
{
"fields": {
"labels": [
"milestone_1",
"group:foo",
"plan:a",
"plan:b"
]
},
"refid": "123"
},{
"fields": {
"labels": [
"milestone_2",
"group:foo",
"plan:a",
"plan"
]
},
"refid": "456"
},[
{"extra_deep":[{
"fields": {
"labels": [
"milestone_3",
"group:foo",
"plan:a",
"plan"
]
},
"refid": "789"
}]
}]
]
}
}
`
assert(t, Get(json, "group.@dig:#(refid=123)|0.fields.labels.0").String() == "milestone_1")
assert(t, Get(json, "group.@dig:#(refid=456)|0.fields.labels.0").String() == "milestone_2")
assert(t, Get(json, "group.@dig:#(refid=789)|0.fields.labels.0").String() == "milestone_3")
json = `
{ "something": {
"anything": {
"abcdefg": {
"finally": {
"important": {
"secret": "password",
"name": "jake"
}
},
"name": "melinda"
}
}
}
}`
assert(t, Get(json, "@dig:name").String() == `["melinda","jake"]`)
assert(t, Get(json, "@dig:secret").String() == `["password"]`)
}