From 34ea5117465465327cfb038ab6d05bdaa6a8cb15 Mon Sep 17 00:00:00 2001 From: Sebastian Spaink Date: Tue, 29 Jun 2021 20:18:21 -0500 Subject: [PATCH 1/5] Support getting index for hastags --- gjson.go | 11 +++++++++- gjson_test.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/gjson.go b/gjson.go index 8e6c099..fa73c7e 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 + // ArrayIndex is the Index of each returned element in the original json + ArrayIndex []int } // String returns a string representation of the value. @@ -1476,6 +1478,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { c.pipe = right c.piped = true } + var arrayIndex = make([]int, 0, 64) var jsons = make([]byte, 0, 64) jsons = append(jsons, '[') for j, k := 0, 0; j < len(alog); j++ { @@ -1490,6 +1493,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 +1505,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { raw = res.String() } jsons = append(jsons, []byte(raw)...) + arrayIndex = append(arrayIndex, res.Index+parentIndex) k++ } } @@ -1509,6 +1514,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.ArrayIndex = arrayIndex return i + 1, true } if rp.alogok { @@ -2046,7 +2052,10 @@ func parseAny(json string, i int, hit bool) (int, Result, bool) { res.Raw = val res.Type = JSON } - return i, res, true + var c parseContext + c.value = res + fillIndex(json, &c) + return i, c.value, true } if json[i] <= ' ' { continue diff --git a/gjson_test.go b/gjson_test.go index 6bc445e..9c650e5 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": { @@ -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,51 @@ func TestModifierDoubleQuotes(t *testing.T) { `{"name":"Product P4","value":"{\"productId\":\"1cc3\",\"vendorId\":\"20de\"}"},`+ `{"name":"Product P4","value":"{\"productId\":\"1dd3\",\"vendorId\":\"30de\"}"}`+ `]`) + +} + +func TestArrayIndex(t *testing.T) { + json := `{ + "vals": [ + [ + 2, + 2, + { + "wut", + "yup" + } + ], + [ + 4, + 5, + 6 + ] + ] + }` + r := Get(json, `vals.#.2`) + fmt.Println(r.ArrayIndex) + fmt.Println(string(json[37])) + + all := Get(json, `@this`) + all.ForEach(func(_, value Result) bool { + println(value.Raw, "index", value.Index) + println(string(json[value.Index : value.Index+len(value.Raw)])) + if value.IsArray() { + value.ForEach(func(_, v Result) bool { + println(v.Raw, "index", v.Index) + parentIndex := value.Index + v.Index + println(string(json[parentIndex : parentIndex+len(v.Raw)])) + + if v.IsArray() { + v.ForEach(func(_, sv Result) bool { + println(sv.Raw, "index", sv.Index+parentIndex) + println(string(json[sv.Index+parentIndex : sv.Index+parentIndex+len(sv.Raw)])) + return true + }) + } + return true + }) + } + return true // keep iterating + }) } From 73b86a9fc9d61b33b60bb3fe3a42aecc969dca94 Mon Sep 17 00:00:00 2001 From: Bas <3441183+BattleBas@users.noreply.github.com> Date: Wed, 30 Jun 2021 00:38:05 -0500 Subject: [PATCH 2/5] Support queries --- README.md | 11 ++++--- gjson.go | 29 ++++++++++------- gjson_test.go | 89 +++++++++++++++++++++++++++++++++------------------ 3 files changed, 81 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 059570f..5a1a374 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.HashtagIndexes // hashtagIndexes 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 fa73c7e..b11f643 100644 --- a/gjson.go +++ b/gjson.go @@ -64,8 +64,8 @@ type Result struct { Num float64 // Index of raw value in original json, zero means index unknown Index int - // ArrayIndex is the Index of each returned element in the original json - ArrayIndex []int + // HashtagIndexes contains the Indexes of the elements returned by a query containing the '#' character + HashtagIndexes []int } // String returns a string representation of the value. @@ -1263,6 +1263,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { var alog []int var partidx int var multires []byte + var hashtagQueryIndex []int rp := parseArrayPath(path) if !rp.arrch { n, ok := parseUint(rp.part) @@ -1283,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) @@ -1314,6 +1319,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { multires = append(multires, ',') } multires = append(multires, raw...) + hashtagQueryIndex = append(hashtagQueryIndex, res.Index+parentIndex) } } else { c.value = res @@ -1478,7 +1484,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { c.pipe = right c.piped = true } - var arrayIndex = make([]int, 0, 64) + var hashtagIndexes = make([]int, 0, 64) var jsons = make([]byte, 0, 64) jsons = append(jsons, '[') for j, k := 0, 0; j < len(alog); j++ { @@ -1505,7 +1511,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { raw = res.String() } jsons = append(jsons, []byte(raw)...) - arrayIndex = append(arrayIndex, res.Index+parentIndex) + hashtagIndexes = append(hashtagIndexes, res.Index+parentIndex) k++ } } @@ -1514,7 +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.ArrayIndex = arrayIndex + c.value.HashtagIndexes = hashtagIndexes return i + 1, true } if rp.alogok { @@ -1530,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, + HashtagIndexes: hashtagQueryIndex, } } else if rp.query.all { c.value = Result{ @@ -2052,10 +2059,10 @@ func parseAny(json string, i int, hit bool) (int, Result, bool) { res.Raw = val res.Type = JSON } - var c parseContext - c.value = res - fillIndex(json, &c) - return i, c.value, 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 9c650e5..4cd151a 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"} @@ -1492,7 +1492,7 @@ func TestDeepSelectors(t *testing.T) { } }, { - "first": "Roger", "last": "Craig", + "first": "Roger", "last": "Craig", "extra": [40,50,60], "details": { "city": "Phoenix", @@ -2122,48 +2122,73 @@ func TestModifierDoubleQuotes(t *testing.T) { } -func TestArrayIndex(t *testing.T) { - json := `{ +func TestHashtagIndexes(t *testing.T) { + var exampleJSON = `{ "vals": [ - [ - 2, - 2, - { - "wut", - "yup" - } - ], - [ - 4, - 5, - 6 - ] + [1,66,{test: 3}], + [4,5,[6]] + ], + "objectArray":[ + {"first": "Dale", "age": 44}, + {"first": "Roger", "age": 68}, ] }` - r := Get(json, `vals.#.2`) - fmt.Println(r.ArrayIndex) - fmt.Println(string(json[37])) - all := Get(json, `@this`) + testCases := []struct { + path string + expected []string + }{ + { + `vals.#.1`, + []string{`6`, "5"}, + }, + { + `vals.#.2`, + []string{"{", "["}, + }, + { + `objectArray.#(age>43)#.first`, + []string{`"`, `"`}, + }, + } + + for _, tc := range testCases { + r := Get(exampleJSON, tc.path) + + assert(t, len(r.HashtagIndexes) == len(tc.expected)) + + for i, a := range r.HashtagIndexes { + 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 { - println(value.Raw, "index", value.Index) - println(string(json[value.Index : value.Index+len(value.Raw)])) if value.IsArray() { value.ForEach(func(_, v Result) bool { - println(v.Raw, "index", v.Index) - parentIndex := value.Index + v.Index - println(string(json[parentIndex : parentIndex+len(v.Raw)])) - if v.IsArray() { v.ForEach(func(_, sv Result) bool { - println(sv.Raw, "index", sv.Index+parentIndex) - println(string(json[sv.Index+parentIndex : sv.Index+parentIndex+len(sv.Raw)])) + if sv.IsObject() { + assert(t, string(exampleJSON[r.HashtagIndexes[0]:r.HashtagIndexes[0]+len(sv.Raw)]) == sv.Raw) + } + if sv.IsArray() { + assert(t, string(exampleJSON[r.HashtagIndexes[1]:r.HashtagIndexes[1]+len(sv.Raw)]) == sv.Raw) + } return true }) } return true }) } - return true // keep iterating + return true }) } From 9b49f6eef56ef445a9464f6a24656e8ae1be3cef Mon Sep 17 00:00:00 2001 From: Bas <3441183+BattleBas@users.noreply.github.com> Date: Wed, 30 Jun 2021 00:39:24 -0500 Subject: [PATCH 3/5] Undo unnecessary change --- gjson_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gjson_test.go b/gjson_test.go index 4cd151a..9a8fdf1 100644 --- a/gjson_test.go +++ b/gjson_test.go @@ -922,7 +922,7 @@ var complicatedJSON = ` "nestedTagged": { "Green": "Green", "Map": { - "this": "that", + "this": "that", "and": "the other thing" }, "Ints": { From 7460ecfe694895d83c3681b7a7f149006a7bafaa Mon Sep 17 00:00:00 2001 From: Bas <3441183+BattleBas@users.noreply.github.com> Date: Thu, 1 Jul 2021 06:00:44 -0500 Subject: [PATCH 4/5] Fix test --- gjson_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gjson_test.go b/gjson_test.go index 9a8fdf1..95ca1f0 100644 --- a/gjson_test.go +++ b/gjson_test.go @@ -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, HashtagIndexes:[]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, HashtagIndexes:[]int(nil)}`, + `gjson.Result{Type:2, Raw:"0", Str:"", Num:0, Index:0, HashtagIndexes:[]int(nil)}`, }, "\n") if output != expect { t.Fatalf("expected '%v', got '%v'", expect, output) From 7405f21134dbe694c1a9338f9e39ec24e7dc0d0d Mon Sep 17 00:00:00 2001 From: Sebastian Spaink Date: Fri, 9 Jul 2021 18:15:56 -0500 Subject: [PATCH 5/5] Rename to indexes Set to nil when modifiers used --- README.md | 2 +- gjson.go | 21 +++++++++++---------- gjson_test.go | 30 +++++++++++++++++------------- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 5a1a374..73f0d66 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ 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.HashtagIndexes // hashtagIndexes contains the indexes of the elements returned by a query containing the '#' character +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 b11f643..e20f367 100644 --- a/gjson.go +++ b/gjson.go @@ -64,8 +64,8 @@ type Result struct { Num float64 // Index of raw value in original json, zero means index unknown Index int - // HashtagIndexes contains the Indexes of the elements returned by a query containing the '#' character - HashtagIndexes []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. @@ -1263,7 +1263,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { var alog []int var partidx int var multires []byte - var hashtagQueryIndex []int + var queryIndexes []int rp := parseArrayPath(path) if !rp.arrch { n, ok := parseUint(rp.part) @@ -1319,7 +1319,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { multires = append(multires, ',') } multires = append(multires, raw...) - hashtagQueryIndex = append(hashtagQueryIndex, res.Index+parentIndex) + queryIndexes = append(queryIndexes, res.Index+parentIndex) } } else { c.value = res @@ -1484,7 +1484,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { c.pipe = right c.piped = true } - var hashtagIndexes = make([]int, 0, 64) + var indexes = make([]int, 0, 64) var jsons = make([]byte, 0, 64) jsons = append(jsons, '[') for j, k := 0, 0; j < len(alog); j++ { @@ -1511,7 +1511,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) { raw = res.String() } jsons = append(jsons, []byte(raw)...) - hashtagIndexes = append(hashtagIndexes, res.Index+parentIndex) + indexes = append(indexes, res.Index+parentIndex) k++ } } @@ -1520,7 +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.HashtagIndexes = hashtagIndexes + c.value.Indexes = indexes return i + 1, true } if rp.alogok { @@ -1536,9 +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, - HashtagIndexes: hashtagQueryIndex, + Raw: string(append(multires, ']')), + Type: JSON, + Indexes: queryIndexes, } } else if rp.query.all { c.value = Result{ @@ -1819,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) diff --git a/gjson_test.go b/gjson_test.go index 95ca1f0..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, HashtagIndexes:[]int(nil)}`, + `Index:0, Indexes:[]int(nil)}`, `gjson.Result{Type:3, Raw:"\"PERSON2\"", Str:"PERSON2", Num:0, ` + - `Index:0, HashtagIndexes:[]int(nil)}`, - `gjson.Result{Type:2, Raw:"0", Str:"", Num:0, Index:0, HashtagIndexes:[]int(nil)}`, + `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", @@ -2122,7 +2122,7 @@ func TestModifierDoubleQuotes(t *testing.T) { } -func TestHashtagIndexes(t *testing.T) { +func TestIndexes(t *testing.T) { var exampleJSON = `{ "vals": [ [1,66,{test: 3}], @@ -2150,14 +2150,18 @@ func TestHashtagIndexes(t *testing.T) { `objectArray.#(age>43)#.first`, []string{`"`, `"`}, }, + { + `objectArray.@reverse.#.first`, + nil, + }, } for _, tc := range testCases { r := Get(exampleJSON, tc.path) - assert(t, len(r.HashtagIndexes) == len(tc.expected)) + assert(t, len(r.Indexes) == len(tc.expected)) - for i, a := range r.HashtagIndexes { + for i, a := range r.Indexes { assert(t, string(exampleJSON[a]) == tc.expected[i]) } } @@ -2178,10 +2182,10 @@ func TestHashtagIndexesMatchesRaw(t *testing.T) { if v.IsArray() { v.ForEach(func(_, sv Result) bool { if sv.IsObject() { - assert(t, string(exampleJSON[r.HashtagIndexes[0]:r.HashtagIndexes[0]+len(sv.Raw)]) == sv.Raw) + assert(t, string(exampleJSON[r.Indexes[0]:r.Indexes[0]+len(sv.Raw)]) == sv.Raw) } if sv.IsArray() { - assert(t, string(exampleJSON[r.HashtagIndexes[1]:r.HashtagIndexes[1]+len(sv.Raw)]) == sv.Raw) + assert(t, string(exampleJSON[r.Indexes[1]:r.Indexes[1]+len(sv.Raw)]) == sv.Raw) } return true })