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"], "children": ["Sara","Alex","Jack"],
"fav.movie": "Deer Hunter", "fav.movie": "Deer Hunter",
"friends": [ "friends": [
{"first": "James", "last": "Murphy"}, {"first": "Dale", "last": "Murphy"},
{"first": "Roger", "last": "Craig"} {"first": "Roger", "last": "Craig"},
{"first": "Jane", "last": "Murphy"}
] ]
} }
``` ```
@ -80,12 +81,16 @@ The dot and wildcard characters can be escaped with '\'.
"child*.2" >> "Jack" "child*.2" >> "Jack"
"c?ildren.0" >> "Sara" "c?ildren.0" >> "Sara"
"fav\.movie" >> "Deer Hunter" "fav\.movie" >> "Deer Hunter"
"friends.#.first" >> ["James","Roger"] "friends.#.first" >> ["Dale","Roger","Jane"]
"friends.1.last" >> "Craig" "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 ## Result Type

View File

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

View File

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