diff --git a/README.md b/README.md index 059570f..73f0d66 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/gjson.go b/gjson.go index 8e6c099..e20f367 100644 --- a/gjson.go +++ b/gjson.go @@ -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. @@ -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) @@ -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) @@ -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 @@ -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++ { @@ -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() { @@ -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++ } } @@ -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 { @@ -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{ @@ -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) @@ -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 diff --git a/gjson_test.go b/gjson_test.go index 6bc445e..c5329c6 100644 --- a/gjson_test.go +++ b/gjson_test.go @@ -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"} @@ -922,7 +922,7 @@ var complicatedJSON = ` "nestedTagged": { "Green": "Green", "Map": { - "this": "that", + "this": "that", "and": "the other thing" }, "Ints": { @@ -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) @@ -1492,7 +1492,7 @@ func TestDeepSelectors(t *testing.T) { } }, { - "first": "Roger", "last": "Craig", + "first": "Roger", "last": "Craig", "extra": [40,50,60], "details": { "city": "Phoenix", @@ -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 + }) }