gojay

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

commit c59a4b578d512909a38fada3747ff978bc79be28
parent 3d991e95a8197d6bc05e10dff748d8a0464784e9
Author: francoispqt <francois@parquet.ninja>
Date:   Fri, 27 Apr 2018 00:03:41 +0800

make unmarshal api safer by copying initial buffer and adding an Unsafe api

Diffstat:
Mbenchmarks/decoder/decoder_bench_large_test.go | 8++++++++
Mbenchmarks/decoder/decoder_bench_medium_test.go | 10++++++++++
Mbenchmarks/decoder/decoder_bench_small_test.go | 8++++++++
Mdecode.go | 12++++++++----
Adecode_unsafe.go | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 142 insertions(+), 4 deletions(-)

diff --git a/benchmarks/decoder/decoder_bench_large_test.go b/benchmarks/decoder/decoder_bench_large_test.go @@ -49,3 +49,11 @@ func BenchmarkGoJayDecodeObjLarge(b *testing.B) { gojay.UnmarshalObject(benchmarks.LargeFixture, &result) } } + +func BenchmarkGoJayUnsafeDecodeObjLarge(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + result := benchmarks.LargePayload{} + gojay.Unsafe.UnmarshalObject(benchmarks.LargeFixture, &result) + } +} diff --git a/benchmarks/decoder/decoder_bench_medium_test.go b/benchmarks/decoder/decoder_bench_medium_test.go @@ -60,3 +60,13 @@ func BenchmarkGoJayDecodeObjMedium(b *testing.B) { } } } +func BenchmarkGoJayUnsafeDecodeObjMedium(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + result := benchmarks.MediumPayload{} + err := gojay.Unsafe.UnmarshalObject(benchmarks.MediumFixture, &result) + if err != nil { + b.Error(err) + } + } +} diff --git a/benchmarks/decoder/decoder_bench_small_test.go b/benchmarks/decoder/decoder_bench_small_test.go @@ -59,3 +59,11 @@ func BenchmarkGoJayDecodeObjSmall(b *testing.B) { gojay.UnmarshalObject(benchmarks.SmallFixture, &result) } } + +func BenchmarkGoJayUnsafeDecodeObjSmall(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + result := benchmarks.SmallPayload{} + gojay.Unsafe.UnmarshalObject(benchmarks.SmallFixture, &result) + } +} diff --git a/decode.go b/decode.go @@ -14,7 +14,8 @@ import ( // overflows the target type, UnmarshalArray skips that field and completes the unmarshaling as best it can. func UnmarshalArray(data []byte, v UnmarshalerArray) error { dec := newDecoder(nil, 0) - dec.data = data + dec.data = make([]byte, len(data)) + copy(dec.data, data) dec.length = len(data) _, err := dec.DecodeArray(v) dec.addToPool() @@ -35,7 +36,8 @@ func UnmarshalArray(data []byte, v UnmarshalerArray) error { // overflows the target type, UnmarshalObject skips that field and completes the unmarshaling as best it can. func UnmarshalObject(data []byte, v UnmarshalerObject) error { dec := newDecoder(nil, 0) - dec.data = data + dec.data = make([]byte, len(data)) + copy(dec.data, data) dec.length = len(data) _, err := dec.DecodeObject(v) dec.addToPool() @@ -113,12 +115,14 @@ func Unmarshal(data []byte, v interface{}) error { case UnmarshalerObject: dec = newDecoder(nil, 0) dec.length = len(data) - dec.data = data + dec.data = make([]byte, len(data)) + copy(dec.data, data) _, err = dec.DecodeObject(vt) case UnmarshalerArray: dec = newDecoder(nil, 0) dec.length = len(data) - dec.data = data + dec.data = make([]byte, len(data)) + copy(dec.data, data) _, err = dec.DecodeArray(vt) default: return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, reflect.TypeOf(vt).String())) diff --git a/decode_unsafe.go b/decode_unsafe.go @@ -0,0 +1,108 @@ +package gojay + +import ( + "fmt" + "reflect" +) + +// Unsafe is the structure holding the unsafe version of the API. +// The difference between unsafe api and regular api is that the regular API +// copies the buffer passed to Unmarshal functions to a new internal buffer. +// Making it safer because internally GoJay uses unsafe.Pointer to transform slice of bytes into a string. +var Unsafe = decUnsafe{} + +type decUnsafe struct{} + +func (u decUnsafe) UnmarshalArray(data []byte, v UnmarshalerArray) error { + dec := newDecoder(nil, 0) + dec.data = data + dec.length = len(data) + _, err := dec.DecodeArray(v) + dec.addToPool() + if err != nil { + return err + } + if dec.err != nil { + return dec.err + } + return nil +} + +func (u decUnsafe) UnmarshalObject(data []byte, v UnmarshalerObject) error { + dec := newDecoder(nil, 0) + dec.data = data + dec.length = len(data) + _, err := dec.DecodeObject(v) + dec.addToPool() + if err != nil { + return err + } + if dec.err != nil { + return dec.err + } + return nil +} + +func (u decUnsafe) Unmarshal(data []byte, v interface{}) error { + var err error + var dec *Decoder + switch vt := v.(type) { + case *string: + dec = newDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.DecodeString(vt) + case *int: + dec = newDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.DecodeInt(vt) + case *int32: + dec = newDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.DecodeInt32(vt) + case *uint32: + dec = newDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.DecodeUint32(vt) + case *int64: + dec = newDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.DecodeInt64(vt) + case *uint64: + dec = newDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.DecodeUint64(vt) + case *float64: + dec = newDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.DecodeFloat64(vt) + case *bool: + dec = newDecoder(nil, 0) + dec.length = len(data) + dec.data = data + err = dec.DecodeBool(vt) + case UnmarshalerObject: + dec = newDecoder(nil, 0) + dec.length = len(data) + dec.data = data + _, err = dec.DecodeObject(vt) + case UnmarshalerArray: + dec = newDecoder(nil, 0) + dec.length = len(data) + dec.data = data + _, err = dec.DecodeArray(vt) + default: + return InvalidUnmarshalError(fmt.Sprintf(invalidUnmarshalErrorMsg, reflect.TypeOf(vt).String())) + } + defer dec.addToPool() + if err != nil { + return err + } + return dec.err +}