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

feat: array iterator #33

Merged
merged 4 commits into from Mar 13, 2022
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
62 changes: 62 additions & 0 deletions 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
}
46 changes: 46 additions & 0 deletions 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)
})
}
59 changes: 59 additions & 0 deletions dec_arr_test.go
@@ -1,6 +1,7 @@
package jx

import (
_ "embed"
"encoding/json"
"io"
"testing"
Expand Down Expand Up @@ -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)
}
}
})
}
39 changes: 23 additions & 16 deletions dec_skip_cases_test.go
Expand Up @@ -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) {
Expand Down