Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to return multiple indexes when multiple matches are found #222

Merged
merged 5 commits into from Sep 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 6 additions & 5 deletions README.md
Expand Up @@ -123,11 +123,12 @@ nil, for JSON null
To directly access the value:

```go
result.Type // can be String, Number, True, False, Null, or JSON
result.Str // holds the string
result.Num // holds the float64 number
result.Raw // holds the raw json
result.Index // index of raw value in original json, zero means index unknown
result.Type // can be String, Number, True, False, Null, or JSON
result.Str // holds the string
result.Num // holds the float64 number
result.Raw // holds the raw json
result.Index // index of raw value in original json, zero means index unknown
result.Indexes // Indexes contains the indexes of the elements returned by a query containing the '#' character
```

There are a variety of handy functions that work on a result:
Expand Down
23 changes: 20 additions & 3 deletions gjson.go
Expand Up @@ -64,6 +64,8 @@ type Result struct {
Num float64
// Index of raw value in original json, zero means index unknown
Index int
// Indexes contains the Indexes of the elements returned by a query containing the '#' character
Indexes []int
}

// String returns a string representation of the value.
Expand Down Expand Up @@ -1261,6 +1263,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
var alog []int
var partidx int
var multires []byte
var queryIndexes []int
rp := parseArrayPath(path)
if !rp.arrch {
n, ok := parseUint(rp.part)
Expand All @@ -1281,6 +1284,10 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
multires = append(multires, '[')
}
}
var tmp parseContext
tmp.value = qval
fillIndex(c.json, &tmp)
parentIndex := tmp.value.Index
var res Result
if qval.Type == JSON {
res = qval.Get(rp.query.path)
Expand Down Expand Up @@ -1312,6 +1319,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
multires = append(multires, ',')
}
multires = append(multires, raw...)
queryIndexes = append(queryIndexes, res.Index+parentIndex)
}
} else {
c.value = res
Expand Down Expand Up @@ -1476,6 +1484,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
c.pipe = right
c.piped = true
}
var indexes = make([]int, 0, 64)
var jsons = make([]byte, 0, 64)
jsons = append(jsons, '[')
for j, k := 0, 0; j < len(alog); j++ {
Expand All @@ -1490,6 +1499,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
}
if idx < len(c.json) && c.json[idx] != ']' {
_, res, ok := parseAny(c.json, idx, true)
parentIndex := res.Index
if ok {
res := res.Get(rp.alogkey)
if res.Exists() {
Expand All @@ -1501,6 +1511,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
raw = res.String()
}
jsons = append(jsons, []byte(raw)...)
indexes = append(indexes, res.Index+parentIndex)
k++
}
}
Expand All @@ -1509,6 +1520,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
jsons = append(jsons, ']')
c.value.Type = JSON
c.value.Raw = string(jsons)
c.value.Indexes = indexes
return i + 1, true
}
if rp.alogok {
Expand All @@ -1524,8 +1536,9 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
if !c.value.Exists() {
if len(multires) > 0 {
c.value = Result{
Raw: string(append(multires, ']')),
Type: JSON,
Raw: string(append(multires, ']')),
Type: JSON,
Indexes: queryIndexes,
}
} else if rp.query.all {
c.value = Result{
Expand Down Expand Up @@ -1806,6 +1819,7 @@ func Get(json, path string) Result {
if len(path) > 0 && (path[0] == '|' || path[0] == '.') {
res := Get(rjson, path[1:])
res.Index = 0
res.Indexes = nil
return res
}
return Parse(rjson)
Expand Down Expand Up @@ -2046,7 +2060,10 @@ func parseAny(json string, i int, hit bool) (int, Result, bool) {
res.Raw = val
res.Type = JSON
}
return i, res, true
var tmp parseContext
tmp.value = res
fillIndex(json, &tmp)
return i, tmp.value, true
}
if json[i] <= ' ' {
continue
Expand Down
92 changes: 84 additions & 8 deletions gjson_test.go
Expand Up @@ -859,9 +859,9 @@ func TestIssue20(t *testing.T) {
}

func TestIssue21(t *testing.T) {
json := `{ "Level1Field1":3,
"Level1Field4":4,
"Level1Field2":{ "Level2Field1":[ "value1", "value2" ],
json := `{ "Level1Field1":3,
"Level1Field4":4,
"Level1Field2":{ "Level2Field1":[ "value1", "value2" ],
"Level2Field2":{ "Level3Field1":[ { "key1":"value1" } ] } } }`
paths := []string{"Level1Field1", "Level1Field2.Level2Field1",
"Level1Field2.Level2Field2.Level3Field1", "Level1Field4"}
Expand Down Expand Up @@ -922,7 +922,7 @@ var complicatedJSON = `
"nestedTagged": {
"Green": "Green",
"Map": {
"this": "that",
"this": "that",
"and": "the other thing"
},
"Ints": {
Expand Down Expand Up @@ -1291,10 +1291,10 @@ func TestArrayValues(t *testing.T) {
}
expect := strings.Join([]string{
`gjson.Result{Type:3, Raw:"\"PERSON1\"", Str:"PERSON1", Num:0, ` +
`Index:0}`,
`Index:0, Indexes:[]int(nil)}`,
`gjson.Result{Type:3, Raw:"\"PERSON2\"", Str:"PERSON2", Num:0, ` +
`Index:0}`,
`gjson.Result{Type:2, Raw:"0", Str:"", Num:0, Index:0}`,
`Index:0, Indexes:[]int(nil)}`,
`gjson.Result{Type:2, Raw:"0", Str:"", Num:0, Index:0, Indexes:[]int(nil)}`,
}, "\n")
if output != expect {
t.Fatalf("expected '%v', got '%v'", expect, output)
Expand Down Expand Up @@ -1492,7 +1492,7 @@ func TestDeepSelectors(t *testing.T) {
}
},
{
"first": "Roger", "last": "Craig",
"first": "Roger", "last": "Craig",
"extra": [40,50,60],
"details": {
"city": "Phoenix",
Expand Down Expand Up @@ -2119,4 +2119,80 @@ func TestModifierDoubleQuotes(t *testing.T) {
`{"name":"Product P4","value":"{\"productId\":\"1cc3\",\"vendorId\":\"20de\"}"},`+
`{"name":"Product P4","value":"{\"productId\":\"1dd3\",\"vendorId\":\"30de\"}"}`+
`]`)

}

func TestIndexes(t *testing.T) {
var exampleJSON = `{
"vals": [
[1,66,{test: 3}],
[4,5,[6]]
],
"objectArray":[
{"first": "Dale", "age": 44},
{"first": "Roger", "age": 68},
]
}`

testCases := []struct {
path string
expected []string
}{
{
`vals.#.1`,
[]string{`6`, "5"},
},
{
`vals.#.2`,
[]string{"{", "["},
},
{
`objectArray.#(age>43)#.first`,
[]string{`"`, `"`},
},
{
`objectArray.@reverse.#.first`,
nil,
},
}

for _, tc := range testCases {
r := Get(exampleJSON, tc.path)

assert(t, len(r.Indexes) == len(tc.expected))

for i, a := range r.Indexes {
assert(t, string(exampleJSON[a]) == tc.expected[i])
}
}
}

func TestHashtagIndexesMatchesRaw(t *testing.T) {
var exampleJSON = `{
"objectArray":[
{"first": "Dale", "age": 44},
{"first": "Roger", "age": 68},
]
}`
r := Get(exampleJSON, `objectArray.#(age>43)#.first`)
all := Get(exampleJSON, `@this`)
all.ForEach(func(_, value Result) bool {
if value.IsArray() {
value.ForEach(func(_, v Result) bool {
if v.IsArray() {
v.ForEach(func(_, sv Result) bool {
if sv.IsObject() {
assert(t, string(exampleJSON[r.Indexes[0]:r.Indexes[0]+len(sv.Raw)]) == sv.Raw)
}
if sv.IsArray() {
assert(t, string(exampleJSON[r.Indexes[1]:r.Indexes[1]+len(sv.Raw)]) == sv.Raw)
}
return true
})
}
return true
})
}
return true
})
}