diff --git a/dec_raw.go b/dec_raw.go new file mode 100644 index 0000000..71fcf7e --- /dev/null +++ b/dec_raw.go @@ -0,0 +1,28 @@ +package jx + +import "github.com/ogen-go/errors" + +// Raw is like Skip(), but saves and returns skipped value as raw json. +// +// Do not retain returned slice, it references underlying buffer. +func (d *Decoder) Raw() ([]byte, error) { + if d.reader != nil { + return nil, errors.New("not implemented for io.Reader") + } + + start := d.head + if err := d.Skip(); err != nil { + return nil, errors.Wrap(err, "skip") + } + + return d.buf[start:d.head], nil +} + +// RawAppend is Raw that appends saved raw json value to buf. +func (d *Decoder) RawAppend(buf []byte) ([]byte, error) { + raw, err := d.Raw() + if err != nil { + return nil, err + } + return append(buf, raw...), err +} diff --git a/dec_raw_test.go b/dec_raw_test.go new file mode 100644 index 0000000..9e61d3e --- /dev/null +++ b/dec_raw_test.go @@ -0,0 +1,84 @@ +package jx + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDecoder_Raw(t *testing.T) { + t.Run("Positive", func(t *testing.T) { + v := `{"foo": [1, 2, 3, 4, 5] }}` + t.Run("Raw", func(t *testing.T) { + d := DecodeStr(v) + require.NoError(t, d.Obj(func(d *Decoder, key string) error { + raw, err := d.Raw() + require.NoError(t, err) + t.Logf("%q", raw) + return err + })) + }) + t.Run("RawAppend", func(t *testing.T) { + d := DecodeStr(v) + require.NoError(t, d.Obj(func(d *Decoder, key string) error { + raw, err := d.RawAppend(nil) + require.NoError(t, err) + t.Logf("%q", raw) + return err + })) + }) + }) + t.Run("Negative", func(t *testing.T) { + v := `{"foo": [1, 2, 3, 4, 5` + t.Run("Raw", func(t *testing.T) { + d := DecodeStr(v) + var called bool + require.Error(t, d.Obj(func(d *Decoder, key string) error { + called = true + raw, err := d.Raw() + require.Error(t, err) + require.Nil(t, raw) + return err + })) + require.True(t, called, "should be called") + }) + t.Run("RawAppend", func(t *testing.T) { + d := DecodeStr(v) + var called bool + require.Error(t, d.Obj(func(d *Decoder, key string) error { + called = true + raw, err := d.RawAppend(make([]byte, 10)) + require.Error(t, err) + require.Nil(t, raw) + return err + })) + require.True(t, called, "should be called") + }) + }) + t.Run("Reader", func(t *testing.T) { + d := Decode(errReader{}, 0) + if _, err := d.Raw(); err == nil { + t.Error("should fail") + } + if _, err := d.RawAppend(nil); err == nil { + t.Error("should fail") + } + }) +} + +func BenchmarkDecoder_Raw(b *testing.B) { + data := []byte(`{"foo": [1,2,3,4,5,6,7,8,9,10,11,12,13,14]}`) + b.ReportAllocs() + + var d Decoder + for i := 0; i < b.N; i++ { + d.ResetBytes(data) + raw, err := d.Raw() + if err != nil { + b.Fatal(err) + } + if len(raw) == 0 { + b.Fatal("blank") + } + } +}