gojay

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

commit 8e6acaa77036a0122630fd41e99ce12c044b8114
parent 51842a2c8e91cafa642cf369872f6e65d433a8c3
Author: francoispqt <francois@parquet.ninja>
Date:   Tue,  1 May 2018 18:58:27 +0800

add IsNil to MarshalerArray interface, add support for OmitEmpy

Diffstat:
Mbenchmarks/benchmarks_large.go | 6++++++
Mbenchmarks/benchmarks_medium.go | 3+++
Mencode.go | 4+---
Mencode_array.go | 61++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mencode_array_test.go | 155++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mencode_bool.go | 32++++++++++++++++++++++++++++++--
Mencode_interface.go | 43+++++++++++++++++++++++++++++++++++++++++++
Mencode_number.go | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mencode_object.go | 54+++++++++++++++++++++++++++++++++++++++++++++++++++---
Mencode_object_test.go | 99++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mencode_string.go | 45++++++++++++++++++++++++++++++++++++++-------
11 files changed, 574 insertions(+), 35 deletions(-)

diff --git a/benchmarks/benchmarks_large.go b/benchmarks/benchmarks_large.go @@ -65,6 +65,9 @@ func (m *DSTopics) MarshalArray(enc *gojay.Encoder) { enc.AddObject(e) } } +func (m *DSTopics) IsNil() bool { + return m == nil +} type DSTopicsList struct { Topics DSTopics @@ -107,6 +110,9 @@ func (m *DSUsers) MarshalArray(enc *gojay.Encoder) { enc.AddObject(e) } } +func (m *DSUsers) IsNil() bool { + return m == nil +} type LargePayload struct { Users DSUsers diff --git a/benchmarks/benchmarks_medium.go b/benchmarks/benchmarks_medium.go @@ -133,6 +133,9 @@ func (m *Avatars) MarshalArray(enc *gojay.Encoder) { enc.AddObject(e) } } +func (m *Avatars) IsNil() bool { + return m == nil +} type CBGravatar struct { Avatars Avatars diff --git a/encode.go b/encode.go @@ -172,6 +172,7 @@ type MarshalerObject interface { // for a slice or an array to be encoded type MarshalerArray interface { MarshalArray(enc *Encoder) + IsNil() bool } // An Encoder writes JSON values to an output stream. @@ -184,9 +185,6 @@ type Encoder struct { func (enc *Encoder) getPreviousRune() (byte, bool) { last := len(enc.buf) - 1 - if last < 0 { - return 0, false - } return enc.buf[last], true } diff --git a/encode_array.go b/encode_array.go @@ -23,20 +23,54 @@ func (enc *Encoder) encodeArray(v MarshalerArray) ([]byte, error) { // 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) { +func (enc *Encoder) AddArray(v MarshalerArray) { + if v.IsNil() { + r, ok := enc.getPreviousRune() + if ok && r != '[' { + enc.writeByte(',') + } + enc.writeByte('[') + enc.writeByte(']') + return + } + r, ok := enc.getPreviousRune() + if ok && r != '[' { + enc.writeByte(',') + } + enc.writeByte('[') + v.MarshalArray(enc) + enc.writeByte(']') +} + +// AddArrayOmitEmpty 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) AddArrayOmitEmpty(v MarshalerArray) { + if v.IsNil() { + return + } r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') } enc.writeByte('[') - value.MarshalArray(enc) + v.MarshalArray(enc) enc.writeByte(']') } // AddArrayKey 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) AddArrayKey(key string, value MarshalerArray) { - // grow to avoid allocs (length of key/value + quotes) +func (enc *Encoder) AddArrayKey(key string, v MarshalerArray) { + if v.IsNil() { + r, ok := enc.getPreviousRune() + if ok && r != '[' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeString(key) + enc.writeBytes(objKeyArr) + enc.writeByte(']') + return + } r, ok := enc.getPreviousRune() if ok && r != '[' && r != '{' { enc.writeByte(',') @@ -44,6 +78,23 @@ func (enc *Encoder) AddArrayKey(key string, value MarshalerArray) { enc.writeByte('"') enc.writeString(key) enc.writeBytes(objKeyArr) - value.MarshalArray(enc) + v.MarshalArray(enc) + enc.writeByte(']') +} + +// AddArrayKeyOmitEmpty adds an array or slice to be encoded and skips it if it is nil. +// Must be called inside an object as it will encode a key. +func (enc *Encoder) AddArrayKeyOmitEmpty(key string, v MarshalerArray) { + if v.IsNil() { + return + } + r, ok := enc.getPreviousRune() + if ok && r != '[' && r != '{' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeString(key) + enc.writeBytes(objKeyArr) + v.MarshalArray(enc) enc.writeByte(']') } diff --git a/encode_array_test.go b/encode_array_test.go @@ -14,6 +14,9 @@ func (t TestEncodingArrStrings) MarshalArray(enc *Encoder) { enc.AddString(e) } } +func (t TestEncodingArrStrings) IsNil() bool { + return len(t) == 0 +} type TestEncodingArr []*TestEncoding @@ -22,6 +25,10 @@ func (t TestEncodingArr) MarshalArray(enc *Encoder) { enc.AddObject(e) } } +func (t TestEncodingArr) IsNil() bool { + return t == nil +} + func TestEncoderArrayObjects(t *testing.T) { v := &TestEncodingArr{ &TestEncoding{ @@ -59,6 +66,7 @@ func TestEncoderArrayObjects(t *testing.T) { }, }, }, + nil, } r, err := Marshal(v) assert.Nil(t, err, "Error should be nil") @@ -67,9 +75,9 @@ func TestEncoderArrayObjects(t *testing.T) { `[{"test":"hello world","test2":"漢字","testInt":1,"testBool":true,`+ `"testArr":[],"testF64":0,"testF32":0,"testInterface":1,"sub":{"test1":10,"test2":"hello world",`+ `"test3":1.23543,"testBool":true,"sub":{"test1":10,"test2":"hello world",`+ - `"test3":0,"testBool":false}}},{"test":"hello world","test2":"漢字","testInt":1,`+ + `"test3":0,"testBool":false,"sub":{}}}},{"test":"hello world","test2":"漢字","testInt":1,`+ `"testBool":true,"testArr":[],"testF64":0,"testF32":0,"sub":{"test1":10,"test2":"hello world","test3":1.23543,`+ - `"testBool":true,"sub":{"test1":10,"test2":"hello world","test3":0,"testBool":false}}}]`, + `"testBool":true,"sub":{"test1":10,"test2":"hello world","test3":0,"testBool":false,"sub":{}}}},{}]`, string(r), "Result of marshalling is different as the one expected") } @@ -81,6 +89,9 @@ func (t testEncodingArrInterfaces) MarshalArray(enc *Encoder) { enc.AddInterface(e) } } +func (t testEncodingArrInterfaces) IsNil() bool { + return t == nil +} func TestEncoderArrayInterfaces(t *testing.T) { v := &testEncodingArrInterfaces{ @@ -96,6 +107,7 @@ func TestEncoderArrayInterfaces(t *testing.T) { float64(1.31), float32(1.31), &TestEncodingArr{}, + &TestEncodingArrStrings{}, true, false, "test", @@ -110,7 +122,7 @@ func TestEncoderArrayInterfaces(t *testing.T) { assert.Nil(t, err, "Error should be nil") assert.Equal( t, - `[1,1,1,1,1,1,1,1,1.31,1.31,[],true,false,"test",{"test":"hello world","test2":"foobar","testInt":1,"testBool":true,"testArr":[],"testF64":0,"testF32":0}]`, + `[1,1,1,1,1,1,1,1,1.31,1.31,[],[],true,false,"test",{"test":"hello world","test2":"foobar","testInt":1,"testBool":true,"testArr":[],"testF64":0,"testF32":0,"sub":{}}]`, string(r), "Result of marshalling is different as the one expected") } @@ -145,7 +157,7 @@ func TestEncoderArrayInterfacesEncoderAPI(t *testing.T) { 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}]`, + `[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,"sub":{}}]`, builder.String(), "Result of marshalling is different as the one expected") } @@ -159,6 +171,141 @@ func TestEncoderArrayInterfacesEncoderAPIWriteError(t *testing.T) { assert.NotNil(t, err, "err should not be nil") } +// Array add with omit key tests + +type TestEncodingIntOmitEmpty []int + +func (t TestEncodingIntOmitEmpty) MarshalArray(enc *Encoder) { + for _, e := range t { + enc.AddIntOmitEmpty(e) + } +} +func (t TestEncodingIntOmitEmpty) IsNil() bool { + return t == nil +} + +type TestEncodingStringOmitEmpty []string + +func (t TestEncodingStringOmitEmpty) MarshalArray(enc *Encoder) { + for _, e := range t { + enc.AddStringOmitEmpty(e) + } +} +func (t TestEncodingStringOmitEmpty) IsNil() bool { + return t == nil +} + +type TestEncodingFloatOmitEmpty []float64 + +func (t TestEncodingFloatOmitEmpty) MarshalArray(enc *Encoder) { + for _, e := range t { + enc.AddFloatOmitEmpty(e) + } +} +func (t TestEncodingFloatOmitEmpty) IsNil() bool { + return t == nil +} + +type TestEncodingFloat32OmitEmpty []float32 + +func (t TestEncodingFloat32OmitEmpty) MarshalArray(enc *Encoder) { + for _, e := range t { + enc.AddFloat32OmitEmpty(e) + } +} +func (t TestEncodingFloat32OmitEmpty) IsNil() bool { + return t == nil +} + +type TestEncodingBoolOmitEmpty []bool + +func (t TestEncodingBoolOmitEmpty) MarshalArray(enc *Encoder) { + for _, e := range t { + enc.AddBoolOmitEmpty(e) + } +} +func (t TestEncodingBoolOmitEmpty) IsNil() bool { + return len(t) == 0 +} + +type TestEncodingArrOmitEmpty []TestEncodingBoolOmitEmpty + +func (t TestEncodingArrOmitEmpty) MarshalArray(enc *Encoder) { + for _, e := range t { + enc.AddArrayOmitEmpty(e) + } +} +func (t TestEncodingArrOmitEmpty) IsNil() bool { + return len(t) == 0 +} + +type TestObjEmpty struct { + empty bool +} + +func (t *TestObjEmpty) MarshalObject(enc *Encoder) { +} + +func (t *TestObjEmpty) IsNil() bool { + return !t.empty +} + +type TestEncodingObjOmitEmpty []*TestObjEmpty + +func (t TestEncodingObjOmitEmpty) MarshalArray(enc *Encoder) { + for _, e := range t { + enc.AddObjectOmitEmpty(e) + } +} +func (t TestEncodingObjOmitEmpty) IsNil() bool { + return t == nil +} + +func TestEncoderArrayOmitEmpty(t *testing.T) { + intArr := TestEncodingIntOmitEmpty{0, 1, 0, 1} + b, err := Marshal(intArr) + assert.Nil(t, err, "err must be nil") + assert.Equal(t, `[1,1]`, string(b), "string(b) must be equal to `[1,1]`") + + floatArr := TestEncodingFloatOmitEmpty{0, 1, 0, 1} + b, err = Marshal(floatArr) + assert.Nil(t, err, "err must be nil") + assert.Equal(t, `[1,1]`, string(b), "string(b) must be equal to `[1,1]`") + + float32Arr := TestEncodingFloat32OmitEmpty{0, 1, 0, 1} + b, err = Marshal(float32Arr) + assert.Nil(t, err, "err must be nil") + assert.Equal(t, `[1,1]`, string(b), "string(b) must be equal to `[1,1]`") + + stringArr := TestEncodingStringOmitEmpty{"", "hello", "", "world"} + b, err = Marshal(stringArr) + assert.Nil(t, err, "err must be nil") + assert.Equal(t, `["hello","world"]`, string(b), "string(b) must be equal to `[\"hello\",\"world\"]`") + + boolArr := TestEncodingBoolOmitEmpty{false, true, false, true} + b, err = Marshal(boolArr) + assert.Nil(t, err, "err must be nil") + assert.Equal(t, `[true,true]`, string(b), "string(b) must be equal to `[true,true]`") + + arrArr := TestEncodingArrOmitEmpty{TestEncodingBoolOmitEmpty{true}, nil, TestEncodingBoolOmitEmpty{true}, nil} + b, err = Marshal(arrArr) + assert.Nil(t, err, "err must be nil") + assert.Equal(t, `[[true],[true]]`, string(b), "string(b) must be equal to `[[true],[true]]`") + + objArr := TestEncodingObjOmitEmpty{&TestObjEmpty{true}, &TestObjEmpty{false}, &TestObjEmpty{true}, &TestObjEmpty{false}} + b, err = Marshal(objArr) + assert.Nil(t, err, "err must be nil") + assert.Equal(t, `[{},{}]`, string(b), "string(b) must be equal to `[{},{}]`") +} + +func TestEncoderAddInterfaceError(t *testing.T) { + builder := &strings.Builder{} + enc := NewEncoder(builder) + enc.AddInterface(nil) + assert.Nil(t, enc.err, "enc.Err() should not be nil") + assert.Equal(t, "", builder.String(), "builder.String() should not be ''") +} + func TestEncoderArrayPooledError(t *testing.T) { v := &testEncodingArrInterfaces{} enc := BorrowEncoder(nil) diff --git a/encode_bool.go b/encode_bool.go @@ -27,18 +27,30 @@ 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(value bool) { +func (enc *Encoder) AddBool(v bool) { r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') } - if value { + if v { enc.writeString("true") } else { enc.writeString("false") } } +// AddBoolOmitEmpty adds a bool to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddBoolOmitEmpty(v bool) { + if v == false { + return + } + r, ok := enc.getPreviousRune() + if ok && r != '[' { + enc.writeByte(',') + } + enc.writeString("true") +} + // 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) { r, ok := enc.getPreviousRune() @@ -50,3 +62,19 @@ func (enc *Encoder) AddBoolKey(key string, value bool) { enc.writeBytes(objKey) enc.buf = strconv.AppendBool(enc.buf, value) } + +// AddBoolKeyOmitEmpty 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) AddBoolKeyOmitEmpty(key string, v bool) { + if v == false { + return + } + r, ok := enc.getPreviousRune() + if ok && r != '{' && r != '[' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeString(key) + enc.writeBytes(objKey) + enc.buf = strconv.AppendBool(enc.buf, v) +} diff --git a/encode_interface.go b/encode_interface.go @@ -130,3 +130,46 @@ func (enc *Encoder) AddInterfaceKey(key string, value interface{}) { return } } + +// AddInterfaceKeyOmitEmpty adds an interface{} to be encoded, must be used inside an object as it will encode a key +func (enc *Encoder) AddInterfaceKeyOmitEmpty(key string, v interface{}) { + switch vt := v.(type) { + case string: + enc.AddStringKeyOmitEmpty(key, vt) + case bool: + enc.AddBoolKeyOmitEmpty(key, vt) + case MarshalerArray: + enc.AddArrayKeyOmitEmpty(key, vt) + case MarshalerObject: + enc.AddObjectKeyOmitEmpty(key, vt) + case int: + enc.AddIntKeyOmitEmpty(key, vt) + case int64: + enc.AddIntKeyOmitEmpty(key, int(vt)) + case int32: + enc.AddIntKeyOmitEmpty(key, int(vt)) + case int16: + enc.AddIntKeyOmitEmpty(key, int(vt)) + case int8: + enc.AddIntKeyOmitEmpty(key, int(vt)) + case uint64: + enc.AddIntKeyOmitEmpty(key, int(vt)) + case uint32: + enc.AddIntKeyOmitEmpty(key, int(vt)) + case uint16: + enc.AddIntKeyOmitEmpty(key, int(vt)) + case uint8: + enc.AddIntKeyOmitEmpty(key, int(vt)) + case float64: + enc.AddFloatKeyOmitEmpty(key, vt) + case float32: + enc.AddFloat32KeyOmitEmpty(key, vt) + default: + t := reflect.TypeOf(vt) + if t != nil { + enc.err = InvalidMarshalError(fmt.Sprintf(invalidMarshalErrorMsg, t.String())) + return + } + return + } +} diff --git a/encode_number.go b/encode_number.go @@ -78,34 +78,89 @@ 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(value int) { +func (enc *Encoder) AddInt(v int) { r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') } - enc.buf = strconv.AppendInt(enc.buf, int64(value), 10) + enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) +} + +// AddIntOmitEmpty adds an int 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) AddIntOmitEmpty(v int) { + if v == 0 { + return + } + r, ok := enc.getPreviousRune() + if ok && r != '[' { + enc.writeByte(',') + } + enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) } // 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(value float64) { +func (enc *Encoder) AddFloat(v float64) { r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') } - enc.buf = strconv.AppendFloat(enc.buf, value, 'f', -1, 64) + enc.buf = strconv.AppendFloat(enc.buf, v, 'f', -1, 64) +} + +// AddFloatOmitEmpty 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) AddFloatOmitEmpty(v float64) { + if v == 0 { + return + } + r, ok := enc.getPreviousRune() + if ok && r != '[' { + enc.writeByte(',') + } + enc.buf = strconv.AppendFloat(enc.buf, v, 'f', -1, 64) } // 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(value float32) { +func (enc *Encoder) AddFloat32(v float32) { r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') } - enc.buf = strconv.AppendFloat(enc.buf, float64(value), 'f', -1, 32) + enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) +} + +// AddFloat32OmitEmpty adds an int 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) AddFloat32OmitEmpty(v float32) { + if v == 0 { + return + } + r, ok := enc.getPreviousRune() + if ok && r != '[' { + enc.writeByte(',') + } + enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) } // 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, value int) { +func (enc *Encoder) AddIntKey(key string, v int) { + r, ok := enc.getPreviousRune() + if ok && r != '{' && r != '[' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeString(key) + enc.writeBytes(objKey) + enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) +} + +// AddIntKeyOmitEmpty 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) AddIntKeyOmitEmpty(key string, v int) { + if v == 0 { + return + } r, ok := enc.getPreviousRune() if ok && r != '{' && r != '[' { enc.writeByte(',') @@ -113,7 +168,7 @@ func (enc *Encoder) AddIntKey(key string, value int) { enc.writeByte('"') enc.writeString(key) enc.writeBytes(objKey) - enc.buf = strconv.AppendInt(enc.buf, int64(value), 10) + enc.buf = strconv.AppendInt(enc.buf, int64(v), 10) } // AddFloatKey adds a float64 to be encoded, must be used inside an object as it will encode a key @@ -128,8 +183,24 @@ func (enc *Encoder) AddFloatKey(key string, value float64) { enc.buf = strconv.AppendFloat(enc.buf, value, 'f', -1, 64) } +// AddFloatKeyOmitEmpty 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) AddFloatKeyOmitEmpty(key string, v float64) { + if v == 0 { + return + } + r, ok := enc.getPreviousRune() + if ok && r != '{' && r != '[' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeString(key) + enc.writeBytes(objKey) + enc.buf = strconv.AppendFloat(enc.buf, v, 'f', -1, 64) +} + // 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, value float32) { +func (enc *Encoder) AddFloat32Key(key string, v float32) { r, ok := enc.getPreviousRune() if ok && r != '{' && r != '[' { enc.writeByte(',') @@ -138,5 +209,21 @@ func (enc *Encoder) AddFloat32Key(key string, value float32) { enc.writeString(key) enc.writeByte('"') enc.writeByte(':') - enc.buf = strconv.AppendFloat(enc.buf, float64(value), 'f', -1, 32) + enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) +} + +// AddFloat32KeyOmitEmpty 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) AddFloat32KeyOmitEmpty(key string, v float32) { + if v == 0 { + return + } + r, ok := enc.getPreviousRune() + if ok && r != '{' && r != '[' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeString(key) + enc.writeBytes(objKey) + enc.buf = strconv.AppendFloat(enc.buf, float64(v), 'f', -1, 32) } diff --git a/encode_object.go b/encode_object.go @@ -32,8 +32,14 @@ func (enc *Encoder) encodeObject(v MarshalerObject) ([]byte, error) { // AddObject adds an object to be encoded, must be used inside a slice or array encoding (does not encode a key) // value must implement MarshalerObject -func (enc *Encoder) AddObject(value MarshalerObject) { - if value.IsNil() { +func (enc *Encoder) AddObject(v MarshalerObject) { + if v.IsNil() { + r, ok := enc.getPreviousRune() + if ok && r != '{' && r != '[' { + enc.writeByte(',') + } + enc.writeByte('{') + enc.writeByte('}') return } r, ok := enc.getPreviousRune() @@ -41,7 +47,23 @@ func (enc *Encoder) AddObject(value MarshalerObject) { enc.writeByte(',') } enc.writeByte('{') - value.MarshalObject(enc) + v.MarshalObject(enc) + enc.writeByte('}') +} + +// AddObjectOmitEmpty adds an object to be encoded or skips it if IsNil returns true. +// Must be used inside a slice or array encoding (does not encode a key) +// value must implement MarshalerObject +func (enc *Encoder) AddObjectOmitEmpty(v MarshalerObject) { + if v.IsNil() { + return + } + r, ok := enc.getPreviousRune() + if ok && r != '[' { + enc.writeByte(',') + } + enc.writeByte('{') + v.MarshalObject(enc) enc.writeByte('}') } @@ -49,6 +71,32 @@ func (enc *Encoder) AddObject(value MarshalerObject) { // value must implement MarshalerObject func (enc *Encoder) AddObjectKey(key string, value MarshalerObject) { if value.IsNil() { + r, ok := enc.getPreviousRune() + if ok && r != '{' && r != '[' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeString(key) + enc.writeBytes(objKeyObj) + enc.writeByte('}') + return + } + r, ok := enc.getPreviousRune() + if ok && r != '{' && r != '[' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeString(key) + enc.writeBytes(objKeyObj) + value.MarshalObject(enc) + enc.writeByte('}') +} + +// AddObjectKeyOmitEmpty adds an object to be encoded or skips it if IsNil returns true. +// Must be used inside a slice or array encoding (does not encode a key) +// value must implement MarshalerObject +func (enc *Encoder) AddObjectKeyOmitEmpty(key string, value MarshalerObject) { + if value.IsNil() { return } r, ok := enc.getPreviousRune() diff --git a/encode_object_test.go b/encode_object_test.go @@ -172,7 +172,7 @@ func TestEncoderObjectComplex(t *testing.T) { assert.Nil(t, err, "Error should be nil") assert.Equal( t, - `{"test":"hello world","test2":"foobar","testInt":1,"testBool":true,"testArr":[{"test":"1","test2":"","testInt":0,"testBool":false,"testArr":[],"testF64":0,"testF32":0}],"testF64":120.15,"testF32":120.53,"testInterface":true,"sub":{"test1":10,"test2":"hello world","test3":1.23543,"testBool":true,"sub":{"test1":10,"test2":"hello world","test3":0,"testBool":false}}}`, + `{"test":"hello world","test2":"foobar","testInt":1,"testBool":true,"testArr":[{"test":"1","test2":"","testInt":0,"testBool":false,"testArr":[],"testF64":0,"testF32":0,"sub":{}}],"testF64":120.15,"testF32":120.53,"testInterface":true,"sub":{"test1":10,"test2":"hello world","test3":1.23543,"testBool":true,"sub":{"test1":10,"test2":"hello world","test3":0,"testBool":false,"sub":{}}}}`, string(r), "Result of marshalling is different as the one expected", ) @@ -289,6 +289,103 @@ func TestEncoderObjectInterfaces(t *testing.T) { "Result of marshalling is different as the one expected") } +type TestObectOmitEmpty struct { + nonNiler int + testInt int + testFloat float64 + testFloat32 float32 + testString string + testBool bool + testObectOmitEmpty *TestObectOmitEmpty + testObect *TestObectOmitEmpty +} + +func (t *TestObectOmitEmpty) IsNil() bool { + return t == nil +} + +func (t *TestObectOmitEmpty) MarshalObject(enc *Encoder) { + enc.AddIntKeyOmitEmpty("testInt", t.testInt) + enc.AddIntKeyOmitEmpty("testIntNotEmpty", 1) + enc.AddFloatKeyOmitEmpty("testFloat", t.testFloat) + enc.AddFloatKeyOmitEmpty("testFloatNotEmpty", 1.1) + enc.AddFloat32KeyOmitEmpty("testFloat32", t.testFloat32) + enc.AddFloat32KeyOmitEmpty("testFloat32NotEmpty", 1.1) + enc.AddStringKeyOmitEmpty("testString", t.testString) + enc.AddStringKeyOmitEmpty("testStringNotEmpty", "foo") + enc.AddBoolKeyOmitEmpty("testBool", t.testBool) + enc.AddBoolKeyOmitEmpty("testBoolNotEmpty", true) + enc.AddObjectKeyOmitEmpty("testObect", t.testObect) + enc.AddObjectKeyOmitEmpty("testObectOmitEmpty", t.testObectOmitEmpty) + enc.AddArrayKeyOmitEmpty("testArrayOmitEmpty", TestEncodingArrStrings{}) + enc.AddArrayKeyOmitEmpty("testArray", TestEncodingArrStrings{"foo"}) +} + +func TestEncoderObjectOmitEmpty(t *testing.T) { + v := &TestObectOmitEmpty{ + nonNiler: 1, + testInt: 0, + testObect: &TestObectOmitEmpty{testInt: 1}, + } + r, err := MarshalObject(v) + assert.Nil(t, err, "Error should be nil") + assert.Equal( + t, + `{"testIntNotEmpty":1,"testFloatNotEmpty":1.1,"testFloat32NotEmpty":1.1,"testStringNotEmpty":"foo","testBoolNotEmpty":true,"testObect":{"testInt":1,"testIntNotEmpty":1,"testFloatNotEmpty":1.1,"testFloat32NotEmpty":1.1,"testStringNotEmpty":"foo","testBoolNotEmpty":true,"testArray":["foo"]},"testArray":["foo"]}`, + string(r), + "Result of marshalling is different as the one expected", + ) +} + +type TestObectOmitEmptyInterface struct{} + +func (t *TestObectOmitEmptyInterface) IsNil() bool { + return t == nil +} + +func (t *TestObectOmitEmptyInterface) MarshalObject(enc *Encoder) { + enc.AddInterfaceKeyOmitEmpty("testInt", 0) + enc.AddInterfaceKeyOmitEmpty("testInt64", int64(0)) + enc.AddInterfaceKeyOmitEmpty("testInt32", int32(0)) + enc.AddInterfaceKeyOmitEmpty("testInt16", int16(0)) + enc.AddInterfaceKeyOmitEmpty("testInt8", int8(0)) + enc.AddInterfaceKeyOmitEmpty("testUint8", uint8(0)) + enc.AddInterfaceKeyOmitEmpty("testUint16", uint16(0)) + enc.AddInterfaceKeyOmitEmpty("testUint32", uint32(0)) + enc.AddInterfaceKeyOmitEmpty("testUint64", uint64(0)) + enc.AddInterfaceKeyOmitEmpty("testIntNotEmpty", 1) + enc.AddInterfaceKeyOmitEmpty("testFloat", 0) + enc.AddInterfaceKeyOmitEmpty("testFloatNotEmpty", 1.1) + enc.AddInterfaceKeyOmitEmpty("testFloat32", float32(0)) + enc.AddInterfaceKeyOmitEmpty("testFloat32NotEmpty", float32(1.1)) + enc.AddInterfaceKeyOmitEmpty("testString", "") + enc.AddInterfaceKeyOmitEmpty("testStringNotEmpty", "foo") + enc.AddInterfaceKeyOmitEmpty("testBool", false) + enc.AddInterfaceKeyOmitEmpty("testBoolNotEmpty", true) + enc.AddInterfaceKeyOmitEmpty("testObectOmitEmpty", nil) + enc.AddInterfaceKeyOmitEmpty("testObect", &TestEncoding{}) + enc.AddInterfaceKeyOmitEmpty("testArr", &TestEncodingArrStrings{}) +} + +func TestEncoderObjectInterfaceOmitEmpty(t *testing.T) { + v := &TestObectOmitEmptyInterface{} + r, err := MarshalObject(v) + assert.Nil(t, err, "Error should be nil") + assert.Equal( + t, + `{"testIntNotEmpty":1,"testFloatNotEmpty":1.1,"testFloat32NotEmpty":1.1,"testStringNotEmpty":"foo","testBoolNotEmpty":true,"testObect":{"test":"","test2":"","testInt":0,"testBool":false,"testArr":[],"testF64":0,"testF32":0,"sub":{}}}`, + string(r), + "Result of marshalling is different as the one expected", + ) +} + +func TestEncoderAddInterfaceKeyError(t *testing.T) { + builder := &strings.Builder{} + enc := NewEncoder(builder) + enc.AddInterfaceKeyOmitEmpty("test", struct{}{}) + assert.NotNil(t, enc.err, "enc.Err() should not be nil") +} + func TestEncoderObjectPooledError(t *testing.T) { v := &TestEncoding{} enc := BorrowEncoder(nil) diff --git a/encode_string.go b/encode_string.go @@ -15,27 +15,58 @@ func (enc *Encoder) EncodeString(s string) error { } // encodeString encodes a string to -func (enc *Encoder) encodeString(s string) ([]byte, error) { +func (enc *Encoder) encodeString(v string) ([]byte, error) { enc.writeByte('"') - enc.writeString(s) + enc.writeString(v) enc.writeByte('"') return enc.buf, nil } // 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(value string) { +func (enc *Encoder) AddString(v string) { r, ok := enc.getPreviousRune() if ok && r != '[' { enc.writeByte(',') } enc.writeByte('"') - enc.writeString(value) + enc.writeString(v) + enc.writeByte('"') +} + +// AddStringOmitEmpty adds a string to be encoded or skips it if it is zero value. +// Must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddStringOmitEmpty(v string) { + if v == "" { + return + } + r, ok := enc.getPreviousRune() + if ok && r != '[' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeString(v) enc.writeByte('"') } // AddStringKey adds a string to be encoded, must be used inside an object as it will encode a key -func (enc *Encoder) AddStringKey(key, value string) { - // grow to avoid allocs (length of key/value + quotes) +func (enc *Encoder) AddStringKey(key, v string) { + r, ok := enc.getPreviousRune() + if ok && r != '{' && r != '[' { + enc.writeByte(',') + } + enc.writeByte('"') + enc.writeString(key) + enc.writeBytes(objKeyStr) + enc.writeString(v) + enc.writeByte('"') +} + +// AddStringKeyOmitEmpty 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) AddStringKeyOmitEmpty(key, v string) { + if v == "" { + return + } r, ok := enc.getPreviousRune() if ok && r != '{' && r != '[' { enc.writeByte(',') @@ -43,6 +74,6 @@ func (enc *Encoder) AddStringKey(key, value string) { enc.writeByte('"') enc.writeString(key) enc.writeBytes(objKeyStr) - enc.writeString(value) + enc.writeString(v) enc.writeByte('"') }