diff --git a/dec_arr_iter.go b/dec_arr_iter.go new file mode 100644 index 0000000..48711a4 --- /dev/null +++ b/dec_arr_iter.go @@ -0,0 +1,62 @@ +package jx + +import ( + "github.com/go-faster/errors" +) + +// ArrIter is decoding array iterator. +type ArrIter struct { + d *Decoder + err error + closed bool + comma bool +} + +// ArrIter creates new array iterator. +func (d *Decoder) ArrIter() (ArrIter, error) { + if err := d.consume('['); err != nil { + return ArrIter{}, errors.Wrap(err, "start") + } + if err := d.incDepth(); err != nil { + return ArrIter{}, errors.Wrap(err, "inc") + } + if _, err := d.more(); err != nil { + return ArrIter{}, err + } + d.unread() + return ArrIter{d: d}, nil +} + +// Next consumes element and returns false, if there is no elements anymore. +func (i *ArrIter) Next() bool { + if i.closed { + return false + } + + dec := i.d + c, err := dec.more() + if err != nil { + i.err = err + return false + } + if c == ']' { + i.closed = true + i.err = dec.decDepth() + return false + } + if i.comma { + if c != ',' { + i.err = badToken(c) + return false + } + } else { + dec.unread() + } + i.comma = true + return true +} + +// Err returns the error, if any, that was encountered during iteration. +func (i *ArrIter) Err() error { + return i.err +} diff --git a/dec_arr_iter_test.go b/dec_arr_iter_test.go new file mode 100644 index 0000000..8d38701 --- /dev/null +++ b/dec_arr_iter_test.go @@ -0,0 +1,46 @@ +package jx + +import ( + "encoding/json" + "io" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDecoder_ArrIter(t *testing.T) { + testIter := func(d *Decoder) error { + iter, err := d.ArrIter() + if err != nil { + return err + } + for iter.Next() { + if err := d.Skip(); err != nil { + return err + } + } + if iter.Next() { + panic("BUG") + } + return iter.Err() + } + for _, s := range testArrs { + checker := require.Error + if json.Valid([]byte(s)) { + checker = require.NoError + } + + d := DecodeStr(s) + checker(t, testIter(d), s) + } + t.Run("Depth", func(t *testing.T) { + d := DecodeStr(`[`) + // Emulate depth + d.depth = maxDepth + require.ErrorIs(t, testIter(d), errMaxDepth) + }) + t.Run("Empty", func(t *testing.T) { + d := DecodeStr(``) + require.ErrorIs(t, testIter(d), io.ErrUnexpectedEOF) + }) +} diff --git a/dec_arr_test.go b/dec_arr_test.go index bfba384..5fc148d 100644 --- a/dec_arr_test.go +++ b/dec_arr_test.go @@ -1,6 +1,7 @@ package jx import ( + _ "embed" "encoding/json" "io" "testing" @@ -79,3 +80,61 @@ func TestDecoder_Elem(t *testing.T) { require.False(t, ok) }) } + +//go:embed testdata/bools.json +var boolsData []byte + +func BenchmarkDecodeBools(b *testing.B) { + b.Run("Callback", func(b *testing.B) { + d := DecodeBytes(boolsData) + r := make([]bool, 0, 100) + + b.SetBytes(int64(len(boolsData))) + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + r = r[:0] + d.ResetBytes(boolsData) + + if err := d.Arr(func(d *Decoder) error { + f, err := d.Bool() + if err != nil { + return err + } + r = append(r, f) + return nil + }); err != nil { + b.Fatal(err) + } + } + }) + b.Run("Iterator", func(b *testing.B) { + d := DecodeBytes(boolsData) + r := make([]bool, 0, 100) + + b.SetBytes(int64(len(boolsData))) + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + r = r[:0] + d.ResetBytes(boolsData) + + iter, err := d.ArrIter() + if err != nil { + b.Fatal(err) + } + for iter.Next() { + v, err := d.Bool() + if err != nil { + b.Fatal(err) + } + r = append(r, v) + } + if err := iter.Err(); err != nil { + b.Fatal(err) + } + } + }) +} diff --git a/dec_skip_cases_test.go b/dec_skip_cases_test.go index 86d68d6..dfd557a 100644 --- a/dec_skip_cases_test.go +++ b/dec_skip_cases_test.go @@ -208,22 +208,29 @@ var testObjs = []string{ } var testArrs = []string{ - `[]`, // valid - `[1]`, // valid - `[ 1, "hello"]`, // valid - `[abc]`, // invalid - `[`, // invalid - `[,`, // invalid - `[[]`, // invalid - "[true,f", // invalid - "[true", // invalid - "[true,", // invalid - "[true]", // invalid - "[true,]", // invalid - "[true,false", // invalid - "[true,false,", // invalid - "[true,false,]", // invalid - "[true,false}", // invalid + `[]`, // valid + `[ ]`, // valid + "[1]", // valid + "[true]", // valid + "[null]", // valid + `[ 1]`, // valid + `[ true]`, // valid + `[ null]`, // valid + `[ "abc" ` + "\n]", // valid + `[ "abc",` + "\n" + `"text"]`, // valid + `[ 1, "hello"]`, // valid + `[abc]`, // invalid + `[`, // invalid + `[,`, // invalid + `[[]`, // invalid + "[true,f", // invalid + "[true", // invalid + "[true,", // invalid + "[true,]", // invalid + "[true,false", // invalid + "[true,false,", // invalid + "[true,false,]", // invalid + "[true,false}", // invalid } func TestDecoder_Skip(t *testing.T) {