gojay

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

commit c5402e694dcd8f75c0dce2882a51e6ee921ea5f1
parent 66f6440df56f8125384dca802568899dae409cf7
Author: francoispqt <francois@parquet.ninja>
Date:   Sun, 29 Apr 2018 00:27:02 +0800

Merge branch 'master' of https://github.com/francoispqt/gojay

Diffstat:
MREADME.md | 2+-
Mdecode.go | 24++++++++++++------------
Mdecode_array.go | 4++--
Mdecode_array_test.go | 35+++++++++++++++++++++++++++++++++++
Mdecode_number.go | 2+-
Mdecode_number_test.go | 67+++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mdecode_object.go | 3---
Mdecode_object_test.go | 9+++++++--
Mdecode_stream_test.go | 13+++++++++++++
Mencode.go | 7+++++++
Aencode_builder_test.go | 17+++++++++++++++++
Mencode_interface.go | 28++++++++++++++--------------
Mencode_interface_test.go | 24++++++++++++++++++++++++
Mencode_object_test.go | 4++--
14 files changed, 186 insertions(+), 53 deletions(-)

diff --git a/README.md b/README.md @@ -397,7 +397,7 @@ func main() { Unsafe API has the same functions than the regular API, it only has `Unmarshal API` for now. It is unsafe because it makes assumptions on the quality of the given JSON. -If you are not sure if you're JSON is valid, don't use the Unsafe API. +If you are not sure if your JSON is valid, don't use the Unsafe API. Also, the `Unsafe` API does not copy the buffer when using Unmarshal API, which, in case of string decoding, can lead to data corruption if a byte buffer is reused. Using the `Decode` API makes `Unsafe` API safer as the io.Reader relies on `copy` builtin method and `Decoder` will have its own internal buffer :) diff --git a/decode.go b/decode.go @@ -13,7 +13,7 @@ import ( // If a JSON value is not appropriate for a given target type, or if a JSON number // overflows the target type, UnmarshalArray skips that field and completes the unmarshaling as best it can. func UnmarshalArray(data []byte, v UnmarshalerArray) error { - dec := BorrowDecoder(nil) + dec := borrowDecoder(nil, 0) defer dec.Release() dec.data = make([]byte, len(data)) copy(dec.data, data) @@ -35,7 +35,7 @@ func UnmarshalArray(data []byte, v UnmarshalerArray) error { // If a JSON value is not appropriate for a given target type, or if a JSON number // overflows the target type, UnmarshalObject skips that field and completes the unmarshaling as best it can. func UnmarshalObject(data []byte, v UnmarshalerObject) error { - dec := BorrowDecoder(nil) + dec := borrowDecoder(nil, 0) defer dec.Release() dec.data = make([]byte, len(data)) copy(dec.data, data) @@ -73,53 +73,53 @@ func Unmarshal(data []byte, v interface{}) error { var dec *Decoder switch vt := v.(type) { case *string: - dec = BorrowDecoder(nil) + dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data err = dec.decodeString(vt) case *int: - dec = BorrowDecoder(nil) + dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data err = dec.decodeInt(vt) case *int32: - dec = BorrowDecoder(nil) + dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data err = dec.decodeInt32(vt) case *uint32: - dec = BorrowDecoder(nil) + dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data err = dec.decodeUint32(vt) case *int64: - dec = BorrowDecoder(nil) + dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data err = dec.decodeInt64(vt) case *uint64: - dec = BorrowDecoder(nil) + dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data err = dec.decodeUint64(vt) case *float64: - dec = BorrowDecoder(nil) + dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data err = dec.decodeFloat64(vt) case *bool: - dec = BorrowDecoder(nil) + dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = data err = dec.decodeBool(vt) case UnmarshalerObject: - dec = BorrowDecoder(nil) + dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = make([]byte, len(data)) copy(dec.data, data) _, err = dec.decodeObject(vt) case UnmarshalerArray: - dec = BorrowDecoder(nil) + dec = borrowDecoder(nil, 0) dec.length = len(data) dec.data = make([]byte, len(data)) copy(dec.data, data) diff --git a/decode_array.go b/decode_array.go @@ -71,7 +71,7 @@ func (dec *Decoder) skipArray() (int, error) { var arraysOpen = 1 var arraysClosed = 0 // var stringOpen byte = 0 - for j := dec.cursor; j < dec.length; j++ { + for j := dec.cursor; j < dec.length || dec.read(); j++ { switch dec.data[j] { case ']': arraysClosed++ @@ -94,7 +94,7 @@ func (dec *Decoder) skipArray() (int, error) { // loop backward and count how many anti slash found // to see if string is effectively escaped ct := 1 - for i := j; i > 0; i-- { + for i := j - 2; i > 0; i-- { if dec.data[i] != '\\' { break } diff --git a/decode_array_test.go b/decode_array_test.go @@ -189,3 +189,38 @@ func TestDecoderSliceDecoderAPIError(t *testing.T) { 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 TestSkipArray(t *testing.T) { + testCases := []struct { + json string + expectations func(*testing.T, int, error) + }{ + { + json: `"testbasic"]`, + expectations: func(t *testing.T, i int, err error) { + assert.Equal(t, len(`"testbasic"]`), i) + assert.Nil(t, err) + }, + }, + { + json: `"test \\\\\" escape"]`, + expectations: func(t *testing.T, i int, err error) { + assert.Equal(t, len(`"test \\\\\" escape"]`), i) + assert.Nil(t, err) + }, + }, + { + json: `"test \\\\\\"]`, + expectations: func(t *testing.T, i int, err error) { + assert.Equal(t, len(`"test \\\\\\"]`), i) + assert.Nil(t, err) + }, + }, + } + + for _, test := range testCases { + dec := NewDecoder(strings.NewReader(test.json)) + i, err := dec.skipArray() + test.expectations(t, i, err) + } +} diff --git a/decode_number.go b/decode_number.go @@ -581,7 +581,7 @@ func (dec *Decoder) atoi32(start, end int) int32 { for i := start + 1; i < end; i++ { intv := int32(digits[dec.data[i]]) if val > maxInt32toMultiply { - dec.err = InvalidTypeError("Overflows int321") + dec.err = InvalidTypeError("Overflows int32") return 0 } val = (val << 3) + (val << 1) diff --git a/decode_number_test.go b/decode_number_test.go @@ -15,12 +15,19 @@ func TestDecoderIntBasic(t *testing.T) { assert.Equal(t, 124, v, "v must be equal to 124") } func TestDecoderIntNegative(t *testing.T) { - json := []byte(`-124`) + 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 @@ -56,6 +63,20 @@ func TestDecoderIntOverfow(t *testing.T) { 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) @@ -68,13 +89,6 @@ func TestDecoderIntPoolError(t *testing.T) { _ = dec.DecodeInt(&result) assert.True(t, false, "should not be called as decoder should have panicked") } -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 TestDecoderInttDecoderAPI(t *testing.T) { var v int dec := NewDecoder(strings.NewReader(`33`)) @@ -92,12 +106,19 @@ func TestDecoderInt32Basic(t *testing.T) { assert.Equal(t, int32(124), v, "v must be equal to 124") } func TestDecoderInt32Negative(t *testing.T) { - json := []byte(`-124`) + 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 @@ -127,7 +148,7 @@ func TestDecoderInt32Big(t *testing.T) { assert.Equal(t, int32(2147483647), v, "int32 must be equal to 2147483647") } func TestDecoderInt32Overflow(t *testing.T) { - json := []byte(`2147483648`) + json := []byte(` 2147483648`) var v int32 err := Unmarshal(json, &v) assert.NotNil(t, err, "err must not be nil as int32 overflows") @@ -162,7 +183,7 @@ func TestDecoderInt32tDecoderAPI(t *testing.T) { } func TestDecoderUint32Basic(t *testing.T) { - json := []byte(`124`) + json := []byte(`124 `) var v uint32 err := Unmarshal(json, &v) assert.Nil(t, err, "Err must be nil") @@ -190,7 +211,7 @@ func TestDecoderUint32InvalidJSON(t *testing.T) { assert.IsType(t, InvalidJSONError(""), err, "err must be of type InvalidJSONError") } func TestDecoderUint32Big(t *testing.T) { - json := []byte(`4294967295`) + json := []byte(`4294967295 `) var v uint32 err := Unmarshal(json, &v) assert.Nil(t, err, "err must not be nil as uint32 does not overflow") @@ -233,7 +254,7 @@ func TestDecoderUint32tDecoderAPI(t *testing.T) { } func TestDecoderInt64Basic(t *testing.T) { - json := []byte(`124`) + json := []byte(`124 `) var v int64 err := Unmarshal(json, &v) assert.Nil(t, err, "Err must be nil") @@ -309,7 +330,7 @@ func TestDecoderInt64tDecoderAPI(t *testing.T) { assert.Equal(t, int64(33), v, "v must be equal to 33") } func TestDecoderUint64Basic(t *testing.T) { - json := []byte(`124`) + json := []byte(` 124 `) var v uint64 err := Unmarshal(json, &v) assert.Nil(t, err, "Err must be nil") @@ -378,15 +399,29 @@ func TestDecoderUint64tDecoderAPI(t *testing.T) { assert.Equal(t, uint64(33), v, "v must be equal to 33") } func TestDecoderFloatBasic(t *testing.T) { - json := []byte(`100.11`) + 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`) + json := []byte(`89899843.3493493 `) var v float64 err := Unmarshal(json, &v) assert.Nil(t, err, "Err must be nil") diff --git a/decode_object.go b/decode_object.go @@ -18,9 +18,6 @@ func (dec *Decoder) DecodeObject(j UnmarshalerObject) error { return err } func (dec *Decoder) decodeObject(j UnmarshalerObject) (int, error) { - if dec.isPooled == 1 { - panic(InvalidUsagePooledDecoderError("Invalid usage of pooled decoder")) - } keys := j.NKeys() for ; dec.cursor < dec.length || dec.read(); dec.cursor++ { switch dec.data[dec.cursor] { diff --git a/decode_object_test.go b/decode_object_test.go @@ -154,8 +154,13 @@ func TestDecodeObjectNull(t *testing.T) { var jsonComplex = []byte(`{ "test": "{\"test\":\"1\",\"test1\":2}", "test2\\n": "\\\\\\\\\\\n", - "testArrSkip": ["testString with escaped \" quotes"], + "testArrSkip": ["testString with escaped \\\" quotes"], "testSkipString": "skip \\ string with \\n escaped char \" ", + "testSkipObject": { + "testSkipSubObj": { + "test": "test" + } + }, "testSkipNumber": 123.23, "testBool": true, "testSub": { @@ -210,7 +215,7 @@ 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 460`, 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 531`, 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, 1, result.Test3, "result.test3 is not expected value") diff --git a/decode_stream_test.go b/decode_stream_test.go @@ -369,6 +369,19 @@ func TestStreamDecodingDeadline(t *testing.T) { assert.Equal(t, now.String(), dec.deadline.String(), "dec.now and now should be equal") } +func TestStreamDecodingDeadlineNotSet(t *testing.T) { + dec := Stream.NewDecoder(&StreamReader{}) + _, isSet := dec.Deadline() + assert.Equal(t, false, isSet, "isSet should be false as deadline is not set") +} + +// this test is only relevant for coverage +func TestStreamDecodingValue(t *testing.T) { + dec := Stream.NewDecoder(&StreamReader{}) + v := dec.Value("") + assert.Nil(t, v, "v should be nil") +} + func TestStreamDecodingErrNotSet(t *testing.T) { dec := Stream.NewDecoder(&StreamReader{}) assert.Nil(t, dec.Err(), "dec.Err should be nim") diff --git a/encode.go b/encode.go @@ -1,5 +1,10 @@ package gojay +import ( + "fmt" + "reflect" +) + // MarshalObject returns the JSON encoding of v. // // It takes a struct implementing Marshaler to a JSON slice of byte @@ -162,6 +167,8 @@ func Marshal(v interface{}) ([]byte, error) { enc := BorrowEncoder() defer enc.Release() return enc.encodeFloat(float64(vt)) + default: + err = InvalidMarshalError(fmt.Sprintf(invalidMarshalErrorMsg, reflect.TypeOf(vt).String())) } return b, err } diff --git a/encode_builder_test.go b/encode_builder_test.go @@ -0,0 +1,16 @@ +package gojay + +import ( + "testing" + "github.com/stretchr/testify/assert" +) + +func TestEncoderBuilderError(t *testing.T) { + enc := NewEncoder() + defer func() { + err := recover() + assert.NotNil(t, err, "err is not nil as we pass an invalid number to grow") + }() + enc.grow(-1) + assert.True(t, false, "should not be called") +} +\ No newline at end of file diff --git a/encode_interface.go b/encode_interface.go @@ -85,37 +85,37 @@ func (enc *Encoder) AddInterface(value interface{}) error { // AddInterfaceKey adds an interface{} to be encoded, must be used inside an object as it will encode a key func (enc *Encoder) AddInterfaceKey(key string, value interface{}) error { - switch value.(type) { + switch vt := value.(type) { case string: - return enc.AddStringKey(key, value.(string)) + return enc.AddStringKey(key, vt) case bool: - return enc.AddBoolKey(key, value.(bool)) + return enc.AddBoolKey(key, vt) case MarshalerArray: return enc.AddArrayKey(key, value.(MarshalerArray)) case MarshalerObject: return enc.AddObjectKey(key, value.(MarshalerObject)) case int: - return enc.AddIntKey(key, value.(int)) + return enc.AddIntKey(key, vt) case int64: - return enc.AddIntKey(key, int(value.(int64))) + return enc.AddIntKey(key, int(vt)) case int32: - return enc.AddIntKey(key, int(value.(int32))) + return enc.AddIntKey(key, int(vt)) case int16: - return enc.AddIntKey(key, int(value.(int16))) + return enc.AddIntKey(key, int(vt)) case int8: - return enc.AddIntKey(key, int(value.(int8))) + return enc.AddIntKey(key, int(vt)) case uint64: - return enc.AddIntKey(key, int(value.(uint64))) + return enc.AddIntKey(key, int(vt)) case uint32: - return enc.AddIntKey(key, int(value.(uint32))) + return enc.AddIntKey(key, int(vt)) case uint16: - return enc.AddIntKey(key, int(value.(uint16))) + return enc.AddIntKey(key, int(vt)) case uint8: - return enc.AddIntKey(key, int(value.(uint8))) + return enc.AddIntKey(key, int(vt)) case float64: - return enc.AddFloatKey(key, value.(float64)) + return enc.AddFloatKey(key, vt) case float32: - return enc.AddFloat32Key(key, value.(float32)) + return enc.AddFloat32Key(key, vt) } return nil diff --git a/encode_interface_test.go b/encode_interface_test.go @@ -1,6 +1,8 @@ package gojay import ( + "fmt" + "reflect" "testing" "github.com/stretchr/testify/assert" @@ -11,6 +13,13 @@ var encoderTestCases = []struct { expectations func(t *testing.T, b []byte, err error) }{ { + v: 100, + expectations: func(t *testing.T, b []byte, err error) { + assert.Nil(t, err, "err should be nil") + assert.Equal(t, "100", string(b), "string(b) should equal 100") + }, + }, + { v: int64(100), expectations: func(t *testing.T, b []byte, err error) { assert.Nil(t, err, "err should be nil") @@ -46,6 +55,13 @@ var encoderTestCases = []struct { }, }, { + v: uint16(100), + expectations: func(t *testing.T, b []byte, err error) { + assert.Nil(t, err, "err should be nil") + assert.Equal(t, "100", string(b), "string(b) should equal 100") + }, + }, + { v: uint8(100), expectations: func(t *testing.T, b []byte, err error) { assert.Nil(t, err, "err should be nil") @@ -94,6 +110,14 @@ var encoderTestCases = []struct { assert.Equal(t, `{"testStr":"漢字","testInt":1,"testInt64":1,"testInt32":1,"testInt16":1,"testInt8":1,"testUint64":1,"testUint32":1,"testUint16":1,"testUint8":1,"testFloat64":1.1,"testFloat32":1.1,"testBool":true}`, string(b), `string(b) should equal {"testStr":"漢字","testInt":1,"testInt64":1,"testInt32":1,"testInt16":1,"testInt8":1,"testUint64":1,"testUint32":1,"testUint16":1,"testUint8":1,"testFloat64":1.1,"testFloat32":1.1,"testBool":true}`) }, }, + { + v: &struct{}{}, + expectations: func(t *testing.T, b []byte, err error) { + assert.NotNil(t, err, "err should be nil") + assert.IsType(t, InvalidMarshalError(""), err, "err should be of type InvalidMarshalError") + assert.Equal(t, fmt.Sprintf(invalidMarshalErrorMsg, reflect.TypeOf(&struct{}{}).String()), err.Error(), "err message should be equal to invalidMarshalErrorMsg") + }, + }, } func TestEncoderInterfaceAllTypesDecoderAPI(t *testing.T) { diff --git a/encode_object_test.go b/encode_object_test.go @@ -74,11 +74,11 @@ func (t *TestEncoding) MarshalObject(enc *Encoder) { enc.AddStringKey("test2", t.test2) enc.AddIntKey("testInt", t.testInt) enc.AddBoolKey("testBool", t.testBool) - enc.AddArrayKey("testArr", t.testArr) + enc.AddInterfaceKey("testArr", t.testArr) enc.AddInterfaceKey("testF64", t.testF64) enc.AddInterfaceKey("testF32", t.testF32) enc.AddInterfaceKey("testInterface", t.testInterface) - enc.AddObjectKey("sub", t.sub) + enc.AddInterfaceKey("sub", t.sub) } type SubObject struct {