gojay

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

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:
Mdecode.go | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mdecode_array.go | 67++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mdecode_array_test.go | 43+++++++++++++++++++++++++++++++++++++++++++
Mdecode_object.go | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdecode_object_test.go | 197++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mencode.go | 2++
Mencode_array.go | 15+++++++++++++++
Mencode_bool.go | 15+++++++++++++++
Mencode_embedded_json.go | 10++++++++++
Mencode_null.go | 5+++++
Mencode_number_float.go | 30++++++++++++++++++++++++++++++
Mencode_number_int.go | 25+++++++++++++++++++++++++
Mencode_number_uint.go | 15+++++++++++++++
Mencode_object.go | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mencode_object_test.go | 243+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mencode_pool.go | 2++
Mencode_string.go | 15+++++++++++++++
Mencode_time.go | 5+++++
Merrors.go | 5+++++
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")