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:
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