gojay

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

commit bf45cd129177639df8c9bfdf126d38ba66db949e
parent 3d991e95a8197d6bc05e10dff748d8a0464784e9
Author: francoispqt <francois@parquet.ninja>
Date:   Thu, 26 Apr 2018 23:09:00 +0800

begin adding support to exponent syntax for numbers unmarshaling

Diffstat:
MREADME.md | 3+++
Mbenchmarks/encoder/encoder_bench_large_test.go | 2+-
Mdecode_number.go | 62+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mdecode_number_test.go | 36++++++++++++++++++++++++++++++++++++
4 files changed, 99 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md @@ -6,6 +6,9 @@ ![MIT License](https://img.shields.io/badge/license-mit-blue.svg?style=flat-square) # GoJay + +**Package is currently at version 0.9 and still under development** + GoJay is a performant JSON encoder/decoder for Golang (currently the most performant, [see benchmarks](#benchmark-results)). It has a simple API and doesn't use reflection. It relies on small interfaces to decode/encode structures and slices. 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_number.go b/decode_number.go @@ -61,6 +61,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 { @@ -350,11 +351,29 @@ 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 ',', '}', ']': 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 + for ; j < dec.length || dec.read(); j++ { + switch dec.data[j] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + continue + case 'e', 'E': + // can try unmarshalling to int as Exponent might change decimal number to non decimal + 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") @@ -362,6 +381,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 diff --git a/decode_number_test.go b/decode_number_test.go @@ -13,6 +13,41 @@ 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 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 @@ -185,6 +220,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