commit f5587822347e46494d4d53e918709c4db4ec2de2
parent 0d1a893740b14aedd39b5ade2e7567645a40eb0b
Author: Francois Parquet <francois.parquet@gmail.com>
Date: Wed, 29 Aug 2018 13:22:08 +0800
Merge pull request #70 from francoispqt/version/1.2.4
Version/1.2.4
Diffstat:
19 files changed, 967 insertions(+), 6 deletions(-)
diff --git a/decode.go b/decode.go
@@ -526,11 +526,21 @@ func (dec *Decoder) AddObject(v UnmarshalerJSONObject) error {
return dec.Object(v)
}
+// AddObjectNull decodes the next key to a UnmarshalerJSONObject.
+func (dec *Decoder) AddObjectNull(v interface{}) error {
+ return dec.ObjectNull(v)
+}
+
// AddArray decodes the next key to a UnmarshalerJSONArray.
func (dec *Decoder) AddArray(v UnmarshalerJSONArray) error {
return dec.Array(v)
}
+// AddArray decodes the next key to a UnmarshalerJSONArray.
+func (dec *Decoder) AddArrayNull(v UnmarshalerJSONArray) error {
+ return dec.ArrayNull(v)
+}
+
// AddInterface decodes the next key to a interface{}.
func (dec *Decoder) AddInterface(v *interface{}) error {
return dec.Interface(v)
@@ -870,9 +880,44 @@ func (dec *Decoder) Object(value UnmarshalerJSONObject) error {
return nil
}
+// ObjectNull decodes the next key to a UnmarshalerJSONObject.
+// v should be a pointer to an UnmarshalerJSONObject,
+// if `null` value is encountered in JSON, it will leave the value v untouched,
+// else it will create a new instance of the UnmarshalerJSONObject behind v.
+func (dec *Decoder) ObjectNull(v interface{}) error {
+ initialKeysDone := dec.keysDone
+ initialChild := dec.child
+ dec.keysDone = 0
+ dec.called = 0
+ dec.child |= 1
+ newCursor, err := dec.decodeObjectNull(v)
+ if err != nil {
+ return err
+ }
+ dec.cursor = newCursor
+ dec.keysDone = initialKeysDone
+ dec.child = initialChild
+ dec.called |= 1
+ return nil
+}
+
// Array decodes the next key to a UnmarshalerJSONArray.
-func (dec *Decoder) Array(value UnmarshalerJSONArray) error {
- newCursor, err := dec.decodeArray(value)
+func (dec *Decoder) Array(v UnmarshalerJSONArray) error {
+ newCursor, err := dec.decodeArray(v)
+ if err != nil {
+ return err
+ }
+ dec.cursor = newCursor
+ dec.called |= 1
+ return nil
+}
+
+// ArrayNull decodes the next key to a UnmarshalerJSONArray.
+// v should be a pointer to an UnmarshalerJSONArray,
+// if `null` value is encountered in JSON, it will leave the value v untouched,
+// else it will create a new instance of the UnmarshalerJSONArray behind v.
+func (dec *Decoder) ArrayNull(v interface{}) error {
+ newCursor, err := dec.decodeArrayNull(v)
if err != nil {
return err
}
@@ -891,6 +936,17 @@ func (dec *Decoder) Interface(value *interface{}) error {
return nil
}
+// Array decodes the next key to a UnmarshalerJSONArray.
+// func (dec *Decoder) ArrayNull(factory func() UnmarshalerJSONArray) error {
+// newCursor, err := dec.decodeArrayNull(factory)
+// if err != nil {
+// return err
+// }
+// dec.cursor = newCursor
+// dec.called |= 1
+// return nil
+// }
+
// Non exported
func isDigit(b byte) bool {
diff --git a/decode_array.go b/decode_array.go
@@ -1,5 +1,7 @@
package gojay
+import "reflect"
+
// DecodeArray reads the next JSON-encoded value from its input and stores it in the value pointed to by v.
//
// v must implement UnmarshalerJSONArray.
@@ -13,6 +15,57 @@ func (dec *Decoder) DecodeArray(arr UnmarshalerJSONArray) error {
return err
}
func (dec *Decoder) decodeArray(arr UnmarshalerJSONArray) (int, error) {
+ for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
+ switch dec.data[dec.cursor] {
+ case ' ', '\n', '\t', '\r', ',':
+ continue
+ case '[':
+ dec.cursor = dec.cursor + 1
+ // array is open, char is not space start readings
+ for dec.nextChar() != 0 {
+ // closing array
+ if dec.data[dec.cursor] == ']' {
+ dec.cursor = dec.cursor + 1
+ return dec.cursor, nil
+ }
+ // calling unmarshall function for each element of the slice
+ err := arr.UnmarshalJSONArray(dec)
+ if err != nil {
+ return 0, err
+ }
+ }
+ return 0, dec.raiseInvalidJSONErr(dec.cursor)
+ case 'n':
+ // is null
+ dec.cursor++
+ err := dec.assertNull()
+ if err != nil {
+ return 0, err
+ }
+ dec.cursor++
+ return dec.cursor, nil
+ case '{', '"', 'f', 't', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ // can't unmarshall to struct
+ // we skip array and set Error
+ dec.err = dec.makeInvalidUnmarshalErr(arr)
+ err := dec.skipData()
+ if err != nil {
+ return 0, err
+ }
+ return dec.cursor, nil
+ default:
+ return 0, dec.raiseInvalidJSONErr(dec.cursor)
+ }
+ }
+ return 0, dec.raiseInvalidJSONErr(dec.cursor)
+}
+func (dec *Decoder) decodeArrayNull(v interface{}) (int, error) {
+ vv := reflect.ValueOf(v)
+ vvt := vv.Type()
+ if vvt.Kind() != reflect.Ptr || vvt.Elem().Kind() != reflect.Ptr {
+ dec.err = ErrUnmarshalPtrExpected
+ return 0, dec.err
+ }
// not an array not an error, but do not know what to do
// do not check syntax
for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
@@ -20,12 +73,21 @@ func (dec *Decoder) decodeArray(arr UnmarshalerJSONArray) (int, error) {
case ' ', '\n', '\t', '\r', ',':
continue
case '[':
- n := 0
dec.cursor = dec.cursor + 1
+ // create our new type
+ elt := vv.Elem()
+ n := reflect.New(elt.Type().Elem())
+ var arr UnmarshalerJSONArray
+ var ok bool
+ if arr, ok = n.Interface().(UnmarshalerJSONArray); !ok {
+ dec.err = dec.makeInvalidUnmarshalErr((UnmarshalerJSONArray)(nil))
+ return 0, dec.err
+ }
// array is open, char is not space start readings
for dec.nextChar() != 0 {
// closing array
if dec.data[dec.cursor] == ']' {
+ elt.Set(n)
dec.cursor = dec.cursor + 1
return dec.cursor, nil
}
@@ -34,7 +96,6 @@ func (dec *Decoder) decodeArray(arr UnmarshalerJSONArray) (int, error) {
if err != nil {
return 0, err
}
- n++
}
return 0, dec.raiseInvalidJSONErr(dec.cursor)
case 'n':
@@ -49,7 +110,7 @@ func (dec *Decoder) decodeArray(arr UnmarshalerJSONArray) (int, error) {
case '{', '"', 'f', 't', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
// can't unmarshall to struct
// we skip array and set Error
- dec.err = dec.makeInvalidUnmarshalErr(arr)
+ dec.err = dec.makeInvalidUnmarshalErr((UnmarshalerJSONArray)(nil))
err := dec.skipData()
if err != nil {
return 0, err
diff --git a/decode_array_test.go b/decode_array_test.go
@@ -371,6 +371,49 @@ func TestSliceObjects(t *testing.T) {
}
}
+type ArrayNull []string
+
+func (a *ArrayNull) UnmarshalJSONArray(dec *Decoder) error {
+ var str string
+ if err := dec.String(&str); err != nil {
+ return err
+ }
+ *a = append(*a, str)
+ return nil
+}
+
+type ObjectArrayNull struct {
+ SubArray *ArrayNull
+}
+
+func (o *ObjectArrayNull) UnmarshalJSONObject(dec *Decoder, k string) error {
+ switch k {
+ case "subarray":
+ return dec.ArrayNull(&o.SubArray)
+ }
+ return nil
+}
+
+func (o *ObjectArrayNull) NKeys() int {
+ return 1
+}
+
+func TestDecodeArrayNullPtr(t *testing.T) {
+ t.Run("sub obj should not be nil", func(t *testing.T) {
+ var o = &ObjectArrayNull{}
+ var err = UnmarshalJSONObject([]byte(`{"subarray": ["test"]}`), o)
+ assert.Nil(t, err)
+ assert.NotNil(t, o.SubArray)
+ assert.Len(t, *o.SubArray, 1)
+ })
+ t.Run("sub array should be nil", func(t *testing.T) {
+ var o = &ObjectArrayNull{}
+ var err = UnmarshalJSONObject([]byte(`{"subarray": null}`), o)
+ assert.Nil(t, err)
+ assert.Nil(t, o.SubArray)
+ })
+}
+
type testChannelArray chan *TestObj
func (c *testChannelArray) UnmarshalJSONArray(dec *Decoder) error {
diff --git a/decode_object.go b/decode_object.go
@@ -1,6 +1,7 @@
package gojay
import (
+ "reflect"
"unsafe"
)
@@ -100,6 +101,106 @@ func (dec *Decoder) decodeObject(j UnmarshalerJSONObject) (int, error) {
return 0, dec.raiseInvalidJSONErr(dec.cursor)
}
+func (dec *Decoder) decodeObjectNull(v interface{}) (int, error) {
+ // make sure the value is a pointer
+ vv := reflect.ValueOf(v)
+ vvt := vv.Type()
+ if vvt.Kind() != reflect.Ptr || vvt.Elem().Kind() != reflect.Ptr {
+ dec.err = ErrUnmarshalPtrExpected
+ return 0, dec.err
+ }
+ for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
+ switch dec.data[dec.cursor] {
+ case ' ', '\n', '\t', '\r', ',':
+ case '{':
+ elt := vv.Elem()
+ n := reflect.New(elt.Type().Elem())
+ elt.Set(n)
+ var j UnmarshalerJSONObject
+ var ok bool
+ if j, ok = n.Interface().(UnmarshalerJSONObject); !ok {
+ dec.err = dec.makeInvalidUnmarshalErr((UnmarshalerJSONObject)(nil))
+ return 0, dec.err
+ }
+ keys := j.NKeys()
+ dec.cursor = dec.cursor + 1
+ // if keys is zero we will parse all keys
+ // we run two loops for micro optimization
+ if keys == 0 {
+ for dec.cursor < dec.length || dec.read() {
+ k, done, err := dec.nextKey()
+ if err != nil {
+ return 0, err
+ } else if done {
+ return dec.cursor, nil
+ }
+ err = j.UnmarshalJSONObject(dec, k)
+ if err != nil {
+ dec.err = err
+ return 0, err
+ } else if dec.called&1 == 0 {
+ err := dec.skipData()
+ if err != nil {
+ return 0, err
+ }
+ } else {
+ dec.keysDone++
+ }
+ dec.called &= 0
+ }
+ } else {
+ for (dec.cursor < dec.length || dec.read()) && dec.keysDone < keys {
+ k, done, err := dec.nextKey()
+ if err != nil {
+ return 0, err
+ } else if done {
+ return dec.cursor, nil
+ }
+ err = j.UnmarshalJSONObject(dec, k)
+ if err != nil {
+ dec.err = err
+ return 0, err
+ } else if dec.called&1 == 0 {
+ err := dec.skipData()
+ if err != nil {
+ return 0, err
+ }
+ } else {
+ dec.keysDone++
+ }
+ dec.called &= 0
+ }
+ }
+ // will get to that point when keysDone is not lower than keys anymore
+ // in that case, we make sure cursor goes to the end of object, but we skip
+ // unmarshalling
+ if dec.child&1 != 0 {
+ end, err := dec.skipObject()
+ dec.cursor = end
+ return dec.cursor, err
+ }
+ return dec.cursor, nil
+ case 'n':
+ dec.cursor++
+ err := dec.assertNull()
+ if err != nil {
+ return 0, err
+ }
+ dec.cursor++
+ return dec.cursor, nil
+ default:
+ // can't unmarshal to struct
+ dec.err = dec.makeInvalidUnmarshalErr((UnmarshalerJSONObject)(nil))
+ err := dec.skipData()
+ if err != nil {
+ return 0, err
+ }
+ return dec.cursor, nil
+ }
+ }
+ return 0, dec.raiseInvalidJSONErr(dec.cursor)
+}
+
func (dec *Decoder) skipObject() (int, error) {
var objectsOpen = 1
var objectsClosed = 0
diff --git a/decode_object_test.go b/decode_object_test.go
@@ -887,6 +887,201 @@ func TestDecodeObjectBasic0Keys(t *testing.T) {
}
}
+type ObjectNull struct {
+ SubObject *ObjectNull
+ SubArray *testSliceBools
+}
+
+func (o *ObjectNull) UnmarshalJSONObject(dec *Decoder, k string) error {
+ switch k {
+ case "subobject":
+ return dec.ObjectNull(&o.SubObject)
+ case "subarray":
+ return dec.ArrayNull(&o.SubArray)
+ }
+ return nil
+}
+
+func (o *ObjectNull) NKeys() int {
+ return 2
+}
+
+func TestDecodeObjectNull(t *testing.T) {
+ t.Run("sub obj should not be nil", func(t *testing.T) {
+ var o = &ObjectNull{}
+ var err = UnmarshalJSONObject([]byte(`{"subobject": {},"subarray":[true]}`), o)
+ assert.Nil(t, err)
+ assert.NotNil(t, o.SubObject)
+ assert.NotNil(t, o.SubArray)
+ })
+ t.Run("sub obj and sub array should be nil", func(t *testing.T) {
+ var o = &ObjectNull{}
+ var err = UnmarshalJSONObject([]byte(`{"subobject": null,"subarray": null}`), o)
+ assert.Nil(t, err)
+ assert.Nil(t, o.SubObject)
+ assert.Nil(t, o.SubArray)
+ })
+ t.Run(
+ "sub obj should not be be nil",
+ func(t *testing.T) {
+ var o = &ObjectNull{}
+ var err = UnmarshalJSONObject([]byte(`{"subobject":{"subobject":{}}}`), DecodeObjectFunc(func(dec *Decoder, k string) error {
+ return dec.ObjectNull(&o.SubObject)
+ }))
+ assert.Nil(t, err)
+ assert.NotNil(t, o.SubObject)
+ },
+ )
+ t.Run(
+ "sub obj should be nil",
+ func(t *testing.T) {
+ var o = &ObjectNull{}
+ var err = UnmarshalJSONObject([]byte(`{"subobject":null}`), DecodeObjectFunc(func(dec *Decoder, k string) error {
+ return dec.ObjectNull(&o.SubObject)
+ }))
+ assert.Nil(t, err)
+ assert.Nil(t, o.SubObject)
+ },
+ )
+ t.Run(
+ "should return an error as type is not ptr",
+ func(t *testing.T) {
+ var err = UnmarshalJSONObject([]byte(`{"key":{}}`), DecodeObjectFunc(func(dec *Decoder, k string) error {
+ return dec.ObjectNull("")
+ }))
+ assert.NotNil(t, err)
+ assert.Equal(t, ErrUnmarshalPtrExpected, err)
+ },
+ )
+ t.Run(
+ "should return an error as type is not ptr",
+ func(t *testing.T) {
+ var err = UnmarshalJSONObject([]byte(`{"key":[]}`), DecodeObjectFunc(func(dec *Decoder, k string) error {
+ return dec.ArrayNull("")
+ }))
+ assert.NotNil(t, err)
+ assert.Equal(t, ErrUnmarshalPtrExpected, err)
+ },
+ )
+ t.Run(
+ "should return an error as type is not ptr to UnmarshalerJSONObject",
+ func(t *testing.T) {
+ var err = UnmarshalJSONObject([]byte(`{"key":{}}`), DecodeObjectFunc(func(dec *Decoder, k string) error {
+ var strPtr = new(string)
+ return dec.ObjectNull(&strPtr)
+ }))
+ assert.NotNil(t, err)
+ assert.IsType(t, InvalidUnmarshalError(""), err)
+ },
+ )
+ t.Run(
+ "should return an error as type is not ptr to UnmarshalerJSONObject",
+ func(t *testing.T) {
+ var err = UnmarshalJSONObject([]byte(`{"key":[]}`), DecodeObjectFunc(func(dec *Decoder, k string) error {
+ var strPtr = new(string)
+ return dec.ArrayNull(&strPtr)
+ }))
+ assert.NotNil(t, err)
+ assert.IsType(t, InvalidUnmarshalError(""), err)
+ },
+ )
+ t.Run(
+ "should return an error as type is not ptr to UnmarshalerJSONObject",
+ func(t *testing.T) {
+ var err = UnmarshalJSONObject([]byte(`{"key":{}}`), DecodeObjectFunc(func(dec *Decoder, k string) error {
+ var strPtr = new(string)
+ return dec.ArrayNull(&strPtr)
+ }))
+ assert.NotNil(t, err)
+ assert.IsType(t, InvalidUnmarshalError(""), err)
+ },
+ )
+ t.Run(
+ "should return an error as type is not ptr to UnmarshalerJSONObject",
+ func(t *testing.T) {
+ var err = UnmarshalJSONObject([]byte(`{"key":"`), DecodeObjectFunc(func(dec *Decoder, k string) error {
+ var strPtr = new(string)
+ return dec.ArrayNull(&strPtr)
+ }))
+ assert.NotNil(t, err)
+ assert.IsType(t, InvalidJSONError(""), err)
+ },
+ )
+ t.Run(
+ "skip data",
+ func(t *testing.T) {
+ var err = UnmarshalJSONObject([]byte(`{"key": ""}`), DecodeObjectFunc(func(dec *Decoder, k string) error {
+ var strPtr = new(string)
+ return dec.ObjectNull(&strPtr)
+ }))
+ assert.NotNil(t, err)
+ assert.IsType(t, InvalidUnmarshalError(""), err)
+ },
+ )
+ t.Run(
+ "invalid JSON for object",
+ func(t *testing.T) {
+ var o = &ObjectNull{}
+ var err = UnmarshalJSONObject([]byte(`{"subobject":{"subobject":a}`), DecodeObjectFunc(func(dec *Decoder, k string) error {
+ return dec.ObjectNull(&o.SubObject)
+ }))
+ assert.NotNil(t, err)
+ assert.IsType(t, InvalidJSONError(""), err)
+ },
+ )
+ t.Run(
+ "invalid JSON for object",
+ func(t *testing.T) {
+ var o = &testSliceBools{}
+ var err = UnmarshalJSONObject([]byte(`{"subobject":a`), DecodeObjectFunc(func(dec *Decoder, k string) error {
+ return dec.ArrayNull(&o)
+ }))
+ assert.NotNil(t, err)
+ assert.IsType(t, InvalidJSONError(""), err)
+ },
+ )
+ t.Run(
+ "invalid JSON for object",
+ func(t *testing.T) {
+ var err = UnmarshalJSONObject([]byte(`{"key":a`), DecodeObjectFunc(func(dec *Decoder, k string) error {
+ var strPtr = new(string)
+ return dec.ObjectNull(&strPtr)
+ }))
+ assert.NotNil(t, err)
+ assert.IsType(t, InvalidJSONError(""), err)
+ },
+ )
+ t.Run(
+ "invalid JSON for object",
+ func(t *testing.T) {
+ var err = UnmarshalJSONObject([]byte(`{"subobject": {},"}`), DecodeObjectFunc(func(dec *Decoder, k string) error {
+ var o = &ObjectNull{}
+ return dec.ObjectNull(&o)
+ }))
+ assert.NotNil(t, err)
+ assert.IsType(t, InvalidJSONError(""), err)
+ },
+ )
+ t.Run(
+ "invalid JSON for object",
+ func(t *testing.T) {
+ var o = &ObjectNull{}
+ var err = UnmarshalJSONObject([]byte(`{"subobject": a`), o)
+ assert.NotNil(t, err)
+ assert.IsType(t, InvalidJSONError(""), err)
+ },
+ )
+ t.Run(
+ "invalid JSON for object",
+ func(t *testing.T) {
+ var o = &ObjectNull{}
+ var err = UnmarshalJSONObject([]byte(`{"subobject": na`), o)
+ assert.NotNil(t, err)
+ assert.IsType(t, InvalidJSONError(""), err)
+ },
+ )
+}
+
func TestDecodeObjectComplex(t *testing.T) {
testCases := []struct {
name string
@@ -1038,7 +1233,7 @@ func TestDecoderObject(t *testing.T) {
assertResult(t, v, err)
}
-func TestDecodeObjectNull(t *testing.T) {
+func TestDecodeObjectJSONNull(t *testing.T) {
json := []byte(`null`)
v := &TestObj{}
err := Unmarshal(json, v)
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
}
@@ -96,6 +120,35 @@ func (enc *Encoder) Object(v MarshalerJSONObject) {
enc.writeByte('}')
}
+// ObjectWithKeys adds an object to be encoded, must be used inside a slice or array encoding (does not encode a key)
+// value must implement MarshalerJSONObject. It will only encode the keys in keys.
+func (enc *Encoder) ObjectWithKeys(v MarshalerJSONObject, keys []string) {
+ if v.IsNil() {
+ enc.grow(2)
+ r := enc.getPreviousRune()
+ if r != '{' && r != '[' {
+ enc.writeByte(',')
+ }
+ enc.writeByte('{')
+ enc.writeByte('}')
+ return
+ }
+ enc.grow(4)
+ r := enc.getPreviousRune()
+ if r != '[' {
+ enc.writeByte(',')
+ }
+ enc.writeByte('{')
+ var origKeys = enc.keys
+ var origHasKeys = enc.hasKeys
+ enc.hasKeys = true
+ enc.keys = keys
+ v.MarshalJSONObject(enc)
+ enc.hasKeys = origHasKeys
+ enc.keys = origKeys
+ enc.writeByte('}')
+}
+
// ObjectOmitEmpty 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 MarshalerJSONObject
@@ -134,6 +187,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()
@@ -158,10 +216,53 @@ func (enc *Encoder) ObjectKey(key string, value MarshalerJSONObject) {
enc.writeByte('}')
}
+// ObjectKeyWithKeys adds a struct to be encoded, must be used inside an object as it will encode a key.
+// Value must implement MarshalerJSONObject. It will only encode the keys in keys.
+func (enc *Encoder) ObjectKeyWithKeys(key string, value MarshalerJSONObject, keys []string) {
+ if enc.hasKeys {
+ if !enc.keyExists(key) {
+ return
+ }
+ }
+ if value.IsNil() {
+ enc.grow(2 + len(key))
+ r := enc.getPreviousRune()
+ if r != '{' {
+ enc.writeByte(',')
+ }
+ enc.writeByte('"')
+ enc.writeStringEscape(key)
+ enc.writeBytes(objKeyObj)
+ enc.writeByte('}')
+ return
+ }
+ enc.grow(5 + len(key))
+ r := enc.getPreviousRune()
+ if r != '{' {
+ enc.writeByte(',')
+ }
+ enc.writeByte('"')
+ enc.writeStringEscape(key)
+ enc.writeBytes(objKeyObj)
+ var origKeys = enc.keys
+ var origHasKeys = enc.hasKeys
+ enc.hasKeys = true
+ enc.keys = keys
+ value.MarshalJSONObject(enc)
+ enc.hasKeys = origHasKeys
+ enc.keys = origKeys
+ enc.writeByte('}')
+}
+
// ObjectKeyOmitEmpty 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 MarshalerJSONObject
func (enc *Encoder) ObjectKeyOmitEmpty(key string, value MarshalerJSONObject) {
+ if enc.hasKeys {
+ if !enc.keyExists(key) {
+ return
+ }
+ }
if value.IsNil() {
return
}
@@ -181,6 +282,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 +322,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,245 @@ 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
+}
+
+type NilObject struct{}
+
+func (n *NilObject) MarshalJSONObject(enc *Encoder) {}
+func (n *NilObject) IsNil() bool { return true }
+
+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'")
+ })
+ t.Run("encode-object-with-keys", func(t *testing.T) {
+ b := &strings.Builder{}
+ enc := NewEncoder(b)
+ err := enc.EncodeObjectKeys(EncodeObjectFunc(func(enc *Encoder) {
+ enc.ObjectKeyWithKeys("test", EncodeObjectFunc(func(enc *Encoder) {
+ enc.StringKey("test", "hello")
+ enc.StringKey("test2", "hello")
+ }), []string{"test"})
+ }), []string{})
+ assert.Nil(t, err, "Error should not be nil")
+ assert.Equal(t, `{}`, b.String())
+ })
+ t.Run("encode-object-with-keys", func(t *testing.T) {
+ b := &strings.Builder{}
+ enc := NewEncoder(b)
+ err := enc.EncodeObject(EncodeObjectFunc(func(enc *Encoder) {
+ enc.ObjectKeyWithKeys("test", EncodeObjectFunc(func(enc *Encoder) {
+ enc.keys = nil
+ enc.StringKey("test", "hello")
+ enc.StringKey("test2", "hello")
+ }), []string{"test"})
+ }))
+ assert.Nil(t, err, "Error should not be nil")
+ assert.Equal(t, `{"test":{}}`, b.String())
+ })
+ t.Run("encode-object-with-keys", func(t *testing.T) {
+ b := &strings.Builder{}
+ enc := NewEncoder(b)
+ err := enc.EncodeObject(EncodeObjectFunc(func(enc *Encoder) {
+ enc.ObjectKeyWithKeys("test", EncodeObjectFunc(func(enc *Encoder) {
+ enc.StringKey("test", "hello")
+ enc.StringKey("test2", "hello")
+ }), []string{"test"})
+ }))
+ assert.Nil(t, err, "Error should not be nil")
+ assert.Equal(t, `{"test":{"test":"hello"}}`, b.String())
+ })
+ t.Run("encode-object-with-keys", func(t *testing.T) {
+ b := &strings.Builder{}
+ enc := NewEncoder(b)
+ err := enc.EncodeObject(EncodeObjectFunc(func(enc *Encoder) {
+ enc.writeByte(' ')
+ enc.ObjectKeyWithKeys("test", EncodeObjectFunc(func(enc *Encoder) {
+ enc.StringKey("test", "hello")
+ enc.StringKey("test2", "hello")
+ }), []string{"test"})
+ }))
+ assert.Nil(t, err, "Error should not be nil")
+ assert.Equal(t, `{ ,"test":{"test":"hello"}}`, b.String())
+ })
+ t.Run("encode-object-with-keys", func(t *testing.T) {
+ b := &strings.Builder{}
+ enc := NewEncoder(b)
+ err := enc.EncodeObject(EncodeObjectFunc(func(enc *Encoder) {
+ enc.writeByte(' ')
+ enc.ObjectKeyWithKeys("test", &NilObject{}, []string{})
+ }))
+ assert.Nil(t, err, "Error should not be nil")
+ assert.Equal(t, `{ ,"test":{}}`, b.String())
+ })
+ t.Run("encode-object-with-keys", func(t *testing.T) {
+ b := &strings.Builder{}
+ enc := NewEncoder(b)
+ err := enc.EncodeArray(EncodeArrayFunc(func(enc *Encoder) {
+ enc.ObjectWithKeys(EncodeObjectFunc(func(enc *Encoder) {
+ enc.StringKey("test", "hello")
+ enc.StringKey("test2", "hello")
+ }), []string{"test"})
+ }))
+ assert.Nil(t, err, "Error should not be nil")
+ assert.Equal(t, `[{"test":"hello"}]`, b.String())
+ })
+ t.Run("encode-object-with-keys", func(t *testing.T) {
+ b := &strings.Builder{}
+ enc := NewEncoder(b)
+ err := enc.EncodeArray(EncodeArrayFunc(func(enc *Encoder) {
+ enc.writeByte(' ')
+ enc.ObjectWithKeys(EncodeObjectFunc(func(enc *Encoder) {
+ enc.StringKey("test", "hello")
+ enc.StringKey("test2", "hello")
+ }), []string{"test"})
+ }))
+ assert.Nil(t, err, "Error should not be nil")
+ assert.Equal(t, `[ ,{"test":"hello"}]`, b.String())
+ })
+ t.Run("encode-object-with-keys", func(t *testing.T) {
+ b := &strings.Builder{}
+ enc := NewEncoder(b)
+ err := enc.EncodeArray(EncodeArrayFunc(func(enc *Encoder) {
+ enc.ObjectWithKeys(&NilObject{}, []string{})
+ }))
+ assert.Nil(t, err, "Error should not be nil")
+ assert.Equal(t, `[{}]`, b.String())
+ })
+ t.Run("encode-object-with-keys", func(t *testing.T) {
+ b := &strings.Builder{}
+ enc := NewEncoder(b)
+ err := enc.EncodeArray(EncodeArrayFunc(func(enc *Encoder) {
+ enc.writeByte(' ')
+ enc.ObjectWithKeys(&NilObject{}, []string{})
+ }))
+ assert.Nil(t, err, "Error should not be nil")
+ assert.Equal(t, `[ ,{}]`, b.String())
+ })
+}
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 != '{' {
diff --git a/errors.go b/errors.go
@@ -1,6 +1,7 @@
package gojay
import (
+ "errors"
"fmt"
)
@@ -81,3 +82,7 @@ type InvalidUsagePooledEncoderError string
func (err InvalidUsagePooledEncoderError) Error() string {
return string(err)
}
+
+// ErrUnmarshalPtrExpected is the error returned when unmarshal expects a pointer value,
+// When using `dec.ObjectNull` or `dec.ArrayNull` for example.
+var ErrUnmarshalPtrExpected = errors.New("Cannot unmarshal to given value, a pointer is expected")