Skip to content

Commit

Permalink
Merge pull request #33 from tdakkota/feat/array-iterator
Browse files Browse the repository at this point in the history
feat: array iterator
  • Loading branch information
ernado committed Mar 13, 2022
2 parents ba26caa + 4c906c3 commit 27bf186
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 16 deletions.
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 @@ -383,22 +383,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

0 comments on commit 27bf186

Please sign in to comment.