gojay

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

commit aa40cf1b0005c69a3fe5a1c172d73590c0da6d17
parent 9d4e062aaa3d62c36207fdbb969c1199bc71f5fd
Author: Francois Parquet <francois.parquet@gmail.com>
Date:   Wed, 29 Aug 2018 12:39:14 +0800

Merge pull request #69 from francoispqt/feature/add-encoding-with-keys

add feature to encode object and only specific keys to be encoded
Diffstat:
Mencode.go | 2++
Mencode_array.go | 15+++++++++++++++
Mencode_bool.go | 15+++++++++++++++
Mencode_embedded_json.go | 10++++++++++
Mencode_null.go | 5+++++
Mencode_number_float.go | 30++++++++++++++++++++++++++++++
Mencode_number_int.go | 25+++++++++++++++++++++++++
Mencode_number_uint.go | 15+++++++++++++++
Mencode_object.go | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Mencode_object_test.go | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mencode_pool.go | 2++
Mencode_string.go | 15+++++++++++++++
Mencode_time.go | 5+++++
13 files changed, 324 insertions(+), 0 deletions(-)

diff --git a/encode.go b/encode.go @@ -205,6 +205,8 @@ type Encoder struct { isPooled byte w io.Writer err error + hasKeys bool + keys []string } // AppendBytes allows a modular usage by appending bytes manually to the current state of the buffer. diff --git a/encode_array.go b/encode_array.go @@ -116,6 +116,11 @@ func (enc *Encoder) ArrayNullEmpty(v MarshalerJSONArray) { // ArrayKey adds an array or slice to be encoded, must be used inside an object as it will encode a key // value must implement Marshaler func (enc *Encoder) ArrayKey(key string, v MarshalerJSONArray) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } if v.IsNil() { enc.grow(2 + len(key)) r := enc.getPreviousRune() @@ -143,6 +148,11 @@ func (enc *Encoder) ArrayKey(key string, v MarshalerJSONArray) { // ArrayKeyOmitEmpty adds an array or slice to be encoded and skips if it is nil. // Must be called inside an object as it will encode a key. func (enc *Encoder) ArrayKeyOmitEmpty(key string, v MarshalerJSONArray) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } if v.IsNil() { return } @@ -161,6 +171,11 @@ func (enc *Encoder) ArrayKeyOmitEmpty(key string, v MarshalerJSONArray) { // ArrayKeyNullEmpty adds an array or slice to be encoded and encodes `null`` if it is nil. // Must be called inside an object as it will encode a key. func (enc *Encoder) ArrayKeyNullEmpty(key string, v MarshalerJSONArray) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(5 + len(key)) r := enc.getPreviousRune() if r != '{' { diff --git a/encode_bool.go b/encode_bool.go @@ -102,6 +102,11 @@ func (enc *Encoder) BoolNullEmpty(v bool) { // BoolKey adds a bool to be encoded, must be used inside an object as it will encode a key. func (enc *Encoder) BoolKey(key string, value bool) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(5 + len(key)) r := enc.getPreviousRune() if r != '{' { @@ -116,6 +121,11 @@ func (enc *Encoder) BoolKey(key string, value bool) { // BoolKeyOmitEmpty adds a bool to be encoded and skips it if it is zero value. // Must be used inside an object as it will encode a key. func (enc *Encoder) BoolKeyOmitEmpty(key string, v bool) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } if v == false { return } @@ -133,6 +143,11 @@ func (enc *Encoder) BoolKeyOmitEmpty(key string, v bool) { // BoolKeyNullEmpty adds a bool to be encoded and skips it if it is zero value. // Must be used inside an object as it will encode a key. func (enc *Encoder) BoolKeyNullEmpty(key string, v bool) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(5 + len(key)) r := enc.getPreviousRune() if r != '{' { diff --git a/encode_embedded_json.go b/encode_embedded_json.go @@ -52,6 +52,11 @@ func (enc *Encoder) AddEmbeddedJSONOmitEmpty(v *EmbeddedJSON) { // 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) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(len(key) + len(*v) + 5) r := enc.getPreviousRune() if r != '{' { @@ -68,6 +73,11 @@ func (enc *Encoder) AddEmbeddedJSONKey(key string, v *EmbeddedJSON) { // 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 enc.hasKeys { + if !enc.keyExists(key) { + return + } + } if v == nil || len(*v) == 0 { return } diff --git a/encode_null.go b/encode_null.go @@ -22,6 +22,11 @@ func (enc *Encoder) AddNullKey(key string) { // NullKey adds a `null` to be encoded. Must be used while encoding an array.` func (enc *Encoder) NullKey(key string) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(5 + len(key)) r := enc.getPreviousRune() if r != '{' { diff --git a/encode_number_float.go b/encode_number_float.go @@ -170,6 +170,11 @@ func (enc *Encoder) AddFloat64KeyOmitEmpty(key string, v float64) { // Float64Key adds a float64 to be encoded, must be used inside an object as it will encode a key func (enc *Encoder) Float64Key(key string, value float64) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } r := enc.getPreviousRune() if r != '{' { enc.writeByte(',') @@ -184,6 +189,11 @@ func (enc *Encoder) Float64Key(key string, value float64) { // Float64KeyOmitEmpty adds a float64 to be encoded and skips it if its value is 0. // Must be used inside an object as it will encode a key func (enc *Encoder) Float64KeyOmitEmpty(key string, v float64) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } if v == 0 { return } @@ -201,6 +211,11 @@ func (enc *Encoder) Float64KeyOmitEmpty(key string, v float64) { // Float64KeyNullEmpty adds a float64 to be encoded and skips it if its value is 0, // must be used inside a slice or array encoding (does not encode a key). func (enc *Encoder) Float64KeyNullEmpty(key string, v float64) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(10 + len(key)) r := enc.getPreviousRune() if r != '{' { @@ -290,6 +305,11 @@ func (enc *Encoder) AddFloat32KeyNullEmpty(key string, v float32) { // Float32Key adds a float32 to be encoded, must be used inside an object as it will encode a key func (enc *Encoder) Float32Key(key string, v float32) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(10 + len(key)) r := enc.getPreviousRune() if r != '{' { @@ -305,6 +325,11 @@ func (enc *Encoder) Float32Key(key string, v float32) { // Float32KeyOmitEmpty adds a float64 to be encoded and skips it if its value is 0. // Must be used inside an object as it will encode a key func (enc *Encoder) Float32KeyOmitEmpty(key string, v float32) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } if v == 0 { return } @@ -322,6 +347,11 @@ func (enc *Encoder) Float32KeyOmitEmpty(key string, v float32) { // Float32KeyNullEmpty adds a float64 to be encoded and skips it if its value is 0. // Must be used inside an object as it will encode a key func (enc *Encoder) Float32KeyNullEmpty(key string, v float32) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(10 + len(key)) r := enc.getPreviousRune() if r != '{' { diff --git a/encode_number_int.go b/encode_number_int.go @@ -115,6 +115,11 @@ func (enc *Encoder) AddIntKeyNullEmpty(key string, v int) { // IntKey adds an int to be encoded, must be used inside an object as it will encode a key func (enc *Encoder) IntKey(key string, v int) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(10 + len(key)) r := enc.getPreviousRune() if r != '{' { @@ -129,6 +134,11 @@ func (enc *Encoder) IntKey(key string, v int) { // IntKeyOmitEmpty adds an int to be encoded and skips it if its value is 0. // Must be used inside an object as it will encode a key. func (enc *Encoder) IntKeyOmitEmpty(key string, v int) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } if v == 0 { return } @@ -146,6 +156,11 @@ func (enc *Encoder) IntKeyOmitEmpty(key string, v int) { // IntKeyNullEmpty adds an int to be encoded and skips it if its value is 0. // Must be used inside an object as it will encode a key. func (enc *Encoder) IntKeyNullEmpty(key string, v int) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(10 + len(key)) r := enc.getPreviousRune() if r != '{' && r != '[' { @@ -236,6 +251,11 @@ func (enc *Encoder) AddInt64KeyNullEmpty(key string, v int64) { // Int64Key adds an int64 to be encoded, must be used inside an object as it will encode a key func (enc *Encoder) Int64Key(key string, v int64) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(10 + len(key)) r := enc.getPreviousRune() if r != '{' { @@ -267,6 +287,11 @@ func (enc *Encoder) Int64KeyOmitEmpty(key string, v int64) { // Int64KeyNullEmpty adds an int64 to be encoded and skips it if its value is 0. // Must be used inside an object as it will encode a key. func (enc *Encoder) Int64KeyNullEmpty(key string, v int64) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(10 + len(key)) r := enc.getPreviousRune() if r != '{' { diff --git a/encode_number_uint.go b/encode_number_uint.go @@ -96,6 +96,11 @@ func (enc *Encoder) AddUint64KeyNullEmpty(key string, v uint64) { // Uint64Key adds an int to be encoded, must be used inside an object as it will encode a key func (enc *Encoder) Uint64Key(key string, v uint64) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(10 + len(key)) r := enc.getPreviousRune() if r != '{' { @@ -110,6 +115,11 @@ func (enc *Encoder) Uint64Key(key string, v uint64) { // Uint64KeyOmitEmpty adds an int to be encoded and skips it if its value is 0. // Must be used inside an object as it will encode a key. func (enc *Encoder) Uint64KeyOmitEmpty(key string, v uint64) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } if v == 0 { return } @@ -127,6 +137,11 @@ func (enc *Encoder) Uint64KeyOmitEmpty(key string, v uint64) { // Uint64KeyNullEmpty adds an int to be encoded and skips it if its value is 0. // Must be used inside an object as it will encode a key. func (enc *Encoder) Uint64KeyNullEmpty(key string, v uint64) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(10 + len(key)) r := enc.getPreviousRune() if r != '{' && r != '[' { diff --git a/encode_object.go b/encode_object.go @@ -23,12 +23,36 @@ func (enc *Encoder) EncodeObject(v MarshalerJSONObject) error { return nil } +// EncodeObjectKeys encodes an object to JSON +func (enc *Encoder) EncodeObjectKeys(v MarshalerJSONObject, keys []string) error { + if enc.isPooled == 1 { + panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) + } + enc.hasKeys = true + enc.keys = keys + _, err := enc.encodeObject(v) + if err != nil { + enc.err = err + return err + } + _, err = enc.Write() + if err != nil { + enc.err = err + return err + } + return nil +} + func (enc *Encoder) encodeObject(v MarshalerJSONObject) ([]byte, error) { enc.grow(500) enc.writeByte('{') if !v.IsNil() { v.MarshalJSONObject(enc) } + if enc.hasKeys { + enc.hasKeys = false + enc.keys = nil + } enc.writeByte('}') return enc.buf, enc.err } @@ -134,6 +158,11 @@ func (enc *Encoder) ObjectNullEmpty(v MarshalerJSONObject) { // ObjectKey adds a struct to be encoded, must be used inside an object as it will encode a key // value must implement MarshalerJSONObject func (enc *Encoder) ObjectKey(key string, value MarshalerJSONObject) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } if value.IsNil() { enc.grow(2 + len(key)) r := enc.getPreviousRune() @@ -162,6 +191,11 @@ func (enc *Encoder) ObjectKey(key string, value MarshalerJSONObject) { // Must be used inside a slice or array encoding (does not encode a key) // value must implement MarshalerJSONObject func (enc *Encoder) ObjectKeyOmitEmpty(key string, value MarshalerJSONObject) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } if value.IsNil() { return } @@ -181,6 +215,11 @@ func (enc *Encoder) ObjectKeyOmitEmpty(key string, value MarshalerJSONObject) { // Must be used inside a slice or array encoding (does not encode a key) // value must implement MarshalerJSONObject func (enc *Encoder) ObjectKeyNullEmpty(key string, value MarshalerJSONObject) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(5 + len(key)) r := enc.getPreviousRune() if r != '{' { @@ -216,3 +255,15 @@ func (f EncodeObjectFunc) MarshalJSONObject(enc *Encoder) { func (f EncodeObjectFunc) IsNil() bool { return f == nil } + +func (enc *Encoder) keyExists(k string) bool { + if enc.keys == nil { + return false + } + for _, key := range enc.keys { + if key == k { + return true + } + } + return false +} diff --git a/encode_object_test.go b/encode_object_test.go @@ -3,6 +3,7 @@ package gojay import ( "strings" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -487,3 +488,136 @@ func TestEncoderObjectNullEmpty(t *testing.T) { }) } } + +type ObjectWithKeys struct { + Str string + Int int + Int64 int64 + Int32 int32 + Int16 int16 + Int8 int8 + Uint64 uint64 + Uint32 uint32 + Uint16 uint16 + Uint8 uint8 + Float64 float64 + Float32 float32 + Bool bool + Obj *ObjectWithKeys + Slice TestEncodingArrStrings + Time *time.Time + EmbeddedJSON *EmbeddedJSON +} + +func (o *ObjectWithKeys) MarshalJSONObject(enc *Encoder) { + enc.StringKey("string", o.Str) + enc.StringKeyOmitEmpty("string", o.Str) + enc.StringKeyNullEmpty("string", o.Str) + enc.IntKey("int", o.Int) + enc.IntKeyOmitEmpty("int", o.Int) + enc.IntKeyNullEmpty("int", o.Int) + enc.Int64Key("int64", o.Int64) + enc.Int64KeyOmitEmpty("int64", o.Int64) + enc.Int64KeyNullEmpty("int64", o.Int64) + enc.Int32Key("int32", o.Int32) + enc.Int32KeyOmitEmpty("int32", o.Int32) + enc.Int32KeyNullEmpty("int32", o.Int32) + enc.Int16Key("int16", o.Int16) + enc.Int16KeyOmitEmpty("int16", o.Int16) + enc.Int16KeyNullEmpty("int16", o.Int16) + enc.Int8Key("int8", o.Int8) + enc.Int8KeyOmitEmpty("int8", o.Int8) + enc.Int8KeyNullEmpty("int8", o.Int8) + enc.Uint64KeyOmitEmpty("uint64", o.Uint64) + enc.Uint64KeyNullEmpty("uint64", o.Uint64) + enc.Uint64Key("uint64", o.Uint64) + enc.Uint32Key("uint32", o.Uint32) + enc.Uint32KeyOmitEmpty("uint32", o.Uint32) + enc.Uint32KeyNullEmpty("uint32", o.Uint32) + enc.Uint16KeyOmitEmpty("uint16", o.Uint16) + enc.Uint16KeyNullEmpty("uint16", o.Uint16) + enc.Uint16Key("uint16", o.Uint16) + enc.Uint8Key("uint8", o.Uint8) + enc.Uint8KeyOmitEmpty("uint8", o.Uint8) + enc.Uint8KeyNullEmpty("uint8", o.Uint8) + enc.Float64Key("float64", o.Float64) + enc.Float64KeyOmitEmpty("float64", o.Float64) + enc.Float64KeyNullEmpty("float64", o.Float64) + enc.Float32Key("float32", o.Float32) + enc.Float32KeyOmitEmpty("float32", o.Float32) + enc.Float32KeyNullEmpty("float32", o.Float32) + enc.BoolKey("bool", o.Bool) + enc.BoolKeyOmitEmpty("bool", o.Bool) + enc.BoolKeyNullEmpty("bool", o.Bool) + enc.ObjectKeyOmitEmpty("object", o.Obj) + enc.ObjectKeyNullEmpty("object", o.Obj) + enc.ObjectKey("object", o.Obj) + enc.ArrayKey("array", o.Slice) + enc.ArrayKeyOmitEmpty("array", o.Slice) + enc.ArrayKeyNullEmpty("array", o.Slice) + enc.TimeKey("time", o.Time, time.RFC3339) + enc.AddEmbeddedJSONKey("ejson", o.EmbeddedJSON) + enc.AddEmbeddedJSONKeyOmitEmpty("ejson", o.EmbeddedJSON) + enc.NullKey("null") +} + +func (o *ObjectWithKeys) IsNil() bool { + return o == nil +} + +func TestEncodeObjectWithKeys(t *testing.T) { + t.Run( + "should not encode any key", + func(t *testing.T) { + var b strings.Builder + var enc = NewEncoder(&b) + var o = &ObjectWithKeys{} + var err = enc.EncodeObjectKeys(o, []string{}) + assert.Nil(t, err) + assert.Equal(t, `{}`, b.String()) + }, + ) + t.Run( + "should encode some keys", + func(t *testing.T) { + var b strings.Builder + var enc = NewEncoder(&b) + var o = &ObjectWithKeys{Str: "hello", Int: 420} + var err = enc.EncodeObjectKeys(o, []string{"string", "int"}) + assert.Nil(t, err) + assert.Equal( + t, + `{"string":"hello","string":"hello","string":"hello","int":420,"int":420,"int":420}`, + b.String(), + ) + }, + ) + t.Run("write-error", func(t *testing.T) { + w := TestWriterError("") + enc := NewEncoder(w) + o := &ObjectWithKeys{Str: "hello", Int: 420} + err := enc.EncodeObjectKeys(o, []string{"string", "int"}) + 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 := &TestEncoding{} + enc := BorrowEncoder(nil) + enc.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnt 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.EncodeObjectKeys(v, []string{}) + assert.True(t, false, "should not be called as it should have panicked") + }) + t.Run("interface-key-error", func(t *testing.T) { + builder := &strings.Builder{} + enc := NewEncoder(builder) + err := enc.EncodeObjectKeys(&testObjectWithUnknownType{struct{}{}}, []string{}) + assert.NotNil(t, err, "Error should not be nil") + assert.Equal(t, "Invalid type struct {} provided to Marshal", err.Error(), "err.Error() should be 'Invalid type struct {} provided to Marshal'") + }) +} diff --git a/encode_pool.go b/encode_pool.go @@ -38,6 +38,8 @@ func BorrowEncoder(w io.Writer) *Encoder { enc.buf = enc.buf[:0] enc.isPooled = 0 enc.err = nil + enc.hasKeys = false + enc.keys = nil return enc } diff --git a/encode_string.go b/encode_string.go @@ -117,6 +117,11 @@ func (enc *Encoder) StringNullEmpty(v string) { // StringKey adds a string to be encoded, must be used inside an object as it will encode a key func (enc *Encoder) StringKey(key, v string) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(len(key) + len(v) + 5) r := enc.getPreviousRune() if r != '{' { @@ -133,6 +138,11 @@ func (enc *Encoder) StringKey(key, v string) { // StringKeyOmitEmpty adds a string to be encoded or skips it if it is zero value. // Must be used inside an object as it will encode a key func (enc *Encoder) StringKeyOmitEmpty(key, v string) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } if v == "" { return } @@ -152,6 +162,11 @@ func (enc *Encoder) StringKeyOmitEmpty(key, v string) { // StringKeyNullEmpty adds a string to be encoded or skips it if it is zero value. // Must be used inside an object as it will encode a key func (enc *Encoder) StringKeyNullEmpty(key, v string) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(len(key) + len(v) + 5) r := enc.getPreviousRune() if r != '{' { diff --git a/encode_time.go b/encode_time.go @@ -32,6 +32,11 @@ func (enc *Encoder) AddTimeKey(key string, t *time.Time, format string) { // TimeKey adds an *time.Time to be encoded with the given format, must be used inside an object as it will encode a key func (enc *Encoder) TimeKey(key string, t *time.Time, format string) { + if enc.hasKeys { + if !enc.keyExists(key) { + return + } + } enc.grow(10 + len(key)) r := enc.getPreviousRune() if r != '{' {