commit 076bacd6bee0bd367dc621e9789fbb9f219c459a
parent 663ab7248ff6fee90c4f7b3359f2cc414fce9a91
Author: Francois Parquet <francois.parquet@gmail.com>
Date: Sat, 28 Apr 2018 21:27:02 +0800
Merge pull request #8 from francoispqt/update/make-pooling-safer-to-use
Update/make pooling safer to use
Diffstat:
36 files changed, 1202 insertions(+), 173 deletions(-)
diff --git a/README.md b/README.md
@@ -10,9 +10,10 @@
GoJay is a performant JSON encoder/decoder for Golang (currently the most performant, [see benchmarks](#benchmark-results)).
-It has a simple API and doesn't use reflection. It relies on small interfaces to decode/encode structures and slices.
+It has a simple API and doesn't use reflection. It relies on small interfaces to decode/encode structures and slices.
+
+Gojay also comes with powerful stream decoding features and an even faster [Unsafe](#unsafe-api) API.
-Gojay also comes with powerful stream decoding features.
# Get started
@@ -22,7 +23,12 @@ go get github.com/francoispqt/gojay
## Decoding
-Example of basic stucture decoding:
+Decoding is done through two different API similar to standard `encoding/json`:
+* [Unmarshal](#unmarshal-api)
+* [Decode](#decode-api)
+
+
+Example of basic stucture decoding with Unmarshal:
```go
import "github.com/francoispqt/gojay"
@@ -57,18 +63,79 @@ func main() {
}
```
-Or with the Decoder API (which takes an io.Reader):
+with Decode:
```go
func main() {
u := &user{}
- dec := gojay.NewDecoder(strings.NewReader(`{"id":1,"name":"gojay","email":"gojay@email.com"}`))
- err := dec.Decode(u)
+ dec := gojay.NewDecoder(bytes.NewReader([]byte(`{"id":1,"name":"gojay","email":"gojay@email.com"}`)))
+ err := dec.DecodeObject(d, u)
if err != nil {
log.Fatal(err)
}
}
```
+### Unmarshal API
+
+Unmarshal API decodes a `[]byte` to a given pointer with a single function.
+
+Behind the doors, Unmarshal API borrows a `*gojay.Decoder` resets its settings and decodes the data to the given pointer and releases the `*gojay.Decoder` to the pool when it finishes, whether it encounters an error or not.
+
+If it cannot find the right Decoding strategy for the type of the given pointer, it returns an `InvalidUnmarshalError`. You can test the error returned by doing `if ok := err.(InvalidUnmarshalError); ok {}`.
+
+Unmarshal API comes with three functions:
+* Unmarshal
+```go
+func Unmarshal(data []byte, v Interface{}) error
+```
+
+* UnmarshalObject
+```go
+func UnmarshalObject(data []byte, v UnmarshalerObject) error
+```
+
+* UnmarshalArray
+```go
+func UnmarshalArray(data []byte, v UnmarshalerArray) error
+```
+
+
+### Decode API
+
+Decode API decodes a `[]byte` to a given pointer by creating or borrowing a `*gojay.Decoder` with an `io.Reader` and calling `Decode` methods.
+
+*Getting a *gojay.Decoder or Borrowing*
+
+You can either get a fresh `*gojay.Decoder` calling `dec := gojay.NewDecoder(io.Reader)` or borrow one from the pool by calling `dec := gojay.BorrowDecoder(io.Reader)`.
+
+After using a decoder, you can release it by calling `dec.Release()`. Beware, if you reuse the decoder after releasing it, it will panic with an error of type `InvalidUsagePooledDecoderError`. If you want to fully benefit from the pooling, you must release your decoders after using.
+
+`*gojay.Decoder` has multiple methods to decode to specific types:
+* Decode
+```go
+func (dec *Decoder) DecodeInt(v *int) error
+```
+* DecodeObject
+```go
+func (dec *Decoder) DecodeObject(v UnmarshalerObject) error
+```
+* DecodeArray
+```go
+func (dec *Decoder) DecodeArray(v UnmarshalerArray) error
+```
+* DecodeInt
+```go
+func (dec *Decoder) DecodeInt(v *int) error
+```
+* DecodeBool
+```go
+func (dec *Decoder) DecodeBool(v *bool) error
+```
+* DecodeString
+```go
+func (dec *Decoder) DecodeString(v *string) error
+```
+
### Structs
#### UnmarshalerObject Interface
@@ -300,6 +367,19 @@ func main() {
}
```
+# Unsafe API
+
+Unsafe API has the same functions than the regular API, it only has `Unmarshal API` for now. It is unsafe because it makes assumptions on the quality of the given JSON.
+
+If you are not sure if you're JSON is valid, don't use the Unsafe API.
+
+Also, the `Unsafe` API does not copy the buffer when using Unmarshal API, which, in case of string decoding, can lead to data corruption if a byte buffer is reused. Using the `Decode` API makes `Unsafe` API safer as the io.Reader relies on `copy` builtin method and `Decoder` will have its own internal buffer :)
+
+Access the `Unsafe` API this way:
+```go
+gojay.Unsafe.Unmarshal(b, v)
+```
+
# Benchmarks
diff --git a/benchmarks/decoder/Makefile b/benchmarks/decoder/Makefile
@@ -20,6 +20,10 @@ testtrace:
.PHONY: benchgojay
benchgojay:
+ go test -benchmem -run=^BenchmarkGoJay -bench=^BenchmarkGoJay -benchtime=30ms
+
+.PHONY: benchgojaycpu
+benchgojaycpu:
go test -benchmem -run=^BenchmarkGoJay -bench=^BenchmarkGoJay -benchtime=30ms -cpuprofile cpu.out
.PHONY: benchjsoniter
diff --git a/benchmarks/encoder/Makefile b/benchmarks/encoder/Makefile
@@ -16,6 +16,10 @@ testtrace:
.PHONY: benchgojay
benchgojay:
+ go test -benchmem -run=^BenchmarkGoJay -bench=^BenchmarkGoJay -benchtime=30ms
+
+.PHONY: benchgojaycpu
+benchgojaycpu:
go test -benchmem -run=^BenchmarkGoJay -bench=^BenchmarkGoJay -benchtime=30ms -cpuprofile cpu.out
.PHONY: benchjsoniter
diff --git a/decode.go b/decode.go
@@ -13,12 +13,12 @@ import (
// If a JSON value is not appropriate for a given target type, or if a JSON number
// overflows the target type, UnmarshalArray skips that field and completes the unmarshaling as best it can.
func UnmarshalArray(data []byte, v UnmarshalerArray) error {
- dec := newDecoder(nil, 0)
+ dec := BorrowDecoder(nil, 0)
+ defer dec.Release()
dec.data = make([]byte, len(data))
copy(dec.data, data)
dec.length = len(data)
- _, err := dec.DecodeArray(v)
- dec.addToPool()
+ _, err := dec.decodeArray(v)
if err != nil {
return err
}
@@ -35,12 +35,12 @@ func UnmarshalArray(data []byte, v UnmarshalerArray) error {
// If a JSON value is not appropriate for a given target type, or if a JSON number
// overflows the target type, UnmarshalObject skips that field and completes the unmarshaling as best it can.
func UnmarshalObject(data []byte, v UnmarshalerObject) error {
- dec := newDecoder(nil, 0)
+ dec := BorrowDecoder(nil, 0)
+ defer dec.Release()
dec.data = make([]byte, len(data))
copy(dec.data, data)
dec.length = len(data)
- _, err := dec.DecodeObject(v)
- dec.addToPool()
+ _, err := dec.decodeObject(v)
if err != nil {
return err
}
@@ -73,61 +73,61 @@ func Unmarshal(data []byte, v interface{}) error {
var dec *Decoder
switch vt := v.(type) {
case *string:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- err = dec.DecodeString(vt)
+ err = dec.decodeString(vt)
case *int:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- err = dec.DecodeInt(vt)
+ err = dec.decodeInt(vt)
case *int32:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- err = dec.DecodeInt32(vt)
+ err = dec.decodeInt32(vt)
case *uint32:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- err = dec.DecodeUint32(vt)
+ err = dec.decodeUint32(vt)
case *int64:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- err = dec.DecodeInt64(vt)
+ err = dec.decodeInt64(vt)
case *uint64:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- err = dec.DecodeUint64(vt)
+ err = dec.decodeUint64(vt)
case *float64:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- err = dec.DecodeFloat64(vt)
+ err = dec.decodeFloat64(vt)
case *bool:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- err = dec.DecodeBool(vt)
+ err = dec.decodeBool(vt)
case UnmarshalerObject:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = make([]byte, len(data))
copy(dec.data, data)
- _, err = dec.DecodeObject(vt)
+ _, err = dec.decodeObject(vt)
case UnmarshalerArray:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = make([]byte, len(data))
copy(dec.data, data)
- _, err = dec.DecodeArray(vt)
+ _, err = dec.decodeArray(vt)
default:
return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, reflect.TypeOf(vt).String()))
}
- defer dec.addToPool()
+ defer dec.Release()
if err != nil {
return err
}
@@ -147,12 +147,6 @@ type UnmarshalerArray interface {
UnmarshalArray(*Decoder) error
}
-// UnmarshalerStream is the interface to implement for a slice, an array or a slice
-// to decode a line delimited JSON to.
-type UnmarshalerStream interface {
- UnmarshalStream(*StreamDecoder) error
-}
-
// A Decoder reads and decodes JSON values from an input stream.
type Decoder struct {
data []byte
@@ -163,34 +157,38 @@ type Decoder struct {
child byte
err error
r io.Reader
+ isPooled byte
}
// Decode reads the next JSON-encoded value from its input and stores it in the value pointed to by v.
//
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
func (dec *Decoder) Decode(v interface{}) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
switch vt := v.(type) {
case *string:
- return dec.DecodeString(vt)
+ return dec.decodeString(vt)
case *int:
- return dec.DecodeInt(vt)
+ return dec.decodeInt(vt)
case *int32:
- return dec.DecodeInt32(vt)
+ return dec.decodeInt32(vt)
case *uint32:
- return dec.DecodeUint32(vt)
+ return dec.decodeUint32(vt)
case *int64:
- return dec.DecodeInt64(vt)
+ return dec.decodeInt64(vt)
case *uint64:
- return dec.DecodeUint64(vt)
+ return dec.decodeUint64(vt)
case *float64:
- return dec.DecodeFloat64(vt)
+ return dec.decodeFloat64(vt)
case *bool:
- return dec.DecodeBool(vt)
+ return dec.decodeBool(vt)
case UnmarshalerObject:
- _, err := dec.DecodeObject(vt)
+ _, err := dec.decodeObject(vt)
return err
case UnmarshalerArray:
- _, err := dec.DecodeArray(vt)
+ _, err := dec.decodeArray(vt)
return err
default:
return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, reflect.TypeOf(vt).String()))
@@ -202,7 +200,7 @@ func (dec *Decoder) Decode(v interface{}) error {
// AddInt decodes the next key to an *int.
// If next key value overflows int, an InvalidTypeError error will be returned.
func (dec *Decoder) AddInt(v *int) error {
- err := dec.DecodeInt(v)
+ err := dec.decodeInt(v)
if err != nil {
return err
}
@@ -213,7 +211,7 @@ func (dec *Decoder) AddInt(v *int) error {
// AddFloat decodes the next key to a *float64.
// If next key value overflows float64, an InvalidTypeError error will be returned.
func (dec *Decoder) AddFloat(v *float64) error {
- err := dec.DecodeFloat64(v)
+ err := dec.decodeFloat64(v)
if err != nil {
return err
}
@@ -225,7 +223,7 @@ func (dec *Decoder) AddFloat(v *float64) error {
// If next key is neither null nor a JSON boolean, an InvalidTypeError will be returned.
// If next key is null, bool will be false.
func (dec *Decoder) AddBool(v *bool) error {
- err := dec.DecodeBool(v)
+ err := dec.decodeBool(v)
if err != nil {
return err
}
@@ -236,7 +234,7 @@ func (dec *Decoder) AddBool(v *bool) error {
// AddString decodes the next key to a *string.
// If next key is not a JSON string nor null, InvalidTypeError will be returned.
func (dec *Decoder) AddString(v *string) error {
- err := dec.DecodeString(v)
+ err := dec.decodeString(v)
if err != nil {
return err
}
@@ -251,7 +249,7 @@ func (dec *Decoder) AddObject(value UnmarshalerObject) error {
dec.keysDone = 0
dec.called = 0
dec.child |= 1
- newCursor, err := dec.DecodeObject(value)
+ newCursor, err := dec.decodeObject(value)
if err != nil {
return err
}
@@ -264,7 +262,7 @@ func (dec *Decoder) AddObject(value UnmarshalerObject) error {
// AddArray decodes the next key to a UnmarshalerArray.
func (dec *Decoder) AddArray(value UnmarshalerArray) error {
- newCursor, err := dec.DecodeArray(value)
+ newCursor, err := dec.decodeArray(value)
if err != nil {
return err
}
@@ -286,9 +284,18 @@ func isDigit(b byte) bool {
func (dec *Decoder) read() bool {
if dec.r != nil {
- // idea is to append data from reader at the end
+ // if we reach the end, double the buffer to ensure there's always more space
+ if len(dec.data) == dec.length {
+ nLen := dec.length * 2
+ Buf := make([]byte, nLen, nLen)
+ copy(Buf, dec.data)
+ dec.data = Buf
+ }
n, err := dec.r.Read(dec.data[dec.length:])
- if err != nil || n == 0 {
+ if err != nil {
+ dec.err = err
+ return false
+ } else if n == 0 {
return false
}
dec.length = dec.length + n
diff --git a/decode_array.go b/decode_array.go
@@ -9,7 +9,14 @@ import (
// v must implement UnmarshalerArray.
//
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
-func (dec *Decoder) DecodeArray(arr UnmarshalerArray) (int, error) {
+func (dec *Decoder) DecodeArray(arr UnmarshalerArray) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
+ _, err := dec.decodeArray(arr)
+ return err
+}
+func (dec *Decoder) decodeArray(arr UnmarshalerArray) (int, error) {
// not an array not an error, but do not know what to do
// do not check syntax
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
diff --git a/decode_array_test.go b/decode_array_test.go
@@ -1,6 +1,7 @@
package gojay
import (
+ "strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -169,3 +170,22 @@ func TestDecoderSliceInvalidJSON(t *testing.T) {
assert.NotNil(t, err, "Err must not be nil as JSON is invalid")
assert.IsType(t, InvalidJSONError(""), err, "err message must be 'Invalid JSON'")
}
+
+func TestDecoderSliceDecoderAPI(t *testing.T) {
+ json := `["string","string1"]`
+ testArr := testSliceStrings{}
+ dec := NewDecoder(strings.NewReader(json))
+ err := dec.DecodeArray(&testArr)
+ assert.Nil(t, err, "Err must be nil")
+ assert.Len(t, testArr, 2, "testArr should be of len 2")
+ assert.Equal(t, "string", testArr[0], "testArr[0] should be 'string'")
+ assert.Equal(t, "string1", testArr[1], "testArr[1] should be 'string1'")
+}
+
+func TestDecoderSliceDecoderAPIError(t *testing.T) {
+ testArr := testSliceInts{}
+ dec := NewDecoder(strings.NewReader(`hello`))
+ err := dec.DecodeArray(&testArr)
+ assert.NotNil(t, err, "Err must not be nil as JSON is invalid")
+ assert.IsType(t, InvalidJSONError(""), err, "err message must be 'Invalid JSON'")
+}
diff --git a/decode_bool.go b/decode_bool.go
@@ -6,6 +6,12 @@ import "fmt"
//
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
func (dec *Decoder) DecodeBool(v *bool) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
+ return dec.decodeBool(v)
+}
+func (dec *Decoder) decodeBool(v *bool) error {
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
switch dec.data[dec.cursor] {
case ' ', '\n', '\t', '\r', ',':
diff --git a/decode_bool_test.go b/decode_bool_test.go
@@ -1,6 +1,7 @@
package gojay
import (
+ "strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -35,7 +36,7 @@ func TestDecoderBoolNonBooleanJSONFalse(t *testing.T) {
var v bool
err := Unmarshal(json, &v)
assert.Nil(t, err, "Err must be nil")
- assert.Equal(t, false, v, "v must be equal to true")
+ assert.Equal(t, false, v, "v must be equal to false")
}
func TestDecoderBoolInvalidJSON(t *testing.T) {
@@ -45,3 +46,24 @@ func TestDecoderBoolInvalidJSON(t *testing.T) {
assert.NotNil(t, err, "Err must not be nil as JSON is invalid")
assert.IsType(t, InvalidJSONError(""), err, "err message must be 'Invalid JSON'")
}
+func TestDecoderBoolDecoderAPI(t *testing.T) {
+ var v bool
+ dec := NewDecoder(strings.NewReader("true"))
+ defer dec.Release()
+ err := dec.DecodeBool(&v)
+ assert.Nil(t, err, "Err must be nil")
+ assert.Equal(t, true, v, "v must be equal to true")
+}
+
+func TestDecoderBoolPoolError(t *testing.T) {
+ v := true
+ dec := NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _ = dec.DecodeBool(&v)
+ assert.True(t, false, "should not be called as decoder should have panicked")
+}
diff --git a/decode_number.go b/decode_number.go
@@ -57,6 +57,12 @@ func init() {
//
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
func (dec *Decoder) DecodeInt(v *int) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
+ return dec.decodeInt(v)
+}
+func (dec *Decoder) decodeInt(v *int) error {
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
switch c := dec.data[dec.cursor]; c {
case ' ', '\n', '\t', '\r', ',':
@@ -101,6 +107,12 @@ func (dec *Decoder) DecodeInt(v *int) error {
//
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
func (dec *Decoder) DecodeInt32(v *int32) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
+ return dec.decodeInt32(v)
+}
+func (dec *Decoder) decodeInt32(v *int32) error {
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
switch c := dec.data[dec.cursor]; c {
case ' ', '\n', '\t', '\r', ',':
@@ -145,6 +157,13 @@ func (dec *Decoder) DecodeInt32(v *int32) error {
//
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
func (dec *Decoder) DecodeUint32(v *uint32) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
+ return dec.decodeUint32(v)
+}
+
+func (dec *Decoder) decodeUint32(v *uint32) error {
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
switch c := dec.data[dec.cursor]; c {
case ' ', '\n', '\t', '\r', ',':
@@ -190,6 +209,13 @@ func (dec *Decoder) DecodeUint32(v *uint32) error {
//
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
func (dec *Decoder) DecodeInt64(v *int64) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
+ return dec.decodeInt64(v)
+}
+
+func (dec *Decoder) decodeInt64(v *int64) error {
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
switch c := dec.data[dec.cursor]; c {
case ' ', '\n', '\t', '\r', ',':
@@ -234,6 +260,12 @@ func (dec *Decoder) DecodeInt64(v *int64) error {
//
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
func (dec *Decoder) DecodeUint64(v *uint64) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
+ return dec.decodeUint64(v)
+}
+func (dec *Decoder) decodeUint64(v *uint64) error {
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
switch c := dec.data[dec.cursor]; c {
case ' ', '\n', '\t', '\r', ',':
@@ -279,6 +311,12 @@ func (dec *Decoder) DecodeUint64(v *uint64) error {
//
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
func (dec *Decoder) DecodeFloat64(v *float64) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
+ return dec.decodeFloat64(v)
+}
+func (dec *Decoder) decodeFloat64(v *float64) error {
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
switch c := dec.data[dec.cursor]; c {
case ' ', '\n', '\t', '\r', ',':
diff --git a/decode_number_test.go b/decode_number_test.go
@@ -1,6 +1,7 @@
package gojay
import (
+ "strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -55,6 +56,18 @@ func TestDecoderIntOverfow(t *testing.T) {
assert.NotNil(t, err, "Err must not be nil as int is overflowing")
assert.Equal(t, 0, v, "v must be equal to 0")
}
+func TestDecoderIntPoolError(t *testing.T) {
+ result := int(1)
+ dec := NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _ = dec.DecodeInt(&result)
+ assert.True(t, false, "should not be called as decoder should have panicked")
+}
func TestDecoderIntOverfow2(t *testing.T) {
json := []byte(`92233720368547758089`)
var v int
@@ -62,6 +75,14 @@ func TestDecoderIntOverfow2(t *testing.T) {
assert.NotNil(t, err, "Err must not be nil as int is overflowing")
assert.Equal(t, 0, v, "v must be equal to 0")
}
+func TestDecoderInttDecoderAPI(t *testing.T) {
+ var v int
+ dec := NewDecoder(strings.NewReader(`33`))
+ defer dec.Release()
+ err := dec.DecodeInt(&v)
+ assert.Nil(t, err, "Err must be nil")
+ assert.Equal(t, int(33), v, "v must be equal to 33")
+}
func TestDecoderInt32Basic(t *testing.T) {
json := []byte(`124`)
@@ -119,6 +140,26 @@ func TestDecoderInt32Overflow2(t *testing.T) {
assert.NotNil(t, err, "err must not be nil as int32 overflows")
assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeError")
}
+func TestDecoderInt32PoolError(t *testing.T) {
+ result := int32(1)
+ dec := NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _ = dec.DecodeInt32(&result)
+ assert.True(t, false, "should not be called as decoder should have panicked")
+}
+func TestDecoderInt32tDecoderAPI(t *testing.T) {
+ var v int32
+ dec := NewDecoder(strings.NewReader(`33`))
+ defer dec.Release()
+ err := dec.DecodeInt32(&v)
+ assert.Nil(t, err, "Err must be nil")
+ assert.Equal(t, int32(33), v, "v must be equal to 33")
+}
func TestDecoderUint32Basic(t *testing.T) {
json := []byte(`124`)
@@ -170,6 +211,26 @@ func TestDecoderUint32Overflow2(t *testing.T) {
assert.NotNil(t, err, "err must not be nil as uint32 overflows")
assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeError")
}
+func TestDecoderUint32PoolError(t *testing.T) {
+ result := uint32(1)
+ dec := NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _ = dec.DecodeUint32(&result)
+ assert.True(t, false, "should not be called as decoder should have panicked")
+}
+func TestDecoderUint32tDecoderAPI(t *testing.T) {
+ var v uint32
+ dec := NewDecoder(strings.NewReader(`33`))
+ defer dec.Release()
+ err := dec.DecodeUint32(&v)
+ assert.Nil(t, err, "Err must be nil")
+ assert.Equal(t, uint32(33), v, "v must be equal to 33")
+}
func TestDecoderInt64Basic(t *testing.T) {
json := []byte(`124`)
@@ -227,6 +288,26 @@ func TestDecoderInt64Overflow2(t *testing.T) {
assert.NotNil(t, err, "err must not be nil as int64 overflows")
assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeError")
}
+func TestDecoderInt64PoolError(t *testing.T) {
+ result := int64(1)
+ dec := NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _ = dec.DecodeInt64(&result)
+ assert.True(t, false, "should not be called as decoder should have panicked")
+}
+func TestDecoderInt64tDecoderAPI(t *testing.T) {
+ var v int64
+ dec := NewDecoder(strings.NewReader(`33`))
+ defer dec.Release()
+ err := dec.DecodeInt64(&v)
+ assert.Nil(t, err, "Err must be nil")
+ assert.Equal(t, int64(33), v, "v must be equal to 33")
+}
func TestDecoderUint64Basic(t *testing.T) {
json := []byte(`124`)
var v uint64
@@ -276,7 +357,26 @@ func TestDecoderUint64Overflow2(t *testing.T) {
assert.NotNil(t, err, "err must not be nil as int32 overflows")
assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeError")
}
-
+func TestDecoderUint64PoolError(t *testing.T) {
+ result := uint64(1)
+ dec := NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _ = dec.DecodeUint64(&result)
+ assert.True(t, false, "should not be called as decoder should have panicked")
+}
+func TestDecoderUint64tDecoderAPI(t *testing.T) {
+ var v uint64
+ dec := NewDecoder(strings.NewReader(`33`))
+ defer dec.Release()
+ err := dec.DecodeUint64(&v)
+ assert.Nil(t, err, "Err must be nil")
+ assert.Equal(t, uint64(33), v, "v must be equal to 33")
+}
func TestDecoderFloatBasic(t *testing.T) {
json := []byte(`100.11`)
var v float64
@@ -308,3 +408,23 @@ func TestDecoderFloatInvalidJSON(t *testing.T) {
assert.NotNil(t, err, "Err must not be nil as JSON is invalid")
assert.IsType(t, InvalidJSONError(""), err, "err message must be 'Invalid JSON'")
}
+func TestDecoderFloatDecoderAPI(t *testing.T) {
+ var v float64
+ dec := NewDecoder(strings.NewReader(`1.25`))
+ defer dec.Release()
+ err := dec.DecodeFloat64(&v)
+ assert.Nil(t, err, "Err must be nil")
+ assert.Equal(t, 1.25, v, "v must be equal to 1.25")
+}
+func TestDecoderFloatPoolError(t *testing.T) {
+ result := float64(1)
+ dec := NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _ = dec.DecodeFloat64(&result)
+ assert.True(t, false, "should not be called as decoder should have panicked")
+}
diff --git a/decode_object.go b/decode_object.go
@@ -10,7 +10,17 @@ import (
// v must implement UnmarshalerObject.
//
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
-func (dec *Decoder) DecodeObject(j UnmarshalerObject) (int, error) {
+func (dec *Decoder) DecodeObject(j UnmarshalerObject) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
+ _, err := dec.decodeObject(j)
+ return err
+}
+func (dec *Decoder) decodeObject(j UnmarshalerObject) (int, error) {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
keys := j.NKeys()
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
switch dec.data[dec.cursor] {
diff --git a/decode_object_test.go b/decode_object_test.go
@@ -1,6 +1,7 @@
package gojay
import (
+ "strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -75,6 +76,27 @@ func (t *TestObj) NKeys() int {
return 8
}
+func assertResult(t *testing.T, v *TestObj, err error) {
+ assert.Nil(t, err, "Err must be nil")
+ assert.Equal(t, 245, v.test, "v.test must be equal to 245")
+ assert.Equal(t, 246, v.test2, "v.test2 must be equal to 246")
+ assert.Equal(t, "string", v.test3, "v.test3 must be equal to 'string'")
+ assert.Equal(t, "complex string with spaces and some slashes\"", v.test4, "v.test4 must be equal to 'string'")
+ assert.Equal(t, -1.15657654376543, v.test5, "v.test5 must be equal to 1.15")
+ assert.Len(t, v.testArr, 2, "v.testArr must be of len 2")
+
+ assert.Equal(t, 121, v.testSubObj.test3, "v.testSubObj.test3 must be equal to 121")
+ assert.Equal(t, 122, v.testSubObj.test4, "v.testSubObj.test4 must be equal to 122")
+ assert.Equal(t, "string", v.testSubObj.test5, "v.testSubObj.test5 must be equal to 'string'")
+ assert.Equal(t, 150, v.testSubObj.testSubSubObj.test3, "v.testSubObj.testSubSubObj.test3 must be equal to 150")
+ assert.Equal(t, 150, v.testSubObj.testSubSubObj2.test3, "v.testSubObj.testSubSubObj2.test3 must be equal to 150")
+
+ assert.Equal(t, 122, v.testSubObj2.test3, "v.testSubObj2.test3 must be equal to 121")
+ assert.Equal(t, 123, v.testSubObj2.test4, "v.testSubObj2.test4 must be equal to 122")
+ assert.Equal(t, "string", v.testSubObj2.test5, "v.testSubObj2.test5 must be equal to 'string'")
+ assert.Equal(t, 151, v.testSubObj2.testSubSubObj.test3, "v.testSubObj2.testSubSubObj.test must be equal to 150")
+}
+
func TestDecoderObject(t *testing.T) {
json := []byte(`{
"test": 245,
@@ -118,24 +140,7 @@ func TestDecoderObject(t *testing.T) {
}`)
v := &TestObj{}
err := Unmarshal(json, v)
- assert.Nil(t, err, "Err must be nil")
- assert.Equal(t, 245, v.test, "v.test must be equal to 245")
- assert.Equal(t, 246, v.test2, "v.test2 must be equal to 246")
- assert.Equal(t, "string", v.test3, "v.test3 must be equal to 'string'")
- assert.Equal(t, "complex string with spaces and some slashes\"", v.test4, "v.test4 must be equal to 'string'")
- assert.Equal(t, -1.15657654376543, v.test5, "v.test5 must be equal to 1.15")
- assert.Len(t, v.testArr, 2, "v.testArr must be of len 2")
-
- assert.Equal(t, 121, v.testSubObj.test3, "v.testSubObj.test3 must be equal to 121")
- assert.Equal(t, 122, v.testSubObj.test4, "v.testSubObj.test4 must be equal to 122")
- assert.Equal(t, "string", v.testSubObj.test5, "v.testSubObj.test5 must be equal to 'string'")
- assert.Equal(t, 150, v.testSubObj.testSubSubObj.test3, "v.testSubObj.testSubSubObj.test3 must be equal to 150")
- assert.Equal(t, 150, v.testSubObj.testSubSubObj2.test3, "v.testSubObj.testSubSubObj2.test3 must be equal to 150")
-
- assert.Equal(t, 122, v.testSubObj2.test3, "v.testSubObj2.test3 must be equal to 121")
- assert.Equal(t, 123, v.testSubObj2.test4, "v.testSubObj2.test4 must be equal to 122")
- assert.Equal(t, "string", v.testSubObj2.test5, "v.testSubObj2.test5 must be equal to 'string'")
- assert.Equal(t, 151, v.testSubObj2.testSubSubObj.test3, "v.testSubObj2.testSubSubObj.test must be equal to 150")
+ assertResult(t, v, err)
}
func TestDecodeObjectNull(t *testing.T) {
@@ -245,7 +250,7 @@ func TestDecodeObjectPartial(t *testing.T) {
"testSkipNumber": 123.23
}`)
dec.length = len(dec.data)
- _, err := dec.DecodeObject(&result)
+ err := dec.DecodeObject(&result)
assert.Nil(t, err, "err should be nil")
assert.NotEqual(t, len(dec.data), dec.cursor)
}
@@ -259,7 +264,75 @@ func TestDecoderObjectInvalidJSON(t *testing.T) {
"testSkipString": "testInvalidJSON\\\\
}`)
dec.length = len(dec.data)
- _, err := dec.DecodeObject(&result)
+ err := dec.DecodeObject(&result)
+ assert.NotNil(t, err, "Err must not be nil as JSON is invalid")
+ assert.IsType(t, InvalidJSONError(""), err, "err message must be 'Invalid JSON'")
+}
+
+func TestDecoderObjectPoolError(t *testing.T) {
+ result := jsonDecodePartial{}
+ dec := NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _ = dec.DecodeObject(&result)
+ assert.True(t, false, "should not be called as decoder should have panicked")
+}
+
+func TestDecoderObjectDecoderAPI(t *testing.T) {
+ json := `{
+ "test": 245,
+ "test2": 246,
+ "test3": "string",
+ "test4": "complex string with spaces and some slashes\"",
+ "test5": -1.15657654376543,
+ "testNull": null,
+ "testArr": [
+ {
+ "test": 245,
+ "test2": 246
+ },
+ {
+ "test": 245,
+ "test2": 246
+ }
+ ],
+ "testSubObj": {
+ "test": 121,
+ "test2": 122,
+ "testNull": null,
+ "testSubSubObj": {
+ "test": 150,
+ "testNull": null
+ },
+ "testSubSubObj2": {
+ "test": 150
+ },
+ "test3": "string"
+ "testNull": null,
+ },
+ "testSubObj2": {
+ "test": 122,
+ "test3": "string"
+ "testSubSubObj": {
+ "test": 151
+ },
+ "test2": 123
+ }
+ }`
+ v := &TestObj{}
+ dec := NewDecoder(strings.NewReader(json))
+ err := dec.DecodeObject(v)
+ assertResult(t, v, err)
+}
+
+func TestDecoderObjectDecoderAPIError(t *testing.T) {
+ testArr := testSliceInts{}
+ dec := NewDecoder(strings.NewReader(`hello`))
+ err := dec.DecodeArray(&testArr)
assert.NotNil(t, err, "Err must not be nil as JSON is invalid")
assert.IsType(t, InvalidJSONError(""), err, "err message must be 'Invalid JSON'")
}
diff --git a/decode_pool.go b/decode_pool.go
@@ -4,13 +4,25 @@ import "io"
var decPool = make(chan *Decoder, 16)
-// NewDecoder returns a new decoder or borrows one from the pool
-// it takes an io.Reader implementation as data input
+// NewDecoder returns a new decoder.
+// It takes an io.Reader implementation as data input.
func NewDecoder(r io.Reader) *Decoder {
- return newDecoder(r, 512)
+ return &Decoder{
+ called: 0,
+ cursor: 0,
+ keysDone: 0,
+ err: nil,
+ r: r,
+ data: make([]byte, 512),
+ length: 0,
+ isPooled: 0,
+ }
}
-func newDecoder(r io.Reader, bufSize int) *Decoder {
+// BorrowDecoder borrows a Decoder from the pool.
+// It takes an io.Reader implementation as data input.
+// It initiates the done channel returned by Done().
+func BorrowDecoder(r io.Reader, bufSize int) *Decoder {
select {
case dec := <-decPool:
dec.called = 0
@@ -19,6 +31,7 @@ func newDecoder(r io.Reader, bufSize int) *Decoder {
dec.err = nil
dec.r = r
dec.length = 0
+ dec.isPooled = 0
if bufSize > 0 {
dec.data = make([]byte, bufSize)
}
@@ -30,16 +43,20 @@ func newDecoder(r io.Reader, bufSize int) *Decoder {
keysDone: 0,
err: nil,
r: r,
+ isPooled: 0,
}
if bufSize > 0 {
dec.data = make([]byte, bufSize)
- dec.length = 0
}
return dec
}
}
-func (dec *Decoder) addToPool() {
+// Release sends back a Decoder to the pool.
+// If a decoder is used after calling Release
+// a panic will be raised with an InvalidUsagePooledDecoderError error.
+func (dec *Decoder) Release() {
+ dec.isPooled = 1
select {
case decPool <- dec:
default:
diff --git a/decode_stream.go b/decode_stream.go
@@ -1,10 +1,15 @@
package gojay
import (
- "io"
"time"
)
+// UnmarshalerStream is the interface to implement for a slice, an array or a slice
+// to decode a line delimited JSON to.
+type UnmarshalerStream interface {
+ UnmarshalStream(*StreamDecoder) error
+}
+
// Stream is a struct holding the Stream api
var Stream = stream{}
@@ -19,24 +24,15 @@ type StreamDecoder struct {
deadline *time.Time
}
-// NewDecoder returns a new decoder or borrows one from the pool.
-// It takes an io.Reader implementation as data input.
-// It initiates the done channel returned by Done().
-func (s stream) NewDecoder(r io.Reader) *StreamDecoder {
- dec := newDecoder(r, 512)
- streamDec := &StreamDecoder{
- Decoder: dec,
- done: make(chan struct{}, 1),
- }
- return streamDec
-}
-
// DecodeStream reads the next line delimited JSON-encoded value from its input and stores it in the value pointed to by c.
//
// c must implement UnmarshalerStream. Ideally c is a channel. See example for implementation.
//
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
func (dec *StreamDecoder) DecodeStream(c UnmarshalerStream) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
if dec.r == nil {
dec.err = NoReaderError("No reader given to decode stream")
close(dec.done)
diff --git a/decode_stream_pool.go b/decode_stream_pool.go
@@ -0,0 +1,49 @@
+package gojay
+
+import "io"
+
+var streamDecPool = make(chan *StreamDecoder, 16)
+
+// NewDecoder returns a new decoder.
+// It takes an io.Reader implementation as data input.
+// It initiates the done channel returned by Done().
+func (s stream) NewDecoder(r io.Reader) *StreamDecoder {
+ dec := NewDecoder(r)
+ streamDec := &StreamDecoder{
+ Decoder: dec,
+ done: make(chan struct{}, 1),
+ }
+ return streamDec
+}
+
+// BorrowDecoder borrows a StreamDecoder a decoder from the pool.
+// It takes an io.Reader implementation as data input.
+// It initiates the done channel returned by Done().
+func (s stream) BorrowDecoder(r io.Reader, bufSize int) *StreamDecoder {
+ select {
+ case streamDec := <-streamDecPool:
+ streamDec.called = 0
+ streamDec.keysDone = 0
+ streamDec.cursor = 0
+ streamDec.err = nil
+ streamDec.r = r
+ streamDec.length = 0
+ streamDec.isPooled = 0
+ streamDec.done = make(chan struct{}, 1)
+ if bufSize > 0 {
+ streamDec.data = make([]byte, bufSize)
+ }
+ return streamDec
+ default:
+ dec := NewDecoder(r)
+ if bufSize > 0 {
+ dec.data = make([]byte, bufSize)
+ dec.length = 0
+ }
+ streamDec := &StreamDecoder{
+ Decoder: dec,
+ done: make(chan struct{}, 1),
+ }
+ return streamDec
+ }
+}
diff --git a/decode_stream_pool_test.go b/decode_stream_pool_test.go
@@ -0,0 +1,100 @@
+package gojay
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestDecodeStreamBorrow(t *testing.T) {
+ // we override the pool chan
+ streamDecPool = make(chan *StreamDecoder, 1)
+ // add one decoder to the channel
+ dec := Stream.NewDecoder(nil)
+ streamDecPool <- dec
+ // borrow one decoder to the channel
+ nDec := Stream.BorrowDecoder(nil, 0)
+ // make sure they are the same
+ assert.Equal(t, dec, nDec, "decoder added to the pool and new decoder should be the same")
+}
+
+func TestDecodeStreamBorrow1(t *testing.T) {
+ // we override the pool chan
+ streamDecPool = make(chan *StreamDecoder, 1)
+ // add one decoder to the channel
+ dec := Stream.NewDecoder(nil)
+ streamDecPool <- dec
+ // reset streamDecPool
+ streamDecPool = make(chan *StreamDecoder, 1)
+ // borrow one decoder to the channel
+ nDec := Stream.BorrowDecoder(nil, 0)
+ // make sure they are the same
+ assert.NotEqual(t, dec, nDec, "decoder added to the pool and new decoder should be the same")
+}
+func TestDecodeStreamBorrow2(t *testing.T) {
+ // we override the pool chan
+ streamDecPool = make(chan *StreamDecoder, 1)
+ // add one decoder to the channel
+ dec := Stream.NewDecoder(nil)
+ dec.data = make([]byte, 128)
+ streamDecPool <- dec
+ // borrow one decoder to the channel
+ nDec := Stream.BorrowDecoder(nil, 512)
+ // make sure they are the same
+ assert.Equal(t, dec, nDec, "decoder added to the pool and new decoder should be the same")
+ assert.Equal(t, 512, len(nDec.data), "len of dec.data should be 512")
+}
+func TestDecodeStreamBorrow3(t *testing.T) {
+ // we override the pool chan
+ streamDecPool = make(chan *StreamDecoder, 16)
+ // borrow one decoder to the channel
+ nDec := Stream.BorrowDecoder(nil, 512)
+ // make sure they are the same
+ assert.Equal(t, 512, len(nDec.data), "len of dec.data should be 512")
+}
+
+func TestDecodeStreamDecodePooledDecoderError(t *testing.T) {
+ // we override the pool chan
+ dec := Stream.NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ var v = 0
+ dec.Decode(&v)
+ // make sure it fails if this is called
+ assert.True(t, false, "should not be called as decoder should have panicked")
+}
+
+func TestDecodeStreamDecodePooledDecoderError1(t *testing.T) {
+ // we override the pool chan
+ dec := Stream.NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ var v = testSliceStrings{}
+ dec.DecodeArray(&v)
+ // make sure they are the same
+ assert.True(t, false, "should not be called as decoder should have panicked")
+}
+
+func TestDecodeStreamDecodePooledDecoderError2(t *testing.T) {
+ // we override the pool chan
+ dec := Stream.NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ assert.Equal(t, "Invalid usage of pooled decoder", err.(InvalidUsagePooledDecoderError).Error(), "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ var v = TestObj{}
+ dec.DecodeObject(&v)
+ // make sure they are the same
+ assert.True(t, false, "should not be called as decoder should have panicked")
+}
diff --git a/decode_stream_test.go b/decode_stream_test.go
@@ -373,3 +373,17 @@ func TestStreamDecodingErrNotSet(t *testing.T) {
dec := Stream.NewDecoder(&StreamReader{})
assert.Nil(t, dec.Err(), "dec.Err should be nim")
}
+
+func TestStreamDecodingPoolError(t *testing.T) {
+ dec := Stream.BorrowDecoder(nil, 0)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledEncoderError")
+ assert.Equal(t, "Invalid usage of pooled decoder", err.(InvalidUsagePooledDecoderError).Error(), "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ testChan := ChannelStreamStrings(make(chan *string))
+ _ = dec.DecodeStream(testChan)
+ assert.True(t, false, "should not be called as it should have panicked")
+}
diff --git a/decode_string.go b/decode_string.go
@@ -9,6 +9,12 @@ import (
//
// See the documentation for Unmarshal for details about the conversion of JSON into a Go value.
func (dec *Decoder) DecodeString(v *string) error {
+ if dec.isPooled == 1 {
+ panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder"))
+ }
+ return dec.decodeString(v)
+}
+func (dec *Decoder) decodeString(v *string) error {
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
switch dec.data[dec.cursor] {
case ' ', '\n', '\t', '\r', ',':
diff --git a/decode_string_test.go b/decode_string_test.go
@@ -1,6 +1,7 @@
package gojay
import (
+ "strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -45,3 +46,25 @@ func TestDecoderStringInvalidType(t *testing.T) {
assert.NotNil(t, err, "Err must not be nil as JSON is invalid")
assert.IsType(t, InvalidTypeError(""), err, "err message must be 'Invalid JSON'")
}
+
+func TestDecoderStringDecoderAPI(t *testing.T) {
+ var v string
+ dec := NewDecoder(strings.NewReader(`"hello world!"`))
+ defer dec.Release()
+ err := dec.DecodeString(&v)
+ assert.Nil(t, err, "Err must be nil")
+ assert.Equal(t, "hello world!", v, "v must be equal to 'hello world!'")
+}
+
+func TestDecoderStringPoolError(t *testing.T) {
+ result := ""
+ dec := NewDecoder(nil)
+ dec.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _ = dec.DecodeString(&result)
+ assert.True(t, false, "should not be called as decoder should have panicked")
+}
diff --git a/decode_unsafe.go b/decode_unsafe.go
@@ -14,11 +14,11 @@ var Unsafe = decUnsafe{}
type decUnsafe struct{}
func (u decUnsafe) UnmarshalArray(data []byte, v UnmarshalerArray) error {
- dec := newDecoder(nil, 0)
+ dec := BorrowDecoder(nil, 0)
+ defer dec.Release()
dec.data = data
dec.length = len(data)
- _, err := dec.DecodeArray(v)
- dec.addToPool()
+ _, err := dec.decodeArray(v)
if err != nil {
return err
}
@@ -29,11 +29,11 @@ func (u decUnsafe) UnmarshalArray(data []byte, v UnmarshalerArray) error {
}
func (u decUnsafe) UnmarshalObject(data []byte, v UnmarshalerObject) error {
- dec := newDecoder(nil, 0)
+ dec := BorrowDecoder(nil, 0)
+ defer dec.Release()
dec.data = data
dec.length = len(data)
- _, err := dec.DecodeObject(v)
- dec.addToPool()
+ _, err := dec.decodeObject(v)
if err != nil {
return err
}
@@ -48,59 +48,59 @@ func (u decUnsafe) Unmarshal(data []byte, v interface{}) error {
var dec *Decoder
switch vt := v.(type) {
case *string:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- err = dec.DecodeString(vt)
+ err = dec.decodeString(vt)
case *int:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- err = dec.DecodeInt(vt)
+ err = dec.decodeInt(vt)
case *int32:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- err = dec.DecodeInt32(vt)
+ err = dec.decodeInt32(vt)
case *uint32:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- err = dec.DecodeUint32(vt)
+ err = dec.decodeUint32(vt)
case *int64:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- err = dec.DecodeInt64(vt)
+ err = dec.decodeInt64(vt)
case *uint64:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- err = dec.DecodeUint64(vt)
+ err = dec.decodeUint64(vt)
case *float64:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- err = dec.DecodeFloat64(vt)
+ err = dec.decodeFloat64(vt)
case *bool:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- err = dec.DecodeBool(vt)
+ err = dec.decodeBool(vt)
case UnmarshalerObject:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- _, err = dec.DecodeObject(vt)
+ _, err = dec.decodeObject(vt)
case UnmarshalerArray:
- dec = newDecoder(nil, 0)
+ dec = BorrowDecoder(nil, 0)
dec.length = len(data)
dec.data = data
- _, err = dec.DecodeArray(vt)
+ _, err = dec.decodeArray(vt)
default:
return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, reflect.TypeOf(vt).String()))
}
- defer dec.addToPool()
+ defer dec.Release()
if err != nil {
return err
}
diff --git a/encode.go b/encode.go
@@ -24,12 +24,8 @@ package gojay
// }
func MarshalObject(v MarshalerObject) ([]byte, error) {
enc := NewEncoder()
- enc.grow(200)
- enc.writeByte('{')
- v.MarshalObject(enc)
- enc.writeByte('}')
- defer enc.addToPool()
- return enc.buf, nil
+ defer enc.Release()
+ return enc.encodeObject(v)
}
// MarshalArray returns the JSON encoding of v.
@@ -59,7 +55,7 @@ func MarshalArray(v MarshalerArray) ([]byte, error) {
enc.writeByte('[')
v.(MarshalerArray).MarshalArray(enc)
enc.writeByte(']')
- defer enc.addToPool()
+ defer enc.Release()
return enc.buf, nil
}
@@ -98,73 +94,73 @@ func Marshal(v interface{}) ([]byte, error) {
var err error = InvalidTypeError("Unknown type to Marshal")
switch vt := v.(type) {
case MarshalerObject:
- enc := NewEncoder()
+ enc := BorrowEncoder()
enc.writeByte('{')
vt.MarshalObject(enc)
enc.writeByte('}')
b = enc.buf
- defer enc.addToPool()
+ defer enc.Release()
return b, nil
case MarshalerArray:
- enc := NewEncoder()
+ enc := BorrowEncoder()
enc.writeByte('[')
vt.MarshalArray(enc)
enc.writeByte(']')
b = enc.buf
- defer enc.addToPool()
+ defer enc.Release()
return b, nil
case string:
- enc := NewEncoder()
+ enc := BorrowEncoder()
b, err = enc.encodeString(vt)
- defer enc.addToPool()
+ defer enc.Release()
case bool:
- enc := NewEncoder()
+ enc := BorrowEncoder()
err = enc.AddBool(vt)
b = enc.buf
- defer enc.addToPool()
+ defer enc.Release()
case int:
- enc := NewEncoder()
+ enc := BorrowEncoder()
b, err = enc.encodeInt(int64(vt))
- defer enc.addToPool()
+ defer enc.Release()
case int64:
- enc := NewEncoder()
- defer enc.addToPool()
+ enc := BorrowEncoder()
+ defer enc.Release()
return enc.encodeInt(vt)
case int32:
- enc := NewEncoder()
- defer enc.addToPool()
+ enc := BorrowEncoder()
+ defer enc.Release()
return enc.encodeInt(int64(vt))
case int16:
- enc := NewEncoder()
- defer enc.addToPool()
+ enc := BorrowEncoder()
+ defer enc.Release()
return enc.encodeInt(int64(vt))
case int8:
- enc := NewEncoder()
- defer enc.addToPool()
+ enc := BorrowEncoder()
+ defer enc.Release()
return enc.encodeInt(int64(vt))
case uint64:
- enc := NewEncoder()
- defer enc.addToPool()
+ enc := BorrowEncoder()
+ defer enc.Release()
return enc.encodeInt(int64(vt))
case uint32:
- enc := NewEncoder()
- defer enc.addToPool()
+ enc := BorrowEncoder()
+ defer enc.Release()
return enc.encodeInt(int64(vt))
case uint16:
- enc := NewEncoder()
- defer enc.addToPool()
+ enc := BorrowEncoder()
+ defer enc.Release()
return enc.encodeInt(int64(vt))
case uint8:
- enc := NewEncoder()
+ enc := BorrowEncoder()
b, err = enc.encodeInt(int64(vt))
- defer enc.addToPool()
+ defer enc.Release()
case float64:
- enc := NewEncoder()
- defer enc.addToPool()
+ enc := BorrowEncoder()
+ defer enc.Release()
return enc.encodeFloat(vt)
case float32:
- enc := NewEncoder()
- defer enc.addToPool()
+ enc := BorrowEncoder()
+ defer enc.Release()
return enc.encodeFloat(float64(vt))
}
return b, err
@@ -184,7 +180,8 @@ type MarshalerArray interface {
// An Encoder writes JSON values to an output stream.
type Encoder struct {
- buf []byte
+ buf []byte
+ isPooled byte
}
func (enc *Encoder) getPreviousRune() (byte, bool) {
diff --git a/encode_array.go b/encode_array.go
@@ -1,5 +1,20 @@
package gojay
+// EncodeArray encodes an implementation of MarshalerArray to JSON
+func (enc *Encoder) EncodeArray(v MarshalerArray) ([]byte, error) {
+ if enc.isPooled == 1 {
+ panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder"))
+ }
+ return enc.encodeArray(v)
+}
+func (enc *Encoder) encodeArray(v MarshalerArray) ([]byte, error) {
+ enc.grow(200)
+ enc.writeByte('[')
+ v.MarshalArray(enc)
+ enc.writeByte(']')
+ return enc.buf, nil
+}
+
// AddArray adds an array or slice to be encoded, must be used inside a slice or array encoding (does not encode a key)
// value must implement Marshaler
func (enc *Encoder) AddArray(value MarshalerArray) error {
diff --git a/encode_array_test.go b/encode_array_test.go
@@ -6,6 +6,14 @@ import (
"github.com/stretchr/testify/assert"
)
+type TestEncodingArrStrings []string
+
+func (t TestEncodingArrStrings) MarshalArray(enc *Encoder) {
+ for _, e := range t {
+ enc.AddString(e)
+ }
+}
+
type TestEncodingArr []*TestEncoding
func (t TestEncodingArr) MarshalArray(enc *Encoder) {
@@ -104,3 +112,51 @@ func TestEncoderArrayInterfaces(t *testing.T) {
string(r),
"Result of marshalling is different as the one expected")
}
+
+func TestEncoderArrayInterfacesEncoderAPI(t *testing.T) {
+ v := &testEncodingArrInterfaces{
+ 1,
+ int64(1),
+ int32(1),
+ int16(1),
+ int8(1),
+ uint64(1),
+ uint32(1),
+ uint16(1),
+ uint8(1),
+ float64(1.31),
+ // float32(1.31),
+ &TestEncodingArr{},
+ true,
+ "test",
+ &TestEncoding{
+ test: "hello world",
+ test2: "foobar",
+ testInt: 1,
+ testBool: true,
+ },
+ }
+ enc := BorrowEncoder()
+ defer enc.Release()
+ r, err := enc.EncodeArray(v)
+ assert.Nil(t, err, "Error should be nil")
+ assert.Equal(
+ t,
+ `[1,1,1,1,1,1,1,1,1.31,[],true,"test",{"test":"hello world","test2":"foobar","testInt":1,"testBool":true,"testArr":[],"testF64":0,"testF32":0}]`,
+ string(r),
+ "Result of marshalling is different as the one expected")
+}
+
+func TestEncoderArrayPooledError(t *testing.T) {
+ v := &testEncodingArrInterfaces{}
+ enc := BorrowEncoder()
+ enc.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError")
+ assert.Equal(t, "Invalid usage of pooled encoder", err.(InvalidUsagePooledEncoderError).Error(), "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _, _ = enc.EncodeArray(v)
+ assert.True(t, false, "should not be called as it should have panicked")
+}
diff --git a/encode_bool.go b/encode_bool.go
@@ -2,6 +2,24 @@ package gojay
import "strconv"
+// EncodeBool encodes a bool to JSON
+func (enc *Encoder) EncodeBool(v bool) ([]byte, error) {
+ if enc.isPooled == 1 {
+ panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder"))
+ }
+ return enc.encodeBool(v)
+}
+
+// encodeBool encodes a bool to JSON
+func (enc *Encoder) encodeBool(v bool) ([]byte, error) {
+ if v {
+ enc.writeString("true")
+ } else {
+ enc.writeString("false")
+ }
+ return enc.buf, nil
+}
+
// AddBool adds a bool to be encoded, must be used inside a slice or array encoding (does not encode a key)
func (enc *Encoder) AddBool(value bool) error {
r, ok := enc.getPreviousRune()
@@ -16,7 +34,7 @@ func (enc *Encoder) AddBool(value bool) error {
return nil
}
-// AddBoolKey adds a bool to be encoded, must be used inside an object as it will encode a key
+// AddBoolKey adds a bool to be encoded, must be used inside an object as it will encode a key.
func (enc *Encoder) AddBoolKey(key string, value bool) error {
r, ok := enc.getPreviousRune()
if ok && r != '{' && r != '[' {
diff --git a/encode_bool_test.go b/encode_bool_test.go
@@ -0,0 +1,36 @@
+package gojay
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestEncoderBoolTrue(t *testing.T) {
+ enc := BorrowEncoder()
+ defer enc.Release()
+ b, err := enc.EncodeBool(true)
+ assert.Nil(t, err, "err must be nil")
+ assert.Equal(t, "true", string(b), "string(b) must be equal to 'true'")
+}
+
+func TestEncoderBoolFalse(t *testing.T) {
+ enc := BorrowEncoder()
+ defer enc.Release()
+ b, err := enc.EncodeBool(false)
+ assert.Nil(t, err, "err must be nil")
+ assert.Equal(t, "false", string(b), "string(b) must be equal to 'false'")
+}
+
+func TestEncoderBoolPoolError(t *testing.T) {
+ enc := BorrowEncoder()
+ enc.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError")
+ assert.Equal(t, "Invalid usage of pooled encoder", err.(InvalidUsagePooledEncoderError).Error(), "err should be of type InvalidUsagePooledEncoderError")
+ }()
+ _, _ = enc.EncodeBool(false)
+ assert.True(t, false, "should not be called as it should have panicked")
+}
diff --git a/encode_interface.go b/encode_interface.go
@@ -1,5 +1,52 @@
package gojay
+import (
+ "fmt"
+ "reflect"
+)
+
+// Encode encodes a value to JSON.
+//
+// If Encode cannot find a way to encode the type to JSON
+// it will return an InvalidMarshalError.
+func (enc *Encoder) Encode(v interface{}) ([]byte, error) {
+ if enc.isPooled == 1 {
+ panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder"))
+ }
+ switch vt := v.(type) {
+ case string:
+ return enc.encodeString(vt)
+ case bool:
+ return enc.encodeBool(vt)
+ case MarshalerArray:
+ return enc.encodeArray(vt)
+ case MarshalerObject:
+ return enc.encodeObject(vt)
+ case int:
+ return enc.encodeInt(int64(vt))
+ case int64:
+ return enc.encodeInt(vt)
+ case int32:
+ return enc.encodeInt(int64(vt))
+ case int8:
+ return enc.encodeInt(int64(vt))
+ case uint64:
+ return enc.encodeInt(int64(vt))
+ case uint32:
+ return enc.encodeInt(int64(vt))
+ case uint16:
+ return enc.encodeInt(int64(vt))
+ case uint8:
+ return enc.encodeInt(int64(vt))
+ case float64:
+ return enc.encodeFloat(vt)
+ case float32:
+ return enc.encodeFloat(float64(vt))
+ default:
+ return nil, InvalidMarshalError(fmt.Sprintf(invalidMarshalErrorMsg, reflect.TypeOf(vt).String()))
+ }
+}
+
// AddInterface adds an interface{} to be encoded, must be used inside a slice or array encoding (does not encode a key)
func (enc *Encoder) AddInterface(value interface{}) error {
switch value.(type) {
diff --git a/encode_interface_test.go b/encode_interface_test.go
@@ -0,0 +1,113 @@
+package gojay
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+var encoderTestCases = []struct {
+ v interface{}
+ expectations func(t *testing.T, b []byte, err error)
+}{
+ {
+ v: int64(100),
+ expectations: func(t *testing.T, b []byte, err error) {
+ assert.Nil(t, err, "err should be nil")
+ assert.Equal(t, "100", string(b), "string(b) should equal 100")
+ },
+ },
+ {
+ v: int32(100),
+ expectations: func(t *testing.T, b []byte, err error) {
+ assert.Nil(t, err, "err should be nil")
+ assert.Equal(t, "100", string(b), "string(b) should equal 100")
+ },
+ },
+ {
+ v: int8(100),
+ expectations: func(t *testing.T, b []byte, err error) {
+ assert.Nil(t, err, "err should be nil")
+ assert.Equal(t, "100", string(b), "string(b) should equal 100")
+ },
+ },
+ {
+ v: uint64(100),
+ expectations: func(t *testing.T, b []byte, err error) {
+ assert.Nil(t, err, "err should be nil")
+ assert.Equal(t, "100", string(b), "string(b) should equal 100")
+ },
+ },
+ {
+ v: uint32(100),
+ expectations: func(t *testing.T, b []byte, err error) {
+ assert.Nil(t, err, "err should be nil")
+ assert.Equal(t, "100", string(b), "string(b) should equal 100")
+ },
+ },
+ {
+ v: uint8(100),
+ expectations: func(t *testing.T, b []byte, err error) {
+ assert.Nil(t, err, "err should be nil")
+ assert.Equal(t, "100", string(b), "string(b) should equal 100")
+ },
+ },
+ {
+ v: float64(100.12),
+ expectations: func(t *testing.T, b []byte, err error) {
+ assert.Nil(t, err, "err should be nil")
+ assert.Equal(t, "100.12", string(b), "string(b) should equal 100.12")
+ },
+ },
+ {
+ v: true,
+ expectations: func(t *testing.T, b []byte, err error) {
+ assert.Nil(t, err, "err should be nil")
+ assert.Equal(t, "true", string(b), "string(b) should equal true")
+ },
+ },
+ {
+ v: "hello world",
+ expectations: func(t *testing.T, b []byte, err error) {
+ assert.Nil(t, err, "err should be nil")
+ assert.Equal(t, `"hello world"`, string(b), `string(b) should equal "hello world"`)
+ },
+ },
+ {
+ v: "hello world",
+ expectations: func(t *testing.T, b []byte, err error) {
+ assert.Nil(t, err, "err should be nil")
+ assert.Equal(t, `"hello world"`, string(b), `string(b) should equal "hello world"`)
+ },
+ },
+ {
+ v: &TestEncodingArrStrings{"hello world", "foo bar"},
+ expectations: func(t *testing.T, b []byte, err error) {
+ assert.Nil(t, err, "err should be nil")
+ assert.Equal(t, `["hello world","foo bar"]`, string(b), `string(b) should equal ["hello world","foo bar"]`)
+ },
+ },
+ {
+ v: &testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true},
+ expectations: func(t *testing.T, b []byte, err error) {
+ assert.Nil(t, err, "err should be nil")
+ assert.Equal(t, `{"testStr":"漢字","testInt":1,"testInt64":1,"testInt32":1,"testInt16":1,"testInt8":1,"testUint64":1,"testUint32":1,"testUint16":1,"testUint8":1,"testFloat64":1.1,"testFloat32":1.1,"testBool":true}`, string(b), `string(b) should equal {"testStr":"漢字","testInt":1,"testInt64":1,"testInt32":1,"testInt16":1,"testInt8":1,"testUint64":1,"testUint32":1,"testUint16":1,"testUint8":1,"testFloat64":1.1,"testFloat32":1.1,"testBool":true}`)
+ },
+ },
+}
+
+func TestEncoderInterfaceAllTypesDecoderAPI(t *testing.T) {
+ for _, test := range encoderTestCases {
+ enc := BorrowEncoder()
+ b, err := enc.Encode(test.v)
+ enc.Release()
+ test.expectations(t, b, err)
+ }
+}
+
+func TestEncoderInterfaceAllTypesMarshalAPI(t *testing.T) {
+ for _, test := range encoderTestCases {
+ b, err := Marshal(test.v)
+ test.expectations(t, b, err)
+ }
+}
diff --git a/encode_number.go b/encode_number.go
@@ -2,6 +2,14 @@ package gojay
import "strconv"
+// EncodeInt encodes an int to JSON
+func (enc *Encoder) EncodeInt(n int64) ([]byte, error) {
+ if enc.isPooled == 1 {
+ panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder"))
+ }
+ return enc.encodeInt(n)
+}
+
// encodeInt encodes an int to JSON
func (enc *Encoder) encodeInt(n int64) ([]byte, error) {
s := strconv.Itoa(int(n))
@@ -9,6 +17,14 @@ func (enc *Encoder) encodeInt(n int64) ([]byte, error) {
return enc.buf, nil
}
+// EncodeFloat encodes a float64 to JSON
+func (enc *Encoder) EncodeFloat(n float64) ([]byte, error) {
+ if enc.isPooled == 1 {
+ panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder"))
+ }
+ return enc.encodeFloat(n)
+}
+
// encodeFloat encodes a float64 to JSON
func (enc *Encoder) encodeFloat(n float64) ([]byte, error) {
s := strconv.FormatFloat(n, 'f', -1, 64)
diff --git a/encode_number_test.go b/encode_number_test.go
@@ -101,3 +101,31 @@ func TestEncoderFloat(t *testing.T) {
string(r),
"Result of marshalling is different as the one expected")
}
+
+func TestEncoderIntPooledError(t *testing.T) {
+ v := 1
+ enc := BorrowEncoder()
+ enc.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError")
+ assert.Equal(t, "Invalid usage of pooled encoder", err.(InvalidUsagePooledEncoderError).Error(), "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _, _ = enc.EncodeInt(int64(v))
+ assert.True(t, false, "should not be called as it should have panicked")
+}
+
+func TestEncoderFloatPooledError(t *testing.T) {
+ v := 1.1
+ enc := BorrowEncoder()
+ enc.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError")
+ assert.Equal(t, "Invalid usage of pooled encoder", err.(InvalidUsagePooledEncoderError).Error(), "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _, _ = enc.EncodeFloat(v)
+ assert.True(t, false, "should not be called as it should have panicked")
+}
diff --git a/encode_object.go b/encode_object.go
@@ -5,6 +5,21 @@ var objKeyObj = []byte(`":{`)
var objKeyArr = []byte(`":[`)
var objKey = []byte(`":`)
+// EncodeObject encodes an object to JSON
+func (enc *Encoder) EncodeObject(v MarshalerObject) ([]byte, error) {
+ if enc.isPooled == 1 {
+ panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder"))
+ }
+ return enc.encodeObject(v)
+}
+func (enc *Encoder) encodeObject(v MarshalerObject) ([]byte, error) {
+ enc.grow(200)
+ enc.writeByte('{')
+ v.MarshalObject(enc)
+ enc.writeByte('}')
+ return enc.buf, nil
+}
+
// AddObject adds an object to be encoded, must be used inside a slice or array encoding (does not encode a key)
// value must implement Marshaler
func (enc *Encoder) AddObject(value MarshalerObject) error {
diff --git a/encode_object_test.go b/encode_object_test.go
@@ -42,7 +42,7 @@ func (t *testObject) MarshalObject(enc *Encoder) {
enc.AddBoolKey("testBool", t.testBool)
}
-func TestEncodeBasicObject(t *testing.T) {
+func TestEncoderObjectBasic(t *testing.T) {
r, err := Marshal(&testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true})
assert.Nil(t, err, "Error should be nil")
assert.Equal(
@@ -101,7 +101,7 @@ func (t *SubObject) MarshalObject(enc *Encoder) {
enc.AddObjectKey("sub", t.sub)
}
-func TestEncoderComplexObject(t *testing.T) {
+func TestEncoderObjectComplex(t *testing.T) {
v := &TestEncoding{
test: "hello world",
test2: "foobar",
@@ -149,7 +149,7 @@ func (t *testEncodingObjInterfaces) MarshalObject(enc *Encoder) {
enc.AddInterfaceKey("interfaceVal", t.interfaceVal)
}
-func TestObjInterfaces(t *testing.T) {
+func TestEncoderObjectInterfaces(t *testing.T) {
v := testEncodingObjInterfaces{"string"}
r, err := Marshal(&v)
assert.Nil(t, err, "Error should be nil")
@@ -247,3 +247,17 @@ func TestObjInterfaces(t *testing.T) {
string(r),
"Result of marshalling is different as the one expected")
}
+
+func TestEncoderObjectPooledError(t *testing.T) {
+ v := &TestEncoding{}
+ enc := BorrowEncoder()
+ enc.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError")
+ assert.Equal(t, "Invalid usage of pooled encoder", err.(InvalidUsagePooledEncoderError).Error(), "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _, _ = enc.EncodeObject(v)
+ assert.True(t, false, "should not be called as it should have panicked")
+}
diff --git a/encode_pool.go b/encode_pool.go
@@ -4,18 +4,29 @@ var encObjPool = make(chan *Encoder, 16)
// NewEncoder returns a new encoder or borrows one from the pool
func NewEncoder() *Encoder {
+ return &Encoder{}
+}
+func newEncoder() *Encoder {
+ return &Encoder{}
+}
+
+// BorrowEncoder borrows an Encoder from the pool.
+func BorrowEncoder() *Encoder {
select {
case enc := <-encObjPool:
+ enc.isPooled = 0
return enc
default:
return &Encoder{}
}
}
-func (enc *Encoder) addToPool() {
+// Release sends back a Encoder to the pool.
+func (enc *Encoder) Release() {
enc.buf = nil
select {
case encObjPool <- enc:
+ enc.isPooled = 1
default:
}
}
diff --git a/encode_pool_test.go b/encode_pool_test.go
@@ -0,0 +1,20 @@
+package gojay
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestEncoderNewFromPool(t *testing.T) {
+ // reset pool
+ encObjPool = make(chan *Encoder, 16)
+ // get new Encoder
+ enc := NewEncoder()
+ // add to pool
+ enc.Release()
+ // borrow encoder
+ nEnc := BorrowEncoder()
+ // make sure it's the same
+ assert.Equal(t, enc, nEnc, "enc and nEnc from pool should be the same")
+}
diff --git a/encode_string.go b/encode_string.go
@@ -1,6 +1,14 @@
package gojay
-// encodeString encodes a string to
+// EncodeString encodes a string to
+func (enc *Encoder) EncodeString(s string) ([]byte, error) {
+ if enc.isPooled == 1 {
+ panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder"))
+ }
+ return enc.encodeString(s)
+}
+
+// encodeString encodes a string to
func (enc *Encoder) encodeString(s string) ([]byte, error) {
enc.writeByte('"')
enc.writeString(s)
@@ -35,4 +43,4 @@ func (enc *Encoder) AddStringKey(key, value string) error {
enc.writeByte('"')
return nil
-}
-\ No newline at end of file
+}
diff --git a/encode_string_test.go b/encode_string_test.go
@@ -25,3 +25,17 @@ func TestEncoderStringUTF8(t *testing.T) {
string(r),
"Result of marshalling is different as the one expected")
}
+
+func TestEncoderStringPooledError(t *testing.T) {
+ v := ""
+ enc := BorrowEncoder()
+ enc.Release()
+ defer func() {
+ err := recover()
+ assert.NotNil(t, err, "err shouldnot be nil")
+ assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError")
+ assert.Equal(t, "Invalid usage of pooled encoder", err.(InvalidUsagePooledEncoderError).Error(), "err should be of type InvalidUsagePooledDecoderError")
+ }()
+ _, _ = enc.EncodeString(v)
+ assert.True(t, false, "should not be called as it should have panicked")
+}
diff --git a/errors.go b/errors.go
@@ -26,6 +26,16 @@ func (err InvalidUnmarshalError) Error() string {
return string(err)
}
+const invalidMarshalErrorMsg = "Invalid type %s provided to Marshal"
+
+// InvalidMarshalError is a type representing an error returned when
+// Encoding did not find the proper way to encode
+type InvalidMarshalError string
+
+func (err InvalidMarshalError) Error() string {
+ return string(err)
+}
+
// NoReaderError is a type representing an error returned when
// decoding requires a reader and none was given
type NoReaderError string
@@ -33,3 +43,19 @@ type NoReaderError string
func (err NoReaderError) Error() string {
return string(err)
}
+
+// InvalidUsagePooledDecoderError is a type representing an error returned
+// when decoding is called on a still pooled Decoder
+type InvalidUsagePooledDecoderError string
+
+func (err InvalidUsagePooledDecoderError) Error() string {
+ return string(err)
+}
+
+// InvalidUsagePooledEncoderError is a type representing an error returned
+// when decoding is called on a still pooled Encoder
+type InvalidUsagePooledEncoderError string
+
+func (err InvalidUsagePooledEncoderError) Error() string {
+ return string(err)
+}