gojay

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

commit 378ef0d1529c71e8c57f99691830b18824ad487f
parent d096375da20dd060cb672ea511e0c993d6e115a2
Author: Francois Parquet <francois.parquet@gmail.com>
Date:   Mon, 14 May 2018 08:30:49 +0800

Merge pull request #25 from francoispqt/update/comply-rfc7159

Add checks for booleans and nulls, add support for exponent syntax for numbers unmarshaling, add tests and better support for escape sequences in decoding
Diffstat:
MREADME.md | 39++++++++++++++++++++++-----------------
Mdecode_array.go | 8++++++--
Mdecode_array_test.go | 40++++++++++++++++++++++++++++++++++++++++
Mdecode_bool.go | 124++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mdecode_bool_test.go | 264++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mdecode_embedded_json.go | 24+++++++++++++++++++++---
Mdecode_embedded_json_test.go | 25++++++++++++++++++++++++-
Mdecode_number.go | 279+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mdecode_number_test.go | 1642+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mdecode_object.go | 29++++++++++++++++++++++++-----
Mdecode_object_test.go | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mdecode_stream_test.go | 1-
Mdecode_string.go | 63++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mdecode_string_test.go | 136++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mdecode_test.go | 16----------------
Mencode.go | 7++++---
Mencode_builder.go | 20++++++++++++--------
Mencode_number.go | 2+-
Mencode_object_test.go | 2+-
Mencode_pool.go | 3---
Mencode_string.go | 21+++++++++++++--------
Mencode_string_test.go | 28++++++++++++++++++++++++++--
Merrors.go | 2++
23 files changed, 2194 insertions(+), 645 deletions(-)

diff --git a/README.md b/README.md @@ -4,9 +4,10 @@ [![Go doc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square )](https://godoc.org/github.com/francoispqt/gojay) ![MIT License](https://img.shields.io/badge/license-mit-blue.svg?style=flat-square) +[![Sourcegraph](https://sourcegraph.com/github.com/francoispqt/gojay/-/badge.svg)](https://sourcegraph.com/github.com/francoispqt/gojay) # GoJay -**Package is currently at version 0.10.5 and still under development** +**Package is currently at version 0.10.8 and safe to use in production** GoJay is a performant JSON encoder/decoder for Golang (currently the most performant, [see benchmarks](#benchmark-results)). @@ -680,11 +681,12 @@ cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/encoder && make bench | | ns/op | bytes/op | allocs/op | |-------------|-------|--------------|-----------| -| Std Library | 4661 | 496 | 12 | -| JsonParser | 1313 | 0 | 0 | -| JsonIter | 899 | 192 | 5 | +| Std Library | 2547 | 496 | 4 | +| JsonIter | 2046 | 312 | 12 | +| JsonParser | 1408 | 0 | 0 | | EasyJson | 929 | 240 | 2 | -| GoJay | 662 | 112 | 1 | +| GoJay | 807 | 256 | 2 | +| GoJay-unsafe| 712 | 112 | 1 | ### Medium Payload [benchmark code is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/decoder/decoder_bench_medium_test.go) @@ -694,10 +696,11 @@ cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/encoder && make bench | | ns/op | bytes/op | allocs/op | |-------------|-------|--------------|-----------| | Std Library | 30148 | 2152 | 496 | +| JsonIter | 16309 | 2976 | 80 | | JsonParser | 7793 | 0 | 0 | | EasyJson | 7957 | 232 | 6 | -| JsonIter | 5967 | 496 | 44 | -| GoJay | 3914 | 128 | 7 | +| GoJay | 4984 | 2448 | 8 | +| GoJay-unsafe| 4809 | 144 | 7 | ### Large Payload [benchmark code is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/decoder/decoder_bench_large_test.go) @@ -706,10 +709,11 @@ cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/encoder && make bench | | ns/op | bytes/op | allocs/op | |-------------|-------|--------------|-----------| +| JsonIter | 210078| 41712 | 1136 | | EasyJson | 106626| 160 | 2 | | JsonParser | 66813 | 0 | 0 | -| JsonIter | 87994 | 6738 | 329 | -| GoJay | 43402 | 1408 | 76 | +| GoJay | 52153 | 31241 | 77 | +| GoJay-unsafe| 48277 | 2561 | 76 | ## Encode @@ -725,7 +729,8 @@ cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/encoder && make bench | Std Library | 1280 | 464 | 3 | | EasyJson | 871 | 944 | 6 | | JsonIter | 866 | 272 | 3 | -| GoJay | 484 | 320 | 2 | +| GoJay | 543 | 112 | 1 | +| GoJay-func | 347 | 0 | 0 | ### Medium Struct [benchmark code is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/encoder/encoder_bench_medium_test.go) @@ -734,10 +739,10 @@ cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/encoder && make bench | | ns/op | bytes/op | allocs/op | |-------------|-------|--------------|-----------| -| Std Library | 3325 | 1496 | 18 | -| EasyJson | 1997 | 1320 | 19 | -| JsonIter | 1939 | 648 | 16 | -| GoJay | 1196 | 936 | 16 | +| Std Library | 5006 | 1496 | 25 | +| JsonIter | 2232 | 1544 | 20 | +| EasyJson | 1997 | 1544 | 19 | +| GoJay | 1522 | 312 | 14 | ### Large Struct [benchmark code is here](https://github.com/francoispqt/gojay/blob/master/benchmarks/encoder/encoder_bench_large_test.go) @@ -746,10 +751,10 @@ cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/encoder && make bench | | ns/op | bytes/op | allocs/op | |-------------|-------|--------------|-----------| -| Std Library | 51317 | 28704 | 326 | -| JsonIter | 35247 | 14608 | 320 | +| Std Library | 66441 | 20576 | 332 | +| JsonIter | 35247 | 20255 | 328 | | EasyJson | 32053 | 15474 | 327 | -| GoJay | 27847 | 27888 | 326 | +| GoJay | 27847 | 9802 | 318 | # Contributing diff --git a/decode_array.go b/decode_array.go @@ -40,10 +40,14 @@ func (dec *Decoder) decodeArray(arr UnmarshalerArray) (int, error) { } n++ } - return dec.cursor, nil + return 0, InvalidJSONError("Invalid JSON could not find array closing bracket") case 'n': // is null - dec.cursor = dec.cursor + 4 + dec.cursor++ + err := dec.assertNull() + if err != nil { + return 0, err + } return dec.cursor, nil case '{', '"', 'f', 't', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': // can't unmarshall to struct diff --git a/decode_array_test.go b/decode_array_test.go @@ -241,6 +241,46 @@ func TestUnmarshalArrays(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 diff --git a/decode_bool.go b/decode_bool.go @@ -17,19 +17,32 @@ func (dec *Decoder) decodeBool(v *bool) error { case ' ', '\n', '\t', '\r', ',': continue case 't': - dec.cursor = dec.cursor + 4 + dec.cursor++ + err := dec.assertTrue() + if err != nil { + return err + } *v = true return nil case 'f': - dec.cursor = dec.cursor + 5 + dec.cursor++ + err := dec.assertFalse() + if err != nil { + return err + } *v = false return nil case 'n': - dec.cursor = dec.cursor + 4 + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } *v = false + dec.cursor++ return nil default: - dec.err = InvalidTypeError( + dec.err = InvalidUnmarshalError( fmt.Sprintf( "Cannot unmarshall to bool, wrong char '%s' found at pos %d", string(dec.data[dec.cursor]), @@ -45,3 +58,106 @@ func (dec *Decoder) decodeBool(v *bool) error { } return nil } + +func (dec *Decoder) assertTrue() error { + i := 0 + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch i { + case 0: + if dec.data[dec.cursor] != 'r' { + return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + case 1: + if dec.data[dec.cursor] != 'u' { + return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + case 2: + if dec.data[dec.cursor] != 'e' { + return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + case 3: + switch dec.data[dec.cursor] { + case ' ', '\t', '\n', ',', ']', '}': + dec.cursor-- + return nil + default: + return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + i++ + } + if i == 3 { + return nil + } + return InvalidJSONError("Invalid JSON") +} + +func (dec *Decoder) assertNull() error { + i := 0 + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch i { + case 0: + if dec.data[dec.cursor] != 'u' { + return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + case 1: + if dec.data[dec.cursor] != 'l' { + return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + case 2: + if dec.data[dec.cursor] != 'l' { + return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + case 3: + switch dec.data[dec.cursor] { + case ' ', '\t', '\n', ',', ']', '}': + dec.cursor-- + return nil + default: + return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + i++ + } + if i == 3 { + return nil + } + return InvalidJSONError("Invalid JSON") +} + +func (dec *Decoder) assertFalse() error { + i := 0 + for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { + switch i { + case 0: + if dec.data[dec.cursor] != 'a' { + return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + case 1: + if dec.data[dec.cursor] != 'l' { + return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + case 2: + if dec.data[dec.cursor] != 's' { + return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + case 3: + if dec.data[dec.cursor] != 'e' { + return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + case 4: + switch dec.data[dec.cursor] { + case ' ', '\t', '\n', ',', ']', '}': + dec.cursor-- + return nil + default: + return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) + } + } + i++ + } + if i == 4 { + return nil + } + return InvalidJSONError("Invalid JSON") +} diff --git a/decode_bool_test.go b/decode_bool_test.go @@ -7,45 +7,237 @@ import ( "github.com/stretchr/testify/assert" ) -func TestDecoderBoolTrue(t *testing.T) { - json := []byte(`true`) - var v bool - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, true, v, "v must be equal to true") -} +func TestDecoderBool(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult bool + expectations func(t *testing.T, v bool, err error) + }{ + { + name: "true-basic", + json: "true", + expectations: func(t *testing.T, v bool, err error) { + assert.Nil(t, err, "err should be nil") + assert.True(t, v, "result should be true") + }, + }, + { + name: "false-basic", + json: "false", + expectations: func(t *testing.T, v bool, err error) { + assert.Nil(t, err, "err should be nil") + assert.False(t, v, "result should be false") + }, + }, + { + name: "null-basic", + json: "null", + expectations: func(t *testing.T, v bool, err error) { + assert.Nil(t, err, "err should be nil") + assert.False(t, v, "result should be false") + }, + }, + { + name: "true-error", + json: "taue", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "true-error2", + json: "trae", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "true-error3", + json: "trua", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "true-error4", + json: "truea", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "true-error5", + json: "t", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "true-error6", + json: "a", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "false-error", + json: "fulse", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "false-error2", + json: "fause", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "false-error3", + json: "falze", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "false-error4", + json: "falso", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "false-error5", + json: "falsea", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "false-error6", + json: "f", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "false-error7", + json: "a", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "null-error", + json: "nall", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "null-error2", + json: "nual", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "null-error3", + json: "nula", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "null-error4", + json: "nulle", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "null-error5", + json: "n", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "null-error6", + json: "a", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "null-skip", + json: "{}", + expectations: func(t *testing.T, v bool, err error) { + assert.NotNil(t, err, "err should not be nil") + assert.IsType(t, InvalidUnmarshalError(""), err, "err should be of type InvalidUnmarshalError") + assert.False(t, v, "result should be false") + }, + }, + { + name: "null-skip", + json: "", + expectations: func(t *testing.T, v bool, err error) { + assert.Nil(t, err, "err should not be nil") + assert.False(t, v, "result should be false") + }, + }, + } -func TestDecoderBoolFalse(t *testing.T) { - json := []byte(`false`) - var v bool - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, false, v, "v must be equal to false") + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v bool + err := Unmarshal(json, &v) + testCase.expectations(t, v, err) + }) + } } -func TestDecoderBoolInvalidType(t *testing.T) { - json := []byte(`"string"`) - var v bool - err := Unmarshal(json, &v) - assert.NotNil(t, err, "Err must not be nil") - assert.Equal(t, false, v, "v must be equal to false as it is zero val") -} - -func TestDecoderBoolNonBooleanJSONFalse(t *testing.T) { - json := []byte(`null`) - var v bool - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, false, v, "v must be equal to false") -} - -func TestDecoderBoolInvalidJSON(t *testing.T) { - json := []byte(`hello`) - var v bool - err := Unmarshal(json, &v) - assert.NotNil(t, err, "Err must not be nil as JSON is invalid") - assert.IsType(t, InvalidJSONError(""), err, "err message must be 'Invalid JSON'") -} func TestDecoderBoolDecoderAPI(t *testing.T) { var v bool dec := BorrowDecoder(strings.NewReader("true")) diff --git a/decode_embedded_json.go b/decode_embedded_json.go @@ -15,13 +15,31 @@ func (dec *Decoder) decodeEmbeddedJSON(ej *EmbeddedJSON) error { case ' ', '\n', '\t', '\r', ',': continue // is null - case 'n', 't': + case 'n': beginOfEmbeddedJSON = dec.cursor - dec.cursor = dec.cursor + 4 + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + dec.cursor++ + case 't': + beginOfEmbeddedJSON = dec.cursor + dec.cursor++ + err := dec.assertTrue() + if err != nil { + return err + } + dec.cursor++ // is false case 'f': beginOfEmbeddedJSON = dec.cursor - dec.cursor = dec.cursor + 5 + dec.cursor++ + err := dec.assertFalse() + if err != nil { + return err + } + dec.cursor++ // is an object case '{': beginOfEmbeddedJSON = dec.cursor diff --git a/decode_embedded_json_test.go b/decode_embedded_json_test.go @@ -38,6 +38,7 @@ func TestDecodeEmbeddedJSONUnmarshalAPI(t *testing.T) { name string json []byte expectedEmbedded string + err bool }{ { name: "decode-basic-string", @@ -74,6 +75,24 @@ func TestDecodeEmbeddedJSONUnmarshalAPI(t *testing.T) { json: []byte(`{"id":"someid","method":"getmydata","params":[1,2,3], "more":123}`), expectedEmbedded: `[1,2,3]`, }, + { + name: "decode-null-err", + json: []byte(`{"id":"someid","method":"getmydata","params":nil, "more":123}`), + expectedEmbedded: ``, + err: true, + }, + { + name: "decode-bool-false-err", + json: []byte(`{"id":"someid","method":"getmydata","params":faulse, "more":123}`), + expectedEmbedded: ``, + err: true, + }, + { + name: "decode-bool-true-err", + json: []byte(`{"id":"someid","method":"getmydata","params":trou, "more":123}`), + expectedEmbedded: ``, + err: true, + }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { @@ -81,7 +100,11 @@ func TestDecodeEmbeddedJSONUnmarshalAPI(t *testing.T) { err := Unmarshal(testCase.json, req) t.Log(req) t.Log(string(req.params)) - assert.Nil(t, err, "err should be nil") + if testCase.err { + assert.NotNil(t, err, "err should not be nil") + } else { + assert.Nil(t, err, "err should be nil") + } assert.Equal(t, testCase.expectedEmbedded, string(req.params), "r.params should be equal to expectedEmbeddedResult") }) } diff --git a/decode_number.go b/decode_number.go @@ -67,6 +67,7 @@ func (dec *Decoder) decodeInt(v *int) error { 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 { @@ -83,7 +84,12 @@ func (dec *Decoder) decodeInt(v *int) error { *v = -int(val) return nil case 'n': - dec.cursor = dec.cursor + 4 + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + dec.cursor++ return nil default: dec.err = InvalidTypeError( @@ -133,7 +139,11 @@ func (dec *Decoder) decodeInt32(v *int32) error { *v = -val return nil case 'n': - dec.cursor = dec.cursor + 4 + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } return nil default: dec.err = InvalidTypeError( @@ -185,7 +195,11 @@ func (dec *Decoder) decodeUint32(v *uint32) error { *v = val return nil case 'n': - dec.cursor = dec.cursor + 4 + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } return nil default: dec.err = InvalidTypeError( @@ -236,7 +250,11 @@ func (dec *Decoder) decodeInt64(v *int64) error { *v = -val return nil case 'n': - dec.cursor = dec.cursor + 4 + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } return nil default: dec.err = InvalidTypeError( @@ -287,7 +305,11 @@ func (dec *Decoder) decodeUint64(v *uint64) error { *v = val return nil case 'n': - dec.cursor = dec.cursor + 4 + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } return nil default: dec.err = InvalidTypeError( @@ -337,7 +359,11 @@ func (dec *Decoder) decodeFloat64(v *float64) error { *v = -val return nil case 'n': - dec.cursor = dec.cursor + 4 + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } return nil default: dec.err = InvalidTypeError( @@ -388,11 +414,54 @@ func (dec *Decoder) getInt64(b byte) (int64, error) { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': end = j continue - case ' ', '\n', '\t', '\r': - continue - case '.', ',', '}', ']': + 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") @@ -400,6 +469,45 @@ func (dec *Decoder) getInt64(b byte) (int64, error) { 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 @@ -409,9 +517,7 @@ func (dec *Decoder) getUint64(b byte) (uint64, error) { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': end = j continue - case ' ', '\n', '\t', '\r': - continue - case '.', ',', '}', ']': + case ' ', '\n', '\t', '\r', '.', ',', '}', ']': dec.cursor = j return dec.atoui64(start, end), nil } @@ -430,9 +536,52 @@ func (dec *Decoder) getInt32(b byte) (int32, error) { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': end = j continue - case ' ', '\n', '\t', '\r': - continue - case '.', ',', '}', ']': + 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 } @@ -442,6 +591,45 @@ func (dec *Decoder) getInt32(b byte) (int32, error) { 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 @@ -485,6 +673,17 @@ func (dec *Decoder) getFloat(b byte) (float64, error) { 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 @@ -494,9 +693,18 @@ func (dec *Decoder) getFloat(b byte) (float64, error) { afterDecimal := dec.atoi64(start, end) pow := pow10uint64[end-start+2] return float64(beforeDecimal+afterDecimal) / float64(pow), nil - case ' ', '\n', '\t', '\r': - continue - case ',', '}', ']': // does not have decimal + 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 } @@ -586,7 +794,7 @@ func (dec *Decoder) atoi32(start, end int) int32 { } val = (val << 3) + (val << 1) if maxInt32-val < intv { - dec.err = InvalidTypeError("Overflows int322") + dec.err = InvalidTypeError("Overflows int32") return 0 } val += intv @@ -628,3 +836,36 @@ func (dec *Decoder) atoui32(start, end int) uint32 { } 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': + end = dec.cursor + case '-': + dec.cursor++ + return -dec.getExponent() + case '+': + dec.cursor++ + return dec.getExponent() + default: + // if nothing return 0 + // could raise error + if start == end { + return 0 + } + return dec.atoi64(start, end) + } + } + return dec.atoi64(start, end) +} diff --git a/decode_number_test.go b/decode_number_test.go @@ -1,520 +1,1156 @@ package gojay import ( + "fmt" + "math" + "reflect" "strings" "testing" "github.com/stretchr/testify/assert" ) -func TestDecoderIntBasic(t *testing.T) { - json := []byte(`124`) - var v int - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, 124, v, "v must be equal to 124") -} -func TestDecoderIntNegative(t *testing.T) { - json := []byte(` -124 `) - var v int - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, -124, v, "v must be equal to -124") -} -func TestDecoderIntNegativeError(t *testing.T) { - json := []byte(` -12x4 `) - var v int - err := Unmarshal(json, &v) - assert.NotNil(t, err, "Err must be nil") - assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") -} -func TestDecoderIntNull(t *testing.T) { - json := []byte(`null`) - var v int - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, int(0), v, "v must be equal to 0") -} -func TestDecoderIntInvalidType(t *testing.T) { - json := []byte(`"string"`) - var v int - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil") - assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeErrorr") -} -func TestDecoderIntInvalidJSON(t *testing.T) { - json := []byte(`123n`) - var v int - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") -} -func TestDecoderIntBig(t *testing.T) { - json := []byte(`9223372036854775807`) - var v int - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, 9223372036854775807, v, "v must be equal to 9223372036854775807") -} -func TestDecoderIntOverfow(t *testing.T) { - json := []byte(`9223372036854775808`) - var v int - err := Unmarshal(json, &v) - assert.NotNil(t, err, "Err must not be nil as int is overflowing") - assert.Equal(t, 0, v, "v must be equal to 0") -} -func TestDecoderIntOverfow2(t *testing.T) { - json := []byte(`92233720368547758089 `) - var v int - err := Unmarshal(json, &v) - assert.NotNil(t, err, "Err must not be nil as int is overflowing") - assert.Equal(t, 0, v, "v must be equal to 0") -} -func TestDecoderIntOverfow3(t *testing.T) { - json := []byte(`92233720368547758089 `) - var v int - err := Unmarshal(json, &v) - assert.NotNil(t, err, "Err must not be nil as int is overflowing") - assert.Equal(t, 0, v, "v must be equal to 0") -} -func TestDecoderIntPoolError(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") -} -func TestDecoderIntDecoderAPI(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") -} - -func TestDecoderIntInvalidJSONError(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 TestDecoderInt32Basic(t *testing.T) { - json := []byte(`124`) - var v int32 - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, int32(124), v, "v must be equal to 124") -} -func TestDecoderInt32Negative(t *testing.T) { - json := []byte(`-124 `) - var v int32 - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, int32(-124), v, "v must be equal to -124") -} -func TestDecoderInt32NegativeError(t *testing.T) { - json := []byte(`-12x4 `) - var v int32 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "Err must be nil") - assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") -} -func TestDecoderInt32Null(t *testing.T) { - json := []byte(`null`) - var v int32 - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, int32(0), v, "v must be equal to 0") -} -func TestDecoderInt32InvalidType(t *testing.T) { - json := []byte(`"string"`) - var v int32 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil") - assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeErrorr") -} -func TestDecoderInt32InvalidJSON(t *testing.T) { - json := []byte(`123n`) - var v int32 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") -} -func TestDecoderInt32Big(t *testing.T) { - json := []byte(`2147483647`) - var v int32 - err := Unmarshal(json, &v) - assert.Nil(t, err, "err must not be nil as int32 does not overflow") - assert.Equal(t, int32(2147483647), v, "int32 must be equal to 2147483647") -} -func TestDecoderInt32Overflow(t *testing.T) { - json := []byte(` 2147483648`) - var v int32 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil as int32 overflows") - assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeError") -} -func TestDecoderInt32Overflow2(t *testing.T) { - json := []byte(`21474836483`) - var v int32 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil as int32 overflows") - assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeError") -} -func TestDecoderInt32PoolError(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") -} -func TestDecoderInt32tDecoderAPI(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") -} - -func TestDecoderInt32InvalidJSONError(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 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") -func TestDecoderUint32Basic(t *testing.T) { - json := []byte(`124 `) - var v uint32 - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, uint32(124), v, "v must be equal to 124") -} -func TestDecoderUint32Null(t *testing.T) { - json := []byte(`null`) - var v uint32 - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, uint32(0), v, "v must be equal to 0") -} -func TestDecoderUint32InvalidType(t *testing.T) { - json := []byte(`"string"`) - var v uint32 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil") - assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeErrorr") -} -func TestDecoderUint32InvalidJSON(t *testing.T) { - json := []byte(`123n`) - var v uint32 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") -} -func TestDecoderUint32Big(t *testing.T) { - json := []byte(`4294967295 `) - var v uint32 - err := Unmarshal(json, &v) - assert.Nil(t, err, "err must not be nil as uint32 does not overflow") - assert.Equal(t, uint32(4294967295), v, "err must be of type InvalidTypeError") -} -func TestDecoderUint32Overflow(t *testing.T) { - json := []byte(`4294967298`) - var v uint32 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil as uint32 overflows") - assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeError") + }) + 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 TestDecoderUint32Overflow2(t *testing.T) { - json := []byte(`42949672983`) - var v uint32 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil as uint32 overflows") - assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeError") -} -func TestDecoderUint32PoolError(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") -} -func TestDecoderUint32tDecoderAPI(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") +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 TestDecoderUint32InvalidJSONError(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 TestDecoderInt64Basic(t *testing.T) { - json := []byte(`124 `) - var v int64 - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, int64(124), v, "v must be equal to 124") -} -func TestDecoderInt64Negative(t *testing.T) { - json := []byte(`-124`) - var v int64 - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, int64(-124), v, "v must be equal to -124") -} -func TestDecoderInt64Null(t *testing.T) { - json := []byte(`null`) - var v int64 - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, int64(0), v, "v must be equal to 0") -} -func TestDecoderInt64InvalidType(t *testing.T) { - json := []byte(`"string"`) - var v int64 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil") - assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeErrorr") -} -func TestDecoderInt64InvalidJSON(t *testing.T) { - json := []byte(`123n`) - var v int64 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") -} -func TestDecoderInt64Big(t *testing.T) { - json := []byte(`9223372036854775807`) - var v int64 - err := Unmarshal(json, &v) - assert.Nil(t, err, "err must not be nil as int64 does not overflow") - assert.Equal(t, int64(9223372036854775807), v, "err must be of type InvalidTypeError") -} -func TestDecoderInt64Overflow(t *testing.T) { - json := []byte(`9223372036854775808`) - var v int64 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil as int64 overflows") - assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeError") -} -func TestDecoderInt64Overflow2(t *testing.T) { - json := []byte(`92233720368547758082`) - var v int64 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil as int64 overflows") - assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeError") -} -func TestDecoderInt64PoolError(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") -} -func TestDecoderInt64DecoderAPI(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") -} -func TestDecoderInt64InvalidJSONError(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 TestDecoderUint64Basic(t *testing.T) { - json := []byte(` 124 `) - var v uint64 - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, uint64(124), v, "v must be equal to 124") -} -func TestDecoderUint64Null(t *testing.T) { - json := []byte(`null`) - var v uint64 - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, uint64(0), v, "v must be equal to 0") -} -func TestDecoderUint64InvalidType(t *testing.T) { - json := []byte(`"string"`) - var v uint64 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil") - assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeErrorr") -} -func TestDecoderUint64InvalidJSON(t *testing.T) { - json := []byte(`123n`) - var v uint64 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil") - assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") -} -func TestDecoderUint64Big(t *testing.T) { - json := []byte(`18446744073709551615`) - var v uint64 - err := Unmarshal(json, &v) - assert.Nil(t, err, "err must not be nil as uint64 does not overflow") - assert.Equal(t, uint64(18446744073709551615), v, "err must be of type InvalidTypeError") -} -func TestDecoderUint64Overflow(t *testing.T) { - json := []byte(`18446744073709551616`) - var v uint64 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil as int32 overflows") - assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeError") -} -func TestDecoderUint64Overflow2(t *testing.T) { - json := []byte(`184467440737095516161`) - var v uint64 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil as int32 overflows") - assert.IsType(t, InvalidTypeError(""), err, "err must be of type InvalidTypeError") -} -func TestDecoderUint64PoolError(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") -} -func TestDecoderUint64tDecoderAPI(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") -} - -func TestDecoderUint64InvalidJSONError(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 TestDecoderFloatBasic(t *testing.T) { - json := []byte(`100.11 `) - var v float64 - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, 100.11, v, "v must be equal to 100.11") -} -func TestDecoderFloatBasic2(t *testing.T) { - json := []byte(` 100.11 `) - var v float64 - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, 100.11, v, "v must be equal to 100.11") -} -func TestDecoderFloatBasic3(t *testing.T) { - json := []byte(` 100 `) - var v float64 - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, float64(100), v, "v must be equal to 100.11") -} - -func TestDecoderFloatBig(t *testing.T) { - json := []byte(`89899843.3493493 `) - var v float64 - err := Unmarshal(json, &v) - assert.Nil(t, err, "Err must be nil") - assert.Equal(t, 89899843.3493493, v, "v must be equal to 8989984340.3493493") -} - -func TestDecoderFloatInvalidType(t *testing.T) { - json := []byte(`"string"`) - var v float64 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "err must not be nil") - assert.IsType(t, InvalidTypeError(""), err, "err must be of type *strconv.NumError") -} - -func TestDecoderFloatInvalidJSON(t *testing.T) { - json := []byte(`hello`) - var v float64 - err := Unmarshal(json, &v) - assert.NotNil(t, err, "Err must not be nil as JSON is invalid") - assert.IsType(t, InvalidJSONError(""), err, "err message must be 'Invalid JSON'") -} -func TestDecoderFloatDecoderAPI(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") -} -func TestDecoderFloatPoolError(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") +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 TestDecoderFloatInvalidJSONError(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")) + _, err := dec.skipNumber() + assert.NotNil(t, err, "err should not be nil") + assert.IsType(t, InvalidJSONError(""), err, "err should be of type InvalidJSONError") + }) } diff --git a/decode_object.go b/decode_object.go @@ -79,8 +79,12 @@ func (dec *Decoder) decodeObject(j UnmarshalerObject) (int, error) { } return dec.cursor, nil case 'n': - // is null - dec.cursor = dec.cursor + 4 + dec.cursor++ + err := dec.assertNull() + if err != nil { + return 0, err + } + dec.cursor++ return dec.cursor, nil default: // can't unmarshall to struct @@ -184,12 +188,27 @@ func (dec *Decoder) skipData() error { case ' ', '\n', '\t', '\r', ',': continue // is null - case 'n', 't': - dec.cursor = dec.cursor + 4 + case 'n': + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + return nil + case 't': + dec.cursor++ + err := dec.assertTrue() + if err != nil { + return err + } return nil // is false case 'f': - dec.cursor = dec.cursor + 5 + dec.cursor++ + err := dec.assertFalse() + if err != nil { + return err + } return nil // is an object case '{': diff --git a/decode_object_test.go b/decode_object_test.go @@ -85,6 +85,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, 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") @@ -154,7 +158,7 @@ func TestDecodeObjectNull(t *testing.T) { var jsonComplex = []byte(`{ "test": "{\"test\":\"1\",\"test1\":2}", - "test2\\n": "\\\\\\\\\\\n", + "test2\\n": "\\\\\\\\\\n", "testArrSkip": ["testString with escaped \\\" quotes"], "testSkipString": "skip \\ string with \\n escaped char \" ", "testSkipObject": { @@ -163,7 +167,11 @@ var jsonComplex = []byte(`{ } }, "testSkipNumber": 123.23, + "testSkipNumber2": 123.23 , "testBool": true, + "testSkipBoolTrue": true, + "testSkipBoolFalse": false, + "testSkipBoolNull": null, "testSub": { "test": "{\"test\":\"1\",\"test1\":2}", "test2\\n": "[1,2,3]", @@ -192,7 +200,7 @@ func (j *jsonObjectComplex) UnmarshalObject(dec *Decoder, key string) error { switch key { case "test": return dec.AddString(&j.Test) - case `test2\n`: + case "test2\n": return dec.AddString(&j.Test2) case "test3": return dec.AddInt(&j.Test3) @@ -216,9 +224,9 @@ func TestDecodeObjComplex(t *testing.T) { result := jsonObjectComplex{} err := UnmarshalObject(jsonComplex, &result) assert.NotNil(t, err, "err should not be as invalid type as been encountered nil") - assert.Equal(t, `Cannot unmarshal to struct, wrong char '"' found at pos 531`, err.Error(), "err should not be as invalid type as been encountered nil") + assert.Equal(t, `Cannot unmarshal to struct, wrong char '"' found at pos 639`, err.Error(), "err should not be as invalid type as been encountered nil") assert.Equal(t, `{"test":"1","test1":2}`, result.Test, "result.Test is not expected value") - assert.Equal(t, `\\\\\\n`, result.Test2, "result.Test2 is not expected value") + assert.Equal(t, "\\\\\\\\\n", result.Test2, "result.Test2 is not expected value") assert.Equal(t, 1, result.Test3, "result.test3 is not expected value") assert.Equal(t, `{"test":"1","test1":2}`, result.testSub.Test, "result.testSub.test is not expected value") assert.Equal(t, `[1,2,3]`, result.testSub.Test2, "result.testSub.test2 is not expected value") @@ -450,3 +458,51 @@ func TestDecoderObjectPoolError(t *testing.T) { _ = dec.DecodeObject(&result) assert.True(t, false, "should not be called as decoder should have panicked") } + +func TestSkipData(t *testing.T) { + testCases := []struct { + name string + err bool + json string + }{ + { + name: "skip-bool-false-err", + json: `fulse`, + err: true, + }, + { + name: "skip-bool-true-err", + json: `trou`, + err: true, + }, + { + name: "skip-bool-null-err", + json: `nil`, + err: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + dec := NewDecoder(strings.NewReader(testCase.json)) + err := dec.skipData() + if testCase.err { + assert.NotNil(t, err, "err should not be nil") + } else { + assert.Nil(t, err, "err should be nil") + } + }) + } + t.Run("error-invalid-json", func(t *testing.T) { + dec := NewDecoder(strings.NewReader("")) + err := dec.skipData() + assert.NotNil(t, err, "err should not be nil as data is empty") + assert.IsType(t, InvalidJSONError(""), err, "err should of type InvalidJSONError") + }) + t.Run("skip-array-error-invalid-json", func(t *testing.T) { + dec := NewDecoder(strings.NewReader("")) + _, err := dec.skipArray() + assert.NotNil(t, err, "err should not be nil as data is empty") + assert.IsType(t, InvalidJSONError(""), err, "err should of type InvalidJSONError") + }) +} diff --git a/decode_stream_test.go b/decode_stream_test.go @@ -141,7 +141,6 @@ func TestStreamDecodingObjectsParallel(t *testing.T) { }, expectations: func(err error, result []*TestObj, t *testing.T) { assert.Nil(t, err, "err should be nil") - assert.Equal(t, 0, result[0].test, "result[0].test should be equal to 0 as input is null") assert.Equal(t, 0, result[0].test2, "result[0].test2 should be equal to 0 as input is null") assert.Equal(t, "", result[0].test3, "result[0].test3 should be equal to \"\" as input is null") diff --git a/decode_string.go b/decode_string.go @@ -33,7 +33,12 @@ func (dec *Decoder) decodeString(v *string) error { return nil // is nil case 'n': - dec.cursor = dec.cursor + 4 + dec.cursor++ + err := dec.assertNull() + if err != nil { + return err + } + dec.cursor++ return nil default: dec.err = InvalidTypeError( @@ -72,21 +77,65 @@ func (dec *Decoder) parseEscapedString() error { dec.length = len(dec.data) dec.cursor -= nSlash - diff return nil - case 'n', 'r', 't': + case 'b': // number of slash must be even // if is odd number of slashes // divide nSlash - 1 by 2 and leave last one // else divide nSlash by 2 and leave the letter var diff int - if nSlash&1 == 1 { - diff = (nSlash - 1) >> 1 - dec.data = append(dec.data[:start+diff], dec.data[dec.cursor-1:]...) + if nSlash&1 != 0 { + return InvalidJSONError("Invalid JSON unescaped character") } else { diff = nSlash >> 1 - dec.data = append(dec.data[:start+diff-1], dec.data[dec.cursor-1:]...) + dec.data = append(append(dec.data[:start+diff-2], '\b'), dec.data[dec.cursor:]...) } dec.length = len(dec.data) - dec.cursor -= nSlash - diff + dec.cursor -= nSlash - diff + 1 + return nil + case 'n': + // number of slash must be even + // if is odd number of slashes + // divide nSlash - 1 by 2 and leave last one + // else divide nSlash by 2 and leave the letter + var diff int + if nSlash&1 != 0 { + return InvalidJSONError("Invalid JSON unescaped character") + } else { + diff = nSlash >> 1 + dec.data = append(append(dec.data[:start+diff-2], '\n'), dec.data[dec.cursor:]...) + } + dec.length = len(dec.data) + dec.cursor -= nSlash - diff + 1 + return nil + case 'r': + // number of slash must be even + // if is odd number of slashes + // divide nSlash - 1 by 2 and leave last one + // else divide nSlash by 2 and leave the letter + var diff int + if nSlash&1 != 0 { + return InvalidJSONError("Invalid JSON unescaped character") + } else { + diff = nSlash >> 1 + dec.data = append(append(dec.data[:start+diff-2], '\r'), dec.data[dec.cursor:]...) + } + dec.length = len(dec.data) + dec.cursor -= nSlash - diff + 1 + return nil + case 't': + // number of slash must be even + // if is odd number of slashes + // divide nSlash - 1 by 2 and leave last one + // else divide nSlash by 2 and leave the letter + var diff int + if nSlash&1 != 0 { + return InvalidJSONError("Invalid JSON unescaped character") + } else { + diff = nSlash >> 1 + dec.data = append(append(dec.data[:start+diff-2], '\t'), dec.data[dec.cursor:]...) + } + dec.length = len(dec.data) + dec.cursor -= nSlash - diff + 1 return nil default: // nSlash must be even diff --git a/decode_string_test.go b/decode_string_test.go @@ -1,6 +1,8 @@ package gojay import ( + "fmt" + "log" "strings" "sync" "testing" @@ -16,12 +18,29 @@ func TestDecoderStringBasic(t *testing.T) { assert.Equal(t, "string", v, "v must be equal to 'string'") } +func TestDecoderStringEmpty(t *testing.T) { + json := []byte(``) + var v string + err := Unmarshal(json, &v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, "", v, "v must be equal to 'string'") +} + +func TestDecoderStringNullInvalid(t *testing.T) { + json := []byte(`nall`) + var v string + err := Unmarshal(json, &v) + assert.NotNil(t, err, "Err must be nil") + assert.IsType(t, InvalidJSONError(""), err, "Err must be nil") + assert.Equal(t, "", v, "v must be equal to 'string'") +} + func TestDecoderStringComplex(t *testing.T) { json := []byte(` "string with spaces and \"escape\"d \"quotes\" and escaped line returns \\n and escaped \\\\ escaped char"`) var v string err := Unmarshal(json, &v) assert.Nil(t, err, "Err must be nil") - assert.Equal(t, "string with spaces and \"escape\"d \"quotes\" and escaped line returns \\n and escaped \\\\ escaped char", v, "v is not equal to the value expected") + assert.Equal(t, "string with spaces and \"escape\"d \"quotes\" and escaped line returns \n and escaped \\\\ escaped char", v, "v is not equal to the value expected") } func TestDecoderStringNull(t *testing.T) { @@ -107,3 +126,118 @@ func TestDecoderSkipStringError(t *testing.T) { assert.NotNil(t, err, "Err must be nil") assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") } + +func TestParseEscapedString(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult string + err bool + errType interface{} + }{ + { + name: "escape quote err", + json: `"test string \" escaped"`, + expectedResult: `test string " escaped`, + err: false, + }, + { + name: "escape quote err2", + json: `"test string \\t escaped"`, + expectedResult: "test string \t escaped", + err: false, + }, + { + name: "escape quote err2", + json: `"test string \\r escaped"`, + expectedResult: "test string \r escaped", + err: false, + }, + { + name: "escape quote err2", + json: `"test string \\b escaped"`, + expectedResult: "test string \b escaped", + err: false, + }, + { + name: "escape quote err", + json: `"test string \\n escaped"`, + expectedResult: "test string \n escaped", + err: false, + }, + { + name: "escape quote err", + json: `"test string \\" escaped"`, + expectedResult: ``, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "escape quote err", + json: `"test string \\\l escaped"`, + expectedResult: ``, + err: true, + errType: InvalidJSONError(""), + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + str := "" + dec := NewDecoder(strings.NewReader(testCase.json)) + err := dec.Decode(&str) + 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 expected type") + } + log.Print(err) + } else { + assert.Nil(t, err, "err should be nil") + } + assert.Equal(t, testCase.expectedResult, str, fmt.Sprintf("str should be equal to '%s'", testCase.expectedResult)) + }) + } + +} + +func TestSkipString(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult string + err bool + errType interface{} + }{ + { + name: "escape quote err", + json: `test string \\" escaped"`, + expectedResult: ``, + err: true, + errType: InvalidJSONError(""), + }, + { + name: "escape quote err", + json: `test string \\\l escaped"`, + expectedResult: ``, + err: true, + errType: InvalidJSONError(""), + }, + } + + for _, testCase := range testCases { + str := "" + dec := NewDecoder(strings.NewReader(testCase.json)) + err := dec.skipString() + 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 expected type") + } + log.Print(err) + } else { + assert.Nil(t, err, "err should be nil") + } + assert.Equal(t, testCase.expectedResult, str, fmt.Sprintf("str should be equal to '%s'", testCase.expectedResult)) + } +} diff --git a/decode_test.go b/decode_test.go @@ -4,7 +4,6 @@ import ( "fmt" "io" "reflect" - "strings" "testing" "github.com/stretchr/testify/assert" @@ -553,18 +552,3 @@ func TestUnmarshalObjects(t *testing.T) { }) } } - -func TestSkipData(t *testing.T) { - t.Run("error-invalid-json", func(t *testing.T) { - dec := NewDecoder(strings.NewReader("")) - err := dec.skipData() - assert.NotNil(t, err, "err should not be nil as data is empty") - assert.IsType(t, InvalidJSONError(""), err, "err should of type InvalidJSONError") - }) - t.Run("skip-array-error-invalid-json", func(t *testing.T) { - dec := NewDecoder(strings.NewReader("")) - _, err := dec.skipArray() - assert.NotNil(t, err, "err should not be nil as data is empty") - assert.IsType(t, InvalidJSONError(""), err, "err should of type InvalidJSONError") - }) -} diff --git a/encode.go b/encode.go @@ -29,7 +29,8 @@ import ( // fmt.Println(b) // {"id":123456} // } func MarshalObject(v MarshalerObject) ([]byte, error) { - enc := newEncoder() + enc := BorrowEncoder(nil) + enc.grow(512) defer enc.Release() return enc.encodeObject(v) } @@ -56,8 +57,8 @@ func MarshalObject(v MarshalerObject) ([]byte, error) { // fmt.Println(b) // [{"id":123456},{"id":7890}] // } func MarshalArray(v MarshalerArray) ([]byte, error) { - enc := newEncoder() - enc.grow(200) + enc := BorrowEncoder(nil) + enc.grow(512) enc.writeByte('[') v.(MarshalerArray).MarshalArray(enc) enc.writeByte(']') diff --git a/encode_builder.go b/encode_builder.go @@ -17,6 +17,10 @@ func (enc *Encoder) writeBytes(p []byte) { enc.buf = append(enc.buf, p...) } +func (enc *Encoder) writeTwoBytes(b1 byte, b2 byte) { + enc.buf = append(enc.buf, b1, b2) +} + // WriteByte appends the byte c to b's Buffer. // The returned error is always nil. func (enc *Encoder) writeByte(c byte) { @@ -34,17 +38,17 @@ func (enc *Encoder) writeStringEscape(s string) { for i := 0; i < l; i++ { switch s[i] { case '\\', '"': - enc.writeByte('\\') - enc.writeByte(s[i]) + enc.writeTwoBytes('\\', s[i]) case '\n': - enc.writeByte('\\') - enc.writeByte('n') + enc.writeTwoBytes('\\', 'n') + case '\f': + enc.writeTwoBytes('\\', 'f') + case '\b': + enc.writeTwoBytes('\\', 'b') case '\r': - enc.writeByte('\\') - enc.writeByte('r') + enc.writeTwoBytes('\\', 'r') case '\t': - enc.writeByte('\\') - enc.writeByte('t') + enc.writeTwoBytes('\\', 't') default: enc.writeByte(s[i]) } diff --git a/encode_number.go b/encode_number.go @@ -111,7 +111,7 @@ func (enc *Encoder) AddInt64(v int64) { enc.buf = strconv.AppendInt(enc.buf, v, 10) } -// AddIntOmitEmpty adds an int to be encoded and skips it if its value is 0, +// AddInt64OmitEmpty adds an int to be encoded and skips it if its value is 0, // must be used inside a slice or array encoding (does not encode a key). func (enc *Encoder) AddInt64OmitEmpty(v int64) { if v == 0 { diff --git a/encode_object_test.go b/encode_object_test.go @@ -300,7 +300,7 @@ func TestEncoderObjectMarshalAPI(t *testing.T) { }) t.Run("marshal-object-func", func(t *testing.T) { f := EncodeObjectFunc(func(enc *Encoder) { - enc.AddStringKey("test", "test") + enc.AddStringKeyOmitEmpty("test", "test") }) r, err := Marshal(f) assert.Nil(t, err, "Error should be nil") diff --git a/encode_pool.go b/encode_pool.go @@ -30,9 +30,6 @@ func init() { func NewEncoder(w io.Writer) *Encoder { return &Encoder{w: w} } -func newEncoder() *Encoder { - return &Encoder{} -} // BorrowEncoder borrows an Encoder from the pool. func BorrowEncoder(w io.Writer) *Encoder { diff --git a/encode_string.go b/encode_string.go @@ -22,6 +22,7 @@ func (enc *Encoder) encodeString(v string) ([]byte, error) { return enc.buf, nil } +// AppendString appends a string to the buffer func (enc *Encoder) AppendString(v string) { enc.grow(len(v) + 2) enc.writeByte('"') @@ -34,9 +35,10 @@ func (enc *Encoder) AddString(v string) { enc.grow(len(v) + 4) r := enc.getPreviousRune() if r != '[' { - enc.writeByte(',') + enc.writeTwoBytes(',', '"') + } else { + enc.writeByte('"') } - enc.writeByte('"') enc.writeStringEscape(v) enc.writeByte('"') } @@ -49,9 +51,10 @@ func (enc *Encoder) AddStringOmitEmpty(v string) { } r := enc.getPreviousRune() if r != '[' { - enc.writeByte(',') + enc.writeTwoBytes(',', '"') + } else { + enc.writeByte('"') } - enc.writeByte('"') enc.writeStringEscape(v) enc.writeByte('"') } @@ -61,9 +64,10 @@ func (enc *Encoder) AddStringKey(key, v string) { enc.grow(len(key) + len(v) + 5) r := enc.getPreviousRune() if r != '{' { - enc.writeByte(',') + enc.writeTwoBytes(',', '"') + } else { + enc.writeByte('"') } - enc.writeByte('"') enc.writeStringEscape(key) enc.writeBytes(objKeyStr) enc.writeStringEscape(v) @@ -79,9 +83,10 @@ func (enc *Encoder) AddStringKeyOmitEmpty(key, v string) { enc.grow(len(key) + len(v) + 5) r := enc.getPreviousRune() if r != '{' { - enc.writeByte(',') + enc.writeTwoBytes(',', '"') + } else { + enc.writeByte('"') } - enc.writeByte('"') enc.writeStringEscape(key) enc.writeBytes(objKeyStr) enc.writeStringEscape(v) diff --git a/encode_string_test.go b/encode_string_test.go @@ -22,11 +22,11 @@ func TestEncoderStringEncodeAPI(t *testing.T) { t.Run("utf8", func(t *testing.T) { builder := &strings.Builder{} enc := NewEncoder(builder) - err := enc.EncodeString("漢字") + err := enc.EncodeString("漢字𩸽") assert.Nil(t, err, "Error should be nil") assert.Equal( t, - `"漢字"`, + `"漢字𩸽"`, builder.String(), "Result of marshalling is different as the one expected") }) @@ -80,6 +80,30 @@ func TestEncoderStringEncodeAPI(t *testing.T) { builder.String(), "Result of marshalling is different as the one expected") }) + t.Run("escaped-sequence3", func(t *testing.T) { + str := "hello \b world 𝄞" + builder := &strings.Builder{} + enc := NewEncoder(builder) + err := enc.EncodeString(str) + assert.Nil(t, err, "Error should be nil") + assert.Equal( + t, + `"hello \b world 𝄞"`, + builder.String(), + "Result of marshalling is different as the one expected") + }) + t.Run("escaped-sequence3", func(t *testing.T) { + str := "hello \f world 𝄞" + builder := &strings.Builder{} + enc := NewEncoder(builder) + err := enc.EncodeString(str) + assert.Nil(t, err, "Error should be nil") + assert.Equal( + t, + "\"hello \\f world 𝄞\"", + builder.String(), + "Result of marshalling is different as the one expected") + }) } func TestEncoderStringEncodeAPIErrors(t *testing.T) { diff --git a/errors.go b/errors.go @@ -1,5 +1,7 @@ package gojay +const invalidJSONCharErrorMsg = "Invalid JSON character %c found at position %d" + // InvalidJSONError is a type representing an error returned when // Decoding encounters invalid JSON. type InvalidJSONError string