Home

Awesome

Jingo

This package provides the ability to encode golang structs to a buffer as JSON.

The main take-aways are

Another JSON Library...why?

Performance. Check out these numbers - they were generated with the gojay (which is fast) perf data, SmallPayload and LargePayload respectively.

Numbers were generated on a Centos 7 Machine, Quad-core Intel(R) Core(TM) i5-4590 CPU @ 3.30GHz.

LibIterns/opB/opallocs/op+/-
jingo10000000208004.8x
stdlib encoding/json1000000100816011x
gojay200000060551211.6x
json-iterator200000082516821.2x
LibIterns/opB/opallocs/op+/-
jingo2000009748003x
stdlib encoding/json5000029854486611x
gojay100000168841830851.7x
json-iterator10000021033487321.4x

These results can be even more pronounced depending on the shape of the struct - these results are based on a struct with a lot of string data in:

LibIterns/opB/opallocs/op+/-
jingo100000002120011.5x
stdlib encoding/json500000244372041x
gojay1000000114751212.1x
json-iterator500000260674450.9x

Example Usage

The usage is similar to that of the stdlib json.Marshal, but we do most of our work upon instantiation of the encoders.

The encoders you have available to you are

They both reference each other and they work in exactly the same way. You'll see, like the stdlib encode/json, there is very little wire-up involved.

package main

import (
    "fmt"
    "github.com/bet365/jingo"
)

// sample struct we'll encode
type MyPayload struct {
    Name string `json:"name"`
    Age int     `json:"age"`
    ID int      // anything we don't annotate doesn't get emitted. 
}

// Create an encoder, letting it know which type of struct we're going to be encoding. 
// You only do this once per type!
var enc = jingo.NewStructEncoder(MyPayload{})

func main() {
    // now lets encode something 
    p := MyPayload{
        Name: "Mr Payload",
        Age: 33,
    }

    // pull a buffer from the pool and pass it along with the struct to Marshal
    buf := jingo.NewBufferFromPool()
    enc.Marshal(&p, buf)

    fmt.Println(buf.String()) // {"name":"Mr Payload","age":33}

    // return the buffer to the pool now we're done
    buf.ReturnToPool()
}

Buffer

Buffer is a simple custom buffer type which complies with io.Writer. Its main benefit being it has pooling built-in. This goes a long way to helping make jingo fast by reducing its allocations and ensuring good write speeds.

Options

There are a couple of subtle ways you can configure the encoders.

How does it work

When you create an instance of an encoder it recursively generates an instruction set which defines how to iteratively encode your structs. This gives it the ability to provide a clear API but with the same benefits as a build-time optimized encoder. It's almost exclusively able to do all type assertions and reflection activity during the compile, then makes ample use of the unsafe package during the instruction-set execution (the Marshal call) to make reading and writing very fast.

As part of the instruction set compilation it also generates static meta-data, i.e field names, brackets, braces etc. These are then chunked into instructions on demand.

Drawbacks?

The package is designed to be performant and as such it is not 100% functionally compatible with stdlib. Specifically.

Contribution Guidelines

Contributions are welcome! Fork the repo and submit a pull request to get your change added.

Please take into consideration whether or not the change aligns with the agenda of the project to avoid having them rejected. For example, when adding a new feature, try to make sure you're creating a new instruction/set for the feature being added - don't add logic to existing instructions at the cost of performance for all other code paths currently using them. It's best to have more instructions with no logic than fewer instructions with a few conditionals that execute at runtime.

Feel free to raise an issue here beforehand to discuss anything with others before your implementation.