gojay

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

commit aa59043b98859004e1f91522a48730eb0c0d3deb
parent cef72a4f0bf03972e0264c36d0d1bad4884f8784
Author: francoispqt <francois@parquet.ninja>
Date:   Tue, 28 Aug 2018 23:50:14 +0800

add decode ObjectNull and ArrayNull

Diffstat:
Mdecode.go | 39++++++++++++++++++++++++---------------
Mdecode_array.go | 117+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mdecode_array_test.go | 43+++++++++++++++++++++++++++++++++++++++++++
Mdecode_object.go | 110+++++++++++++++----------------------------------------------------------------
Mdecode_object_test.go | 31+++++++++++++++++++++++++++++++
Merrors.go | 5+++++
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")