gojay

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

commit 01327d5a36628c9b867fff44a54f83bf9185a9f5
parent d3845d8d57a705dec739b5c831eefdb14c6cb749
Author: francoispqt <francois@parquet.ninja>
Date:   Sun,  6 May 2018 13:24:44 +0800

add support for encoding of EmbedddedJSON

Diffstat:
Mencode.go | 4++++
Aencode_embedded_json.go | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aencode_embedded_json_test.go | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mencode_interface.go | 2++
4 files changed, 226 insertions(+), 0 deletions(-)

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())) } 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, ok := enc.getPreviousRune() + if ok && 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, ok := enc.getPreviousRune() + if ok && 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, ok := enc.getPreviousRune() + if ok && 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, ok := enc.getPreviousRune() + if ok && 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())) }