gojay

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

commit 5dcfb64ffcaabc9f5815a3cd5df8d0d438204ca7
parent 1cd188c35a270d25b3adc37e013059898510cddb
Author: francoispqt <francois@parquet.ninja>
Date:   Tue,  1 May 2018 22:17:04 +0800

add tests and update readme

Diffstat:
MREADME.md | 108++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mdecode_bool_test.go | 2+-
Adecode_pool_test.go | 26++++++++++++++++++++++++++
Mencode_stream_test.go | 17+++++++++++++++++
4 files changed, 110 insertions(+), 43 deletions(-)

diff --git a/README.md b/README.md @@ -30,6 +30,7 @@ go get github.com/francoispqt/gojay * [Encoder](#encoding) * [Decoder](#decoding) +* [Stream API](#stream-api) ## Decoding @@ -244,45 +245,6 @@ func (c ChannelArray) UnmarshalArray(dec *gojay.Decoder) error { } ``` -### Stream Decoding -GoJay ships with a powerful stream decoder. - -It allows to read continuously from an io.Reader stream and do JIT decoding writing unmarshalled JSON to a channel to allow async consuming. - -When using the Stream API, the Decoder implements context.Context to provide graceful cancellation. - -Example: -```go -type ChannelStream chan *TestObj -// implement UnmarshalerStream -func (c ChannelStream) UnmarshalStream(dec *gojay.StreamDecoder) error { - obj := &TestObj{} - if err := dec.AddObject(obj); err != nil { - return err - } - c <- obj - return nil -} - -func main() { - // create our channel which will receive our objects - streamChan := ChannelStream(make(chan *TestObj)) - // get a reader implementing io.Reader - reader := getAnIOReaderStream() - dec := gojay.Stream.NewDecoder(reader) - // start decoding (will block the goroutine until something is written to the ReadWriter) - go dec.DecodeStream(streamChan) - for { - select { - case v := <-streamChan: - // do something with my TestObj - case <-dec.Done(): - os.Exit("finished reading stream") - } - } -} -``` - ### Other types To decode other types (string, int, int32, int64, uint32, uint64, float, booleans), you don't need to implement any interface. @@ -511,8 +473,63 @@ func main() { } ``` -### Stream Encoding +# Stream API + +### Stream Decoding +GoJay ships with a powerful stream decoder. + +It allows to read continuously from an io.Reader stream and do JIT decoding writing unmarshalled JSON to a channel to allow async consuming. + +When using the Stream API, the Decoder implements context.Context to provide graceful cancellation. + +To decode a stream of JSON, you must call `gojay.Stream.DecodeStream` and pass it a `UnmarshalerStream` implementation. + +```go +type UnmarshalerStream interface { + UnmarshalStream(*StreamDecoder) error +} +``` + +Example of implementation of stream reading from a WebSocket connection: +```go +// implement UnmarshalerStream +type ChannelStream chan *TestObj + +func (c ChannelStream) UnmarshalStream(dec *gojay.StreamDecoder) error { + obj := &TestObj{} + if err := dec.AddObject(obj); err != nil { + return err + } + c <- obj + return nil +} + +func main() { + // get our websocket connection + origin := "http://localhost/" + url := "ws://localhost:12345/ws" + ws, err := websocket.Dial(url, "", origin) + if err != nil { + log.Fatal(err) + } + // create our channel which will receive our objects + streamChan := ChannelStream(make(chan *TestObj)) + // get a reader implementing io.Reader + dec := gojay.Stream.NewDecoder(ws) + // start decoding (will block the goroutine until something is written to the ReadWriter) + go dec.DecodeStream(streamChan) + for { + select { + case v := <-streamChan: + // Got something from my websocket! + case <-dec.Done(): + os.Exit("finished reading from WebSocket") + } + } +} +``` +## Stream Encoding GoJay ships with a powerful stream encoder part of the Stream API. It allows to write continuously to an io.Writer and do JIT encoding of data fed to a channel to allow async consuming. You can set multiple consumers on the channel to be as performant as possible. Consumers are non blocking and are scheduled individually in their own go routine. @@ -527,7 +544,7 @@ type MarshalerStream interface { } ``` -Example of implementation of stream writing to stdout: +Example of implementation of stream writing to a WebSocket: ```go // Our structure which will be pushed to our stream type user struct { @@ -559,11 +576,18 @@ func (s StreamChan) MarshalStream(enc *gojay.StreamEncoder) { // Our main function func main() { + // get our websocket connection + origin := "http://localhost/" + url := "ws://localhost:12345/ws" + ws, err := websocket.Dial(url, "", origin) + if err != nil { + log.Fatal(err) + } // we borrow an encoder set stdout as the writer, // set the number of consumer to 10 // and tell the encoder to separate each encoded element // added to the channel by a new line character - enc := gojay.Stream.BorrowEncoder(os.Stdout).NConsumer(10).LineDelimited() + enc := gojay.Stream.BorrowEncoder(ws).NConsumer(10).LineDelimited() // instantiate our MarshalerStream s := StreamChan(make(chan *user)) // start the stream encoder diff --git a/decode_bool_test.go b/decode_bool_test.go @@ -48,7 +48,7 @@ func TestDecoderBoolInvalidJSON(t *testing.T) { } func TestDecoderBoolDecoderAPI(t *testing.T) { var v bool - dec := NewDecoder(strings.NewReader("true")) + dec := BorrowDecoder(strings.NewReader("true")) defer dec.Release() err := dec.DecodeBool(&v) assert.Nil(t, err, "Err must be nil") diff --git a/decode_pool_test.go b/decode_pool_test.go @@ -0,0 +1,26 @@ +package gojay + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecoderBorrowFromPool(t *testing.T) { + // reset pool + decPool = make(chan *Decoder, 16) + // borrow decoder + dec := BorrowDecoder(strings.NewReader("")) + // release + dec.Release() + // get from pool + nDec := BorrowDecoder(strings.NewReader("")) + // assert same + assert.Equal(t, dec, nDec, "both decoders should be the same") +} + +func TestDecoderBorrowFromPoolSetBuffSize(t *testing.T) { + dec := borrowDecoder(nil, 512) + assert.Len(t, dec.data, 512, "data buffer should be of len 512") +} diff --git a/encode_stream_test.go b/encode_stream_test.go @@ -70,6 +70,23 @@ func TestEncodeStreamSingleConsumer(t *testing.T) { } } } +func TestEncodeStreamSingleConsumerNilValue(t *testing.T) { + expectedStr := `` + // create our writer + w := &TestWriter{target: 100, mux: &sync.RWMutex{}} + enc := Stream.NewEncoder(w).LineDelimited() + w.enc = enc + s := StreamChanObject(make(chan *testObject)) + go enc.EncodeStream(s) + go feedStreamNil(s, 100) + select { + case <-enc.Done(): + assert.Nil(t, enc.Err(), "enc.Err() should not be nil") + for _, b := range w.result { + assert.Equal(t, expectedStr, string(b), "every byte buffer should be equal to expected string") + } + } +} func TestEncodeStreamSingleConsumerInt(t *testing.T) { // create our writer w := &TestWriter{target: 100, mux: &sync.RWMutex{}}