gojay

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

commit d7f8c9705bb104ba347eae7e74ae11807c2b9469
parent 0353576c09fce13a46aa90817b690abaf5516763
Author: francoispqt <francois@parquet.ninja>
Date:   Thu, 17 May 2018 23:53:15 +0800

refactor tests, add (u)int8-16 to decode

Diffstat:
Mdecode.go | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mdecode_array.go | 3++-
Mdecode_array_test.go | 489+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mdecode_bool.go | 2++
Mdecode_number.go | 794++-----------------------------------------------------------------------------
Adecode_number_float.go | 237+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adecode_number_float_test.go | 387+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adecode_number_int.go | 819+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adecode_number_int_test.go | 1131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdecode_number_test.go | 1139-------------------------------------------------------------------------------
Adecode_number_uint.go | 434+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adecode_number_uint_test.go | 590+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdecode_object.go | 5++++-
Mdecode_object_test.go | 73++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mdecode_string.go | 94+------------------------------------------------------------------------------
Mdecode_string_test.go | 2+-
Adecode_string_unicode.go | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mencode.go | 2+-
Mencode_interface_test.go | 2+-
Mencode_object_test.go | 42+++---------------------------------------
Merrors.go | 10+---------
Agojay_test.go | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
22 files changed, 4438 insertions(+), 2212 deletions(-)

diff --git a/decode.go b/decode.go @@ -82,21 +82,41 @@ func Unmarshal(data []byte, v interface{}) error { dec.length = len(data) dec.data = data err = dec.decodeInt(vt) - case *int32: + case *int8: dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data - err = dec.decodeInt32(vt) - case *uint32: + err = dec.decodeInt8(vt) + case *int16: dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data - err = dec.decodeUint32(vt) + err = dec.decodeInt16(vt) + case *int32: + dec = borrowDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.decodeInt32(vt) case *int64: dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data err = dec.decodeInt64(vt) + case *uint8: + dec = borrowDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.decodeUint8(vt) + case *uint16: + dec = borrowDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.decodeUint16(vt) + case *uint32: + dec = borrowDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.decodeUint32(vt) case *uint64: dec = borrowDecoder(nil, 0) dec.length = len(data) @@ -107,6 +127,11 @@ func Unmarshal(data []byte, v interface{}) error { dec.length = len(data) dec.data = data err = dec.decodeFloat64(vt) + case *float32: + dec = borrowDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.decodeFloat32(vt) case *bool: dec = borrowDecoder(nil, 0) dec.length = len(data) @@ -167,40 +192,53 @@ func (dec *Decoder) Decode(v interface{}) error { if dec.isPooled == 1 { panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) } + var err error switch vt := v.(type) { case *string: - return dec.decodeString(vt) + err = dec.decodeString(vt) case *int: - return dec.decodeInt(vt) + err = dec.decodeInt(vt) + case *int8: + err = dec.decodeInt8(vt) + case *int16: + err = dec.decodeInt16(vt) case *int32: - return dec.decodeInt32(vt) - case *uint32: - return dec.decodeUint32(vt) + err = dec.decodeInt32(vt) case *int64: - return dec.decodeInt64(vt) + err = dec.decodeInt64(vt) + case *uint8: + err = dec.decodeUint8(vt) + case *uint16: + err = dec.decodeUint16(vt) + case *uint32: + err = dec.decodeUint32(vt) case *uint64: - return dec.decodeUint64(vt) + err = dec.decodeUint64(vt) case *float64: - return dec.decodeFloat64(vt) + err = dec.decodeFloat64(vt) + case *float32: + err = dec.decodeFloat32(vt) case *bool: - return dec.decodeBool(vt) + err = dec.decodeBool(vt) case UnmarshalerJSONObject: - _, err := dec.decodeObject(vt) - return err + _, err = dec.decodeObject(vt) case UnmarshalerJSONArray: - _, err := dec.decodeArray(vt) - return err + _, err = dec.decodeArray(vt) case *EmbeddedJSON: - return dec.decodeEmbeddedJSON(vt) + err = dec.decodeEmbeddedJSON(vt) default: return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, reflect.TypeOf(vt).String())) } + if err != nil { + return err + } + return dec.err } // ADD VALUES FUNCTIONS // AddInt decodes the next key to an *int. -// If next key value overflows int, an InvalidTypeError error will be returned. +// If next key value overflows int, an InvalidUnmarshalError error will be returned. func (dec *Decoder) AddInt(v *int) error { err := dec.decodeInt(v) if err != nil { @@ -210,8 +248,96 @@ func (dec *Decoder) AddInt(v *int) error { return nil } +// AddInt8 decodes the next key to an *int. +// If next key value overflows int8, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddInt8(v *int8) error { + err := dec.decodeInt8(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + +// AddInt16 decodes the next key to an *int. +// If next key value overflows int16, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddInt16(v *int16) error { + err := dec.decodeInt16(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + +// AddInt32 decodes the next key to an *int. +// If next key value overflows int32, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddInt32(v *int32) error { + err := dec.decodeInt32(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + +// AddInt64 decodes the next key to an *int. +// If next key value overflows int64, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddInt64(v *int64) error { + err := dec.decodeInt64(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + +// AddUint8 decodes the next key to an *int. +// If next key value overflows uint8, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddUint8(v *uint8) error { + err := dec.decodeUint8(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + +// AddUint16 decodes the next key to an *int. +// If next key value overflows uint16, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddUint16(v *uint16) error { + err := dec.decodeUint16(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + +// AddUint32 decodes the next key to an *int. +// If next key value overflows uint32, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddUint32(v *uint32) error { + err := dec.decodeUint32(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + +// AddUint64 decodes the next key to an *int. +// If next key value overflows uint64, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddUint64(v *uint64) error { + err := dec.decodeUint64(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + // AddFloat decodes the next key to a *float64. -// If next key value overflows float64, an InvalidTypeError error will be returned. +// If next key value overflows float64, an InvalidUnmarshalError error will be returned. func (dec *Decoder) AddFloat(v *float64) error { err := dec.decodeFloat64(v) if err != nil { @@ -221,8 +347,19 @@ func (dec *Decoder) AddFloat(v *float64) error { return nil } +// AddFloat32 decodes the next key to a *float64. +// If next key value overflows float64, an InvalidUnmarshalError error will be returned. +func (dec *Decoder) AddFloat32(v *float32) error { + err := dec.decodeFloat32(v) + if err != nil { + return err + } + dec.called |= 1 + return nil +} + // AddBool decodes the next key to a *bool. -// If next key is neither null nor a JSON boolean, an InvalidTypeError will be returned. +// If next key is neither null nor a JSON boolean, an InvalidUnmarshalError will be returned. // If next key is null, bool will be false. func (dec *Decoder) AddBool(v *bool) error { err := dec.decodeBool(v) @@ -234,7 +371,7 @@ func (dec *Decoder) AddBool(v *bool) error { } // AddString decodes the next key to a *string. -// If next key is not a JSON string nor null, InvalidTypeError will be returned. +// If next key is not a JSON string nor null, InvalidUnmarshalError will be returned. func (dec *Decoder) AddString(v *string) error { err := dec.decodeString(v) if err != nil { diff --git a/decode_array.go b/decode_array.go @@ -48,11 +48,12 @@ func (dec *Decoder) decodeArray(arr UnmarshalerJSONArray) (int, error) { 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 = InvalidTypeError( + dec.err = InvalidUnmarshalError( fmt.Sprintf( "Cannot unmarshall to array, wrong char '%s' found at pos %d", string(dec.data[dec.cursor]), diff --git a/decode_array_test.go b/decode_array_test.go @@ -1,12 +1,87 @@ package gojay import ( + "log" "strings" "testing" "github.com/stretchr/testify/assert" ) +type testSliceInts []int + +func (t *testSliceInts) UnmarshalJSONArray(dec *Decoder) error { + i := 0 + if err := dec.AddInt(&i); err != nil { + return err + } + *t = append(*t, i) + return nil +} + +func TestSliceInts(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult testSliceInts + err bool + errType interface{} + }{ + { + name: "basic-test", + json: "[1,2,3,43567788543,45777655,432,0]", + expectedResult: testSliceInts{1, 2, 3, 43567788543, 45777655, 432, 0}, + }, + { + name: "basic-test", + json: "[1,2,3,43567788543,null,432,0]", + expectedResult: testSliceInts{1, 2, 3, 43567788543, 0, 432, 0}, + }, + { + name: "empty", + json: "[]", + expectedResult: testSliceInts{}, + }, + { + name: "floats", + json: "[1,2,3,43567788543,457.7765,432,0,0.45]", + expectedResult: testSliceInts{1, 2, 3, 43567788543, 457, 432, 0, 0}, + }, + { + name: "invalid-type", + json: `[1,2,3,43567788543,457.7765,432,0,"test"]`, + expectedResult: testSliceInts{1, 2, 3, 43567788543, 457, 432, 0, 0}, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "invalid-json", + json: `[1,2,3",43567788543,457.7765,432,0,"test"]`, + expectedResult: testSliceInts{1, 2, 3, 43567788543, 457, 432, 0, 0}, + err: true, + errType: InvalidJSONError(""), + }, + } + + for _, testCase := range testCases { + s := make(testSliceInts, 0) + dec := BorrowDecoder(strings.NewReader(testCase.json)) + defer dec.Release() + err := dec.Decode(&s) + if testCase.err { + assert.NotNil(t, err, "err should not be nil") + if testCase.errType != nil { + assert.IsType(t, testCase.errType, err, "err should be of the given type") + } + continue + } + log.Print(s) + for k, v := range testCase.expectedResult { + assert.Equal(t, v, s[k], "value at given index should be the same as expected results") + } + } +} + type testSliceStrings []string func (t *testSliceStrings) UnmarshalJSONArray(dec *Decoder) error { @@ -18,23 +93,282 @@ func (t *testSliceStrings) UnmarshalJSONArray(dec *Decoder) error { return nil } -type testSliceInts []*int +func TestSliceStrings(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult testSliceStrings + err bool + errType interface{} + }{ + { + name: "basic-test", + json: `["hello world", "hey" , "foo","bar"]`, + expectedResult: testSliceStrings{"hello world", "hey", "foo", "bar"}, + }, + { + name: "basic-test", + json: `["hello world", "hey" , "foo","bar \\n escape"]`, + expectedResult: testSliceStrings{"hello world", "hey", "foo", "bar \n escape"}, + }, + { + name: "basic-test", + json: `["hello world", "hey" , null,"bar \\n escape"]`, + expectedResult: testSliceStrings{"hello world", "hey", "", "bar \n escape"}, + }, + { + name: "invalid-type", + json: `["foo",1,2,3,"test"]`, + expectedResult: testSliceStrings{}, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "invalid-json", + json: `["hello world]`, + expectedResult: testSliceStrings{}, + err: true, + errType: InvalidJSONError(""), + }, + } -func (t *testSliceInts) UnmarshalJSONArray(dec *Decoder) error { - i := 0 - ptr := &i - *t = append(*t, ptr) - return dec.AddInt(ptr) + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + s := make(testSliceStrings, 0) + dec := BorrowDecoder(strings.NewReader(testCase.json)) + defer dec.Release() + err := dec.Decode(&s) + if testCase.err { + assert.NotNil(t, err, "err should not be nil") + if testCase.errType != nil { + assert.IsType(t, testCase.errType, err, "err should be of the given type") + } + return + } + assert.Nil(t, err, "err should be nil") + for k, v := range testCase.expectedResult { + assert.Equal(t, v, s[k], "value at given index should be the same as expected results") + } + }) + } } -type testSliceObj []*TestObj +type testSliceBools []bool -func (t *testSliceObj) UnmarshalJSONArray(dec *Decoder) error { - obj := &TestObj{} +func (t *testSliceBools) UnmarshalJSONArray(dec *Decoder) error { + b := false + if err := dec.AddBool(&b); err != nil { + return err + } + *t = append(*t, b) + return nil +} + +func TestSliceBools(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult testSliceBools + err bool + errType interface{} + }{ + { + name: "basic-test", + json: `[true, false, false, true, true, false]`, + expectedResult: testSliceBools{true, false, false, true, true, false}, + }, + { + name: "basic-test2", + json: `[true, false, false, true, null,null,true,false]`, + expectedResult: testSliceBools{true, false, false, true, false, false, true, false}, + }, + { + name: "invalid-type", + json: `["foo",1,2,3,"test"]`, + expectedResult: testSliceBools{}, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "invalid-json", + json: `["hello world]`, + expectedResult: testSliceBools{}, + err: true, + errType: InvalidJSONError(""), + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + s := make(testSliceBools, 0) + dec := BorrowDecoder(strings.NewReader(testCase.json)) + defer dec.Release() + err := dec.Decode(&s) + if testCase.err { + assert.NotNil(t, err, "err should not be nil") + if testCase.errType != nil { + assert.IsType(t, testCase.errType, err, "err should be of the given type") + } + return + } + log.Print(s, testCase.name) + assert.Nil(t, err, "err should be nil") + for k, v := range testCase.expectedResult { + assert.Equal(t, v, s[k], "value at given index should be the same as expected results") + } + }) + } +} + +type testSliceSlicesSlices []testSliceInts + +func (t *testSliceSlicesSlices) UnmarshalJSONArray(dec *Decoder) error { + sl := make(testSliceInts, 0) + if err := dec.AddArray(&sl); err != nil { + return err + } + *t = append(*t, sl) + return nil +} + +func TestSliceSlices(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult testSliceSlicesSlices + err bool + errType interface{} + }{ + { + name: "basic-test", + json: `[[1,2],[1,2],[1,2]]`, + expectedResult: testSliceSlicesSlices{testSliceInts{1, 2}, testSliceInts{1, 2}, testSliceInts{1, 2}}, + }, + { + name: "basic-test", + json: `[[1,2],null,[1,2]]`, + expectedResult: testSliceSlicesSlices{testSliceInts{1, 2}, testSliceInts{}, testSliceInts{1, 2}}, + }, + { + name: "invalid-type", + json: `["foo",1,2,3,"test"]`, + expectedResult: testSliceSlicesSlices{}, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "invalid-json", + json: `["hello world]`, + expectedResult: testSliceSlicesSlices{}, + err: true, + errType: InvalidJSONError(""), + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + s := make(testSliceSlicesSlices, 0) + dec := BorrowDecoder(strings.NewReader(testCase.json)) + defer dec.Release() + err := dec.Decode(&s) + if testCase.err { + assert.NotNil(t, err, "err should not be nil") + if testCase.errType != nil { + assert.IsType(t, testCase.errType, err, "err should be of the given type") + } + return + } + log.Print(s, testCase.name) + assert.Nil(t, err, "err should be nil") + for k, v := range testCase.expectedResult { + assert.Equal(t, v, s[k], "value at given index should be the same as expected results") + } + }) + } +} + +type testSliceObjects []*testObject + +func (t *testSliceObjects) UnmarshalJSONArray(dec *Decoder) error { + obj := &testObject{} *t = append(*t, obj) return dec.AddObject(obj) } +func TestSliceObjects(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult testSliceObjects + err bool + errType interface{} + }{ + { + name: "basic-test", + json: `[{"testStr":"foo bar","testInt":123},{"testStr":"foo bar","testInt":123}]`, + expectedResult: testSliceObjects{ + &testObject{ + testStr: "foo bar", + testInt: 123, + }, + &testObject{ + testStr: "foo bar", + testInt: 123, + }, + }, + }, + { + name: "basic-test", + json: `[{"testStr":"foo bar","testInt":123},null,{"testStr":"foo bar","testInt":123}]`, + expectedResult: testSliceObjects{ + &testObject{ + testStr: "foo bar", + testInt: 123, + }, + &testObject{}, + &testObject{ + testStr: "foo bar", + testInt: 123, + }, + }, + }, + { + name: "invalid-type", + json: `["foo",1,2,3,"test"]`, + expectedResult: testSliceObjects{}, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "invalid-json", + json: `["hello world]`, + expectedResult: testSliceObjects{}, + err: true, + errType: InvalidJSONError(""), + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + s := make(testSliceObjects, 0) + dec := BorrowDecoder(strings.NewReader(testCase.json)) + defer dec.Release() + err := dec.Decode(&s) + if testCase.err { + assert.NotNil(t, err, "err should not be nil") + if testCase.errType != nil { + assert.IsType(t, testCase.errType, err, "err should be of the given type") + } + return + } + assert.Nil(t, err, "err should be nil") + for k, v := range testCase.expectedResult { + assert.Equal(t, *v, *s[k], "value at given index should be the same as expected results") + } + }) + } +} + type testChannelArray chan *TestObj func (c *testChannelArray) UnmarshalJSONArray(dec *Decoder) error { @@ -46,16 +380,6 @@ func (c *testChannelArray) UnmarshalJSONArray(dec *Decoder) error { return nil } -func TestDecoderSliceOfStringsBasic(t *testing.T) { - json := []byte(`["string","string1"]`) - testArr := testSliceStrings{} - err := Unmarshal(json, &testArr) - assert.Nil(t, err, "Err must be nil") - assert.Len(t, testArr, 2, "testArr should be of len 2") - assert.Equal(t, "string", testArr[0], "testArr[0] should be 'string'") - assert.Equal(t, "string1", testArr[1], "testArr[1] should be 'string1'") -} - func TestDecoderSliceNull(t *testing.T) { json := []byte(`null`) v := &testSliceStrings{} @@ -64,70 +388,11 @@ func TestDecoderSliceNull(t *testing.T) { assert.Equal(t, len(*v), 0, "v must be of len 0") } -func TestDecoderSliceArrayOfIntsBasic(t *testing.T) { - json := []byte(`[ - 1, - 2 - ]`) - testArr := testSliceInts{} - err := UnmarshalJSONArray(json, &testArr) - assert.Nil(t, err, "Err must be nil") - assert.Len(t, testArr, 2, "testArr should be of len 2") - assert.Equal(t, 1, *testArr[0], "testArr[0] should be 1") - assert.Equal(t, 2, *testArr[1], "testArr[1] should be 2") -} - -func TestDecoderSliceArrayOfIntsBigInts(t *testing.T) { - json := []byte(`[ - 789034384533530523, - 545344023293232032 - ]`) - testArr := testSliceInts{} - err := UnmarshalJSONArray(json, &testArr) - assert.Nil(t, err, "Err must be nil") - assert.Len(t, testArr, 2, "testArr should be of len 2") - assert.Equal(t, 789034384533530523, *testArr[0], "testArr[0] should be 789034384533530523") - assert.Equal(t, 545344023293232032, *testArr[1], "testArr[1] should be 545344023293232032") -} - -func TestDecoderSliceOfObjectsBasic(t *testing.T) { - json := []byte(`[ - { - "test": 245, - "test2": -246, - "test3": "string" - }, - { - "test": 247, - "test2": 248, - "test3": "string" - }, - { - "test": 777, - "test2": 456, - "test3": "string" - } - ]`) - testArr := testSliceObj{} - err := Unmarshal(json, &testArr) - assert.Nil(t, err, "Err must be nil") - assert.Len(t, testArr, 3, "testArr should be of len 2") - assert.Equal(t, 245, testArr[0].test, "testArr[0] should be 245") - assert.Equal(t, -246, testArr[0].test2, "testArr[0] should be 246") - assert.Equal(t, "string", testArr[0].test3, "testArr[0].test3 should be 'string'") - assert.Equal(t, 247, testArr[1].test, "testArr[1] should be 247") - assert.Equal(t, 248, testArr[1].test2, "testArr[1] should be 248") - assert.Equal(t, "string", testArr[1].test3, "testArr[1].test3 should be 'string'") - assert.Equal(t, 777, testArr[2].test, "testArr[2] should be 777") - assert.Equal(t, 456, testArr[2].test2, "testArr[2] should be 456") - assert.Equal(t, "string", testArr[2].test3, "testArr[2].test3 should be 'string'") -} - func TestDecodeSliceInvalidType(t *testing.T) { - result := testSliceObj{} + result := testSliceObjects{} err := UnmarshalJSONArray([]byte(`{}`), &result) assert.NotNil(t, err, "err should not be nil") - assert.IsType(t, InvalidTypeError(""), err, "err should be of type InvalidTypeError") + assert.IsType(t, InvalidUnmarshalError(""), err, "err should be of type InvalidUnmarshalError") assert.Equal(t, "Cannot unmarshall to array, wrong char '{' found at pos 0", err.Error(), "err should not be nil") } @@ -241,46 +506,6 @@ func TestUnmarshalJSONArrays(t *testing.T) { } } -func TestDecodeArrayEmpty(t *testing.T) { - v := new(testDecodeSlice) - dec := NewDecoder(strings.NewReader("")) - err := dec.Decode(v) - assert.NotNil(t, err, "err should not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") -} - -func TestDecodeArrayInvalidJSONError(t *testing.T) { - v := new(testSliceStrings) - dec := NewDecoder(strings.NewReader(`["test",""`)) - err := dec.Decode(v) - assert.NotNil(t, err, "err should not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") -} - -func TestDecodeArrayInvalidJSONError2(t *testing.T) { - v := new(testSliceStrings) - dec := NewDecoder(strings.NewReader(`["test","\\""]`)) - err := dec.Decode(v) - assert.NotNil(t, err, "err should not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") -} - -func TestDecodeArraySkipError(t *testing.T) { - v := new(testDecodeSlice) - dec := NewDecoder(strings.NewReader("34fef")) - err := dec.Decode(v) - assert.NotNil(t, err, "err should not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") -} - -func TestDecodeArrayNullError(t *testing.T) { - v := new(testDecodeSlice) - dec := NewDecoder(strings.NewReader("nall")) - err := dec.Decode(v) - assert.NotNil(t, err, "err should not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") -} - func TestSkipArray(t *testing.T) { testCases := []struct { json string @@ -315,3 +540,27 @@ func TestSkipArray(t *testing.T) { test.expectations(t, i, err) } } + +func TestDecodeArrayEmpty(t *testing.T) { + v := new(testDecodeSlice) + dec := NewDecoder(strings.NewReader("")) + err := dec.Decode(v) + assert.NotNil(t, err, "err should not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") +} + +func TestDecodeArraySkipError(t *testing.T) { + v := new(testDecodeSlice) + dec := NewDecoder(strings.NewReader("34fef")) + err := dec.Decode(v) + assert.NotNil(t, err, "err should not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") +} + +func TestDecodeArrayNullError(t *testing.T) { + v := new(testDecodeSlice) + dec := NewDecoder(strings.NewReader("nall")) + err := dec.Decode(v) + assert.NotNil(t, err, "err should not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") +} diff --git a/decode_bool.go b/decode_bool.go @@ -23,6 +23,7 @@ func (dec *Decoder) decodeBool(v *bool) error { return err } *v = true + dec.cursor++ return nil case 'f': dec.cursor++ @@ -31,6 +32,7 @@ func (dec *Decoder) decodeBool(v *bool) error { return err } *v = false + dec.cursor++ return nil case 'n': dec.cursor++ diff --git a/decode_number.go b/decode_number.go @@ -1,23 +1,27 @@ package gojay import ( - "fmt" + "math" ) var digits []int8 -const maxUint32 = uint32(0xffffffff) -const maxUint64 = uint64(0xffffffffffffffff) -const maxInt32 = int32(0x7fffffff) -const maxInt64 = int64(0x7fffffffffffffff) -const maxInt64toMultiply = int64(0x7fffffffffffffff) / 10 -const maxInt32toMultiply = int32(0x7fffffff) / 10 -const maxUint32toMultiply = uint32(0xffffffff) / 10 -const maxUint64toMultiply = uint64(0xffffffffffffffff) / 10 +const maxInt64toMultiply = math.MaxInt64 / 10 +const maxInt32toMultiply = math.MaxInt32 / 10 +const maxInt16toMultiply = math.MaxInt16 / 10 +const maxInt8toMultiply = math.MaxInt8 / 10 +const maxUint8toMultiply = math.MaxUint8 / 10 +const maxUint16toMultiply = math.MaxUint16 / 10 +const maxUint32toMultiply = math.MaxUint32 / 10 +const maxUint64toMultiply = math.MaxUint64 / 10 const maxUint32Length = 10 const maxUint64Length = 20 +const maxUint16Length = 5 +const maxUint8Length = 3 const maxInt32Length = 10 const maxInt64Length = 19 +const maxInt16Length = 5 +const maxInt8Length = 3 const invalidNumber = int8(-1) var pow10uint64 = [20]uint64{ @@ -53,336 +57,6 @@ func init() { } } -// DecodeInt reads the next JSON-encoded value from its input and stores it in the int pointed to by v. -// -// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. -func (dec *Decoder) DecodeInt(v *int) error { - if dec.isPooled == 1 { - panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) - } - return dec.decodeInt(v) -} -func (dec *Decoder) decodeInt(v *int) error { - for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { - switch c := dec.data[dec.cursor]; c { - case ' ', '\n', '\t', '\r', ',': - continue - // we don't look for 0 as leading zeros are invalid per RFC - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - val, err := dec.getInt64(c) - if err != nil { - return err - } - *v = int(val) - return nil - case '-': - dec.cursor = dec.cursor + 1 - val, err := dec.getInt64(dec.data[dec.cursor]) - if err != nil { - return err - } - *v = -int(val) - return nil - case 'n': - dec.cursor++ - err := dec.assertNull() - if err != nil { - return err - } - dec.cursor++ - return nil - default: - dec.err = InvalidTypeError( - fmt.Sprintf( - "Cannot unmarshall to int, wrong char '%s' found at pos %d", - string(dec.data[dec.cursor]), - dec.cursor, - ), - ) - err := dec.skipData() - if err != nil { - return err - } - return nil - } - } - return InvalidJSONError("Invalid JSON while parsing int") -} - -// DecodeInt32 reads the next JSON-encoded value from its input and stores it in the int32 pointed to by v. -// -// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. -func (dec *Decoder) DecodeInt32(v *int32) error { - if dec.isPooled == 1 { - panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) - } - return dec.decodeInt32(v) -} -func (dec *Decoder) decodeInt32(v *int32) error { - for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { - switch c := dec.data[dec.cursor]; c { - case ' ', '\n', '\t', '\r', ',': - continue - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - val, err := dec.getInt32(c) - if err != nil { - return err - } - *v = val - return nil - case '-': - dec.cursor = dec.cursor + 1 - val, err := dec.getInt32(dec.data[dec.cursor]) - if err != nil { - return err - } - *v = -val - return nil - case 'n': - dec.cursor++ - err := dec.assertNull() - if err != nil { - return err - } - return nil - default: - dec.err = InvalidTypeError( - fmt.Sprintf( - "Cannot unmarshall to int, wrong char '%s' found at pos %d", - string(dec.data[dec.cursor]), - dec.cursor, - ), - ) - err := dec.skipData() - if err != nil { - return err - } - return nil - } - } - return InvalidJSONError("Invalid JSON while parsing int") -} - -// DecodeUint32 reads the next JSON-encoded value from its input and stores it in the uint32 pointed to by v. -// -// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. -func (dec *Decoder) DecodeUint32(v *uint32) error { - if dec.isPooled == 1 { - panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) - } - return dec.decodeUint32(v) -} - -func (dec *Decoder) decodeUint32(v *uint32) error { - for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { - switch c := dec.data[dec.cursor]; c { - case ' ', '\n', '\t', '\r', ',': - continue - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - val, err := dec.getUint32(c) - if err != nil { - return err - } - *v = val - return nil - case '-': - dec.cursor = dec.cursor + 1 - val, err := dec.getUint32(dec.data[dec.cursor]) - if err != nil { - return err - } - // unsigned int so we don't bother with the sign - *v = val - return nil - case 'n': - dec.cursor++ - err := dec.assertNull() - if err != nil { - return err - } - return nil - default: - dec.err = InvalidTypeError( - fmt.Sprintf( - "Cannot unmarshall to int, wrong char '%s' found at pos %d", - string(dec.data[dec.cursor]), - dec.cursor, - ), - ) - err := dec.skipData() - if err != nil { - return err - } - return nil - } - } - return InvalidJSONError("Invalid JSON while parsing int") -} - -// DecodeInt64 reads the next JSON-encoded value from its input and stores it in the int64 pointed to by v. -// -// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. -func (dec *Decoder) DecodeInt64(v *int64) error { - if dec.isPooled == 1 { - panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) - } - return dec.decodeInt64(v) -} - -func (dec *Decoder) decodeInt64(v *int64) error { - for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { - switch c := dec.data[dec.cursor]; c { - case ' ', '\n', '\t', '\r', ',': - continue - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - val, err := dec.getInt64(c) - if err != nil { - return err - } - *v = val - return nil - case '-': - dec.cursor = dec.cursor + 1 - val, err := dec.getInt64(dec.data[dec.cursor]) - if err != nil { - return err - } - *v = -val - return nil - case 'n': - dec.cursor++ - err := dec.assertNull() - if err != nil { - return err - } - return nil - default: - dec.err = InvalidTypeError( - fmt.Sprintf( - "Cannot unmarshall to int, wrong char '%s' found at pos %d", - string(dec.data[dec.cursor]), - dec.cursor, - ), - ) - err := dec.skipData() - if err != nil { - return err - } - return nil - } - } - return InvalidJSONError("Invalid JSON while parsing int") -} - -// DecodeUint64 reads the next JSON-encoded value from its input and stores it in the uint64 pointed to by v. -// -// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. -func (dec *Decoder) DecodeUint64(v *uint64) error { - if dec.isPooled == 1 { - panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) - } - return dec.decodeUint64(v) -} -func (dec *Decoder) decodeUint64(v *uint64) error { - for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { - switch c := dec.data[dec.cursor]; c { - case ' ', '\n', '\t', '\r', ',': - continue - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - val, err := dec.getUint64(c) - if err != nil { - return err - } - *v = val - return nil - case '-': - dec.cursor = dec.cursor + 1 - val, err := dec.getUint64(dec.data[dec.cursor]) - if err != nil { - return err - } - // unsigned int so we don't bother with the sign - *v = val - return nil - case 'n': - dec.cursor++ - err := dec.assertNull() - if err != nil { - return err - } - return nil - default: - dec.err = InvalidTypeError( - fmt.Sprintf( - "Cannot unmarshall to int, wrong char '%s' found at pos %d", - string(dec.data[dec.cursor]), - dec.cursor, - ), - ) - err := dec.skipData() - if err != nil { - return err - } - return nil - } - } - return InvalidJSONError("Invalid JSON while parsing int") -} - -// DecodeFloat64 reads the next JSON-encoded value from its input and stores it in the float64 pointed to by v. -// -// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. -func (dec *Decoder) DecodeFloat64(v *float64) error { - if dec.isPooled == 1 { - panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) - } - return dec.decodeFloat64(v) -} -func (dec *Decoder) decodeFloat64(v *float64) error { - for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { - switch c := dec.data[dec.cursor]; c { - case ' ', '\n', '\t', '\r', ',': - continue - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - val, err := dec.getFloat(c) - if err != nil { - return err - } - *v = val - return nil - case '-': - dec.cursor = dec.cursor + 1 - val, err := dec.getFloat(c) - if err != nil { - return err - } - *v = -val - return nil - case 'n': - dec.cursor++ - err := dec.assertNull() - if err != nil { - return err - } - return nil - default: - dec.err = InvalidTypeError( - fmt.Sprintf( - "Cannot unmarshall to float, wrong char '%s' found at pos %d", - string(dec.data[dec.cursor]), - dec.cursor, - ), - ) - err := dec.skipData() - if err != nil { - return err - } - return nil - } - } - return InvalidJSONError("Invalid JSON while parsing float") -} - func (dec *Decoder) skipNumber() (int, error) { end := dec.cursor + 1 // look for following numbers @@ -405,452 +79,12 @@ func (dec *Decoder) skipNumber() (int, error) { return end, nil } -func (dec *Decoder) getInt64(b byte) (int64, error) { - var end = dec.cursor - var start = dec.cursor - // look for following numbers - for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { - switch dec.data[j] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - end = j - continue - case ' ', '\t', '\n', ',', '}', ']': - dec.cursor = j - return dec.atoi64(start, end), nil - case '.': - // if dot is found - // look for exponent (e,E) as exponent can change the - // way number should be parsed to int. - // if no exponent found, just unmarshal the number before decimal point - startDecimal := j + 1 - endDecimal := j + 1 - j++ - for ; j < dec.length || dec.read(); j++ { - switch dec.data[j] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - endDecimal = j - continue - case 'e', 'E': - dec.cursor = j + 1 - // can try unmarshalling to int as Exponent might change decimal number to non decimal - // let's get the float value first - // we get part before decimal as integer - beforeDecimal := dec.atoi64(start, end) - // get number after the decimal point - // multiple the before decimal point portion by 10 using bitwise - for i := startDecimal; i <= endDecimal; i++ { - beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) - } - // then we add both integers - // then we divide the number by the power found - afterDecimal := dec.atoi64(startDecimal, endDecimal) - pow := pow10uint64[endDecimal-startDecimal+2] - floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) - // we have the floating value, now multiply by the exponent - exp := dec.getExponent() - val := floatVal * float64(pow10uint64[exp+1]) - return int64(val), nil - case ' ', '\t', '\n', ',', ']', '}': - dec.cursor = j - return dec.atoi64(start, end), nil - default: - dec.cursor = j - return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) - } - } - return dec.atoi64(start, end), nil - case 'e', 'E': - // get init n - return dec.getInt64WithExp(dec.atoi64(start, end), j+1) - } - // invalid json we expect numbers, dot (single one), comma, or spaces - return 0, InvalidJSONError("Invalid JSON while parsing number") - } - return dec.atoi64(start, end), nil -} - -func (dec *Decoder) getInt64WithExp(init int64, cursor int) (int64, error) { - var exp uint64 - var sign = int64(1) - for ; cursor < dec.length || dec.read(); cursor++ { - switch dec.data[cursor] { - case '+': - continue - case '-': - sign = -1 - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - uintv := uint64(digits[dec.data[cursor]]) - exp = (exp << 3) + (exp << 1) + uintv - cursor++ - for ; cursor < dec.length || dec.read(); cursor++ { - switch dec.data[cursor] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - uintv := uint64(digits[dec.data[cursor]]) - exp = (exp << 3) + (exp << 1) + uintv - case ' ', '\t', '\n', '}', ',', ']': - if sign == -1 { - return init * (1 / int64(pow10uint64[exp+1])), nil - } - return init * int64(pow10uint64[exp+1]), nil - default: - return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) - } - } - if sign == -1 { - return init * (1 / int64(pow10uint64[exp+1])), nil - } - return init * int64(pow10uint64[exp+1]), nil - default: - dec.err = InvalidJSONError("Invalid JSON") - return 0, dec.err - } - } - return 0, InvalidJSONError("Invalid JSON") -} - -func (dec *Decoder) getUint64(b byte) (uint64, error) { - var end = dec.cursor - var start = dec.cursor - // look for following numbers - for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { - switch dec.data[j] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - end = j - continue - case ' ', '\n', '\t', '\r', '.', ',', '}', ']': - dec.cursor = j - return dec.atoui64(start, end), nil - } - // invalid json we expect numbers, dot (single one), comma, or spaces - return 0, InvalidJSONError("Invalid JSON while parsing number") - } - return dec.atoui64(start, end), nil -} - -func (dec *Decoder) getInt32(b byte) (int32, error) { - var end = dec.cursor - var start = dec.cursor - // look for following numbers - for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { - switch dec.data[j] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - end = j - continue - case '.': - // if dot is found - // look for exponent (e,E) as exponent can change the - // way number should be parsed to int. - // if no exponent found, just unmarshal the number before decimal point - startDecimal := j + 1 - endDecimal := j + 1 - j++ - for ; j < dec.length || dec.read(); j++ { - switch dec.data[j] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - endDecimal = j - continue - case 'e', 'E': - dec.cursor = j + 1 - // can try unmarshalling to int as Exponent might change decimal number to non decimal - // let's get the float value first - // we get part before decimal as integer - beforeDecimal := dec.atoi64(start, end) - // get number after the decimal point - // multiple the before decimal point portion by 10 using bitwise - for i := startDecimal; i <= endDecimal; i++ { - beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) - } - // then we add both integers - // then we divide the number by the power found - afterDecimal := dec.atoi64(startDecimal, endDecimal) - pow := pow10uint64[endDecimal-startDecimal+2] - floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) - // we have the floating value, now multiply by the exponent - exp := dec.getExponent() - val := floatVal * float64(pow10uint64[exp+1]) - return int32(val), nil - case ' ', '\t', '\n', ',', ']', '}': - dec.cursor = j - return dec.atoi32(start, end), nil - default: - dec.cursor = j - return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) - } - } - return dec.atoi32(start, end), nil - case 'e', 'E': - // get init n - return dec.getInt32WithExp(dec.atoi32(start, end), j+1) - case ' ', '\n', '\t', '\r', ',', '}', ']': - dec.cursor = j - return dec.atoi32(start, end), nil - } - // invalid json we expect numbers, dot (single one), comma, or spaces - return 0, InvalidJSONError("Invalid JSON while parsing number") - } - return dec.atoi32(start, end), nil -} - -func (dec *Decoder) getInt32WithExp(init int32, cursor int) (int32, error) { - var exp uint32 - var sign = int32(1) - for ; cursor < dec.length || dec.read(); cursor++ { - switch dec.data[cursor] { - case '+': - continue - case '-': - sign = -1 - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - uintv := uint32(digits[dec.data[cursor]]) - exp = (exp << 3) + (exp << 1) + uintv - cursor++ - for ; cursor < dec.length || dec.read(); cursor++ { - switch dec.data[cursor] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - uintv := uint32(digits[dec.data[cursor]]) - exp = (exp << 3) + (exp << 1) + uintv - case ' ', '\t', '\n', '}', ',', ']': - if sign == -1 { - return init * (1 / int32(pow10uint64[exp+1])), nil - } - return init * int32(pow10uint64[exp+1]), nil - default: - return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) - } - } - if sign == -1 { - return init * (1 / int32(pow10uint64[exp+1])), nil - } - return init * int32(pow10uint64[exp+1]), nil - default: - dec.err = InvalidJSONError("Invalid JSON") - return 0, dec.err - } - } - return 0, InvalidJSONError("Invalid JSON") -} - -func (dec *Decoder) getUint32(b byte) (uint32, error) { - var end = dec.cursor - var start = dec.cursor - // look for following numbers - for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { - switch dec.data[j] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - end = j - continue - case ' ', '\n', '\t', '\r': - continue - case '.', ',', '}', ']': - dec.cursor = j - return dec.atoui32(start, end), nil - } - // invalid json we expect numbers, dot (single one), comma, or spaces - return 0, InvalidJSONError("Invalid JSON while parsing number") - } - return dec.atoui32(start, end), nil -} - -func (dec *Decoder) getFloat(b byte) (float64, error) { - var end = dec.cursor - var start = dec.cursor - // look for following numbers - for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { - switch dec.data[j] { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - end = j - continue - case '.': - // we get part before decimal as integer - beforeDecimal := dec.atoi64(start, end) - // then we get part after decimal as integer - start = j + 1 - // get number after the decimal point - // multiple the before decimal point portion by 10 using bitwise - for i := j + 1; i < dec.length || dec.read(); i++ { - c := dec.data[i] - if isDigit(c) { - end = i - beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) - continue - } else if c == 'e' || c == 'E' { - afterDecimal := dec.atoi64(start, end) - dec.cursor = i + 1 - pow := pow10uint64[end-start+2] - floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) - exp := dec.getExponent() - // if exponent is negative - if exp < 0 { - return float64(floatVal) * (1 / float64(pow10uint64[exp*-1+1])), nil - } - return float64(floatVal) * float64(pow10uint64[exp+1]), nil - } - dec.cursor = i - break - } - // then we add both integers - // then we divide the number by the power found - afterDecimal := dec.atoi64(start, end) - pow := pow10uint64[end-start+2] - return float64(beforeDecimal+afterDecimal) / float64(pow), nil - case 'e', 'E': - dec.cursor = dec.cursor + 2 - // we get part before decimal as integer - beforeDecimal := uint64(dec.atoi64(start, end)) - // get exponent - exp := dec.getExponent() - // if exponent is negative - if exp < 0 { - return float64(beforeDecimal) * (1 / float64(pow10uint64[exp*-1+1])), nil - } - return float64(beforeDecimal) * float64(pow10uint64[exp+1]), nil - case ' ', '\n', '\t', '\r', ',', '}', ']': // does not have decimal - dec.cursor = j - return float64(dec.atoi64(start, end)), nil - } - // invalid json we expect numbers, dot (single one), comma, or spaces - return 0, InvalidJSONError("Invalid JSON while parsing number") - } - return float64(dec.atoi64(start, end)), nil -} - -func (dec *Decoder) atoi64(start, end int) int64 { - var ll = end + 1 - start - var val = int64(digits[dec.data[start]]) - end = end + 1 - if ll < maxInt64Length { - for i := start + 1; i < end; i++ { - intv := int64(digits[dec.data[i]]) - val = (val << 3) + (val << 1) + intv - } - return val - } else if ll == maxInt64Length { - for i := start + 1; i < end; i++ { - intv := int64(digits[dec.data[i]]) - if val > maxInt64toMultiply { - dec.err = InvalidTypeError("Overflows int64") - return 0 - } - val = (val << 3) + (val << 1) - if maxInt64-val < intv { - dec.err = InvalidTypeError("Overflows int64") - return 0 - } - val += intv - } - } else { - dec.err = InvalidTypeError("Overflows int64") - return 0 - } - return val -} - -func (dec *Decoder) atoui64(start, end int) uint64 { - var ll = end + 1 - start - var val = uint64(digits[dec.data[start]]) - end = end + 1 - if ll < maxUint64Length { - for i := start + 1; i < end; i++ { - uintv := uint64(digits[dec.data[i]]) - val = (val << 3) + (val << 1) + uintv - } - } else if ll == maxUint64Length { - for i := start + 1; i < end; i++ { - uintv := uint64(digits[dec.data[i]]) - if val > maxUint64toMultiply { - dec.err = InvalidTypeError("Overflows uint64") - return 0 - } - val = (val << 3) + (val << 1) - if maxUint64-val < uintv { - dec.err = InvalidTypeError("Overflows uint64") - return 0 - } - val += uintv - } - } else { - dec.err = InvalidTypeError("Overflows uint64") - return 0 - } - return val -} - -func (dec *Decoder) atoi32(start, end int) int32 { - var ll = end + 1 - start - var val = int32(digits[dec.data[start]]) - end = end + 1 - // overflowing - if ll < maxInt32Length { - for i := start + 1; i < end; i++ { - intv := int32(digits[dec.data[i]]) - val = (val << 3) + (val << 1) + intv - } - } else if ll == maxInt32Length { - for i := start + 1; i < end; i++ { - intv := int32(digits[dec.data[i]]) - if val > maxInt32toMultiply { - dec.err = InvalidTypeError("Overflows int32") - return 0 - } - val = (val << 3) + (val << 1) - if maxInt32-val < intv { - dec.err = InvalidTypeError("Overflows int32") - return 0 - } - val += intv - } - } else { - dec.err = InvalidTypeError("Overflows int32") - return 0 - } - return val -} - -func (dec *Decoder) atoui32(start, end int) uint32 { - var ll = end + 1 - start - var val uint32 - val = uint32(digits[dec.data[start]]) - end = end + 1 - if ll < maxUint32Length { - for i := start + 1; i < end; i++ { - uintv := uint32(digits[dec.data[i]]) - val = (val << 3) + (val << 1) + uintv - } - } else if ll == maxUint32Length { - for i := start + 1; i < end; i++ { - uintv := uint32(digits[dec.data[i]]) - if val > maxUint32toMultiply { - dec.err = InvalidTypeError("Overflows uint32") - return 0 - } - val = (val << 3) + (val << 1) - if maxUint32-val < uintv { - dec.err = InvalidTypeError("Overflows int32") - return 0 - } - val += uintv - } - } else if ll > maxUint32Length { - dec.err = InvalidTypeError("Overflows uint32") - val = 0 - } - return val -} - func (dec *Decoder) getExponent() int64 { start := dec.cursor end := dec.cursor for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { switch dec.data[dec.cursor] { // is positive - case '0': - // skip leading zeroes - if start == end { - start = dec.cursor - end = dec.cursor - continue - } - end = dec.cursor - case '1', '2', '3', '4', '5', '6', '7', '8', '9': + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': end = dec.cursor case '-': dec.cursor++ diff --git a/decode_number_float.go b/decode_number_float.go @@ -0,0 +1,237 @@ +package gojay + +import "fmt" + +// DecodeFloat64 reads the next JSON-encoded value from its input and stores it in the float64 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeFloat64(v *float64) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeFloat64(v) +} +func (dec *Decoder) decodeFloat64(v *float64) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getFloat(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getFloat(c) + if err != nil { + return err + } + *v = -val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + default: + dec.err = InvalidUnmarshalError( + fmt.Sprintf( + "Cannot unmarshall to float, wrong char '%s' found at pos %d", + string(dec.data[dec.cursor]), + dec.cursor, + ), + ) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return InvalidJSONError("Invalid JSON while parsing float") +} + +func (dec *Decoder) getFloat(b byte) (float64, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case '.': + // we get part before decimal as integer + beforeDecimal := dec.atoi64(start, end) + // then we get part after decimal as integer + start = j + 1 + // get number after the decimal point + // multiple the before decimal point portion by 10 using bitwise + for i := j + 1; i < dec.length || dec.read(); i++ { + c := dec.data[i] + if isDigit(c) { + end = i + beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) + continue + } else if c == 'e' || c == 'E' { + afterDecimal := dec.atoi64(start, end) + dec.cursor = i + 1 + pow := pow10uint64[end-start+2] + floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) + exp := dec.getExponent() + // if exponent is negative + if exp < 0 { + return float64(floatVal) * (1 / float64(pow10uint64[exp*-1+1])), nil + } + return float64(floatVal) * float64(pow10uint64[exp+1]), nil + } + dec.cursor = i + break + } + // then we add both integers + // then we divide the number by the power found + afterDecimal := dec.atoi64(start, end) + pow := pow10uint64[end-start+2] + return float64(beforeDecimal+afterDecimal) / float64(pow), nil + case 'e', 'E': + dec.cursor = dec.cursor + 2 + // we get part before decimal as integer + beforeDecimal := uint64(dec.atoi64(start, end)) + // get exponent + exp := dec.getExponent() + // if exponent is negative + if exp < 0 { + return float64(beforeDecimal) * (1 / float64(pow10uint64[exp*-1+1])), nil + } + return float64(beforeDecimal) * float64(pow10uint64[exp+1]), nil + case ' ', '\n', '\t', '\r', ',', '}', ']': // does not have decimal + dec.cursor = j + return float64(dec.atoi64(start, end)), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, InvalidJSONError("Invalid JSON while parsing number") + } + return float64(dec.atoi64(start, end)), nil +} + +// DecodeFloat32 reads the next JSON-encoded value from its input and stores it in the float32 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeFloat32(v *float32) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeFloat32(v) +} +func (dec *Decoder) decodeFloat32(v *float32) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getFloat32(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getFloat32(c) + if err != nil { + return err + } + *v = -val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + default: + dec.err = InvalidUnmarshalError( + fmt.Sprintf( + "Cannot unmarshall to float, wrong char '%s' found at pos %d", + string(dec.data[dec.cursor]), + dec.cursor, + ), + ) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return InvalidJSONError("Invalid JSON while parsing float") +} + +func (dec *Decoder) getFloat32(b byte) (float32, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case '.': + // we get part before decimal as integer + beforeDecimal := dec.atoi32(start, end) + // then we get part after decimal as integer + start = j + 1 + // get number after the decimal point + // multiple the before decimal point portion by 10 using bitwise + for i := j + 1; i < dec.length || dec.read(); i++ { + c := dec.data[i] + if isDigit(c) { + end = i + beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) + continue + } else if c == 'e' || c == 'E' { + afterDecimal := dec.atoi32(start, end) + dec.cursor = i + 1 + pow := pow10uint64[end-start+2] + floatVal := float32(beforeDecimal+afterDecimal) / float32(pow) + exp := dec.getExponent() + // if exponent is negative + if exp < 0 { + return float32(floatVal) * (1 / float32(pow10uint64[exp*-1+1])), nil + } + return float32(floatVal) * float32(pow10uint64[exp+1]), nil + } + dec.cursor = i + break + } + // then we add both integers + // then we divide the number by the power found + afterDecimal := dec.atoi32(start, end) + pow := pow10uint64[end-start+2] + return float32(beforeDecimal+afterDecimal) / float32(pow), nil + case 'e', 'E': + dec.cursor = dec.cursor + 2 + // we get part before decimal as integer + beforeDecimal := uint32(dec.atoi32(start, end)) + // get exponent + exp := dec.getExponent() + // if exponent is negative + if exp < 0 { + return float32(beforeDecimal) * (1 / float32(pow10uint64[exp*-1+1])), nil + } + return float32(beforeDecimal) * float32(pow10uint64[exp+1]), nil + case ' ', '\n', '\t', '\r', ',', '}', ']': // does not have decimal + dec.cursor = j + return float32(dec.atoi32(start, end)), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, InvalidJSONError("Invalid JSON while parsing number") + } + return float32(dec.atoi32(start, end)), nil +} diff --git a/decode_number_float_test.go b/decode_number_float_test.go @@ -0,0 +1,387 @@ +package gojay + +import ( + "fmt" + "math" + "reflect" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecoderFloat64(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult float64 + err bool + errType interface{} + }{ + { + name: "basic-exponent-positive-positive-exp", + json: "1e2", + expectedResult: 100, + }, + { + name: "basic-exponent-positive-positive-exp2", + json: "5e+06", + expectedResult: 5000000, + }, + { + name: "basic-exponent-positive-positive-exp3", + json: "3e+3", + expectedResult: 3000, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-exponent-positive-positive-exp4", + json: "8e+005", + expectedResult: 800000, + }, + { + name: "basic-exponent-positive-negative-exp", + json: "1e-2", + expectedResult: 0.01, + }, + { + name: "basic-exponent-positive-negative-exp2", + json: "5e-6", + expectedResult: 0.000005, + }, + { + name: "basic-exponent-positive-negative-exp3", + json: "3e-3", + expectedResult: 0.003, + }, + { + name: "basic-exponent-positive-negative-exp4", + json: "8e-005", + expectedResult: 0.00008, + }, + { + name: "basic-exponent-negative-positive-exp", + json: "-1e2", + expectedResult: -100, + }, + { + name: "basic-exponent-negative-positive-exp2", + json: "-5e+06", + expectedResult: -5000000, + }, + { + name: "basic-exponent-negative-positive-exp3", + json: "-3e03", + expectedResult: -3000, + }, + { + name: "basic-float2", + json: "877 ", + expectedResult: 877, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8e+005", + expectedResult: -800000, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8.2e-005", + expectedResult: -0.000082, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2.4595, + }, + { + name: "basic-float2", + json: "877", + expectedResult: 877, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: -7.8876, + }, + { + name: "basic-float", + json: "2.4595e1", + expectedResult: 24.595, + }, + { + name: "basic-float2", + json: "-7.8876e002", + expectedResult: -788.76, + }, + { + name: "error", + json: "83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error", + json: "-83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v float64 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult*1000000, math.Round(v*1000000), fmt.Sprintf("v must be equal to %f", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := float64(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeFloat64(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + }) + t.Run("decoder-api", func(t *testing.T) { + var v float64 + dec := NewDecoder(strings.NewReader(`1.25`)) + defer dec.Release() + err := dec.DecodeFloat64(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, 1.25, v, "v must be equal to 1.25") + }) + t.Run("decoder-api-json-error", func(t *testing.T) { + var v float64 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeFloat64(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} + +func TestDecoderFloat32(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult float32 + err bool + errType interface{} + }{ + { + name: "basic-exponent-positive-positive-exp", + json: "1e2", + expectedResult: 100, + }, + { + name: "basic-exponent-positive-positive-exp2", + json: "5e+06", + expectedResult: 5000000, + }, + { + name: "basic-exponent-positive-positive-exp3", + json: "3e+3", + expectedResult: 3000, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-exponent-positive-positive-exp4", + json: "8e+005", + expectedResult: 800000, + }, + { + name: "basic-exponent-positive-negative-exp", + json: "1e-2", + expectedResult: 0.01, + }, + { + name: "basic-exponent-positive-negative-exp2", + json: "5e-6", + expectedResult: 0.000005, + }, + { + name: "basic-exponent-positive-negative-exp3", + json: "3e-3", + expectedResult: 0.003, + }, + { + name: "basic-exponent-positive-negative-exp4", + json: "8e-005", + expectedResult: 0.00008, + }, + { + name: "basic-exponent-negative-positive-exp", + json: "-1e2", + expectedResult: -100, + }, + { + name: "basic-exponent-negative-positive-exp2", + json: "-5e+06", + expectedResult: -5000000, + }, + { + name: "basic-exponent-negative-positive-exp3", + json: "-3e03", + expectedResult: -3000, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8e+005", + expectedResult: -800000, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8.2e-005", + expectedResult: -0.000082, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2.4595, + }, + { + name: "basic-float2", + json: "877", + expectedResult: 877, + }, + { + name: "basic-float2", + json: "877 ", + expectedResult: 877, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: -7.8876, + }, + { + name: "basic-float", + json: "2.459e1", + expectedResult: 24.59, + }, + { + name: "basic-float2", + json: "-7.8876e002", + expectedResult: -788.76, + }, + { + name: "error", + json: "83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error", + json: "-83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v float32 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, float64(testCase.expectedResult*1000000), math.Round(float64(v*1000000)), fmt.Sprintf("v must be equal to %f", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := float32(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeFloat32(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + }) + t.Run("decoder-api", func(t *testing.T) { + var v float32 + dec := NewDecoder(strings.NewReader(`1.25`)) + defer dec.Release() + err := dec.DecodeFloat32(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, float32(1.25), v, "v must be equal to 1.25") + }) + t.Run("decoder-api-json-error", func(t *testing.T) { + var v float32 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeFloat32(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} diff --git a/decode_number_int.go b/decode_number_int.go @@ -0,0 +1,819 @@ +package gojay + +import ( + "fmt" + "math" +) + +// DecodeInt reads the next JSON-encoded value from its input and stores it in the int pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeInt(v *int) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeInt(v) +} +func (dec *Decoder) decodeInt(v *int) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + // we don't look for 0 as leading zeros are invalid per RFC + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getInt64(c) + if err != nil { + return err + } + *v = int(val) + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getInt64(dec.data[dec.cursor]) + if err != nil { + return err + } + *v = -int(val) + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + dec.cursor++ + return nil + default: + dec.err = InvalidUnmarshalError( + fmt.Sprintf( + "Cannot unmarshall to int, wrong char '%s' found at pos %d", + string(dec.data[dec.cursor]), + dec.cursor, + ), + ) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return InvalidJSONError("Invalid JSON while parsing int") +} + +// DecodeInt16 reads the next JSON-encoded value from its input and stores it in the int16 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeInt16(v *int16) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeInt16(v) +} +func (dec *Decoder) decodeInt16(v *int16) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + // we don't look for 0 as leading zeros are invalid per RFC + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getInt16(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getInt16(dec.data[dec.cursor]) + if err != nil { + return err + } + *v = -val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + dec.cursor++ + return nil + default: + dec.err = InvalidUnmarshalError( + fmt.Sprintf( + "Cannot unmarshall to int, wrong char '%s' found at pos %d", + string(dec.data[dec.cursor]), + dec.cursor, + ), + ) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return InvalidJSONError("Invalid JSON while parsing int") +} + +func (dec *Decoder) getInt16(b byte) (int16, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case '.': + // if dot is found + // look for exponent (e,E) as exponent can change the + // way number should be parsed to int. + // if no exponent found, just unmarshal the number before decimal point + startDecimal := j + 1 + endDecimal := j + 1 + j++ + for ; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + endDecimal = j + continue + case 'e', 'E': + dec.cursor = j + 1 + // can try unmarshalling to int as Exponent might change decimal number to non decimal + // let's get the float value first + // we get part before decimal as integer + beforeDecimal := dec.atoi16(start, end) + // get number after the decimal point + // multiple the before decimal point portion by 10 using bitwise + for i := startDecimal; i <= endDecimal; i++ { + beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) + } + // then we add both integers + // then we divide the number by the power found + afterDecimal := dec.atoi16(startDecimal, endDecimal) + pow := pow10uint64[endDecimal-startDecimal+2] + floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) + // we have the floating value, now multiply by the exponent + exp := dec.getExponent() + val := floatVal * float64(pow10uint64[exp+1]) + return int16(val), nil + case ' ', '\t', '\n', ',', ']', '}': + dec.cursor = j + return dec.atoi16(start, end), nil + default: + dec.cursor = j + return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + return dec.atoi16(start, end), nil + case 'e', 'E': + // get init n + return dec.getInt16WithExp(dec.atoi16(start, end), j+1) + case ' ', '\n', '\t', '\r', ',', '}', ']': + dec.cursor = j + return dec.atoi16(start, end), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, InvalidJSONError("Invalid JSON while parsing number") + } + return dec.atoi16(start, end), nil +} + +func (dec *Decoder) getInt16WithExp(init int16, cursor int) (int16, error) { + var exp uint16 + var sign = int16(1) + for ; cursor < dec.length || dec.read(); cursor++ { + switch dec.data[cursor] { + case '+': + continue + case '-': + sign = -1 + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + uintv := uint16(digits[dec.data[cursor]]) + exp = (exp << 3) + (exp << 1) + uintv + cursor++ + for ; cursor < dec.length || dec.read(); cursor++ { + switch dec.data[cursor] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + uintv := uint16(digits[dec.data[cursor]]) + exp = (exp << 3) + (exp << 1) + uintv + case ' ', '\t', '\n', '}', ',', ']': + if sign == -1 { + return init * (1 / int16(pow10uint64[exp+1])), nil + } + return init * int16(pow10uint64[exp+1]), nil + default: + return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + if sign == -1 { + return init * (1 / int16(pow10uint64[exp+1])), nil + } + return init * int16(pow10uint64[exp+1]), nil + default: + dec.err = InvalidJSONError("Invalid JSON") + return 0, dec.err + } + } + return 0, InvalidJSONError("Invalid JSON") +} + +// DecodeInt8 reads the next JSON-encoded value from its input and stores it in the int8 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeInt8(v *int8) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeInt8(v) +} +func (dec *Decoder) decodeInt8(v *int8) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + // we don't look for 0 as leading zeros are invalid per RFC + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getInt8(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getInt8(dec.data[dec.cursor]) + if err != nil { + return err + } + *v = -val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + dec.cursor++ + return nil + default: + dec.err = InvalidUnmarshalError( + fmt.Sprintf( + "Cannot unmarshall to int, wrong char '%s' found at pos %d", + string(dec.data[dec.cursor]), + dec.cursor, + ), + ) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return InvalidJSONError("Invalid JSON while parsing int") +} + +func (dec *Decoder) getInt8(b byte) (int8, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case '.': + // if dot is found + // look for exponent (e,E) as exponent can change the + // way number should be parsed to int. + // if no exponent found, just unmarshal the number before decimal point + startDecimal := j + 1 + endDecimal := j + 1 + j++ + for ; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + endDecimal = j + continue + case 'e', 'E': + dec.cursor = j + 1 + // can try unmarshalling to int as Exponent might change decimal number to non decimal + // let's get the float value first + // we get part before decimal as integer + beforeDecimal := dec.atoi8(start, end) + // get number after the decimal point + // multiple the before decimal point portion by 10 using bitwise + for i := startDecimal; i <= endDecimal; i++ { + beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) + } + // then we add both integers + // then we divide the number by the power found + afterDecimal := dec.atoi8(startDecimal, endDecimal) + pow := pow10uint64[endDecimal-startDecimal+2] + floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) + // we have the floating value, now multiply by the exponent + exp := dec.getExponent() + val := floatVal * float64(pow10uint64[exp+1]) + return int8(val), nil + case ' ', '\t', '\n', ',', ']', '}': + dec.cursor = j + return dec.atoi8(start, end), nil + default: + dec.cursor = j + return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + return dec.atoi8(start, end), nil + case 'e', 'E': + // get init n + return dec.getInt8WithExp(dec.atoi8(start, end), j+1) + case ' ', '\n', '\t', '\r', ',', '}', ']': + dec.cursor = j + return dec.atoi8(start, end), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, InvalidJSONError("Invalid JSON while parsing number") + } + return dec.atoi8(start, end), nil +} + +func (dec *Decoder) getInt8WithExp(init int8, cursor int) (int8, error) { + var exp uint8 + var sign = int8(1) + for ; cursor < dec.length || dec.read(); cursor++ { + switch dec.data[cursor] { + case '+': + continue + case '-': + sign = -1 + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + uintv := uint8(digits[dec.data[cursor]]) + exp = (exp << 3) + (exp << 1) + uintv + cursor++ + for ; cursor < dec.length || dec.read(); cursor++ { + switch dec.data[cursor] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + uintv := uint8(digits[dec.data[cursor]]) + exp = (exp << 3) + (exp << 1) + uintv + case ' ', '\t', '\n', '}', ',', ']': + if sign == -1 { + return init * (1 / int8(pow10uint64[exp+1])), nil + } + return init * int8(pow10uint64[exp+1]), nil + default: + return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + if sign == -1 { + return init * (1 / int8(pow10uint64[exp+1])), nil + } + return init * int8(pow10uint64[exp+1]), nil + default: + dec.err = InvalidJSONError("Invalid JSON") + return 0, dec.err + } + } + return 0, InvalidJSONError("Invalid JSON") +} + +// DecodeInt32 reads the next JSON-encoded value from its input and stores it in the int32 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeInt32(v *int32) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeInt32(v) +} +func (dec *Decoder) decodeInt32(v *int32) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getInt32(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getInt32(dec.data[dec.cursor]) + if err != nil { + return err + } + *v = -val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + default: + dec.err = InvalidUnmarshalError( + fmt.Sprintf( + "Cannot unmarshall to int, wrong char '%s' found at pos %d", + string(dec.data[dec.cursor]), + dec.cursor, + ), + ) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return InvalidJSONError("Invalid JSON while parsing int") +} + +func (dec *Decoder) getInt32(b byte) (int32, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case '.': + // if dot is found + // look for exponent (e,E) as exponent can change the + // way number should be parsed to int. + // if no exponent found, just unmarshal the number before decimal point + startDecimal := j + 1 + endDecimal := j + 1 + j++ + for ; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + endDecimal = j + continue + case 'e', 'E': + dec.cursor = j + 1 + // can try unmarshalling to int as Exponent might change decimal number to non decimal + // let's get the float value first + // we get part before decimal as integer + beforeDecimal := dec.atoi64(start, end) + // get number after the decimal point + // multiple the before decimal point portion by 10 using bitwise + for i := startDecimal; i <= endDecimal; i++ { + beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) + } + // then we add both integers + // then we divide the number by the power found + afterDecimal := dec.atoi64(startDecimal, endDecimal) + pow := pow10uint64[endDecimal-startDecimal+2] + floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) + // we have the floating value, now multiply by the exponent + exp := dec.getExponent() + val := floatVal * float64(pow10uint64[exp+1]) + return int32(val), nil + case ' ', '\t', '\n', ',', ']', '}': + dec.cursor = j + return dec.atoi32(start, end), nil + default: + dec.cursor = j + return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + return dec.atoi32(start, end), nil + case 'e', 'E': + // get init n + return dec.getInt32WithExp(dec.atoi32(start, end), j+1) + case ' ', '\n', '\t', '\r', ',', '}', ']': + dec.cursor = j + return dec.atoi32(start, end), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, InvalidJSONError("Invalid JSON while parsing number") + } + return dec.atoi32(start, end), nil +} + +func (dec *Decoder) getInt32WithExp(init int32, cursor int) (int32, error) { + var exp uint32 + var sign = int32(1) + for ; cursor < dec.length || dec.read(); cursor++ { + switch dec.data[cursor] { + case '+': + continue + case '-': + sign = -1 + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + uintv := uint32(digits[dec.data[cursor]]) + exp = (exp << 3) + (exp << 1) + uintv + cursor++ + for ; cursor < dec.length || dec.read(); cursor++ { + switch dec.data[cursor] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + uintv := uint32(digits[dec.data[cursor]]) + exp = (exp << 3) + (exp << 1) + uintv + case ' ', '\t', '\n', '}', ',', ']': + if sign == -1 { + return init * (1 / int32(pow10uint64[exp+1])), nil + } + return init * int32(pow10uint64[exp+1]), nil + default: + return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + if sign == -1 { + return init * (1 / int32(pow10uint64[exp+1])), nil + } + return init * int32(pow10uint64[exp+1]), nil + default: + dec.err = InvalidJSONError("Invalid JSON") + return 0, dec.err + } + } + return 0, InvalidJSONError("Invalid JSON") +} + +// DecodeInt64 reads the next JSON-encoded value from its input and stores it in the int64 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeInt64(v *int64) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeInt64(v) +} + +func (dec *Decoder) decodeInt64(v *int64) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getInt64(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getInt64(dec.data[dec.cursor]) + if err != nil { + return err + } + *v = -val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + default: + dec.err = InvalidUnmarshalError( + fmt.Sprintf( + "Cannot unmarshall to int, wrong char '%s' found at pos %d", + string(dec.data[dec.cursor]), + dec.cursor, + ), + ) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return InvalidJSONError("Invalid JSON while parsing int") +} + +func (dec *Decoder) getInt64(b byte) (int64, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case ' ', '\t', '\n', ',', '}', ']': + dec.cursor = j + return dec.atoi64(start, end), nil + case '.': + // if dot is found + // look for exponent (e,E) as exponent can change the + // way number should be parsed to int. + // if no exponent found, just unmarshal the number before decimal point + startDecimal := j + 1 + endDecimal := j + 1 + j++ + for ; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + endDecimal = j + continue + case 'e', 'E': + dec.cursor = j + 1 + // can try unmarshalling to int as Exponent might change decimal number to non decimal + // let's get the float value first + // we get part before decimal as integer + beforeDecimal := dec.atoi64(start, end) + // get number after the decimal point + // multiple the before decimal point portion by 10 using bitwise + for i := startDecimal; i <= endDecimal; i++ { + beforeDecimal = (beforeDecimal << 3) + (beforeDecimal << 1) + } + // then we add both integers + // then we divide the number by the power found + afterDecimal := dec.atoi64(startDecimal, endDecimal) + pow := pow10uint64[endDecimal-startDecimal+2] + floatVal := float64(beforeDecimal+afterDecimal) / float64(pow) + // we have the floating value, now multiply by the exponent + exp := dec.getExponent() + val := floatVal * float64(pow10uint64[exp+1]) + return int64(val), nil + case ' ', '\t', '\n', ',', ']', '}': + dec.cursor = j + return dec.atoi64(start, end), nil + default: + dec.cursor = j + return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + return dec.atoi64(start, end), nil + case 'e', 'E': + // get init n + return dec.getInt64WithExp(dec.atoi64(start, end), j+1) + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, InvalidJSONError("Invalid JSON while parsing number") + } + return dec.atoi64(start, end), nil +} + +func (dec *Decoder) getInt64WithExp(init int64, cursor int) (int64, error) { + var exp uint64 + var sign = int64(1) + for ; cursor < dec.length || dec.read(); cursor++ { + switch dec.data[cursor] { + case '+': + continue + case '-': + sign = -1 + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + uintv := uint64(digits[dec.data[cursor]]) + exp = (exp << 3) + (exp << 1) + uintv + cursor++ + for ; cursor < dec.length || dec.read(); cursor++ { + switch dec.data[cursor] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + uintv := uint64(digits[dec.data[cursor]]) + exp = (exp << 3) + (exp << 1) + uintv + case ' ', '\t', '\n', '}', ',', ']': + if sign == -1 { + return init * (1 / int64(pow10uint64[exp+1])), nil + } + return init * int64(pow10uint64[exp+1]), nil + default: + return 0, InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + if sign == -1 { + return init * (1 / int64(pow10uint64[exp+1])), nil + } + return init * int64(pow10uint64[exp+1]), nil + default: + dec.err = InvalidJSONError("Invalid JSON") + return 0, dec.err + } + } + return 0, InvalidJSONError("Invalid JSON") +} + +func (dec *Decoder) atoi64(start, end int) int64 { + var ll = end + 1 - start + var val = int64(digits[dec.data[start]]) + end = end + 1 + if ll < maxInt64Length { + for i := start + 1; i < end; i++ { + intv := int64(digits[dec.data[i]]) + val = (val << 3) + (val << 1) + intv + } + return val + } else if ll == maxInt64Length { + for i := start + 1; i < end; i++ { + intv := int64(digits[dec.data[i]]) + if val > maxInt64toMultiply { + dec.err = InvalidUnmarshalError("Overflows int64") + return 0 + } + val = (val << 3) + (val << 1) + if math.MaxInt64-val < intv { + dec.err = InvalidUnmarshalError("Overflows int64") + return 0 + } + val += intv + } + } else { + dec.err = InvalidUnmarshalError("Overflows int64") + return 0 + } + return val +} + +func (dec *Decoder) atoi32(start, end int) int32 { + var ll = end + 1 - start + var val = int32(digits[dec.data[start]]) + end = end + 1 + // overflowing + if ll < maxInt32Length { + for i := start + 1; i < end; i++ { + intv := int32(digits[dec.data[i]]) + val = (val << 3) + (val << 1) + intv + } + } else if ll == maxInt32Length { + for i := start + 1; i < end; i++ { + intv := int32(digits[dec.data[i]]) + if val > maxInt32toMultiply { + dec.err = InvalidUnmarshalError("Overflows int32") + return 0 + } + val = (val << 3) + (val << 1) + if math.MaxInt32-val < intv { + dec.err = InvalidUnmarshalError("Overflows int32") + return 0 + } + val += intv + } + } else { + dec.err = InvalidUnmarshalError("Overflows int32") + return 0 + } + return val +} + +func (dec *Decoder) atoi16(start, end int) int16 { + var ll = end + 1 - start + var val = int16(digits[dec.data[start]]) + end = end + 1 + // overflowing + if ll < maxInt16Length { + for i := start + 1; i < end; i++ { + intv := int16(digits[dec.data[i]]) + val = (val << 3) + (val << 1) + intv + } + } else if ll == maxInt16Length { + for i := start + 1; i < end; i++ { + intv := int16(digits[dec.data[i]]) + if val > maxInt16toMultiply { + dec.err = InvalidUnmarshalError("Overflows int32") + return 0 + } + val = (val << 3) + (val << 1) + if math.MaxInt16-val < intv { + dec.err = InvalidUnmarshalError("Overflows int32") + return 0 + } + val += intv + } + } else { + dec.err = InvalidUnmarshalError("Overflows int32") + return 0 + } + return val +} + +func (dec *Decoder) atoi8(start, end int) int8 { + var ll = end + 1 - start + var val = int8(digits[dec.data[start]]) + end = end + 1 + // overflowing + if ll < maxInt8Length { + for i := start + 1; i < end; i++ { + intv := int8(digits[dec.data[i]]) + val = (val << 3) + (val << 1) + intv + } + } else if ll == maxInt8Length { + for i := start + 1; i < end; i++ { + intv := int8(digits[dec.data[i]]) + if val > maxInt8toMultiply { + dec.err = InvalidUnmarshalError("Overflows int32") + return 0 + } + val = (val << 3) + (val << 1) + if math.MaxInt8-val < intv { + dec.err = InvalidUnmarshalError("Overflows int32") + return 0 + } + val += intv + } + } else { + dec.err = InvalidUnmarshalError("Overflows int32") + return 0 + } + return val +} diff --git a/decode_number_int_test.go b/decode_number_int_test.go @@ -0,0 +1,1131 @@ +package gojay + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecoderInt(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult int + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: "1039405", + expectedResult: 1039405, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: -2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-big", + json: "9223372036854775807", + expectedResult: 9223372036854775807, + }, + { + name: "basic-big-overflow", + json: "9223372036854775808", + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "basic-big-overflow2", + json: "92233720368547758089", + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "basic-big-overflow3", + json: "92233720368547758089 ", + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "basic-negative2", + json: "-2349557", + expectedResult: -2349557, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: -7, + }, + { + name: "basic-float2", + json: "-7.8876 ", + expectedResult: -7, + }, + { + name: "basic-float2", + json: "-7.8876a", + expectedResult: 0, + err: true, + }, + { + name: "basic-exponent-positive-positive-exp", + json: "1e2", + expectedResult: 100, + }, + { + name: "basic-exponent-positive-positive-exp2", + json: "5e+06", + expectedResult: 5000000, + }, + { + name: "basic-exponent-positive-positive-exp2", + json: "5.01e+10", + expectedResult: 50100000000, + }, + { + name: "basic-exponent-positive-positive-exp3", + json: "3e+3", + expectedResult: 3000, + }, + { + name: "basic-exponent-positive-positive-exp4", + json: "8e+005", + expectedResult: 800000, + }, + { + name: "basic-exponent-positive-negative-exp", + json: "1e-2", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp2", + json: "5e-6", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp3", + json: "3e-3", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp4", + json: "8e-005", + expectedResult: 0, + }, + { + name: "basic-exponent-negative-positive-exp", + json: "-1e2", + expectedResult: -100, + }, + { + name: "basic-exponent-negative-positive-exp2", + json: "-5e+06", + expectedResult: -5000000, + }, + { + name: "basic-exponent-negative-positive-exp3", + json: "-3e03", + expectedResult: -3000, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8e+005", + expectedResult: -800000, + }, + { + name: "error1", + json: "132zz4", + expectedResult: 0, + err: true, + }, + { + name: "negative-error2", + json: " -1213xdde2323 ", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error3", + json: "-8e+00$aa5", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v int + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := int(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeInt(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + }) + t.Run("decoder-api", func(t *testing.T) { + var v int + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeInt(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-invalid-json", func(t *testing.T) { + var v int + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeInt(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} + +func TestDecoderInt64(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult int64 + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: " 1039405", + expectedResult: 1039405, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: -2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-big", + json: "9223372036854775807", + expectedResult: 9223372036854775807, + }, + { + name: "basic-big-overflow", + json: " 9223372036854775808", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow2", + json: "92233720368547758089", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow3", + json: "92233720368547758089 ", + expectedResult: 0, + err: true, + }, + { + name: "basic-negative2", + json: "-2349557", + expectedResult: -2349557, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: -7, + }, + { + name: "basic-float2", + json: "-7.8876a", + expectedResult: 0, + err: true, + }, + { + name: "basic-exponent-positive-positive-exp", + json: "1e2", + expectedResult: 100, + }, + { + name: "basic-exponent-positive-positive-exp2", + json: "5e+06 ", + expectedResult: 5000000, + }, + { + name: "basic-exponent-positive-positive-exp3", + json: "3e+3", + expectedResult: 3000, + }, + { + name: "basic-exponent-positive-positive-exp4", + json: "8e+005", + expectedResult: 800000, + }, + { + name: "basic-exponent-positive-negative-exp", + json: "1e-2 ", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp2", + json: "5e-6", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp3", + json: "3e-3", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp4", + json: "8e-005", + expectedResult: 0, + }, + { + name: "basic-exponent-negative-positive-exp", + json: "-1e2", + expectedResult: -100, + }, + { + name: "basic-exponent-negative-positive-exp2", + json: "-5e+06", + expectedResult: -5000000, + }, + { + name: "basic-exponent-negative-positive-exp2", + json: "-5.4e+06", + expectedResult: -5400000, + }, + { + name: "basic-exponent-negative-positive-exp3", + json: "-3e03", + expectedResult: -3000, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8e+005", + expectedResult: -800000, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "8ea+00a5", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error1", + json: "132zz4", + expectedResult: 0, + err: true, + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v int64 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := int64(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeInt64(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + }) + t.Run("decoder-api", func(t *testing.T) { + var v int64 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeInt64(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int64(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-invalid-json", func(t *testing.T) { + var v int64 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeInt64(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} + +func TestDecoderInt32(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult int32 + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: " 1039405", + expectedResult: 1039405, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: -2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-negative2", + json: "-2349557", + expectedResult: -2349557, + }, + { + name: "basic-big", + json: " 2147483647", + expectedResult: 2147483647, + }, + { + name: "basic-big-overflow", + json: " 2147483648", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow2", + json: "21474836483", + expectedResult: 0, + err: true, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: -7, + }, + { + name: "basic-float2", + json: "-7.8876a", + expectedResult: 0, + err: true, + }, + { + name: "basic-exponent-positive-positive-exp", + json: "1.2E2", + expectedResult: 120, + }, + { + name: "basic-exponent-positive-positive-exp1", + json: "3.5e+005 ", + expectedResult: 350000, + }, + { + name: "basic-exponent-positive-positive-exp1", + json: "3.5e+005", + expectedResult: 350000, + }, + { + name: "basic-exponent-positive-positive-exp2", + json: "5e+06", + expectedResult: 5000000, + }, + { + name: "basic-exponent-positive-positive-exp3", + json: "3e+3", + expectedResult: 3000, + }, + { + name: "basic-exponent-positive-positive-exp4", + json: "8e+005 ", + expectedResult: 800000, + }, + { + name: "basic-exponent-positive-negative-exp", + json: "1e-2 ", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp2", + json: "5E-6", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp3", + json: "3e-3", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp4", + json: "8e-005", + expectedResult: 0, + }, + { + name: "basic-exponent-negative-positive-exp", + json: "-1e2", + expectedResult: -100, + }, + { + name: "basic-exponent-negative-positive-exp2", + json: "-5e+06", + expectedResult: -5000000, + }, + { + name: "basic-exponent-negative-positive-exp3", + json: "-3e03", + expectedResult: -3000, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8e+005", + expectedResult: -800000, + }, + { + name: "basic-float", + json: "8.32 ", + expectedResult: 8, + }, + { + name: "error", + json: "83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error", + json: "8ea00$aa5", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error2", + json: "-8e+00$aa5", + expectedResult: 0, + err: true, + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v int32 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := int32(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeInt32(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + + }) + t.Run("decoder-api", func(t *testing.T) { + var v int32 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeInt32(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int32(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-invalid-json", func(t *testing.T) { + var v int32 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeInt32(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} + +func TestDecoderInt16(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult int16 + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: " 5321", + expectedResult: 5321, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: -2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-negative2", + json: "-2456", + expectedResult: -2456, + }, + { + name: "basic-big", + json: " 24566", + expectedResult: 24566, + }, + { + name: "basic-big-overflow", + json: " 2147483648", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow2", + json: "21474836483", + expectedResult: 0, + err: true, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: -7, + }, + { + name: "basic-float2", + json: "-7.8876a", + expectedResult: 0, + err: true, + }, + { + name: "basic-exponent-positive-positive-exp", + json: "1.2E2", + expectedResult: 120, + }, + { + name: "basic-exponent-positive-positive-exp1", + json: "3.5e+001 ", + expectedResult: 35, + }, + { + name: "basic-exponent-positive-positive-exp1", + json: "3.5e+002", + expectedResult: 350, + }, + { + name: "basic-exponent-positive-positive-exp2", + json: "5e+03", + expectedResult: 5000, + }, + { + name: "basic-exponent-positive-positive-exp3", + json: "3e+3", + expectedResult: 3000, + }, + { + name: "basic-exponent-positive-positive-exp4", + json: "8e+02 ", + expectedResult: 800, + }, + { + name: "basic-exponent-positive-negative-exp", + json: "1e-2 ", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp2", + json: "5E-6", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp3", + json: "3e-3", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp4", + json: "8e-005", + expectedResult: 0, + }, + { + name: "basic-exponent-negative-positive-exp", + json: "-1e2", + expectedResult: -100, + }, + { + name: "basic-exponent-negative-positive-exp2", + json: "-5e+03", + expectedResult: -5000, + }, + { + name: "basic-exponent-negative-positive-exp3", + json: "-3e03", + expectedResult: -3000, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8e+003", + expectedResult: -8000, + }, + { + name: "basic-float", + json: "8.32 ", + expectedResult: 8, + }, + { + name: "error", + json: "83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error", + json: "8ea00$aa5", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error2", + json: "-8e+00$aa5", + expectedResult: 0, + err: true, + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v int16 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := int16(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeInt16(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + + }) + t.Run("decoder-api", func(t *testing.T) { + var v int16 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeInt16(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int16(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-invalid-json", func(t *testing.T) { + var v int16 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeInt16(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} + +func TestDecoderInt8(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult int8 + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: " 127", + expectedResult: 127, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: -2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-negative2", + json: "-123", + expectedResult: -123, + }, + { + name: "basic-big", + json: " 43", + expectedResult: 43, + }, + { + name: "basic-big-overflow", + json: " 2147483648", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow2", + json: "21474836483", + expectedResult: 0, + err: true, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: -7, + }, + { + name: "basic-float2", + json: "-7.8876a", + expectedResult: 0, + err: true, + }, + { + name: "basic-exponent-positive-positive-exp", + json: "1.2E2", + expectedResult: 120, + }, + { + name: "basic-exponent-positive-positive-exp1", + json: "3.5e+001 ", + expectedResult: 35, + }, + { + name: "basic-exponent-positive-positive-exp1", + json: "3.5e+001", + expectedResult: 35, + }, + { + name: "basic-exponent-positive-positive-exp2", + json: "5e+01", + expectedResult: 50, + }, + { + name: "basic-exponent-positive-negative-exp", + json: "1e-2 ", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp2", + json: "5E-6", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp3", + json: "3e-3", + expectedResult: 0, + }, + { + name: "basic-exponent-positive-negative-exp4", + json: "8e-005", + expectedResult: 0, + }, + { + name: "basic-exponent-negative-positive-exp", + json: "-1e2", + expectedResult: -100, + }, + { + name: "basic-exponent-negative-positive-exp2", + json: "-5e+01", + expectedResult: -50, + }, + { + name: "basic-exponent-negative-positive-exp3", + json: "-3e01", + expectedResult: -30, + }, + { + name: "basic-exponent-negative-positive-exp4", + json: "-8e+001", + expectedResult: -80, + }, + { + name: "basic-float", + json: "8.32 ", + expectedResult: 8, + }, + { + name: "error", + json: "83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error", + json: "8ea00$aa5", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error2", + json: "-8e+00$aa5", + expectedResult: 0, + err: true, + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v int8 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := int8(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeInt8(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + + }) + t.Run("decoder-api", func(t *testing.T) { + var v int8 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeInt8(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int8(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-invalid-json", func(t *testing.T) { + var v int8 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeInt8(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} diff --git a/decode_number_test.go b/decode_number_test.go @@ -1,1151 +1,12 @@ package gojay import ( - "fmt" - "math" - "reflect" "strings" "testing" "github.com/stretchr/testify/assert" ) -func TestDecoderInt(t *testing.T) { - testCases := []struct { - name string - json string - expectedResult int - err bool - errType interface{} - }{ - { - name: "basic-positive", - json: "100", - expectedResult: 100, - }, - { - name: "basic-positive2", - json: "1039405", - expectedResult: 1039405, - }, - { - name: "basic-negative", - json: "-2", - expectedResult: -2, - }, - { - name: "basic-null", - json: "null", - expectedResult: 0, - }, - { - name: "basic-null-err", - json: "nxll", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "basic-big", - json: "9223372036854775807", - expectedResult: 9223372036854775807, - }, - { - name: "basic-big-overflow", - json: "9223372036854775808", - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - { - name: "basic-big-overflow2", - json: "92233720368547758089", - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - { - name: "basic-big-overflow3", - json: "92233720368547758089 ", - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - { - name: "basic-negative2", - json: "-2349557", - expectedResult: -2349557, - }, - { - name: "basic-float", - json: "2.4595", - expectedResult: 2, - }, - { - name: "basic-float2", - json: "-7.8876", - expectedResult: -7, - }, - { - name: "basic-float2", - json: "-7.8876 ", - expectedResult: -7, - }, - { - name: "basic-float2", - json: "-7.8876a", - expectedResult: 0, - err: true, - }, - { - name: "basic-exponent-positive-positive-exp", - json: "1e2", - expectedResult: 100, - }, - { - name: "basic-exponent-positive-positive-exp2", - json: "5e+06", - expectedResult: 5000000, - }, - { - name: "basic-exponent-positive-positive-exp3", - json: "3e+3", - expectedResult: 3000, - }, - { - name: "basic-exponent-positive-positive-exp4", - json: "8e+005", - expectedResult: 800000, - }, - { - name: "basic-exponent-positive-negative-exp", - json: "1e-2", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp2", - json: "5e-6", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp3", - json: "3e-3", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp4", - json: "8e-005", - expectedResult: 0, - }, - { - name: "basic-exponent-negative-positive-exp", - json: "-1e2", - expectedResult: -100, - }, - { - name: "basic-exponent-negative-positive-exp2", - json: "-5e+06", - expectedResult: -5000000, - }, - { - name: "basic-exponent-negative-positive-exp3", - json: "-3e03", - expectedResult: -3000, - }, - { - name: "basic-exponent-negative-positive-exp4", - json: "-8e+005", - expectedResult: -800000, - }, - { - name: "error1", - json: "132zz4", - expectedResult: 0, - err: true, - }, - { - name: "negative-error2", - json: " -1213xdde2323 ", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "error3", - json: "-8e+00$aa5", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "invalid-type", - json: `"string"`, - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - json := []byte(testCase.json) - var v int - err := Unmarshal(json, &v) - if testCase.err { - assert.NotNil(t, err, "Err must not be nil") - if testCase.errType != nil { - assert.IsType( - t, - testCase.errType, - err, - fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), - ) - } - } else { - assert.Nil(t, err, "Err must be nil") - } - assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) - }) - } - t.Run("pool-error", func(t *testing.T) { - result := int(1) - dec := NewDecoder(nil) - dec.Release() - defer func() { - err := recover() - assert.NotNil(t, err, "err shouldnot be nil") - assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") - }() - _ = dec.DecodeInt(&result) - assert.True(t, false, "should not be called as decoder should have panicked") - }) - t.Run("decoder-api", func(t *testing.T) { - var v int - dec := NewDecoder(strings.NewReader(`33`)) - defer dec.Release() - err := dec.DecodeInt(&v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, int(33), v, "v must be equal to 33") - }) - t.Run("decoder-api-invalid-json", func(t *testing.T) { - var v int - dec := NewDecoder(strings.NewReader(``)) - defer dec.Release() - err := dec.DecodeInt(&v) - assert.NotNil(t, err, "Err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") - }) -} -func TestDecoderInt64(t *testing.T) { - testCases := []struct { - name string - json string - expectedResult int64 - err bool - errType interface{} - }{ - { - name: "basic-positive", - json: "100", - expectedResult: 100, - }, - { - name: "basic-positive2", - json: " 1039405", - expectedResult: 1039405, - }, - { - name: "basic-negative", - json: "-2", - expectedResult: -2, - }, - { - name: "basic-null", - json: "null", - expectedResult: 0, - }, - { - name: "basic-null-err", - json: "nxll", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "basic-big", - json: "9223372036854775807", - expectedResult: 9223372036854775807, - }, - { - name: "basic-big-overflow", - json: " 9223372036854775808", - expectedResult: 0, - err: true, - }, - { - name: "basic-big-overflow2", - json: "92233720368547758089", - expectedResult: 0, - err: true, - }, - { - name: "basic-big-overflow3", - json: "92233720368547758089 ", - expectedResult: 0, - err: true, - }, - { - name: "basic-negative2", - json: "-2349557", - expectedResult: -2349557, - }, - { - name: "basic-float", - json: "2.4595", - expectedResult: 2, - }, - { - name: "basic-float2", - json: "-7.8876", - expectedResult: -7, - }, - { - name: "basic-float2", - json: "-7.8876a", - expectedResult: 0, - err: true, - }, - { - name: "basic-exponent-positive-positive-exp", - json: "1e2", - expectedResult: 100, - }, - { - name: "basic-exponent-positive-positive-exp2", - json: "5e+06 ", - expectedResult: 5000000, - }, - { - name: "basic-exponent-positive-positive-exp3", - json: "3e+3", - expectedResult: 3000, - }, - { - name: "basic-exponent-positive-positive-exp4", - json: "8e+005", - expectedResult: 800000, - }, - { - name: "basic-exponent-positive-negative-exp", - json: "1e-2 ", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp2", - json: "5e-6", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp3", - json: "3e-3", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp4", - json: "8e-005", - expectedResult: 0, - }, - { - name: "basic-exponent-negative-positive-exp", - json: "-1e2", - expectedResult: -100, - }, - { - name: "basic-exponent-negative-positive-exp2", - json: "-5e+06", - expectedResult: -5000000, - }, - { - name: "basic-exponent-negative-positive-exp2", - json: "-5.4e+06", - expectedResult: -5400000, - }, - { - name: "basic-exponent-negative-positive-exp3", - json: "-3e03", - expectedResult: -3000, - }, - { - name: "basic-exponent-negative-positive-exp4", - json: "-8e+005", - expectedResult: -800000, - }, - { - name: "basic-exponent-negative-positive-exp4", - json: "8ea+00a5", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "error1", - json: "132zz4", - expectedResult: 0, - err: true, - }, - { - name: "invalid-type", - json: `"string"`, - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - json := []byte(testCase.json) - var v int64 - err := Unmarshal(json, &v) - if testCase.err { - assert.NotNil(t, err, "Err must not be nil") - if testCase.errType != nil { - assert.IsType( - t, - testCase.errType, - err, - fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), - ) - } - } else { - assert.Nil(t, err, "Err must be nil") - } - assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) - }) - } - t.Run("pool-error", func(t *testing.T) { - result := int64(1) - dec := NewDecoder(nil) - dec.Release() - defer func() { - err := recover() - assert.NotNil(t, err, "err shouldnot be nil") - assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") - }() - _ = dec.DecodeInt64(&result) - assert.True(t, false, "should not be called as decoder should have panicked") - }) - t.Run("decoder-api", func(t *testing.T) { - var v int64 - dec := NewDecoder(strings.NewReader(`33`)) - defer dec.Release() - err := dec.DecodeInt64(&v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, int64(33), v, "v must be equal to 33") - }) - t.Run("decoder-api-invalid-json", func(t *testing.T) { - var v int64 - dec := NewDecoder(strings.NewReader(``)) - defer dec.Release() - err := dec.DecodeInt64(&v) - assert.NotNil(t, err, "Err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") - }) -} -func TestDecoderUint64(t *testing.T) { - testCases := []struct { - name string - json string - expectedResult uint64 - err bool - errType interface{} - }{ - { - name: "basic-positive", - json: "100", - expectedResult: 100, - }, - { - name: "basic-positive2", - json: " 1039405", - expectedResult: 1039405, - }, - { - name: "basic-negative", - json: "-2", - expectedResult: 2, - }, - { - name: "basic-null", - json: "null", - expectedResult: 0, - }, - { - name: "basic-null-err", - json: "nxll", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "basic-big", - json: "18446744073709551615", - expectedResult: 18446744073709551615, - }, - { - name: "basic-big-overflow", - json: "18446744073709551616", - expectedResult: 0, - err: true, - }, - { - name: "basic-big-overflow2", - json: "184467440737095516161", - expectedResult: 0, - err: true, - }, - { - name: "basic-negative2", - json: "-2349557", - expectedResult: 2349557, - }, - { - name: "basic-float", - json: "2.4595", - expectedResult: 2, - }, - { - name: "basic-float2", - json: "-7.8876", - expectedResult: 7, - }, - { - name: "error1", - json: "132zz4", - expectedResult: 0, - err: true, - }, - { - name: "error", - json: "-83zez4", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "invalid-type", - json: `"string"`, - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - json := []byte(testCase.json) - var v uint64 - err := Unmarshal(json, &v) - if testCase.err { - assert.NotNil(t, err, "Err must not be nil") - if testCase.errType != nil { - assert.IsType( - t, - testCase.errType, - err, - fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), - ) - } - } else { - assert.Nil(t, err, "Err must be nil") - } - assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) - }) - } - t.Run("pool-error", func(t *testing.T) { - result := uint64(1) - dec := NewDecoder(nil) - dec.Release() - defer func() { - err := recover() - assert.NotNil(t, err, "err shouldnot be nil") - assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") - }() - _ = dec.DecodeUint64(&result) - assert.True(t, false, "should not be called as decoder should have panicked") - }) - t.Run("decoder-api", func(t *testing.T) { - var v uint64 - dec := NewDecoder(strings.NewReader(`33`)) - defer dec.Release() - err := dec.DecodeUint64(&v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, uint64(33), v, "v must be equal to 33") - }) - t.Run("decoder-api-json-error", func(t *testing.T) { - var v uint64 - dec := NewDecoder(strings.NewReader(``)) - defer dec.Release() - err := dec.DecodeUint64(&v) - assert.NotNil(t, err, "Err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") - }) -} -func TestDecoderInt32(t *testing.T) { - testCases := []struct { - name string - json string - expectedResult int32 - err bool - errType interface{} - }{ - { - name: "basic-positive", - json: "100", - expectedResult: 100, - }, - { - name: "basic-positive2", - json: " 1039405", - expectedResult: 1039405, - }, - { - name: "basic-negative", - json: "-2", - expectedResult: -2, - }, - { - name: "basic-null", - json: "null", - expectedResult: 0, - }, - { - name: "basic-null-err", - json: "nxll", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "basic-negative2", - json: "-2349557", - expectedResult: -2349557, - }, - { - name: "basic-big", - json: " 2147483647", - expectedResult: 2147483647, - }, - { - name: "basic-big-overflow", - json: " 2147483648", - expectedResult: 0, - err: true, - }, - { - name: "basic-big-overflow2", - json: "21474836483", - expectedResult: 0, - err: true, - }, - { - name: "basic-float", - json: "2.4595", - expectedResult: 2, - }, - { - name: "basic-float2", - json: "-7.8876", - expectedResult: -7, - }, - { - name: "basic-float2", - json: "-7.8876a", - expectedResult: 0, - err: true, - }, - { - name: "basic-exponent-positive-positive-exp", - json: "1.2E2", - expectedResult: 120, - }, - { - name: "basic-exponent-positive-positive-exp1", - json: "3.5e+005 ", - expectedResult: 350000, - }, - { - name: "basic-exponent-positive-positive-exp1", - json: "3.5e+005", - expectedResult: 350000, - }, - { - name: "basic-exponent-positive-positive-exp2", - json: "5e+06", - expectedResult: 5000000, - }, - { - name: "basic-exponent-positive-positive-exp3", - json: "3e+3", - expectedResult: 3000, - }, - { - name: "basic-exponent-positive-positive-exp4", - json: "8e+005 ", - expectedResult: 800000, - }, - { - name: "basic-exponent-positive-negative-exp", - json: "1e-2 ", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp2", - json: "5E-6", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp3", - json: "3e-3", - expectedResult: 0, - }, - { - name: "basic-exponent-positive-negative-exp4", - json: "8e-005", - expectedResult: 0, - }, - { - name: "basic-exponent-negative-positive-exp", - json: "-1e2", - expectedResult: -100, - }, - { - name: "basic-exponent-negative-positive-exp2", - json: "-5e+06", - expectedResult: -5000000, - }, - { - name: "basic-exponent-negative-positive-exp3", - json: "-3e03", - expectedResult: -3000, - }, - { - name: "basic-exponent-negative-positive-exp4", - json: "-8e+005", - expectedResult: -800000, - }, - { - name: "basic-float", - json: "8.32 ", - expectedResult: 8, - }, - { - name: "error", - json: "83zez4", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "error", - json: "8ea00$aa5", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "error2", - json: "-8e+00$aa5", - expectedResult: 0, - err: true, - }, - { - name: "invalid-type", - json: `"string"`, - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - json := []byte(testCase.json) - var v int32 - err := Unmarshal(json, &v) - if testCase.err { - assert.NotNil(t, err, "Err must not be nil") - if testCase.errType != nil { - assert.IsType( - t, - testCase.errType, - err, - fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), - ) - } - } else { - assert.Nil(t, err, "Err must be nil") - } - assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) - }) - } - t.Run("pool-error", func(t *testing.T) { - result := int32(1) - dec := NewDecoder(nil) - dec.Release() - defer func() { - err := recover() - assert.NotNil(t, err, "err shouldnot be nil") - assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") - }() - _ = dec.DecodeInt32(&result) - assert.True(t, false, "should not be called as decoder should have panicked") - - }) - t.Run("decoder-api", func(t *testing.T) { - var v int32 - dec := NewDecoder(strings.NewReader(`33`)) - defer dec.Release() - err := dec.DecodeInt32(&v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, int32(33), v, "v must be equal to 33") - }) - t.Run("decoder-api-invalid-json", func(t *testing.T) { - var v int32 - dec := NewDecoder(strings.NewReader(``)) - defer dec.Release() - err := dec.DecodeInt32(&v) - assert.NotNil(t, err, "Err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") - }) -} - -func TestDecoderUint32(t *testing.T) { - testCases := []struct { - name string - json string - expectedResult uint32 - err bool - errType interface{} - }{ - { - name: "basic-positive", - json: "100", - expectedResult: 100, - }, - { - name: "basic-positive2", - json: "1039405", - expectedResult: 1039405, - }, - { - name: "basic-negative", - json: "-2", - expectedResult: 2, - }, - { - name: "basic-null", - json: "null", - expectedResult: 0, - }, - { - name: "basic-null-err", - json: "nxll", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "basic-negative2", - json: "-2349557", - expectedResult: 2349557, - }, - { - name: "basic-big", - json: "4294967295", - expectedResult: 4294967295, - }, - { - name: "basic-big-overflow", - json: " 4294967298", - expectedResult: 0, - err: true, - }, - { - name: "basic-big-overflow2", - json: "42949672983", - expectedResult: 0, - err: true, - }, - { - name: "basic-float", - json: "2.4595", - expectedResult: 2, - }, - { - name: "basic-float2", - json: "-7.8876", - expectedResult: 7, - }, - { - name: "error", - json: "83zez4", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "error", - json: "-83zez4", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "invalid-type", - json: `"string"`, - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - { - name: "invalid-json", - json: `123invalid`, - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - json := []byte(testCase.json) - var v uint32 - err := Unmarshal(json, &v) - if testCase.err { - assert.NotNil(t, err, "Err must not be nil") - if testCase.errType != nil { - assert.IsType( - t, - testCase.errType, - err, - fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), - ) - } - } else { - assert.Nil(t, err, "Err must be nil") - } - assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) - }) - } - t.Run("pool-error", func(t *testing.T) { - result := uint32(1) - dec := NewDecoder(nil) - dec.Release() - defer func() { - err := recover() - assert.NotNil(t, err, "err shouldnot be nil") - assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") - }() - _ = dec.DecodeUint32(&result) - assert.True(t, false, "should not be called as decoder should have panicked") - }) - t.Run("decoder-api", func(t *testing.T) { - var v uint32 - dec := NewDecoder(strings.NewReader(`33`)) - defer dec.Release() - err := dec.DecodeUint32(&v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, uint32(33), v, "v must be equal to 33") - }) - t.Run("decoder-api-json-error", func(t *testing.T) { - var v uint32 - dec := NewDecoder(strings.NewReader(``)) - defer dec.Release() - err := dec.DecodeUint32(&v) - assert.NotNil(t, err, "Err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") - }) -} - -func TestDecoderFloat64(t *testing.T) { - testCases := []struct { - name string - json string - expectedResult float64 - err bool - errType interface{} - }{ - { - name: "basic-exponent-positive-positive-exp", - json: "1e2", - expectedResult: 100, - }, - { - name: "basic-exponent-positive-positive-exp2", - json: "5e+06", - expectedResult: 5000000, - }, - { - name: "basic-exponent-positive-positive-exp3", - json: "3e+3", - expectedResult: 3000, - }, - { - name: "basic-null", - json: "null", - expectedResult: 0, - }, - { - name: "basic-null-err", - json: "nxll", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "basic-exponent-positive-positive-exp4", - json: "8e+005", - expectedResult: 800000, - }, - { - name: "basic-exponent-positive-negative-exp", - json: "1e-2", - expectedResult: 0.01, - }, - { - name: "basic-exponent-positive-negative-exp2", - json: "5e-6", - expectedResult: 0.000005, - }, - { - name: "basic-exponent-positive-negative-exp3", - json: "3e-3", - expectedResult: 0.003, - }, - { - name: "basic-exponent-positive-negative-exp4", - json: "8e-005", - expectedResult: 0.00008, - }, - { - name: "basic-exponent-negative-positive-exp", - json: "-1e2", - expectedResult: -100, - }, - { - name: "basic-exponent-negative-positive-exp2", - json: "-5e+06", - expectedResult: -5000000, - }, - { - name: "basic-exponent-negative-positive-exp3", - json: "-3e03", - expectedResult: -3000, - }, - { - name: "basic-exponent-negative-positive-exp4", - json: "-8e+005", - expectedResult: -800000, - }, - { - name: "basic-exponent-negative-positive-exp4", - json: "-8.2e-005", - expectedResult: -0.000082, - }, - { - name: "basic-float", - json: "2.4595", - expectedResult: 2.4595, - }, - { - name: "basic-float2", - json: "877", - expectedResult: 877, - }, - { - name: "basic-float2", - json: "-7.8876", - expectedResult: -7.8876, - }, - { - name: "basic-float", - json: "2.4595e1", - expectedResult: 24.595, - }, - { - name: "basic-float2", - json: "-7.8876e002", - expectedResult: -788.76, - }, - { - name: "error", - json: "83zez4", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "error", - json: "-83zez4", - expectedResult: 0, - err: true, - errType: InvalidJSONError(""), - }, - { - name: "invalid-type", - json: `"string"`, - expectedResult: 0, - err: true, - errType: InvalidTypeError(""), - }, - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - json := []byte(testCase.json) - var v float64 - err := Unmarshal(json, &v) - if testCase.err { - assert.NotNil(t, err, "Err must not be nil") - if testCase.errType != nil { - assert.IsType( - t, - testCase.errType, - err, - fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), - ) - } - } else { - assert.Nil(t, err, "Err must be nil") - } - assert.Equal(t, testCase.expectedResult*1000000, math.Round(v*1000000), fmt.Sprintf("v must be equal to %f", testCase.expectedResult)) - }) - } - t.Run("pool-error", func(t *testing.T) { - result := float64(1) - dec := NewDecoder(nil) - dec.Release() - defer func() { - err := recover() - assert.NotNil(t, err, "err shouldnot be nil") - assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") - }() - _ = dec.DecodeFloat64(&result) - assert.True(t, false, "should not be called as decoder should have panicked") - }) - t.Run("decoder-api", func(t *testing.T) { - var v float64 - dec := NewDecoder(strings.NewReader(`1.25`)) - defer dec.Release() - err := dec.DecodeFloat64(&v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, 1.25, v, "v must be equal to 1.25") - }) - t.Run("decoder-api-json-error", func(t *testing.T) { - var v float64 - dec := NewDecoder(strings.NewReader(``)) - defer dec.Release() - err := dec.DecodeFloat64(&v) - assert.NotNil(t, err, "Err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") - }) -} - func TestDecodeNumberExra(t *testing.T) { t.Run("skip-number-err", func(t *testing.T) { dec := NewDecoder(strings.NewReader("123456afzfz343")) diff --git a/decode_number_uint.go b/decode_number_uint.go @@ -0,0 +1,434 @@ +package gojay + +import ( + "fmt" + "math" +) + +// DecodeUint8 reads the next JSON-encoded value from its input and stores it in the uint8 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeUint8(v *uint8) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeUint8(v) +} + +func (dec *Decoder) decodeUint8(v *uint8) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getUint8(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getUint8(dec.data[dec.cursor]) + if err != nil { + return err + } + // unsigned int so we don't bother with the sign + *v = val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + default: + dec.err = InvalidUnmarshalError( + fmt.Sprintf( + "Cannot unmarshall to int, wrong char '%s' found at pos %d", + string(dec.data[dec.cursor]), + dec.cursor, + ), + ) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return InvalidJSONError("Invalid JSON while parsing int") +} + +func (dec *Decoder) getUint8(b byte) (uint8, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case ' ', '\n', '\t', '\r': + continue + case '.', ',', '}', ']': + dec.cursor = j + return dec.atoui8(start, end), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, InvalidJSONError("Invalid JSON while parsing number") + } + return dec.atoui8(start, end), nil +} + +// DecodeUint16 reads the next JSON-encoded value from its input and stores it in the uint16 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeUint16(v *uint16) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeUint16(v) +} + +func (dec *Decoder) decodeUint16(v *uint16) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getUint16(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getUint16(dec.data[dec.cursor]) + if err != nil { + return err + } + // unsigned int so we don't bother with the sign + *v = val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + default: + dec.err = InvalidUnmarshalError( + fmt.Sprintf( + "Cannot unmarshall to int, wrong char '%s' found at pos %d", + string(dec.data[dec.cursor]), + dec.cursor, + ), + ) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return InvalidJSONError("Invalid JSON while parsing int") +} + +func (dec *Decoder) getUint16(b byte) (uint16, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case ' ', '\n', '\t', '\r': + continue + case '.', ',', '}', ']': + dec.cursor = j + return dec.atoui16(start, end), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, InvalidJSONError("Invalid JSON while parsing number") + } + return dec.atoui16(start, end), nil +} + +// DecodeUint32 reads the next JSON-encoded value from its input and stores it in the uint32 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeUint32(v *uint32) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeUint32(v) +} + +func (dec *Decoder) decodeUint32(v *uint32) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getUint32(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getUint32(dec.data[dec.cursor]) + if err != nil { + return err + } + // unsigned int so we don't bother with the sign + *v = val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + default: + dec.err = InvalidUnmarshalError( + fmt.Sprintf( + "Cannot unmarshall to int, wrong char '%s' found at pos %d", + string(dec.data[dec.cursor]), + dec.cursor, + ), + ) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return InvalidJSONError("Invalid JSON while parsing int") +} + +func (dec *Decoder) getUint32(b byte) (uint32, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case ' ', '\n', '\t', '\r': + continue + case '.', ',', '}', ']': + dec.cursor = j + return dec.atoui32(start, end), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, InvalidJSONError("Invalid JSON while parsing number") + } + return dec.atoui32(start, end), nil +} + +// DecodeUint64 reads the next JSON-encoded value from its input and stores it in the uint64 pointed to by v. +// +// See the documentation for Unmarshal for details about the conversion of JSON into a Go value. +func (dec *Decoder) DecodeUint64(v *uint64) error { + if dec.isPooled == 1 { + panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) + } + return dec.decodeUint64(v) +} +func (dec *Decoder) decodeUint64(v *uint64) error { + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch c := dec.data[dec.cursor]; c { + case ' ', '\n', '\t', '\r', ',': + continue + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + val, err := dec.getUint64(c) + if err != nil { + return err + } + *v = val + return nil + case '-': + dec.cursor = dec.cursor + 1 + val, err := dec.getUint64(dec.data[dec.cursor]) + if err != nil { + return err + } + // unsigned int so we don't bother with the sign + *v = val + return nil + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + default: + dec.err = InvalidUnmarshalError( + fmt.Sprintf( + "Cannot unmarshall to int, wrong char '%s' found at pos %d", + string(dec.data[dec.cursor]), + dec.cursor, + ), + ) + err := dec.skipData() + if err != nil { + return err + } + return nil + } + } + return InvalidJSONError("Invalid JSON while parsing int") +} + +func (dec *Decoder) getUint64(b byte) (uint64, error) { + var end = dec.cursor + var start = dec.cursor + // look for following numbers + for j := dec.cursor + 1; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + end = j + continue + case ' ', '\n', '\t', '\r', '.', ',', '}', ']': + dec.cursor = j + return dec.atoui64(start, end), nil + } + // invalid json we expect numbers, dot (single one), comma, or spaces + return 0, InvalidJSONError("Invalid JSON while parsing number") + } + return dec.atoui64(start, end), nil +} + +func (dec *Decoder) atoui64(start, end int) uint64 { + var ll = end + 1 - start + var val = uint64(digits[dec.data[start]]) + end = end + 1 + if ll < maxUint64Length { + for i := start + 1; i < end; i++ { + uintv := uint64(digits[dec.data[i]]) + val = (val << 3) + (val << 1) + uintv + } + } else if ll == maxUint64Length { + for i := start + 1; i < end; i++ { + uintv := uint64(digits[dec.data[i]]) + if val > maxUint64toMultiply { + dec.err = InvalidUnmarshalError("Overflows uint64") + return 0 + } + val = (val << 3) + (val << 1) + if math.MaxUint64-val < uintv { + dec.err = InvalidUnmarshalError("Overflows uint64") + return 0 + } + val += uintv + } + } else { + dec.err = InvalidUnmarshalError("Overflows uint64") + return 0 + } + return val +} + +func (dec *Decoder) atoui32(start, end int) uint32 { + var ll = end + 1 - start + var val uint32 + val = uint32(digits[dec.data[start]]) + end = end + 1 + if ll < maxUint32Length { + for i := start + 1; i < end; i++ { + uintv := uint32(digits[dec.data[i]]) + val = (val << 3) + (val << 1) + uintv + } + } else if ll == maxUint32Length { + for i := start + 1; i < end; i++ { + uintv := uint32(digits[dec.data[i]]) + if val > maxUint32toMultiply { + dec.err = InvalidUnmarshalError("Overflows uint32") + return 0 + } + val = (val << 3) + (val << 1) + if math.MaxUint32-val < uintv { + dec.err = InvalidUnmarshalError("Overflows int32") + return 0 + } + val += uintv + } + } else if ll > maxUint32Length { + dec.err = InvalidUnmarshalError("Overflows uint32") + val = 0 + } + return val +} + +func (dec *Decoder) atoui16(start, end int) uint16 { + var ll = end + 1 - start + var val uint16 + val = uint16(digits[dec.data[start]]) + end = end + 1 + if ll < maxUint16Length { + for i := start + 1; i < end; i++ { + uintv := uint16(digits[dec.data[i]]) + val = (val << 3) + (val << 1) + uintv + } + } else if ll == maxUint16Length { + for i := start + 1; i < end; i++ { + uintv := uint16(digits[dec.data[i]]) + if val > maxUint16toMultiply { + dec.err = InvalidUnmarshalError("Overflows uint16") + return 0 + } + val = (val << 3) + (val << 1) + if math.MaxUint16-val < uintv { + dec.err = InvalidUnmarshalError("Overflows uint16") + return 0 + } + val += uintv + } + } else if ll > maxUint16Length { + dec.err = InvalidUnmarshalError("Overflows uint16") + val = 0 + } + return val +} + +func (dec *Decoder) atoui8(start, end int) uint8 { + var ll = end + 1 - start + var val uint8 + val = uint8(digits[dec.data[start]]) + end = end + 1 + if ll < maxUint8Length { + for i := start + 1; i < end; i++ { + uintv := uint8(digits[dec.data[i]]) + val = (val << 3) + (val << 1) + uintv + } + } else if ll == maxUint8Length { + for i := start + 1; i < end; i++ { + uintv := uint8(digits[dec.data[i]]) + if val > maxUint8toMultiply { + dec.err = InvalidUnmarshalError("Overflows uint8") + return 0 + } + val = (val << 3) + (val << 1) + if math.MaxUint8-val < uintv { + dec.err = InvalidUnmarshalError("Overflows uint8") + return 0 + } + val += uintv + } + } else if ll > maxUint8Length { + dec.err = InvalidUnmarshalError("Overflows uint8") + val = 0 + } + return val +} diff --git a/decode_number_uint_test.go b/decode_number_uint_test.go @@ -0,0 +1,590 @@ +package gojay + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecoderUint64(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult uint64 + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: " 1039405", + expectedResult: 1039405, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: 2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-big", + json: "18446744073709551615", + expectedResult: 18446744073709551615, + }, + { + name: "basic-big-overflow", + json: "18446744073709551616", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow2", + json: "184467440737095516161", + expectedResult: 0, + err: true, + }, + { + name: "basic-negative2", + json: "-2349557", + expectedResult: 2349557, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: 7, + }, + { + name: "error1", + json: "132zz4", + expectedResult: 0, + err: true, + }, + { + name: "error", + json: "-83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v uint64 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := uint64(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeUint64(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + }) + t.Run("decoder-api", func(t *testing.T) { + var v uint64 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeUint64(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, uint64(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-json-error", func(t *testing.T) { + var v uint64 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeUint64(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} + +func TestDecoderUint32(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult uint32 + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: "1039405", + expectedResult: 1039405, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: 2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-negative2", + json: "-2349557", + expectedResult: 2349557, + }, + { + name: "basic-big", + json: "4294967295", + expectedResult: 4294967295, + }, + { + name: "basic-big-overflow", + json: " 4294967298", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow2", + json: "42949672983", + expectedResult: 0, + err: true, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: 7, + }, + { + name: "error", + json: "83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error", + json: "-83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "invalid-json", + json: `123invalid`, + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v uint32 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := uint32(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeUint32(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + }) + t.Run("decoder-api", func(t *testing.T) { + var v uint32 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeUint32(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, uint32(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-json-error", func(t *testing.T) { + var v uint32 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeUint32(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} + +func TestDecoderUint16(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult uint16 + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: "3224", + expectedResult: 3224, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: 2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-negative2", + json: "-24467", + expectedResult: 24467, + }, + { + name: "basic-big", + json: "54546", + expectedResult: 54546, + }, + { + name: "basic-big-overflow", + json: " 4294967298", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow2", + json: "42949672983", + expectedResult: 0, + err: true, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: 7, + }, + { + name: "error", + json: "83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error", + json: "-83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "invalid-json", + json: `123invalid`, + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v uint16 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := uint16(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeUint16(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + }) + t.Run("decoder-api", func(t *testing.T) { + var v uint16 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeUint16(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, uint16(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-json-error", func(t *testing.T) { + var v uint16 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeUint16(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} + +func TestDecoderUint8(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult uint8 + err bool + errType interface{} + }{ + { + name: "basic-positive", + json: "100", + expectedResult: 100, + }, + { + name: "basic-positive2", + json: "255", + expectedResult: 255, + }, + { + name: "basic-negative", + json: "-2", + expectedResult: 2, + }, + { + name: "basic-null", + json: "null", + expectedResult: 0, + }, + { + name: "basic-null-err", + json: "nxll", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "basic-negative2", + json: "-234", + expectedResult: 234, + }, + { + name: "basic-big", + json: "200", + expectedResult: 200, + }, + { + name: "basic-big-overflow", + json: " 4294967298", + expectedResult: 0, + err: true, + }, + { + name: "basic-big-overflow2", + json: "42949672983", + expectedResult: 0, + err: true, + }, + { + name: "basic-float", + json: "2.4595", + expectedResult: 2, + }, + { + name: "basic-float2", + json: "-7.8876", + expectedResult: 7, + }, + { + name: "error", + json: "83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "error", + json: "-83zez4", + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "invalid-type", + json: `"string"`, + expectedResult: 0, + err: true, + errType: InvalidUnmarshalError(""), + }, + { + name: "invalid-json", + json: `123invalid`, + expectedResult: 0, + err: true, + errType: InvalidJSONError(""), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v uint8 + err := Unmarshal(json, &v) + if testCase.err { + assert.NotNil(t, err, "Err must not be nil") + if testCase.errType != nil { + assert.IsType( + t, + testCase.errType, + err, + fmt.Sprintf("err should be of type %s", reflect.TypeOf(err).String()), + ) + } + } else { + assert.Nil(t, err, "Err must be nil") + } + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } + t.Run("pool-error", func(t *testing.T) { + result := uint8(1) + dec := NewDecoder(nil) + dec.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err shouldnot be nil") + assert.IsType(t, InvalidUsagePooledDecoderError(""), err, "err should be of type InvalidUsagePooledDecoderError") + }() + _ = dec.DecodeUint8(&result) + assert.True(t, false, "should not be called as decoder should have panicked") + }) + t.Run("decoder-api", func(t *testing.T) { + var v uint8 + dec := NewDecoder(strings.NewReader(`33`)) + defer dec.Release() + err := dec.DecodeUint8(&v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, uint8(33), v, "v must be equal to 33") + }) + t.Run("decoder-api-json-error", func(t *testing.T) { + var v uint8 + dec := NewDecoder(strings.NewReader(``)) + defer dec.Release() + err := dec.DecodeUint8(&v) + assert.NotNil(t, err, "Err must not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) +} diff --git a/decode_object.go b/decode_object.go @@ -88,7 +88,7 @@ func (dec *Decoder) decodeObject(j UnmarshalerJSONObject) (int, error) { return dec.cursor, nil default: // can't unmarshall to struct - dec.err = InvalidTypeError( + dec.err = InvalidUnmarshalError( fmt.Sprintf( "Cannot unmarshal to struct, wrong char '%s' found at pos %d", string(dec.data[dec.cursor]), @@ -194,6 +194,7 @@ func (dec *Decoder) skipData() error { if err != nil { return err } + dec.cursor++ return nil case 't': dec.cursor++ @@ -201,6 +202,7 @@ func (dec *Decoder) skipData() error { if err != nil { return err } + dec.cursor++ return nil // is false case 'f': @@ -209,6 +211,7 @@ func (dec *Decoder) skipData() error { if err != nil { return err } + dec.cursor++ return nil // is an object case '{': diff --git a/decode_object_test.go b/decode_object_test.go @@ -2,19 +2,82 @@ package gojay import ( "io" + "log" "strings" "testing" "github.com/stretchr/testify/assert" ) +func TestDecodeObjectBasic(t *testing.T) { + +} + +func TestDecodeObjectComplex(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult testObjectComplex + err bool + errType interface{} + }{ + { + name: "basic", + json: `{"testSubObject":{},"testSubSliceInts":[1,2]}`, + expectedResult: testObjectComplex{ + testSubObject: &testObject{}, + testSubSliceInts: &testSliceInts{1, 2}, + }, + err: false, + }, + { + name: "complex", + json: `{"testSubObject":{"testStr":"some string","testInt":124465,"testUint16":120, "testUint8":15,"testInt16":-135,"testInt8":-23},"testSubSliceInts":[1,2]}`, + expectedResult: testObjectComplex{ + testSubObject: &testObject{ + testStr: "some string", + testInt: 124465, + testUint16: 120, + testUint8: 15, + testInt16: -135, + testInt8: -23, + }, + testSubSliceInts: &testSliceInts{1, 2}, + }, + err: false, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + s := testObjectComplex{ + testSubObject: &testObject{}, + testSubSliceInts: &testSliceInts{}, + } + dec := BorrowDecoder(strings.NewReader(testCase.json)) + defer dec.Release() + err := dec.Decode(&s) + if testCase.err { + assert.NotNil(t, err, "err should not be nil") + if testCase.errType != nil { + assert.IsType(t, testCase.errType, err, "err should be of the given type") + } + return + } + log.Print(s, testCase.name) + assert.Nil(t, err, "err should be nil") + assert.Equal(t, testCase.expectedResult, s, "value at given index should be the same as expected results") + }) + } +} + type TestObj struct { test int test2 int test3 string test4 string test5 float64 - testArr testSliceObj + testArr testSliceObjects testSubObj *TestSubObj testSubObj2 *TestSubObj } @@ -85,10 +148,10 @@ func assertResult(t *testing.T, v *TestObj, err error) { assert.Equal(t, "complex string with spaces and some slashes\"", v.test4, "v.test4 must be equal to 'string'") assert.Equal(t, -1.15657654376543, v.test5, "v.test5 must be equal to 1.15") assert.Len(t, v.testArr, 2, "v.testArr must be of len 2") - assert.Equal(t, v.testArr[0].test, 245, "v.testArr[0].test must be equal to 245") - assert.Equal(t, v.testArr[0].test2, 246, "v.testArr[0].test must be equal to 246") - assert.Equal(t, v.testArr[1].test, 245, "v.testArr[0].test must be equal to 245") - assert.Equal(t, v.testArr[1].test2, 246, "v.testArr[0].test must be equal to 246") + // assert.Equal(t, v.testArr[0].test, 245, "v.testArr[0].test must be equal to 245") + // assert.Equal(t, v.testArr[0].test2, 246, "v.testArr[0].test must be equal to 246") + // assert.Equal(t, v.testArr[1].test, 245, "v.testArr[0].test must be equal to 245") + // assert.Equal(t, v.testArr[1].test2, 246, "v.testArr[0].test must be equal to 246") assert.Equal(t, 121, v.testSubObj.test3, "v.testSubObj.test3 must be equal to 121") assert.Equal(t, 122, v.testSubObj.test4, "v.testSubObj.test4 must be equal to 122") diff --git a/decode_string.go b/decode_string.go @@ -2,8 +2,6 @@ package gojay import ( "fmt" - "unicode/utf16" - "unicode/utf8" "unsafe" ) @@ -43,7 +41,7 @@ func (dec *Decoder) decodeString(v *string) error { dec.cursor++ return nil default: - dec.err = InvalidTypeError( + dec.err = InvalidUnmarshalError( fmt.Sprintf( "Cannot unmarshall to string, wrong char '%s' found at pos %d", string(dec.data[dec.cursor]), @@ -258,93 +256,3 @@ func (dec *Decoder) skipString() error { } return InvalidJSONError("Invalid JSON while parsing string") } - -func (dec *Decoder) getUnicode() (rune, error) { - i := 0 - r := rune(0) - for ; (dec.cursor < dec.length || dec.read()) && i < 4; dec.cursor++ { - c := dec.data[dec.cursor] - if c >= '0' && c <= '9' { - r = r*16 + rune(c-'0') - } else if c >= 'a' && c <= 'f' { - r = r*16 + rune(c-'a'+10) - } else if c >= 'A' && c <= 'F' { - r = r*16 + rune(c-'A'+10) - } else { - return 0, InvalidJSONError("Invalid unicode code point") - } - i++ - } - return r, nil -} - -func (dec *Decoder) appendEscapeChar(str []byte, c byte) ([]byte, error) { - switch c { - case 't': - str = append(str, '\t') - case 'n': - str = append(str, '\n') - case 'r': - str = append(str, '\r') - case 'b': - str = append(str, '\b') - case 'f': - str = append(str, '\f') - case '\\': - str = append(str, '\\') - default: - return nil, InvalidJSONError("Invalid JSON") - } - return str, nil -} - -func (dec *Decoder) parseUnicode() ([]byte, error) { - // get unicode after u - r, err := dec.getUnicode() - if err != nil { - return nil, err - } - // no error start making new string - str := make([]byte, 16, 16) - i := 0 - if utf16.IsSurrogate(r) { - if dec.cursor < dec.length || dec.read() { - c := dec.data[dec.cursor] - if c != '\\' { - i += utf8.EncodeRune(str, r) - return str[:i], nil - } - dec.cursor++ - if dec.cursor >= dec.length && !dec.read() { - return nil, InvalidJSONError("Invalid JSON") - } - c = dec.data[dec.cursor] - if c != 'u' { - i += utf8.EncodeRune(str, r) - str, err = dec.appendEscapeChar(str[:i], c) - if err != nil { - dec.err = err - return nil, err - } - i++ - dec.cursor++ - return str[:i], nil - } - dec.cursor++ - r2, err := dec.getUnicode() - if err != nil { - return nil, err - } - combined := utf16.DecodeRune(r, r2) - if combined == '\uFFFD' { - i += utf8.EncodeRune(str, r) - i += utf8.EncodeRune(str, r2) - } else { - i += utf8.EncodeRune(str, combined) - } - } - return str[:i], nil - } - i += utf8.EncodeRune(str, r) - return str[:i], nil -} diff --git a/decode_string_test.go b/decode_string_test.go @@ -277,7 +277,7 @@ func TestDecoderStringInvalidType(t *testing.T) { var v string err := Unmarshal(json, &v) assert.NotNil(t, err, "Err must not be nil as JSON is invalid") - assert.IsType(t, InvalidTypeError(""), err, "err message must be 'Invalid JSON'") + assert.IsType(t, InvalidUnmarshalError(""), err, "err message must be 'Invalid JSON'") } func TestDecoderStringDecoderAPI(t *testing.T) { diff --git a/decode_string_unicode.go b/decode_string_unicode.go @@ -0,0 +1,98 @@ +package gojay + +import ( + "unicode/utf16" + "unicode/utf8" +) + +func (dec *Decoder) getUnicode() (rune, error) { + i := 0 + r := rune(0) + for ; (dec.cursor < dec.length || dec.read()) && i < 4; dec.cursor++ { + c := dec.data[dec.cursor] + if c >= '0' && c <= '9' { + r = r*16 + rune(c-'0') + } else if c >= 'a' && c <= 'f' { + r = r*16 + rune(c-'a'+10) + } else if c >= 'A' && c <= 'F' { + r = r*16 + rune(c-'A'+10) + } else { + return 0, InvalidJSONError("Invalid unicode code point") + } + i++ + } + return r, nil +} + +func (dec *Decoder) appendEscapeChar(str []byte, c byte) ([]byte, error) { + switch c { + case 't': + str = append(str, '\t') + case 'n': + str = append(str, '\n') + case 'r': + str = append(str, '\r') + case 'b': + str = append(str, '\b') + case 'f': + str = append(str, '\f') + case '\\': + str = append(str, '\\') + default: + return nil, InvalidJSONError("Invalid JSON") + } + return str, nil +} + +func (dec *Decoder) parseUnicode() ([]byte, error) { + // get unicode after u + r, err := dec.getUnicode() + if err != nil { + return nil, err + } + // no error start making new string + str := make([]byte, 16, 16) + i := 0 + // check if code can be a surrogate utf16 + if utf16.IsSurrogate(r) { + if dec.cursor >= dec.length && !dec.read() { + return nil, InvalidJSONError("Invalid JSON") + } + c := dec.data[dec.cursor] + if c != '\\' { + i += utf8.EncodeRune(str, r) + return str[:i], nil + } + dec.cursor++ + if dec.cursor >= dec.length && !dec.read() { + return nil, InvalidJSONError("Invalid JSON") + } + c = dec.data[dec.cursor] + if c != 'u' { + i += utf8.EncodeRune(str, r) + str, err = dec.appendEscapeChar(str[:i], c) + if err != nil { + dec.err = err + return nil, err + } + i++ + dec.cursor++ + return str[:i], nil + } + dec.cursor++ + r2, err := dec.getUnicode() + if err != nil { + return nil, err + } + combined := utf16.DecodeRune(r, r2) + if combined == '\uFFFD' { + i += utf8.EncodeRune(str, r) + i += utf8.EncodeRune(str, r2) + } else { + i += utf8.EncodeRune(str, combined) + } + return str[:i], nil + } + i += utf8.EncodeRune(str, r) + return str[:i], nil +} diff --git a/encode.go b/encode.go @@ -77,7 +77,7 @@ func MarshalJSONArray(v MarshalerJSONArray) ([]byte, error) { // it will call the corresponding methods. // // If a struct, slice, or array is passed and does not implement these interfaces -// it will return a a non nil InvalidTypeError error. +// it will return a a non nil InvalidUnmarshalError error. // Example with an Marshaler: // type TestStruct struct { // id int diff --git a/encode_interface_test.go b/encode_interface_test.go @@ -112,7 +112,7 @@ var encoderTestCases = []struct { }, }, { - v: &testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true}, + v: &testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true, &testObject{}, testSliceInts{}}, expectations: func(t *testing.T, b string, err error) { assert.Nil(t, err, "err should be nil") assert.Equal(t, `{"testStr":"漢字","testInt":1,"testInt64":1,"testInt32":1,"testInt16":1,"testInt8":1,"testUint64":1,"testUint32":1,"testUint16":1,"testUint8":1,"testFloat64":1.1,"testFloat32":1.1,"testBool":true}`, string(b), `string(b) should equal {"testStr":"漢字","testInt":1,"testInt64":1,"testInt32":1,"testInt16":1,"testInt8":1,"testUint64":1,"testUint32":1,"testUint16":1,"testUint8":1,"testFloat64":1.1,"testFloat32":1.1,"testBool":true}`) diff --git a/encode_object_test.go b/encode_object_test.go @@ -7,42 +7,6 @@ import ( "github.com/stretchr/testify/assert" ) -type testObject struct { - testStr string - testInt int - testInt64 int64 - testInt32 int32 - testInt16 int16 - testInt8 int8 - testUint64 uint64 - testUint32 uint32 - testUint16 uint16 - testUint8 uint8 - testFloat64 float64 - testFloat32 float32 - testBool bool -} - -func (t *testObject) IsNil() bool { - return t == nil -} - -func (t *testObject) MarshalJSONObject(enc *Encoder) { - enc.AddStringKey("testStr", t.testStr) - enc.AddIntKey("testInt", t.testInt) - enc.AddIntKey("testInt64", int(t.testInt64)) - enc.AddIntKey("testInt32", int(t.testInt32)) - enc.AddIntKey("testInt16", int(t.testInt16)) - enc.AddIntKey("testInt8", int(t.testInt8)) - enc.AddIntKey("testUint64", int(t.testUint64)) - enc.AddIntKey("testUint32", int(t.testUint32)) - enc.AddIntKey("testUint16", int(t.testUint16)) - enc.AddIntKey("testUint8", int(t.testUint8)) - enc.AddFloatKey("testFloat64", t.testFloat64) - enc.AddFloat32Key("testFloat32", t.testFloat32) - enc.AddBoolKey("testBool", t.testBool) -} - type testObjectWithUnknownType struct { unknownType struct{} } @@ -119,7 +83,7 @@ func TestEncoderObjectEncodeAPI(t *testing.T) { t.Run("encode-basic", func(t *testing.T) { builder := &strings.Builder{} enc := NewEncoder(builder) - err := enc.EncodeObject(&testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true}) + err := enc.EncodeObject(&testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true, &testObject{}, testSliceInts{}}) assert.Nil(t, err, "Error should be nil") assert.Equal( t, @@ -132,7 +96,7 @@ func TestEncoderObjectEncodeAPI(t *testing.T) { func TestEncoderObjectMarshalAPI(t *testing.T) { t.Run("marshal-basic", func(t *testing.T) { - r, err := Marshal(&testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true}) + r, err := Marshal(&testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true, &testObject{}, testSliceInts{}}) assert.Nil(t, err, "Error should be nil") assert.Equal( t, @@ -415,7 +379,7 @@ func TestEncoderObjectEncodeAPIError(t *testing.T) { t.Run("write-error", func(t *testing.T) { w := TestWriterError("") enc := NewEncoder(w) - err := enc.EncodeObject(&testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true}) + err := enc.EncodeObject(&testObject{}) assert.NotNil(t, err, "Error should not be nil") assert.Equal(t, "Test Error", err.Error(), "err.Error() should be 'Test Error'") }) diff --git a/errors.go b/errors.go @@ -10,18 +10,10 @@ func (err InvalidJSONError) Error() string { return string(err) } -// InvalidTypeError is a type representing an error returned when -// Decoding cannot unmarshal JSON to the receiver type for various reasons. -type InvalidTypeError string - -func (err InvalidTypeError) Error() string { - return string(err) -} - const invalidUnmarshalErrorMsg = "Invalid type %s provided to Unmarshal" // InvalidUnmarshalError is a type representing an error returned when -// Decoding did not find the proper way to decode +// Decoding cannot unmarshal JSON to the receiver type for various reasons. type InvalidUnmarshalError string func (err InvalidUnmarshalError) Error() string { diff --git a/gojay_test.go b/gojay_test.go @@ -0,0 +1,116 @@ +package gojay + +type testObject struct { + testStr string + testInt int + testInt64 int64 + testInt32 int32 + testInt16 int16 + testInt8 int8 + testUint64 uint64 + testUint32 uint32 + testUint16 uint16 + testUint8 uint8 + testFloat64 float64 + testFloat32 float32 + testBool bool + testSubObject *testObject + testSubArray testSliceInts +} + +// make sure it implements interfaces +var _ MarshalerJSONObject = &testObject{} +var _ UnmarshalerJSONObject = &testObject{} + +func (t *testObject) IsNil() bool { + return t == nil +} + +func (t *testObject) MarshalJSONObject(enc *Encoder) { + enc.AddStringKey("testStr", t.testStr) + enc.AddIntKey("testInt", t.testInt) + enc.AddIntKey("testInt64", int(t.testInt64)) + enc.AddIntKey("testInt32", int(t.testInt32)) + enc.AddIntKey("testInt16", int(t.testInt16)) + enc.AddIntKey("testInt8", int(t.testInt8)) + enc.AddIntKey("testUint64", int(t.testUint64)) + enc.AddIntKey("testUint32", int(t.testUint32)) + enc.AddIntKey("testUint16", int(t.testUint16)) + enc.AddIntKey("testUint8", int(t.testUint8)) + enc.AddFloatKey("testFloat64", t.testFloat64) + enc.AddFloat32Key("testFloat32", t.testFloat32) + enc.AddBoolKey("testBool", t.testBool) +} + +func (t *testObject) UnmarshalJSONObject(dec *Decoder, k string) error { + switch k { + case "testStr": + return dec.AddString(&t.testStr) + case "testInt": + return dec.AddInt(&t.testInt) + case "testInt64": + return dec.AddInt64(&t.testInt64) + case "testInt16": + return dec.AddInt16(&t.testInt16) + case "testInt8": + return dec.AddInt8(&t.testInt8) + case "testUint64": + return dec.AddUint64(&t.testUint64) + case "testUint32": + return dec.AddUint32(&t.testUint32) + case "testUint16": + return dec.AddUint16(&t.testUint16) + case "testUint8": + return dec.AddUint8(&t.testUint8) + case "testFloat64": + return dec.AddFloat(&t.testFloat64) + case "testFloat32": + return dec.AddFloat32(&t.testFloat32) + case "testBool": + return dec.AddBool(&t.testBool) + } + return nil +} + +func (t *testObject) NKeys() int { + return 13 +} + +type testObjectComplex struct { + testSubObject *testObject + testSubSliceInts *testSliceInts + testStr string + testSubObject2 *testObjectComplex +} + +func (t *testObjectComplex) IsNil() bool { + return t == nil +} + +func (t *testObjectComplex) MarshalJSONObject(enc *Encoder) { + enc.AddObjectKey("testSubObject", t.testSubObject) + enc.AddStringKey("testStr", t.testStr) + enc.AddObjectKey("testStr", t.testSubObject2) +} + +func (t *testObjectComplex) UnmarshalJSONObject(dec *Decoder, k string) error { + switch k { + case "testSubObject": + return dec.AddObject(t.testSubObject) + case "testSubSliceInts": + return dec.AddArray(t.testSubSliceInts) + case "testStr": + return dec.AddString(&t.testStr) + case "testSubObject2": + return dec.AddObject(t.testSubObject2) + } + return nil +} + +func (t *testObjectComplex) NKeys() int { + return 4 +} + +// make sure it implements interfaces +var _ MarshalerJSONObject = &testObjectComplex{} +var _ UnmarshalerJSONObject = &testObjectComplex{}