gojay

high performance JSON encoder/decoder with stream API for Golang
git clone git://git.lair.cx/gojay
Log | Files | Refs | README | LICENSE

commit 7a565adc8b519c6b40a3016bea655a24261bcc90
parent 514aec19fd89e723006ae08b3dcc094a206e85d4
Author: Francois Parquet <francois.parquet@gmail.com>
Date:   Mon,  7 May 2018 00:05:40 +0800

Merge pull request #21 from francoispqt/version/v0.10.5

Version/v0.10.5 - Add support for EmbeddedJSON - Optimise Encoding 
Diffstat:
MREADME.md | 2+-
Mdecode.go | 2++
Adecode_embedded_json.go | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adecode_embedded_json_test.go | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdecode_pool.go | 21++++++++++++++++++++-
Mdecode_stream_pool.go | 2+-
Mencode.go | 30+++++++++++++++++++++++++-----
Mencode_array.go | 26+++++++++++++-------------
Mencode_bool.go | 18+++++++++---------
Aencode_embedded_json.go | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aencode_embedded_json_test.go | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mencode_interface.go | 2++
Mencode_number.go | 56++++++++++++++++++++++++++++----------------------------
Mencode_object.go | 26+++++++++++++-------------
Mencode_pool.go | 28+++++++++++++++++++++++-----
Mencode_stream.go | 2+-
Mencode_string.go | 25++++++++++++++++---------
Mencode_test.go | 38+++++++++++++++++++++++++++++++++++++-
18 files changed, 631 insertions(+), 87 deletions(-)

diff --git a/README.md b/README.md @@ -6,7 +6,7 @@ ![MIT License](https://img.shields.io/badge/license-mit-blue.svg?style=flat-square) # GoJay -**Package is currently at version 0.10.4 and still under development** +**Package is currently at version 0.10.5 and still under development** GoJay is a performant JSON encoder/decoder for Golang (currently the most performant, [see benchmarks](#benchmark-results)). diff --git a/decode.go b/decode.go @@ -190,6 +190,8 @@ func (dec *Decoder) Decode(v interface{}) error { case UnmarshalerArray: _, err := dec.decodeArray(vt) return err + case *EmbeddedJSON: + return dec.decodeEmbeddedJSON(vt) default: return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, reflect.TypeOf(vt).String())) } diff --git a/decode_embedded_json.go b/decode_embedded_json.go @@ -0,0 +1,63 @@ +package gojay + +// EmbeddedJSON is a raw encoded JSON value. +// It can be used to delay JSON decoding or precompute a JSON encoding. +type EmbeddedJSON []byte + +func (dec *Decoder) decodeEmbeddedJSON(ej *EmbeddedJSON) error { + var err error + if ej == nil { + return InvalidUnmarshalError("Invalid nil pointer given") + } + var beginOfEmbeddedJSON int + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch dec.data[dec.cursor] { + case ' ', '\n', '\t', '\r', ',': + continue + // is null + case 'n', 't': + beginOfEmbeddedJSON = dec.cursor + dec.cursor = dec.cursor + 4 + // is false + case 'f': + beginOfEmbeddedJSON = dec.cursor + dec.cursor = dec.cursor + 5 + // is an object + case '{': + beginOfEmbeddedJSON = dec.cursor + dec.cursor = dec.cursor + 1 + dec.cursor, err = dec.skipObject() + // is string + case '"': + beginOfEmbeddedJSON = dec.cursor + dec.cursor = dec.cursor + 1 + err = dec.skipString() // why no new dec.cursor in result? + // is array + case '[': + beginOfEmbeddedJSON = dec.cursor + dec.cursor = dec.cursor + 1 + dec.cursor, err = dec.skipArray() + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-': + beginOfEmbeddedJSON = dec.cursor + dec.cursor, err = dec.skipNumber() + } + break + } + if err == nil { + if dec.cursor-1 > beginOfEmbeddedJSON { + *ej = append(*ej, dec.data[beginOfEmbeddedJSON:dec.cursor]...) + } + } + return err +} + +// AddEmbeddedJSON adds an EmbeddedsJSON to the value pointed by v. +// It can be used to delay JSON decoding or precompute a JSON encoding. +func (dec *Decoder) AddEmbeddedJSON(v *EmbeddedJSON) error { + err := dec.decodeEmbeddedJSON(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} diff --git a/decode_embedded_json_test.go b/decode_embedded_json_test.go @@ -0,0 +1,157 @@ +package gojay + +import ( + "bytes" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +type Request struct { + id string + method string + params EmbeddedJSON + more int +} + +func (r *Request) UnmarshalObject(dec *Decoder, key string) error { + switch key { + case "id": + return dec.AddString(&r.id) + case "method": + return dec.AddString(&r.method) + case "params": + return dec.AddEmbeddedJSON(&r.params) + case "more": + dec.AddInt(&r.more) + } + return nil +} + +func (r *Request) NKeys() int { + return 4 +} + +func TestDecodeEmbeddedJSONUnmarshalAPI(t *testing.T) { + testCases := []struct { + name string + json []byte + expectedEmbedded string + }{ + { + name: "decode-basic-string", + json: []byte(`{"id":"someid","method":"getmydata","params":"raw data", "more":123}`), + expectedEmbedded: `"raw data"`, + }, + { + name: "decode-basic-int", + json: []byte(`{"id":"someid","method":"getmydata","params":12345, "more":123}`), + expectedEmbedded: `12345`, + }, + { + name: "decode-basic-int", + json: []byte(`{"id":"someid","method":"getmydata","params":true, "more":123}`), + expectedEmbedded: `true`, + }, + { + name: "decode-basic-int", + json: []byte(`{"id":"someid","method":"getmydata","params": false, "more":123}`), + expectedEmbedded: `false`, + }, + { + name: "decode-basic-int", + json: []byte(`{"id":"someid","method":"getmydata","params":null, "more":123}`), + expectedEmbedded: `null`, + }, + { + name: "decode-basic-object", + json: []byte(`{"id":"someid","method":"getmydata","params":{"example":"of raw data"}, "more":123}`), + expectedEmbedded: `{"example":"of raw data"}`, + }, + { + name: "decode-basic-object", + json: []byte(`{"id":"someid","method":"getmydata","params":[1,2,3], "more":123}`), + expectedEmbedded: `[1,2,3]`, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + req := &Request{} + err := Unmarshal(testCase.json, req) + t.Log(req) + t.Log(string(req.params)) + assert.Nil(t, err, "err should be nil") + assert.Equal(t, testCase.expectedEmbedded, string(req.params), "r.params should be equal to expectedEmbeddedResult") + }) + } +} + +func TestDecodeEmbeddedJSONDecodeAPI(t *testing.T) { + testCases := []struct { + name string + json []byte + expectedEmbedded string + }{ + { + name: "decode-basic-string", + json: []byte(`{"id":"someid","method":"getmydata","params":"raw data", "more":123}`), + expectedEmbedded: `"raw data"`, + }, + { + name: "decode-basic-int", + json: []byte(`{"id":"someid","method":"getmydata","params":12345, "more":123}`), + expectedEmbedded: `12345`, + }, + { + name: "decode-basic-int", + json: []byte(`{"id":"someid","method":"getmydata","params":true, "more":123}`), + expectedEmbedded: `true`, + }, + { + name: "decode-basic-int", + json: []byte(`{"id":"someid","method":"getmydata","params": false, "more":123}`), + expectedEmbedded: `false`, + }, + { + name: "decode-basic-int", + json: []byte(`{"id":"someid","method":"getmydata","params":null, "more":123}`), + expectedEmbedded: `null`, + }, + { + name: "decode-basic-object", + json: []byte(`{"id":"someid","method":"getmydata","params":{"example":"of raw data"}, "more":123}`), + expectedEmbedded: `{"example":"of raw data"}`, + }, + { + name: "decode-basic-object", + json: []byte(`{"id":"someid","method":"getmydata","params":[1,2,3], "more":123}`), + expectedEmbedded: `[1,2,3]`, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + ej := EmbeddedJSON([]byte{}) + dec := BorrowDecoder(bytes.NewReader(testCase.json)) + err := dec.Decode(&ej) + assert.Nil(t, err, "err should be nil") + assert.Equal(t, string(testCase.json), string(ej), "r.params should be equal to expectedEmbeddedResult") + }) + } +} + +func TestDecodeEmbeededJSONNil(t *testing.T) { + dec := BorrowDecoder(strings.NewReader(`"bar"`)) + var ej *EmbeddedJSON + err := dec.decodeEmbeddedJSON(ej) + assert.NotNil(t, err, `err should not be nil a nil pointer is given`) + assert.IsType(t, InvalidUnmarshalError(""), err, `err should not be of type InvalidUnmarshalError`) +} + +func TestDecodeEmbeededJSONNil2(t *testing.T) { + dec := BorrowDecoder(strings.NewReader(`"bar"`)) + var ej *EmbeddedJSON + err := dec.AddEmbeddedJSON(ej) + assert.NotNil(t, err, `err should not be nil a nil pointer is given`) + assert.IsType(t, InvalidUnmarshalError(""), err, `err should not be of type InvalidUnmarshalError`) +} diff --git a/decode_pool.go b/decode_pool.go @@ -2,7 +2,26 @@ package gojay import "io" -var decPool = make(chan *Decoder, 16) +var decPool = make(chan *Decoder, 32) + +func init() { +initStreamDecPool: + for { + select { + case streamDecPool <- Stream.NewDecoder(nil): + default: + break initStreamDecPool + } + } +initDecPool: + for { + select { + case decPool <- NewDecoder(nil): + default: + break initDecPool + } + } +} // NewDecoder returns a new decoder. // It takes an io.Reader implementation as data input. diff --git a/decode_stream_pool.go b/decode_stream_pool.go @@ -2,7 +2,7 @@ package gojay import "io" -var streamDecPool = make(chan *StreamDecoder, 16) +var streamDecPool = make(chan *StreamDecoder, 32) // NewDecoder returns a new StreamDecoder. // It takes an io.Reader implementation as data input. diff --git a/encode.go b/encode.go @@ -157,6 +157,10 @@ func Marshal(v interface{}) ([]byte, error) { enc := BorrowEncoder(nil) defer enc.Release() return enc.encodeFloat32(vt) + case *EmbeddedJSON: + enc := BorrowEncoder(nil) + defer enc.Release() + return enc.encodeEmbeddedJSON(vt) default: return nil, InvalidMarshalError(fmt.Sprintf(invalidMarshalErrorMsg, reflect.TypeOf(vt).String())) } @@ -183,13 +187,29 @@ type Encoder struct { err error } -func (enc *Encoder) getPreviousRune() (byte, bool) { - last := len(enc.buf) - 1 - return enc.buf[last], true +// AppendBytes allows a modular usage by appending bytes manually to the current state of the buffer. +func (enc *Encoder) AppendBytes(b []byte) { + enc.writeBytes(b) +} + +// AppendByte allows a modular usage by appending a single byte manually to the current state of the buffer. +func (enc *Encoder) AppendByte(b byte) { + enc.writeByte(b) } -func (enc *Encoder) write() (int, error) { +// Buf returns the Encoder's buffer. +func (enc *Encoder) Buf() []byte { + return enc.buf +} + +// Write writes to the io.Writer and resets the buffer. +func (enc *Encoder) Write() (int, error) { i, err := enc.w.Write(enc.buf) - enc.buf = make([]byte, 0, 512) + enc.buf = enc.buf[:0] return i, err } + +func (enc *Encoder) getPreviousRune() byte { + last := len(enc.buf) - 1 + return enc.buf[last] +} diff --git a/encode_array.go b/encode_array.go @@ -6,7 +6,7 @@ func (enc *Encoder) EncodeArray(v MarshalerArray) error { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) } _, _ = enc.encodeArray(v) - _, err := enc.write() + _, err := enc.Write() if err != nil { enc.err = err return err @@ -26,8 +26,8 @@ func (enc *Encoder) encodeArray(v MarshalerArray) ([]byte, error) { func (enc *Encoder) AddArray(v MarshalerArray) { if v.IsNil() { enc.grow(3) - r, ok := enc.getPreviousRune() - if ok && r != '[' { + r := enc.getPreviousRune() + if r != '[' { enc.writeByte(',') } enc.writeByte('[') @@ -35,8 +35,8 @@ func (enc *Encoder) AddArray(v MarshalerArray) { return } enc.grow(100) - r, ok := enc.getPreviousRune() - if ok && r != '[' { + r := enc.getPreviousRune() + if r != '[' { enc.writeByte(',') } enc.writeByte('[') @@ -51,8 +51,8 @@ func (enc *Encoder) AddArrayOmitEmpty(v MarshalerArray) { return } enc.grow(4) - r, ok := enc.getPreviousRune() - if ok && r != '[' { + r := enc.getPreviousRune() + if r != '[' { enc.writeByte(',') } enc.writeByte('[') @@ -65,8 +65,8 @@ func (enc *Encoder) AddArrayOmitEmpty(v MarshalerArray) { func (enc *Encoder) AddArrayKey(key string, v MarshalerArray) { if v.IsNil() { enc.grow(2 + len(key)) - r, ok := enc.getPreviousRune() - if ok && r != '[' { + r := enc.getPreviousRune() + if r != '[' { enc.writeByte(',') } enc.writeByte('"') @@ -76,8 +76,8 @@ func (enc *Encoder) AddArrayKey(key string, v MarshalerArray) { return } enc.grow(5 + len(key)) - r, ok := enc.getPreviousRune() - if ok && r != '[' && r != '{' { + r := enc.getPreviousRune() + if r != '[' && r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -94,8 +94,8 @@ func (enc *Encoder) AddArrayKeyOmitEmpty(key string, v MarshalerArray) { return } enc.grow(5 + len(key)) - r, ok := enc.getPreviousRune() - if ok && r != '[' && r != '{' { + r := enc.getPreviousRune() + if r != '[' && r != '{' { enc.writeByte(',') } enc.writeByte('"') diff --git a/encode_bool.go b/encode_bool.go @@ -8,7 +8,7 @@ func (enc *Encoder) EncodeBool(v bool) error { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) } _, _ = enc.encodeBool(v) - _, err := enc.write() + _, err := enc.Write() if err != nil { enc.err = err return err @@ -30,8 +30,8 @@ func (enc *Encoder) encodeBool(v bool) ([]byte, error) { // 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(v bool) { enc.grow(5) - r, ok := enc.getPreviousRune() - if ok && r != '[' { + r := enc.getPreviousRune() + if r != '[' { enc.writeByte(',') } if v { @@ -47,8 +47,8 @@ func (enc *Encoder) AddBoolOmitEmpty(v bool) { return } enc.grow(5) - r, ok := enc.getPreviousRune() - if ok && r != '[' { + r := enc.getPreviousRune() + if r != '[' { enc.writeByte(',') } enc.writeString("true") @@ -57,8 +57,8 @@ func (enc *Encoder) AddBoolOmitEmpty(v bool) { // 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) { enc.grow(5 + len(key)) - r, ok := enc.getPreviousRune() - if ok && r != '{' && r != '[' { + r := enc.getPreviousRune() + if r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') @@ -74,8 +74,8 @@ func (enc *Encoder) AddBoolKeyOmitEmpty(key string, v bool) { return } enc.grow(5 + len(key)) - r, ok := enc.getPreviousRune() - if ok && r != '{' && r != '[' { + r := enc.getPreviousRune() + if r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') diff --git a/encode_embedded_json.go b/encode_embedded_json.go @@ -0,0 +1,83 @@ +package gojay + +// EncodeEmbeddedJSON encodes an embedded JSON. +// is basically sets the internal buf as the value pointed by v and calls the io.Writer.Write() +func (enc *Encoder) EncodeEmbeddedJSON(v *EmbeddedJSON) error { + if enc.isPooled == 1 { + panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) + } + enc.buf = *v + _, err := enc.Write() + if err != nil { + return err + } + return nil +} + +func (enc *Encoder) encodeEmbeddedJSON(v *EmbeddedJSON) ([]byte, error) { + enc.writeBytes(*v) + return enc.buf, nil +} + +// AddEmbeddedJSON adds an EmbeddedJSON to be encoded. +// +// It basically blindly writes the bytes to the final buffer. Therefore, +// it expects the JSON to be of proper format. +func (enc *Encoder) AddEmbeddedJSON(v *EmbeddedJSON) { + enc.grow(len(*v) + 4) + r := enc.getPreviousRune() + if r != '[' { + enc.writeByte(',') + } + enc.writeBytes(*v) +} + +// AddEmbeddedJSONOmitEmpty adds an EmbeddedJSON to be encoded or skips it if nil pointer or empty. +// +// It basically blindly writes the bytes to the final buffer. Therefore, +// it expects the JSON to be of proper format. +func (enc *Encoder) AddEmbeddedJSONOmitEmpty(v *EmbeddedJSON) { + if v == nil || len(*v) == 0 { + return + } + r := enc.getPreviousRune() + if r != '[' { + enc.writeByte(',') + } + enc.writeBytes(*v) +} + +// AddEmbeddedJSONKey adds an EmbeddedJSON and a key to be encoded. +// +// It basically blindly writes the bytes to the final buffer. Therefore, +// it expects the JSON to be of proper format. +func (enc *Encoder) AddEmbeddedJSONKey(key string, v *EmbeddedJSON) { + enc.grow(len(key) + len(*v) + 5) + r := enc.getPreviousRune() + if r != '{' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeStringEscape(key) + enc.writeBytes(objKey) + enc.writeBytes(*v) +} + +// AddEmbeddedJSONKeyOmitEmpty adds an EmbeddedJSON and a key to be encoded or skips it if nil pointer or empty. +// +// It basically blindly writes the bytes to the final buffer. Therefore, +// it expects the JSON to be of proper format. +func (enc *Encoder) AddEmbeddedJSONKeyOmitEmpty(key string, v *EmbeddedJSON) { + if v == nil || len(*v) == 0 { + return + } + enc.grow(len(key) + len(*v) + 5) + r := enc.getPreviousRune() + if r != '{' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeStringEscape(key) + enc.writeBytes(objKey) + enc.writeBytes(*v) +} diff --git a/encode_embedded_json_test.go b/encode_embedded_json_test.go @@ -0,0 +1,137 @@ +package gojay + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func (r *Request) MarshalObject(enc *Encoder) { + enc.AddStringKey("id", r.id) + enc.AddStringKey("method", r.method) + enc.AddEmbeddedJSONKey("params", &r.params) + params2 := EmbeddedJSON([]byte(``)) + enc.AddEmbeddedJSONKeyOmitEmpty("params2", &params2) + params3 := EmbeddedJSON([]byte(`"test"`)) + enc.AddEmbeddedJSONKeyOmitEmpty("params3", &params3) + enc.AddIntKey("more", r.more) +} + +func (r *Request) IsNil() bool { + return r == nil +} + +type EmbeddedJSONArr []EmbeddedJSON + +func (ear EmbeddedJSONArr) MarshalArray(enc *Encoder) { + for _, e := range ear { + enc.AddEmbeddedJSON(&e) + } +} + +func (ear EmbeddedJSONArr) IsNil() bool { + return len(ear) == 0 +} + +type EmbeddedJSONOmitEmptyArr []EmbeddedJSON + +func (ear EmbeddedJSONOmitEmptyArr) MarshalArray(enc *Encoder) { + for _, e := range ear { + enc.AddEmbeddedJSONOmitEmpty(&e) + } +} + +func (ear EmbeddedJSONOmitEmptyArr) IsNil() bool { + return len(ear) == 0 +} + +func TestEncodingEmbeddedJSON(t *testing.T) { + t.Run("basic-embedded-json", func(t *testing.T) { + ej := EmbeddedJSON([]byte(`"test"`)) + b := &strings.Builder{} + enc := BorrowEncoder(b) + err := enc.Encode(&ej) + assert.Nil(t, err, "err should be nil") + assert.Equal(t, b.String(), `"test"`, "b should be equal to content of EmbeddedJSON") + }) + t.Run("basic-embedded-json-marshal-api", func(t *testing.T) { + ej := EmbeddedJSON([]byte(`"test"`)) + b, err := Marshal(&ej) + assert.Nil(t, err, "err should be nil") + assert.Equal(t, string(b), `"test"`, "b should be equal to content of EmbeddedJSON") + }) + t.Run("object-embedded-json", func(t *testing.T) { + req := Request{ + id: "test", + method: "GET", + params: EmbeddedJSON([]byte(`"test"`)), + } + b := &strings.Builder{} + enc := BorrowEncoder(b) + err := enc.EncodeObject(&req) + assert.Nil(t, err, "err should be nil") + assert.Equal( + t, + b.String(), + `{"id":"test","method":"GET","params":"test","params3":"test","more":0}`, + "b should be equal to content of EmbeddedJSON", + ) + }) + t.Run("array-embedded-json", func(t *testing.T) { + ear := EmbeddedJSONArr{ + []byte(`"test"`), + []byte(`{"test":"test"}`), + } + b := &strings.Builder{} + enc := BorrowEncoder(b) + err := enc.EncodeArray(ear) + assert.Nil(t, err, "err should be nil") + assert.Equal( + t, + b.String(), + `["test",{"test":"test"}]`, + "b should be equal to content of EmbeddedJSON", + ) + }) + t.Run("array-embedded-json-omit-empty", func(t *testing.T) { + ear := EmbeddedJSONOmitEmptyArr{ + []byte(`"test"`), + []byte(``), + []byte(`{"test":"test"}`), + []byte(``), + []byte(`{"test":"test"}`), + } + b := &strings.Builder{} + enc := BorrowEncoder(b) + err := enc.EncodeArray(ear) + assert.Nil(t, err, "err should be nil") + assert.Equal( + t, + b.String(), + `["test",{"test":"test"},{"test":"test"}]`, + "b should be equal to content of EmbeddedJSON", + ) + }) + t.Run("write-error", func(t *testing.T) { + w := TestWriterError("") + v := EmbeddedJSON([]byte(`"test"`)) + enc := NewEncoder(w) + err := enc.EncodeEmbeddedJSON(&v) + assert.NotNil(t, err, "Error should not be nil") + assert.Equal(t, "Test Error", err.Error(), "err.Error() should be 'Test Error'") + }) + t.Run("pool-error", func(t *testing.T) { + v := EmbeddedJSON([]byte(`"test"`)) + enc := BorrowEncoder(nil) + 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.EncodeEmbeddedJSON(&v) + assert.True(t, false, "should not be called as it should have panicked") + }) +} diff --git a/encode_interface.go b/encode_interface.go @@ -42,6 +42,8 @@ func (enc *Encoder) Encode(v interface{}) error { return enc.EncodeFloat(vt) case float32: return enc.EncodeFloat32(vt) + case *EmbeddedJSON: + return enc.EncodeEmbeddedJSON(vt) default: return InvalidMarshalError(fmt.Sprintf(invalidMarshalErrorMsg, reflect.TypeOf(vt).String())) } diff --git a/encode_number.go b/encode_number.go @@ -8,7 +8,7 @@ func (enc *Encoder) EncodeInt(n int) error { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) } _, _ = enc.encodeInt(n) - _, err := enc.write() + _, err := enc.Write() if err != nil { return err } @@ -27,7 +27,7 @@ func (enc *Encoder) EncodeInt64(n int64) error { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) } _, _ = enc.encodeInt64(n) - _, err := enc.write() + _, err := enc.Write() if err != nil { return err } @@ -46,7 +46,7 @@ func (enc *Encoder) EncodeFloat(n float64) error { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) } _, _ = enc.encodeFloat(n) - _, err := enc.write() + _, err := enc.Write() if err != nil { return err } @@ -65,7 +65,7 @@ func (enc *Encoder) EncodeFloat32(n float32) error { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) } _, _ = enc.encodeFloat32(n) - _, err := enc.write() + _, err := enc.Write() if err != nil { return err } @@ -80,8 +80,8 @@ func (enc *Encoder) encodeFloat32(n float32) ([]byte, error) { // AddInt adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) AddInt(v int) { enc.grow(10) - r, ok := enc.getPreviousRune() - if ok && r != '[' { + r := enc.getPreviousRune() + if r != '[' { enc.writeByte(',') } enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) @@ -94,8 +94,8 @@ func (enc *Encoder) AddIntOmitEmpty(v int) { return } enc.grow(10) - r, ok := enc.getPreviousRune() - if ok && r != '[' { + r := enc.getPreviousRune() + if r != '[' { enc.writeByte(',') } enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) @@ -104,8 +104,8 @@ func (enc *Encoder) AddIntOmitEmpty(v int) { // AddFloat adds a float64 to be encoded, must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) AddFloat(v float64) { enc.grow(10) - r, ok := enc.getPreviousRune() - if ok && r != '[' { + r := enc.getPreviousRune() + if r != '[' { enc.writeByte(',') } enc.buf = strconv.AppendFloat(enc.buf, v, 'f', -1, 64) @@ -118,8 +118,8 @@ func (enc *Encoder) AddFloatOmitEmpty(v float64) { return } enc.grow(10) - r, ok := enc.getPreviousRune() - if ok && r != '[' { + r := enc.getPreviousRune() + if r != '[' { enc.writeByte(',') } enc.buf = strconv.AppendFloat(enc.buf, v, 'f', -1, 64) @@ -127,8 +127,8 @@ func (enc *Encoder) AddFloatOmitEmpty(v float64) { // AddFloat32 adds a float32 to be encoded, must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) AddFloat32(v float32) { - r, ok := enc.getPreviousRune() - if ok && r != '[' { + r := enc.getPreviousRune() + if r != '[' { enc.writeByte(',') } enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) @@ -141,8 +141,8 @@ func (enc *Encoder) AddFloat32OmitEmpty(v float32) { return } enc.grow(10) - r, ok := enc.getPreviousRune() - if ok && r != '[' { + r := enc.getPreviousRune() + if r != '[' { enc.writeByte(',') } enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) @@ -151,8 +151,8 @@ func (enc *Encoder) AddFloat32OmitEmpty(v float32) { // AddIntKey adds an int to be encoded, must be used inside an object as it will encode a key func (enc *Encoder) AddIntKey(key string, v int) { enc.grow(10 + len(key)) - r, ok := enc.getPreviousRune() - if ok && r != '{' && r != '[' { + r := enc.getPreviousRune() + if r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') @@ -168,8 +168,8 @@ func (enc *Encoder) AddIntKeyOmitEmpty(key string, v int) { return } enc.grow(10 + len(key)) - r, ok := enc.getPreviousRune() - if ok && r != '{' && r != '[' { + r := enc.getPreviousRune() + if r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') @@ -180,8 +180,8 @@ func (enc *Encoder) AddIntKeyOmitEmpty(key string, v int) { // AddFloatKey adds a float64 to be encoded, must be used inside an object as it will encode a key func (enc *Encoder) AddFloatKey(key string, value float64) { - r, ok := enc.getPreviousRune() - if ok && r != '{' && r != '[' { + r := enc.getPreviousRune() + if r != '{' && r != '[' { enc.writeByte(',') } enc.grow(10) @@ -198,8 +198,8 @@ func (enc *Encoder) AddFloatKeyOmitEmpty(key string, v float64) { return } enc.grow(10 + len(key)) - r, ok := enc.getPreviousRune() - if ok && r != '{' && r != '[' { + r := enc.getPreviousRune() + if r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') @@ -211,8 +211,8 @@ func (enc *Encoder) AddFloatKeyOmitEmpty(key string, v float64) { // AddFloat32Key adds a float32 to be encoded, must be used inside an object as it will encode a key func (enc *Encoder) AddFloat32Key(key string, v float32) { enc.grow(10 + len(key)) - r, ok := enc.getPreviousRune() - if ok && r != '{' && r != '[' { + r := enc.getPreviousRune() + if r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') @@ -229,8 +229,8 @@ func (enc *Encoder) AddFloat32KeyOmitEmpty(key string, v float32) { return } enc.grow(10 + len(key)) - r, ok := enc.getPreviousRune() - if ok && r != '{' && r != '[' { + r := enc.getPreviousRune() + if r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('"') diff --git a/encode_object.go b/encode_object.go @@ -15,7 +15,7 @@ func (enc *Encoder) EncodeObject(v MarshalerObject) error { enc.err = err return err } - _, err = enc.write() + _, err = enc.Write() if err != nil { enc.err = err return err @@ -38,8 +38,8 @@ func (enc *Encoder) encodeObject(v MarshalerObject) ([]byte, error) { func (enc *Encoder) AddObject(v MarshalerObject) { if v.IsNil() { enc.grow(2) - r, ok := enc.getPreviousRune() - if ok && r != '{' && r != '[' { + r := enc.getPreviousRune() + if r != '{' && r != '[' { enc.writeByte(',') } enc.writeByte('{') @@ -47,8 +47,8 @@ func (enc *Encoder) AddObject(v MarshalerObject) { return } enc.grow(4) - r, ok := enc.getPreviousRune() - if ok && r != '[' { + r := enc.getPreviousRune() + if r != '[' { enc.writeByte(',') } enc.writeByte('{') @@ -64,8 +64,8 @@ func (enc *Encoder) AddObjectOmitEmpty(v MarshalerObject) { return } enc.grow(2) - r, ok := enc.getPreviousRune() - if ok && r != '[' { + r := enc.getPreviousRune() + if r != '[' { enc.writeByte(',') } enc.writeByte('{') @@ -78,8 +78,8 @@ func (enc *Encoder) AddObjectOmitEmpty(v MarshalerObject) { func (enc *Encoder) AddObjectKey(key string, value MarshalerObject) { if value.IsNil() { enc.grow(2 + len(key)) - r, ok := enc.getPreviousRune() - if ok && r != '{' { + r := enc.getPreviousRune() + if r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -89,8 +89,8 @@ func (enc *Encoder) AddObjectKey(key string, value MarshalerObject) { return } enc.grow(5 + len(key)) - r, ok := enc.getPreviousRune() - if ok && r != '{' { + r := enc.getPreviousRune() + if r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -108,8 +108,8 @@ func (enc *Encoder) AddObjectKeyOmitEmpty(key string, value MarshalerObject) { return } enc.grow(5 + len(key)) - r, ok := enc.getPreviousRune() - if ok && r != '{' { + r := enc.getPreviousRune() + if r != '{' { enc.writeByte(',') } enc.writeByte('"') diff --git a/encode_pool.go b/encode_pool.go @@ -2,8 +2,27 @@ package gojay import "io" -var encPool = make(chan *Encoder, 16) -var streamEncPool = make(chan *StreamEncoder, 16) +var encPool = make(chan *Encoder, 32) +var streamEncPool = make(chan *StreamEncoder, 32) + +func init() { +initStreamEncPool: + for { + select { + case streamEncPool <- Stream.NewEncoder(nil): + default: + break initStreamEncPool + } + } +initEncPool: + for { + select { + case encPool <- NewEncoder(nil): + default: + break initEncPool + } + } +} // NewEncoder returns a new encoder or borrows one from the pool func NewEncoder(w io.Writer) *Encoder { @@ -20,16 +39,15 @@ func BorrowEncoder(w io.Writer) *Encoder { enc.isPooled = 0 enc.w = w enc.err = nil - enc.buf = make([]byte, 0) return enc default: - return &Encoder{w: w} + return &Encoder{w: w, buf: make([]byte, 0, 512)} } } // Release sends back a Encoder to the pool. func (enc *Encoder) Release() { - enc.buf = nil + enc.buf = enc.buf[:0] enc.isPooled = 1 select { case encPool <- enc: diff --git a/encode_stream.go b/encode_stream.go @@ -180,7 +180,7 @@ func consume(init *StreamEncoder, s *StreamEncoder, m MarshalerStream) { init.Cancel(s.Encoder.err) return } - i, err := s.Encoder.write() + i, err := s.Encoder.Write() if err != nil || i == 0 { init.Cancel(err) return diff --git a/encode_string.go b/encode_string.go @@ -6,7 +6,7 @@ func (enc *Encoder) EncodeString(s string) error { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) } _, _ = enc.encodeString(s) - _, err := enc.write() + _, err := enc.Write() if err != nil { enc.err = err return err @@ -22,11 +22,18 @@ func (enc *Encoder) encodeString(v string) ([]byte, error) { return enc.buf, nil } +func (enc *Encoder) AppendString(v string) { + enc.grow(len(v) + 2) + enc.writeByte('"') + enc.writeStringEscape(v) + enc.writeByte('"') +} + // AddString adds a string to be encoded, must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) AddString(v string) { enc.grow(len(v) + 4) - r, ok := enc.getPreviousRune() - if ok && r != '[' { + r := enc.getPreviousRune() + if r != '[' { enc.writeByte(',') } enc.writeByte('"') @@ -40,8 +47,8 @@ func (enc *Encoder) AddStringOmitEmpty(v string) { if v == "" { return } - r, ok := enc.getPreviousRune() - if ok && r != '[' { + r := enc.getPreviousRune() + if r != '[' { enc.writeByte(',') } enc.writeByte('"') @@ -52,8 +59,8 @@ func (enc *Encoder) AddStringOmitEmpty(v string) { // AddStringKey adds a string to be encoded, must be used inside an object as it will encode a key func (enc *Encoder) AddStringKey(key, v string) { enc.grow(len(key) + len(v) + 5) - r, ok := enc.getPreviousRune() - if ok && r != '{' { + r := enc.getPreviousRune() + if r != '{' { enc.writeByte(',') } enc.writeByte('"') @@ -70,8 +77,8 @@ func (enc *Encoder) AddStringKeyOmitEmpty(key, v string) { return } enc.grow(len(key) + len(v) + 5) - r, ok := enc.getPreviousRune() - if ok && r != '{' { + r := enc.getPreviousRune() + if r != '{' { enc.writeByte(',') } enc.writeByte('"') diff --git a/encode_test.go b/encode_test.go @@ -1,9 +1,45 @@ package gojay -import "errors" +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) type TestWriterError string func (t TestWriterError) Write(b []byte) (int, error) { return 0, errors.New("Test Error") } + +func TestAppendBytes(t *testing.T) { + b := []byte(``) + enc := NewEncoder(nil) + enc.buf = b + enc.AppendBytes([]byte(`true`)) + assert.Equal(t, string(enc.buf), `true`, "string(enc.buf) should equal to true") +} + +func TestAppendByte(t *testing.T) { + b := []byte(``) + enc := NewEncoder(nil) + enc.buf = b + enc.AppendByte(1) + assert.Equal(t, enc.buf[0], uint8(0x1), "b[0] should equal to 1") +} + +func TestAppendString(t *testing.T) { + b := []byte(``) + enc := NewEncoder(nil) + enc.buf = b + enc.AppendString("true") + assert.Equal(t, string(enc.buf), `"true"`, "string(enc.buf) should equal to true") +} + +func TestBuf(t *testing.T) { + b := []byte(`test`) + enc := NewEncoder(nil) + enc.buf = b + assert.Equal(t, b, enc.Buf(), "enc.Buf() should equal to b") +}