gojay

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

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:
Mdecode.go | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mdecode_array.go | 67++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mdecode_array_test.go | 43+++++++++++++++++++++++++++++++++++++++++++
Mdecode_object.go | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdecode_object_test.go | 197++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Merrors.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")