GoJay is a performant JSON encoder/decoder for Golang (currently the most performant, see benchmarks).
It has a simple API and doesn't use reflection. It relies on small interfaces to decode/encode structures and slices.
Gojay also comes with powerful stream decoding features.
go get github.com/francoispqt/gojay
Example of basic stucture decoding:
import "github.com/francoispqt/gojay"
type user struct {
id int
name string
email string
}
// implement UnmarshalerObject
func (u *user) UnmarshalObject(dec *gojay.Decoder, key string) error {
switch k {
case "id":
return dec.AddInt(&u.id)
case "name":
return dec.AddString(&u.name)
case "email":
return dec.AddString(&u.email)
}
return nil
}
func (u *user) NKeys() int {
return 3
}
func main() {
u := &user{}
d := []byte(`{"id":1,"name":"gojay","email":"[email protected]"}`)
err := gojay.UnmarshalObject(d, user)
if err != nil {
log.Fatal(err)
}
}
To unmarshal a JSON object to a structure, the structure must implement the UnmarshalerObject interface:
type UnmarshalerObject interface {
UnmarshalObject(*Decoder, string) error
NKeys() int
}
UnmarshalObject method takes two arguments, the first one is a pointer to the Decoder (*gojay.Decoder) and the second one is the string value of the current key being parsed. If the JSON data is not an object, the UnmarshalObject method will never be called.
NKeys method must return the number of keys to Unmarshal in the JSON object.
Example of implementation:
type user struct {
id int
name string
email string
}
// implement UnmarshalerObject
func (u *user) UnmarshalObject(dec *gojay.Decoder, key string) error {
switch k {
case "id":
return dec.AddInt(&u.id)
case "name":
return dec.AddString(&u.name)
case "email":
return dec.AddString(&u.email)
}
return nil
}
func (u *user) NKeys() int {
return 3
}
To unmarshal a JSON object to a slice an array or a channel, it must implement the UnmarshalerArray interface:
type UnmarshalerArray interface {
UnmarshalArray(*Decoder) error
}
UnmarshalArray method takes one argument, a pointer to the Decoder (*gojay.Decoder). If the JSON data is not an array, the Unmarshal method will never be called.
Example of implementation with a slice:
type testSlice []string
// implement UnmarshalerArray
func (t *testStringArr) UnmarshalArray(dec *gojay.Decoder) error {
str := ""
if err := dec.AddString(&str); err != nil {
return err
}
*t = append(*t, str)
return nil
}
Example of implementation with a channel:
type ChannelString chan string
// implement UnmarshalerArray
func (c *ChannelArray) UnmarshalArray(dec *gojay.Decoder) error {
str := ""
if err := dec.AddString(&str); err != nil {
return err
}
*c <- str
return nil
}
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:
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")
}
}
}
To decode other types (string, int, int32, int64, uint32, uint64, float, booleans), you don't need to implement any interface.
Example of encoding strings:
func main() {
json := []byte(`"Jay"`)
var v string
err := Unmarshal(json, &v)
if err != nil {
log.Fatal(err)
}
fmt.Println(v) // Jay
}
Example of basic structure encoding:
import "github.com/francoispqt/gojay"
type user struct {
id int
name string
email string
}
// implement MarshalerObject
func (u *user) MarshalObject(dec *gojay.Decoder, key string) {
dec.AddIntKey("id", u.id)
dec.AddStringKey("name", u.name)
dec.AddStringKey("email", u.email)
}
func (u *user) IsNil() bool {
return u == nil
}
func main() {
u := &user{1, "gojay", "[email protected]"}
b, _ := gojay.MarshalObject(user)
fmt.Println(string(b)) // {"id":1,"name":"gojay","email":"[email protected]"}
}
To encode a structure, the structure must implement the MarshalerObject interface:
type MarshalerObject interface {
MarshalObject(enc *Encoder)
IsNil() bool
}
MarshalObject method takes one argument, a pointer to the Encoder (*gojay.Encoder). The method must add all the keys in the JSON Object by calling Decoder's methods.
IsNil method returns a boolean indicating if the interface underlying value is nil or not. It is used to safely ensure that the underlying value is not nil without using Reflection.
Example of implementation:
type user struct {
id int
name string
email string
}
// implement MarshalerObject
func (u *user) MarshalObject(dec *gojay.Decoder, key string) {
dec.AddIntKey("id", u.id)
dec.AddStringKey("name", u.name)
dec.AddStringKey("email", u.email)
}
func (u *user) IsNil() bool {
return u == nil
}
To encode an array or a slice, the slice/array must implement the MarshalerArray interface:
type MarshalerArray interface {
MarshalArray(enc *Encoder)
}
MarshalArray method takes one argument, a pointer to the Encoder (*gojay.Encoder). The method must add all element in the JSON Array by calling Decoder's methods.
Example of implementation:
type users []*user
// implement MarshalerArray
func (u *users) MarshalArray(dec *Decoder) error {
for _, e := range u {
err := enc.AddObject(e)
if err != nil {
return err
}
}
return nil
}
To encode other types (string, int, float, booleans), you don't need to implement any interface.
Example of encoding strings:
func main() {
name := "Jay"
b, err := gojay.Marshal(&name)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b)) // "Jay"
}
Benchmarks encode and decode three different data based on size (small, medium, large).
To run benchmark for decoder:
cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/decoder && make bench
To run benchmark for encoder:
cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/encoder && make bench
ns/op | bytes/op | allocs/op | |
---|---|---|---|
Std Library | 4661 | 496 | 12 |
JsonParser | 1313 | 0 | 0 |
JsonIter | 899 | 192 | 5 |
GoJay | 662 | 112 | 1 |
ns/op | bytes/op | allocs/op | |
---|---|---|---|
Std Library | 30148 | 2152 | 496 |
JsonParser | 7793 | 0 | 0 |
JsonIter | 5967 | 496 | 44 |
GoJay | 3914 | 128 | 12 |
ns/op | bytes/op | allocs/op | |
---|---|---|---|
JsonParser | 66813 | 0 | 0 |
JsonIter | 87994 | 6738 | 329 |
GoJay | 43402 | 1408 | 76 |
ns/op | bytes/op | allocs/op | |
---|---|---|---|
Std Library | 1280 | 464 | 3 |
JsonIter | 866 | 272 | 3 |
GoJay | 484 | 320 | 2 |
ns/op | bytes/op | allocs/op | |
---|---|---|---|
Std Library | 3325 | 1496 | 18 |
JsonIter | 1939 | 648 | 16 |
GoJay | 1196 | 936 | 16 |
ns/op | bytes/op | allocs/op | |
---|---|---|---|
Std Library | 51317 | 28704 | 326 |
JsonIter | 35247 | 14608 | 320 |
GoJay | 27847 | 27888 | 326 |
Contributions are welcome :)
If you encounter issues please report it in Github and/or send an email at [email protected]