commit aa59043b98859004e1f91522a48730eb0c0d3deb
parent cef72a4f0bf03972e0264c36d0d1bad4884f8784
Author: francoispqt <francois@parquet.ninja>
Date: Tue, 28 Aug 2018 23:50:14 +0800
add decode ObjectNull and ArrayNull
Diffstat:
6 files changed, 188 insertions(+), 157 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)
@@ -871,13 +881,16 @@ func (dec *Decoder) Object(value UnmarshalerJSONObject) error {
}
// ObjectNull decodes the next key to a UnmarshalerJSONObject.
-func (dec *Decoder) ObjectNullFactory(factory func() UnmarshalerJSONObject) error {
+// 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(factory)
+ newCursor, err := dec.decodeObjectNull(v)
if err != nil {
return err
}
@@ -888,27 +901,23 @@ func (dec *Decoder) ObjectNullFactory(factory func() UnmarshalerJSONObject) erro
return nil
}
-// ObjectNull decodes the next key to a UnmarshalerJSONObject.
-func (dec *Decoder) ObjectNullReflect(v interface{}) error {
- initialKeysDone := dec.keysDone
- initialChild := dec.child
- dec.keysDone = 0
- dec.called = 0
- dec.child |= 1
- newCursor, err := dec.decodeObjectNullReflect(v)
+// Array decodes the next key to a UnmarshalerJSONArray.
+func (dec *Decoder) Array(v UnmarshalerJSONArray) error {
+ newCursor, err := dec.decodeArray(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)
+// 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
}
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
@@ -62,56 +123,6 @@ func (dec *Decoder) decodeArray(arr UnmarshalerJSONArray) (int, error) {
return 0, dec.raiseInvalidJSONErr(dec.cursor)
}
-// func (dec *Decoder) decodeArrayNull(factory func() UnmarshalerJSONArray) (int, error) {
-// // 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++ {
-// switch dec.data[dec.cursor] {
-// case ' ', '\n', '\t', '\r', ',':
-// continue
-// case '[':
-// n := 0
-// 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
-// }
-// n++
-// }
-// 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) skipArray() (int, error) {
var arraysOpen = 1
var arraysClosed = 0
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
@@ -104,100 +104,28 @@ func (dec *Decoder) decodeObject(j UnmarshalerJSONObject) (int, error) {
}
return 0, dec.raiseInvalidJSONErr(dec.cursor)
}
-func (dec *Decoder) decodeObjectNull(factory func() UnmarshalerJSONObject) (int, error) {
- for ; dec.cursor < dec.length || dec.read(); dec.cursor++ {
- switch dec.data[dec.cursor] {
- case ' ', '\n', '\t', '\r', ',':
- case '{':
- var j = factory()
- 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) decodeObjectNullReflect(v interface{}) (int, error) {
+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 '{':
- var vv = reflect.ValueOf(v).Elem()
- var n = reflect.New(vv.Type().Elem())
- vv.Set(n)
- var j = n.Interface().(UnmarshalerJSONObject)
+ 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
@@ -441,3 +369,7 @@ func (f DecodeObjectFunc) UnmarshalJSONObject(dec *Decoder, k string) error {
func (f DecodeObjectFunc) NKeys() int {
return 0
}
+
+func makeUnmarshalerJSONObject(v *interface{}) UnmarshalerJSONObject {
+ return nil
+}
diff --git a/decode_object_test.go b/decode_object_test.go
@@ -887,6 +887,37 @@ func TestDecodeObjectBasic0Keys(t *testing.T) {
}
}
+type ObjectNull struct {
+ SubObject *ObjectNull
+}
+
+func (o *ObjectNull) UnmarshalJSONObject(dec *Decoder, k string) error {
+ switch k {
+ case "subobject":
+ return dec.ObjectNull(&o.SubObject)
+ }
+ return nil
+}
+
+func (o *ObjectNull) NKeys() int {
+ return 1
+}
+
+func TestDecodeObjectNullPtr(t *testing.T) {
+ t.Run("sub obj should not be nil", func(t *testing.T) {
+ var o = &ObjectNull{}
+ var err = UnmarshalJSONObject([]byte(`{"subobject": {}}`), o)
+ 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`), o)
+ assert.Nil(t, err)
+ assert.Nil(t, o.SubObject)
+ })
+}
+
func TestDecodeObjectComplex(t *testing.T) {
testCases := []struct {
name string
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")