gojay

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

commit a9541d970e672141c20ed15f636c355d4da87a27
parent e463341caf8850e339dff20d8ebab3db8962ad67
Author: francoispqt <francois@parquet.ninja>
Date:   Sun, 13 May 2018 13:27:48 +0800

add exponent syntax and check of bools

Diffstat:
MREADME.md | 3++-
Mbenchmarks/encoder/encoder_bench_large_test.go | 2+-
Mdecode_bool.go | 44++++++++++++++++++++++++++++++++++----------
Mdecode_bool_test.go | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdecode_number.go | 220+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mdecode_number_test.go | 293+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdecode_object_test.go | 4++++
Mencode_number.go | 2+-
Mencode_string.go | 1+
9 files changed, 728 insertions(+), 17 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 1.0.0 and safe to use in production** GoJay is a performant JSON encoder/decoder for Golang (currently the most performant, [see benchmarks](#benchmark-results)). diff --git a/benchmarks/encoder/encoder_bench_large_test.go b/benchmarks/encoder/encoder_bench_large_test.go @@ -28,7 +28,7 @@ func BenchmarkJsonIterEncodeLargeStruct(b *testing.B) { } } } - +b func BenchmarkEasyJsonEncodeObjLarge(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { diff --git a/decode_bool.go b/decode_bool.go @@ -75,13 +75,21 @@ func (dec *Decoder) assertTrue() error { if dec.data[dec.cursor] != 'e' { return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) } - return nil - default: - 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++ } - return InvalidJSONError("Invalid JSO") + if i == 3 { + return nil + } + return InvalidJSONError("Invalid JSON") } func (dec *Decoder) assertNull() error { @@ -100,12 +108,20 @@ func (dec *Decoder) assertNull() error { if dec.data[dec.cursor] != 'l' { return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) } - return nil - default: - 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") } @@ -129,11 +145,19 @@ func (dec *Decoder) assertFalse() error { if dec.data[dec.cursor] != 'e' { return InvalidJSONError(fmt.Sprintf(invalidJSONCharErrorMsg, dec.data[dec.cursor], dec.cursor)) } - return nil - default: - 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,6 +7,182 @@ import ( "github.com/stretchr/testify/assert" ) +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-error4", + 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") + }, + }, + { + 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") + }, + }, + { + 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") + }, + }, + { + 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") + }, + }, + { + 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") + }, + }, + { + 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") + }, + }, + { + 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") + }, + }, + { + 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") + }, + }, + { + 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") + }, + }, + { + 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") + }, + }, + { + 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") + }, + }, + { + 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") + }, + }, + } + + 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 TestDecoderBoolTrue(t *testing.T) { json := []byte(`true`) var v bool 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 { @@ -413,11 +414,51 @@ 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 + default: + dec.cursor = j + return dec.atoi64(start, end), nil + } + } + 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") @@ -425,6 +466,43 @@ 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 + default: + if sign == -1 { + return init * (1 / int64(pow10uint64[exp+1])), nil + } + return init * int64(pow10uint64[exp+1]), nil + } + } + 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 @@ -457,7 +535,49 @@ func (dec *Decoder) getInt32(b byte) (int32, error) { 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 + default: + dec.cursor = j + return dec.atoi32(start, end), nil + } + } + return dec.atoi32(start, end), nil + case 'e', 'E': + // get init n + return dec.getInt32WithExp(dec.atoi32(start, end), j+1) + case ',', '}', ']': dec.cursor = j return dec.atoi32(start, end), nil } @@ -467,6 +587,43 @@ 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 + default: + if sign == -1 { + return init * (1 / int32(pow10uint64[exp+1])), nil + } + return init * int32(pow10uint64[exp+1]), nil + } + } + 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 @@ -510,6 +667,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 @@ -519,6 +687,17 @@ 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 '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': continue case ',', '}', ']': // does not have decimal @@ -653,3 +832,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,12 +1,248 @@ package gojay import ( + "fmt" + "log" + "math" "strings" "testing" "github.com/stretchr/testify/assert" ) +func TestDecoderInt64Exponent(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult int64 + }{ + { + 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, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v int64 + err := Unmarshal(json, &v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } +} +func TestDecoderInt32Exponent(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult int32 + }{ + { + 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, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v int32 + err := Unmarshal(json, &v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, testCase.expectedResult, v, fmt.Sprintf("v must be equal to %d", testCase.expectedResult)) + }) + } +} + +func TestDecoderFloat64Exponent(t *testing.T) { + testCases := []struct { + name string + json string + expectedResult float64 + }{ + { + 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.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, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + json := []byte(testCase.json) + var v float64 + err := Unmarshal(json, &v) + log.Print(v) + 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)) + }) + } +} func TestDecoderIntBasic(t *testing.T) { json := []byte(`124`) var v int @@ -14,6 +250,62 @@ func TestDecoderIntBasic(t *testing.T) { assert.Nil(t, err, "Err must be nil") assert.Equal(t, 124, v, "v must be equal to 124") } +func TestDecoderIntExponent(t *testing.T) { + json := []byte(`1E+2`) + var v int + err := Unmarshal(json, &v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, 100, v, "v must be equal to 100") +} +func TestDecoderIntExponent1(t *testing.T) { + json := []byte(`4E+2`) + var v int + err := Unmarshal(json, &v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, 400, v, "v must be equal to 100") +} +func TestDecoderIntExponentComplex(t *testing.T) { + json := []byte(`-3E-004`) + var v int + err := Unmarshal(json, &v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, 0, v, "v must be equal to 0") +} +func TestDecoderIntExponentComplex1(t *testing.T) { + json := []byte(`-3.12E+005`) + var v int + err := Unmarshal(json, &v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, -312000, v, "v must be equal to -312000") +} +func TestDecoderFloatExponentComplex1(t *testing.T) { + json := []byte(`-3E-004`) + var v float64 + err := Unmarshal(json, &v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int64(-0.0003*10000), int64(v*10000), "v must be equal to -0.0003") +} +func TestDecoderFloatExponentComplex2(t *testing.T) { + json := []byte(`-3E+004`) + var v float64 + err := Unmarshal(json, &v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, float64(-30000), float64(v), "v must be equal to -30000") +} +func TestDecoderFloatExponentComplex3(t *testing.T) { + json := []byte(`-3.12E-004`) + var v float64 + err := Unmarshal(json, &v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, int64(-0.000312*1000000), int64(v*1000000), "v must be equal to -30000") +} +func TestDecoderIntExponentComplex2(t *testing.T) { + json := []byte(`3.12E+005`) + var v int + err := Unmarshal(json, &v) + assert.Nil(t, err, "Err must be nil") + assert.Equal(t, 312000, v, "v must be equal to 312000") +} func TestDecoderIntNegative(t *testing.T) { json := []byte(` -124 `) var v int @@ -294,6 +586,7 @@ func TestDecoderInt64Negative(t *testing.T) { 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 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") 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_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('"')