gojay

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

commit c8f9802a10eda2a3ec8c968008044d917537a36c
parent b6aae198173375dd983ada5cbc8d54d37de45eaa
Author: francoispqt <francois@parquet.ninja>
Date:   Tue,  1 May 2018 20:45:16 +0800

updated readme and simplify encode stream

Diffstat:
MREADME.md | 79++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mencode_stream.go | 13++++++-------
Mencode_stream_test.go | 23++++++++++-------------
3 files changed, 94 insertions(+), 21 deletions(-)

diff --git a/README.md b/README.md @@ -28,6 +28,10 @@ This is how GoJay aims to be a very fast, JIT stream parser with 0 reflection, l go get github.com/francoispqt/gojay ``` +* [Encoder](#encoding) +* [Decoder](#decoding) + + ## Decoding Decoding is done through two different API similar to standard `encoding/json`: @@ -322,7 +326,10 @@ func (u *user) IsNil() bool { func main() { u := &user{1, "gojay", "gojay@email.com"} - b, _ := gojay.MarshalObject(u) + b, err := gojay.MarshalObject(u) + if err != nil { + log.Fatal(err) + } fmt.Println(string(b)) // {"id":1,"name":"gojay","email":"gojay@email.com"} } ``` @@ -504,6 +511,76 @@ func main() { } ``` +### 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. + +When using the Stream API, the Encoder implements context.Context to provide graceful cancellation. + +To encode a stream of data, you must call `EncodeStream` and pass it a `MarshalerStream` implementation. + +```go +type MarshalerStream interface { + MarshalStream(enc *StreamEncoder) +} +``` + +Example of implementation of stream writing to stdout: +```go +// Our structure which will be pushed to our stream +type user struct { + id int + name string + email string +} + +func (u *user) MarshalObject(enc *gojay.Encoder) { + enc.AddIntKey("id", u.id) + enc.AddStringKey("name", u.name) + enc.AddStringKey("id", u.email) +} +func (u *user) IsNil() bool { + return u == nil +} + +// Our MarshalerStream implementation +type StreamChan chan *user + +func (s StreamChan) MarshalStream(enc *gojay.StreamEncoder) { + select { + case <-enc.Done(): + return + case o := <-s: + enc.AddObject(o) + } +} + +// Our main function +func main() { + // 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() + // instantiate our MarshalerStream + s := StreamChan(make(chan *user)) + // start the stream encoder + // will block its goroutine until enc.Cancel(error) is called + // or until something is written to then channel + go enc.EncodeStream(s) + // write to our MarshalerStream + for i := 0; i < 1000; i++ { + s<-&user{i,"username","user@email.com"} + } + // Wait + select { + case <-enc.Done(): + } +} +``` + # Unsafe API 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. diff --git a/encode_stream.go b/encode_stream.go @@ -5,7 +5,7 @@ import "strconv" // MarshalerStream is the interface to implement // to continuously encode of stream of data. type MarshalerStream interface { - MarshalStream(enc *StreamEncoder) error + MarshalStream(enc *StreamEncoder) } // A StreamEncoder reads and encodes values to JSON from an input stream. @@ -105,15 +105,14 @@ func (s *StreamEncoder) Cancel(err error) { // AddObject adds an object to be encoded, must be used inside a slice or array encoding (does not encode a key) // value must implement MarshalerObject -func (s *StreamEncoder) AddObject(v MarshalerObject) error { +func (s *StreamEncoder) AddObject(v MarshalerObject) { if v.IsNil() { - return nil + return } s.Encoder.writeByte('{') v.MarshalObject(s.Encoder) s.Encoder.writeByte('}') s.Encoder.writeByte(s.delimiter) - return nil } // AddInt adds an int to be encoded, must be used inside a slice or array encoding (does not encode a key) @@ -137,9 +136,9 @@ func consume(init *StreamEncoder, s *StreamEncoder, m MarshalerStream) { case <-init.Done(): return default: - err := m.MarshalStream(s) - if err != nil { - init.Cancel(err) + m.MarshalStream(s) + if s.Encoder.err != nil { + init.Cancel(s.Encoder.err) return } i, err := s.Encoder.write() diff --git a/encode_stream_test.go b/encode_stream_test.go @@ -1,7 +1,6 @@ package gojay import ( - "errors" "sync" "testing" @@ -10,47 +9,45 @@ import ( type StreamChanObject chan *testObject -func (s StreamChanObject) MarshalStream(enc *StreamEncoder) error { +func (s StreamChanObject) MarshalStream(enc *StreamEncoder) { select { case <-enc.Done(): - return enc.Err() + return case o := <-s: - return enc.AddObject(o) + enc.AddObject(o) } } type StreamChanInt chan int -func (s StreamChanInt) MarshalStream(enc *StreamEncoder) error { +func (s StreamChanInt) MarshalStream(enc *StreamEncoder) { select { case <-enc.Done(): - return enc.Err() + return case o := <-s: enc.AddInt(o) - return nil } } type StreamChanFloat chan float64 -func (s StreamChanFloat) MarshalStream(enc *StreamEncoder) error { +func (s StreamChanFloat) MarshalStream(enc *StreamEncoder) { select { case <-enc.Done(): - return enc.Err() + return case o := <-s: enc.AddFloat(o) - return nil } } type StreamChanError chan *testObject -func (s StreamChanError) MarshalStream(enc *StreamEncoder) error { +func (s StreamChanError) MarshalStream(enc *StreamEncoder) { select { case <-enc.Done(): - return enc.Err() + return case <-s: - return errors.New("Test Error") + enc.AddInterface(struct{}{}) } }