Query array for multiple matches

It's now possible to query an array for multiple matches by adding the
'#' character immediately following the query.

For example, using the following JSON:

  {
    "friends": [
      {"first": "Dale", "last": "Murphy"},
      {"first": "Roger", "last": "Craig"},
      {"first": "Jane", "last": "Murphy"}
    ]
  }

To return the first match:

  `friends.#[last="Murphy"].first` >> "Dale"

To return all matches:

  `friends.#[last="Murphy"]#.first` >> ["Dale","Jane"]

Thanks to @chuttam for requesting this feature, closes #15.
This commit is contained in:
Josh Baker 2016-11-30 07:52:25 -07:00
parent 1303e83611
commit 86b1b630e4
3 changed files with 49 additions and 14 deletions

View File

@ -66,8 +66,9 @@ The dot and wildcard characters can be escaped with '\'.
"children": ["Sara","Alex","Jack"],
"fav.movie": "Deer Hunter",
"friends": [
{"first": "James", "last": "Murphy"},
{"first": "Roger", "last": "Craig"}
{"first": "Dale", "last": "Murphy"},
{"first": "Roger", "last": "Craig"},
{"first": "Jane", "last": "Murphy"}
]
}
```
@ -80,12 +81,16 @@ The dot and wildcard characters can be escaped with '\'.
"child*.2" >> "Jack"
"c?ildren.0" >> "Sara"
"fav\.movie" >> "Deer Hunter"
"friends.#.first" >> ["James","Roger"]
"friends.#.first" >> ["Dale","Roger","Jane"]
"friends.1.last" >> "Craig"
```
To query an array:
To query an array for the first match:
```
`friends.#[last="Murphy"].first` >> "James"
`friends.#[last="Murphy"].first` >> "Dale"
```
To query an array for all matches:
```
`friends.#[last="Murphy"]#.first` >> ["Dale","Jane"]
```
## Result Type

View File

@ -554,6 +554,7 @@ type arrayPathResult struct {
path string
op string
value string
all bool
}
}
@ -644,6 +645,9 @@ func parseArrayPath(path string) (r arrayPathResult) {
}
}
} else if path[i] == ']' {
if i+1 < len(path) && path[i+1] == '#' {
r.query.all = true
}
break
}
}
@ -975,6 +979,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
var h int
var alog []int
var partidx int
var multires []byte
rp := parseArrayPath(path)
if !rp.arrch {
n, err := strconv.ParseUint(rp.part, 10, 64)
@ -1031,12 +1036,21 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
res := Get(val, rp.query.path)
if queryMatches(&rp, res) {
if rp.more {
c.value = Get(val, rp.path)
res = Get(val, rp.path)
} else {
c.value.Raw = val
c.value.Type = JSON
res = Result{Raw: val, Type: JSON}
}
if rp.query.all {
if len(multires) == 0 {
multires = append(multires, '[')
} else {
multires = append(multires, ',')
}
multires = append(multires, res.Raw...)
} else {
c.value = res
return i, true
}
return i, true
}
} else if hit {
if rp.alogok {
@ -1123,6 +1137,12 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
return i + 1, true
}
}
if len(multires) > 0 && !c.value.Exists() {
c.value = Result{
Raw: string(append(multires, ']')),
Type: JSON,
}
}
return i + 1, false
}
break

View File

@ -107,17 +107,20 @@ var basicJSON = `{"age":100, "name":{"here":"B\\\"R"},
{
"firstName": "Brett",
"lastName": "McLaughlin",
"email": "aaaa"
"email": "aaaa",
"tag": "good"
},
{
"firstName": "Jason",
"lastName": "Hunter",
"email": "bbbb"
"email": "bbbb",
"tag": "bad"
},
{
"firstName": "Elliotte",
"lastName": "Harold",
"email": "cccc"
"email": "cccc",
"tag":, "good"
},
{
"firstName": 1002.3,
@ -152,10 +155,17 @@ func get(json, path string) Result {
func TestBasic(t *testing.T) {
var mtok Result
mtok = get(basicJSON, `loggy.programmers.#[tag="good"].firstName`)
if mtok.String() != "Brett" {
t.Fatalf("expected %v, got %v", "Brett", mtok.String())
}
mtok = get(basicJSON, `loggy.programmers.#[tag="good"]#.firstName`)
if mtok.String() != `["Brett","Elliotte"]` {
t.Fatalf("expected %v, got %v", `["Brett","Elliotte"]`, mtok.String())
}
mtok = get(basicJSON, `loggy.programmers.#[age=101].firstName`)
if mtok.String() != "1002.3" {
t.Fatalf("expected %v, got %v", "1002,3", mtok.String())
t.Fatalf("expected %v, got %v", "1002.3", mtok.String())
}
mtok = get(basicJSON, `loggy.programmers.#[firstName == "Brett"].email`)