commit 9d4e062aaa3d62c36207fdbb969c1199bc71f5fd
parent 0d1a893740b14aedd39b5ade2e7567645a40eb0b
Author: Francois Parquet <francois.parquet@gmail.com>
Date: Wed, 29 Aug 2018 12:39:00 +0800
Merge pull request #67 from francoispqt/feature/decode-null-non-primitive
Feature/decode null non primitive
Diffstat:
M | decode.go | | | 60 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
M | decode_array.go | | | 67 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
M | decode_array_test.go | | | 43 | +++++++++++++++++++++++++++++++++++++++++++ |
M | decode_object.go | | | 105 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | decode_object_test.go | | | 197 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
M | errors.go | | | 5 | +++++ |
6 files changed, 471 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,9 +1,14 @@
package gojay
import (
+ "reflect"
"unsafe"
)
+type IsNiler interface {
+ IsNil() bool
+}
+
// DecodeObject reads the next JSON-encoded value from its input and stores it in the value pointed to by v.
//
// v must implement UnmarshalerJSONObject.
@@ -100,6 +105,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/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")