diff --git a/gjson.go b/gjson.go index a27840f..7f8101d 100644 --- a/gjson.go +++ b/gjson.go @@ -8,6 +8,7 @@ package gjson import ( + "iter" "strconv" "strings" "time" @@ -230,80 +231,83 @@ func (t Result) IsBool() bool { // value of each item. If the result is an Array, the iterator will only pass // the value of each item. If the result is not a JSON array or object, the // iterator will pass back one value equal to the result. -func (t Result) ForEach(iterator func(key, value Result) bool) { - if !t.Exists() { - return - } - if t.Type != JSON { - iterator(Result{}, t) - return - } - json := t.Raw - var obj bool - var i int - var key, value Result - for ; i < len(json); i++ { - if json[i] == '{' { - i++ - key.Type = String - obj = true - break - } else if json[i] == '[' { - i++ - key.Type = Number - key.Num = -1 - break - } - if json[i] > ' ' { +func (t Result) ForEach() iter.Seq2[Result, Result] { + return func(yield func(Result, Result) bool) { + if !t.Exists() { return } - } - var str string - var vesc bool - var ok bool - var idx int - for ; i < len(json); i++ { - if obj { - if json[i] != '"' { - continue + if t.Type != JSON { + if !yield(Result{}, t) { + return + } + } + json := t.Raw + var obj bool + var i int + var key, value Result + for ; i < len(json); i++ { + if json[i] == '{' { + i++ + key.Type = String + obj = true + break + } else if json[i] == '[' { + i++ + key.Type = Number + key.Num = -1 + break + } + if json[i] > ' ' { + return + } + } + var str string + var vesc bool + var ok bool + var idx int + for ; i < len(json); i++ { + if obj { + if json[i] != '"' { + continue + } + s := i + i, str, vesc, ok = parseString(json, i+1) + if !ok { + return + } + if vesc { + key.Str = unescape(str[1 : len(str)-1]) + } else { + key.Str = str[1 : len(str)-1] + } + key.Raw = str + key.Index = s + t.Index + } else { + key.Num += 1 + } + for ; i < len(json); i++ { + if json[i] <= ' ' || json[i] == ',' || json[i] == ':' { + continue + } + break } s := i - i, str, vesc, ok = parseString(json, i+1) + i, value, ok = parseAny(json, i, true) if !ok { return } - if vesc { - key.Str = unescape(str[1 : len(str)-1]) + if t.Indexes != nil { + if idx < len(t.Indexes) { + value.Index = t.Indexes[idx] + } } else { - key.Str = str[1 : len(str)-1] - } - key.Raw = str - key.Index = s + t.Index - } else { - key.Num += 1 - } - for ; i < len(json); i++ { - if json[i] <= ' ' || json[i] == ',' || json[i] == ':' { - continue + value.Index = s + t.Index } - break - } - s := i - i, value, ok = parseAny(json, i, true) - if !ok { - return - } - if t.Indexes != nil { - if idx < len(t.Indexes) { - value.Index = t.Indexes[idx] + if !yield(key, value) { + return } - } else { - value.Index = s + t.Index - } - if !iterator(key, value) { - return + idx++ } - idx++ } } @@ -322,7 +326,7 @@ func (t Result) Map() map[string]Result { func (t Result) Get(path string) Result { r := Get(t.Raw, path) if r.Indexes != nil { - for i := 0; i < len(r.Indexes); i++ { + for i := range r.Indexes { r.Indexes[i] += t.Index } } else { @@ -333,9 +337,9 @@ func (t Result) Get(path string) Result { type arrayOrMapResult struct { a []Result - ai []interface{} + ai []any o map[string]Result - oi map[string]interface{} + oi map[string]any vc byte } @@ -370,13 +374,13 @@ func (t Result) arrayOrMap(vc byte, valueize bool) (r arrayOrMapResult) { } if r.vc == '{' { if valueize { - r.oi = make(map[string]interface{}) + r.oi = make(map[string]any) } else { r.o = make(map[string]Result) } } else { if valueize { - r.ai = make([]interface{}, 0) + r.ai = make([]any, 0) } else { r.a = make([]Result, 0) } @@ -449,11 +453,11 @@ func (t Result) arrayOrMap(vc byte, valueize bool) (r arrayOrMapResult) { end: if t.Indexes != nil { if len(t.Indexes) != len(r.a) { - for i := 0; i < len(r.a); i++ { + for i := range r.a { r.a[i].Index = 0 } } else { - for i := 0; i < len(r.a); i++ { + for i := range r.a { r.a[i].Index = t.Indexes[i] } } @@ -667,7 +671,7 @@ func (t Result) Exists() bool { // nil, for JSON null // map[string]interface{}, for JSON objects // []interface{}, for JSON arrays -func (t Result) Value() interface{} { +func (t Result) Value() any { if t.Type == String { return t.Str } @@ -1817,7 +1821,7 @@ func splitPossiblePipe(path string) (left, right string, ok bool) { // take a quick peek for the pipe character. If found we'll split the piped // part of the path into the c.pipe field and shorten the rp. var possible bool - for i := 0; i < len(path); i++ { + for i := range len(path) { if path[i] == '|' { possible = true break @@ -1997,7 +2001,7 @@ func nameOfLast(path string) string { } func isSimpleName(component string) bool { - for i := 0; i < len(component); i++ { + for i := range len(component) { if component[i] < ' ' { return false } @@ -2624,7 +2628,7 @@ func validstring(data []byte, i int) (outi int, ok bool) { return i, false case '"', '\\', '/', 'b', 'f', 'n', 'r', 't': case 'u': - for j := 0; j < 4; j++ { + for range 4 { i++ if i >= len(data) { return i, false @@ -2950,13 +2954,13 @@ func ModifierExists(name string, fn func(json, arg string) string) bool { // cleanWS remove any non-whitespace from string func cleanWS(s string) string { - for i := 0; i < len(s); i++ { + for i := range len(s) { switch s[i] { case ' ', '\t', '\n', '\r': continue default: var s2 []byte - for i := 0; i < len(s); i++ { + for i := range len(s) { switch s[i] { case ' ', '\t', '\n', '\r': s2 = append(s2, s[i]) @@ -2972,7 +2976,7 @@ func cleanWS(s string) string { func modPretty(json, arg string) string { if len(arg) > 0 { opts := *pretty.DefaultOptions - Parse(arg).ForEach(func(key, value Result) bool { + for key, value := range Parse(arg).ForEach() { switch key.String() { case "sortKeys": opts.SortKeys = value.Bool() @@ -2983,8 +2987,7 @@ func modPretty(json, arg string) string { case "width": opts.Width = int(value.Int()) } - return true - }) + } return bytesString(pretty.PrettyOptions(stringBytes(json), &opts)) } return bytesString(pretty.Pretty(stringBytes(json))) @@ -3005,10 +3008,9 @@ func modReverse(json, arg string) string { res := Parse(json) if res.IsArray() { var values []Result - res.ForEach(func(_, value Result) bool { + for _, value := range res.ForEach() { values = append(values, value) - return true - }) + } out := make([]byte, 0, len(json)) out = append(out, '[') for i, j := len(values)-1, 0; i >= 0; i, j = i-1, j+1 { @@ -3022,10 +3024,9 @@ func modReverse(json, arg string) string { } if res.IsObject() { var keyValues []Result - res.ForEach(func(key, value Result) bool { + for key, value := range res.ForEach() { keyValues = append(keyValues, key, value) - return true - }) + } out := make([]byte, 0, len(json)) out = append(out, '{') for i, j := len(keyValues)-2, 0; i >= 0; i, j = i-2, j+1 { @@ -3058,26 +3059,26 @@ func modFlatten(json, arg string) string { } var deep bool if arg != "" { - Parse(arg).ForEach(func(key, value Result) bool { - if key.String() == "deep" { - deep = value.Bool() + for k, v := range Parse(arg).ForEach() { + if k.String() == "deep" { + deep = v.Bool() + //break// todo } - return true - }) + } } var out []byte out = append(out, '[') var idx int - res.ForEach(func(_, value Result) bool { + for _, v := range res.ForEach() { var raw string - if value.IsArray() { + if v.IsArray() { if deep { - raw = unwrap(modFlatten(value.Raw, arg)) + raw = unwrap(modFlatten(v.Raw, arg)) } else { - raw = unwrap(value.Raw) + raw = unwrap(v.Raw) } } else { - raw = value.Raw + raw = v.Raw } raw = strings.TrimSpace(raw) if len(raw) > 0 { @@ -3087,8 +3088,7 @@ func modFlatten(json, arg string) string { out = append(out, raw...) idx++ } - return true - }) + } out = append(out, ']') return bytesString(out) } @@ -3105,7 +3105,7 @@ func modKeys(json, arg string) string { var out strings.Builder out.WriteByte('[') var i int - v.ForEach(func(key, _ Result) bool { + for key := range v.ForEach() { if i > 0 { out.WriteByte(',') } @@ -3115,8 +3115,7 @@ func modKeys(json, arg string) string { out.WriteString("null") } i++ - return true - }) + } out.WriteByte(']') return out.String() } @@ -3135,14 +3134,13 @@ func modValues(json, arg string) string { var out strings.Builder out.WriteByte('[') var i int - v.ForEach(func(_, value Result) bool { + for _, value := range v.ForEach() { if i > 0 { out.WriteByte(',') } out.WriteString(value.Raw) i++ - return true - }) + } out.WriteByte(']') return out.String() } @@ -3167,48 +3165,44 @@ func modJoin(json, arg string) string { } var preserve bool if arg != "" { - Parse(arg).ForEach(func(key, value Result) bool { + for key, value := range Parse(arg).ForEach() { if key.String() == "preserve" { preserve = value.Bool() } - return true - }) + } } var out []byte out = append(out, '{') if preserve { // Preserve duplicate keys. var idx int - res.ForEach(func(_, value Result) bool { + for _, value := range res.ForEach() { if !value.IsObject() { - return true + break } if idx > 0 { out = append(out, ',') } out = append(out, unwrap(value.Raw)...) idx++ - return true - }) + } } else { // Deduplicate keys and generate an object with stable ordering. var keys []Result kvals := make(map[string]Result) - res.ForEach(func(_, value Result) bool { + for _, value := range res.ForEach() { if !value.IsObject() { - return true + break } - value.ForEach(func(key, value Result) bool { + for key, value := range value.ForEach() { k := key.String() if _, ok := kvals[k]; !ok { keys = append(keys, key) } kvals[k] = value - return true - }) - return true - }) - for i := 0; i < len(keys); i++ { + } + } + for i := range keys { if i > 0 { out = append(out, ',') } @@ -3253,21 +3247,19 @@ func modGroup(json, arg string) string { return "" } var all [][]byte - res.ForEach(func(key, value Result) bool { + for _, value := range res.ForEach() { if !value.IsArray() { - return true + break } var idx int - value.ForEach(func(_, value Result) bool { + for key, value := range value.ForEach() { if idx == len(all) { all = append(all, []byte{}) } all[idx] = append(all[idx], ("," + key.Raw + ":" + value.Raw)...) idx++ - return true - }) - return true - }) + } + } var data []byte data = append(data, '[') for i, item := range all { @@ -3435,10 +3427,9 @@ func (t Result) Paths(json string) []string { return nil } paths := make([]string, 0, len(t.Indexes)) - t.ForEach(func(_, value Result) bool { + for _, value := range t.ForEach() { paths = append(paths, value.Path(json)) - return true - }) + } if len(paths) != len(t.Indexes) { return nil } @@ -3586,10 +3577,9 @@ func parseRecursiveDescent(all []Result, parent Result, path string) []Result { all = append(all, res) } if parent.IsArray() || parent.IsObject() { - parent.ForEach(func(_, val Result) bool { - all = parseRecursiveDescent(all, val, path) - return true - }) + for _, value := range parent.ForEach() { + all = parseRecursiveDescent(all, value, path) + } } return all } diff --git a/gjson_test.go b/gjson_test.go index 079e1cf..c129e97 100644 --- a/gjson_test.go +++ b/gjson_test.go @@ -28,7 +28,7 @@ func TestRandomData(t *testing.T) { }() rand.Seed(time.Now().UnixNano()) b := make([]byte, 200) - for i := 0; i < 2000000; i++ { + for range 2000000 { n, err := rand.Read(b[:rand.Int()%len(b)]) if err != nil { t.Fatal(err) @@ -42,7 +42,7 @@ func TestRandomData(t *testing.T) { func TestRandomValidStrings(t *testing.T) { rand.Seed(time.Now().UnixNano()) b := make([]byte, 200) - for i := 0; i < 100000; i++ { + for range 100000 { n, err := rand.Read(b[:rand.Int()%len(b)]) if err != nil { t.Fatal(err) @@ -159,25 +159,23 @@ func TestPath(t *testing.T) { } obj := Parse(json) - obj.ForEach(func(key, val Result) bool { + for key, val := range obj.ForEach() { kp := key.Path(json) assert(t, kp == "") vp := val.Path(json) if vp == "name" { // there are two "name" keys - return true + break } val2 := obj.Get(vp) assert(t, val2.Raw == val.Raw) - return true - }) + } arr := obj.Get("loggy.programmers") - arr.ForEach(func(_, val Result) bool { + for _, val := range arr.ForEach() { vp := val.Path(json) val2 := Get(json, vp) assert(t, val2.Raw == val.Raw) - return true - }) + } get := func(path string) { r1 := Get(json, path) path2 := r1.Path(json) @@ -222,7 +220,7 @@ func TestManyVariousPathCounts(t *testing.T) { expects := []string{"a", "b", "c"} for _, count := range counts { var gpaths []string - for i := 0; i < count; i++ { + for i := range count { if i < len(paths) { gpaths = append(gpaths, paths[i]) } else { @@ -230,7 +228,7 @@ func TestManyVariousPathCounts(t *testing.T) { } } results := GetMany(json, gpaths...) - for i := 0; i < len(paths); i++ { + for i := range paths { if results[i].String() != expects[i] { t.Fatalf("expected '%v', got '%v'", expects[i], results[i].String()) @@ -241,12 +239,12 @@ func TestManyVariousPathCounts(t *testing.T) { func TestManyRecursion(t *testing.T) { var json string var path string - for i := 0; i < 100; i++ { + for range 100 { json += `{"a":` path += ".a" } json += `"b"` - for i := 0; i < 100; i++ { + for range 100 { json += `}` } path = path[1:] @@ -405,23 +403,22 @@ func TestTypes(t *testing.T) { assert(t, (Result{Type: Number, Num: 1}).Float() == 1) } func TestForEach(t *testing.T) { - Result{}.ForEach(nil) - Result{Type: String, Str: "Hello"}.ForEach(func(_, value Result) bool { + Result{}.ForEach() + result := Result{Type: String, Str: "Hello"} + for _, value := range result.ForEach() { assert(t, value.String() == "Hello") - return false - }) - Result{Type: JSON, Raw: "*invalid*"}.ForEach(nil) + } + Result{Type: JSON, Raw: "*invalid*"}.ForEach() json := ` {"name": {"first": "Janet","last": "Prichard"}, "asd\nf":"\ud83d\udd13","age": 47}` var count int - ParseBytes([]byte(json)).ForEach(func(key, value Result) bool { + for range ParseBytes([]byte(json)).ForEach() { count++ - return true - }) + } assert(t, count == 3) - ParseBytes([]byte(`{"bad`)).ForEach(nil) - ParseBytes([]byte(`{"ok":"bad`)).ForEach(nil) + ParseBytes([]byte(`{"bad`)).ForEach() + ParseBytes([]byte(`{"ok":"bad`)).ForEach() } func TestMap(t *testing.T) { assert(t, len(ParseBytes([]byte(`"asdf"`)).Map()) == 0) @@ -434,17 +431,17 @@ func TestMap(t *testing.T) { func TestBasic1(t *testing.T) { mtok := get(basicJSON, `loggy.programmers`) var count int - mtok.ForEach(func(key, value Result) bool { + for key, value := range mtok.ForEach() { assert(t, key.Exists()) assert(t, key.String() == fmt.Sprint(count)) assert(t, key.Int() == int64(count)) count++ if count == 3 { - return false + break } if count == 1 { i := 0 - value.ForEach(func(key, value Result) bool { + for key, value := range value.ForEach() { switch i { case 0: if key.String() != "firstName" || @@ -465,11 +462,9 @@ func TestBasic1(t *testing.T) { } } i++ - return true - }) + } } - return true - }) + } if count != 3 { t.Fatalf("expected %v, got %v", 3, count) } @@ -614,7 +609,7 @@ func TestBasic5(t *testing.T) { t.Fatal("expecting '"+`{"what is a wren?":"a bird"}`+"'", "got", token.String()) } - _ = token.Value().(map[string]interface{}) + _ = token.Value().(map[string]any) if get(basicJSON, "").Value() != nil { t.Fatal("should be nil") @@ -622,8 +617,8 @@ func TestBasic5(t *testing.T) { get(basicJSON, "vals.hello") - type msi = map[string]interface{} - type fi = []interface{} + type msi = map[string]any + type fi = []any mm := Parse(basicJSON).Value().(msi) fn := mm["loggy"].(msi)["programmers"].(fi)[1].(msi)["firstName"].(string) if fn != "Jason" { @@ -776,8 +771,8 @@ var exampleJSON = `{ }` func TestUnmarshalMap(t *testing.T) { - var m1 = Parse(exampleJSON).Value().(map[string]interface{}) - var m2 map[string]interface{} + var m1 = Parse(exampleJSON).Value().(map[string]any) + var m2 map[string]any if err := json.Unmarshal([]byte(exampleJSON), &m2); err != nil { t.Fatal(err) } @@ -874,12 +869,12 @@ func testMany(t *testing.T, json string, paths, expected []string) { func testManyAny(t *testing.T, json string, paths, expected []string, bytes bool) { var result []Result - for i := 0; i < 2; i++ { + for i := range 2 { var which string if i == 0 { which = "Get" result = nil - for j := 0; j < len(expected); j++ { + for j := range expected { if bytes { result = append(result, GetBytes([]byte(json), paths[j])) } else { @@ -894,7 +889,7 @@ func testManyAny(t *testing.T, json string, paths, expected []string, result = GetMany(json, paths...) } } - for j := 0; j < len(expected); j++ { + for j := range expected { if result[j].String() != expected[j] { t.Fatalf("Using key '%s' for '%s'\nexpected '%v', got '%v'", paths[j], which, expected[j], result[j].String()) @@ -939,7 +934,7 @@ func TestRandomMany(t *testing.T) { }() rand.Seed(time.Now().UnixNano()) b := make([]byte, 512) - for i := 0; i < 50000; i++ { + for range 50000 { n, err := rand.Read(b[:rand.Int()%len(b)]) if err != nil { t.Fatal(err) @@ -949,12 +944,12 @@ func TestRandomMany(t *testing.T) { for i := range paths { var b []byte n := rand.Int() % 5 - for j := 0; j < n; j++ { + for j := range n { if j > 0 { b = append(b, '.') } nn := rand.Int() % 10 - for k := 0; k < nn; k++ { + for range nn { b = append(b, 'a'+byte(rand.Int()%26)) } } @@ -1358,21 +1353,21 @@ func TestArrayValues(t *testing.T) { } func BenchmarkValid(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { Valid(complicatedJSON) } } func BenchmarkValidBytes(b *testing.B) { complicatedJSON := []byte(complicatedJSON) - for i := 0; i < b.N; i++ { + for b.Loop() { ValidBytes(complicatedJSON) } } func BenchmarkGoStdlibValidBytes(b *testing.B) { complicatedJSON := []byte(complicatedJSON) - for i := 0; i < b.N; i++ { + for b.Loop() { json.Valid(complicatedJSON) } } @@ -2348,7 +2343,7 @@ func TestNaNInf(t *testing.T) { math.Copysign(0, -1), 0} assert(t, int(Get(json, `#`).Int()) == len(raws)) - for i := 0; i < len(raws); i++ { + for i := range raws { r := Get(json, fmt.Sprintf("%d", i)) assert(t, r.Raw == raws[i]) assert(t, r.Num == nums[i] || (math.IsNaN(r.Num) && math.IsNaN(nums[i]))) @@ -2356,13 +2351,12 @@ func TestNaNInf(t *testing.T) { } var i int - Parse(json).ForEach(func(_, r Result) bool { + for _, r := range Parse(json).ForEach() { assert(t, r.Raw == raws[i]) assert(t, r.Num == nums[i] || (math.IsNaN(r.Num) && math.IsNaN(nums[i]))) assert(t, r.Type == Number) i++ - return true - }) + } // Parse should also return valid numbers assert(t, math.IsNaN(Parse("nan").Float())) @@ -2454,7 +2448,7 @@ func TestQueryGetPath(t *testing.T) { Get(readmeJSON, "friends.#(last=Murphy)#").Paths(readmeJSON), " ") == "friends.0 friends.2") arr := Get(readmeJSON, "friends.#.first").Array() - for i := 0; i < len(arr); i++ { + for i := range arr { assert(t, arr[i].Path(readmeJSON) == fmt.Sprintf("friends.%d.first", i)) } } @@ -2490,7 +2484,7 @@ func TestStaticJSON(t *testing.T) { func TestArrayKeys(t *testing.T) { N := 100 json := "[" - for i := 0; i < N; i++ { + for i := range N { if i > 0 { json += "," } @@ -2498,12 +2492,11 @@ func TestArrayKeys(t *testing.T) { } json += "]" var i int - Parse(json).ForEach(func(key, value Result) bool { + for key := range Parse(json).ForEach() { assert(t, key.String() == fmt.Sprint(i)) assert(t, key.Int() == int64(i)) i++ - return true - }) + } assert(t, i == N) } @@ -2549,7 +2542,7 @@ func TestGroup(t *testing.T) { assert(t, res == `["123"]`) } -func goJSONMarshal(i interface{}) ([]byte, error) { +func goJSONMarshal(i any) ([]byte, error) { buffer := &bytes.Buffer{} encoder := json.NewEncoder(buffer) encoder.SetEscapeHTML(!DisableEscapeHTML) diff --git a/go.mod b/go.mod index 6f64083..9f5bb54 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/tidwall/gjson -go 1.12 +go 1.24.3 require ( github.com/tidwall/match v1.1.1 - github.com/tidwall/pretty v1.2.0 + github.com/tidwall/pretty v1.2.1 ) diff --git a/go.sum b/go.sum index be39c8c..28ed2ff 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,4 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=