commit 80615bf03f9ed133e5fddcf16cc21d9ba1f9b727
parent 0d1a893740b14aedd39b5ade2e7567645a40eb0b
Author: francoispqt <francois@parquet.ninja>
Date: Wed, 29 Aug 2018 11:50:10 +0800
add feature to encode object and only specific keys to be encoded
Diffstat:
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 != '{' {