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:
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('"')
}