Jonfk

Go and JSON, various useful libraries and utilities

12 Aug 2016

JSON is probably one of the most popular data interchange format on the web. Go with its extensive standard library has good support for JSON. If your requirements for JSON serialization and deserialization are not specific, I highly recommend to simply start by using "encoding/json" from the standard library and reading how it works (blog, docs, examples). Here I will give a few libraries for JSON in Go and explain what differentiates them from “encoding/json” and each other.

First, why would we want to use anything other than "encoding/json"? Well to provide flexibility and simplicity in its usage, "encoding/json" makes use of reflection on the structs that defines the structure of the JSON you are expecting to serialize or deserialize and because of that it incurs a heavy performance penalty.

What if we didn’t have to reflect on a struct and simply insert data to a map[string]interface{} object? Well "encoding/json" allows you to do that but the resulting data structure ends up frustrating to work with, with casting all over the place and is hard to navigate fluently.

Structured Data

To avoid reflection, these libraries provide a code generator to be run over structs defining the structure of the JSON and fields in the same format as "encoding/json". The generated code is now the parsing code for the JSON structure.

Both ffjson and easyjson provide a drop in replacement for "encoding/json" which means that after generating code for parsing, you should be able to use the same api calls as before but with ffjson or easyjson.

The advantage of this is that you now have specific parsing code for the JSON structure you are expecting. This also gives a nice speed up in the order of about 2 to 3 times faster than encoding/json.

The disadvantage is that you will need to run the code generator every time you change the struct defining the JSON structure. There is a simple fix for this by using the go generate command and //go:generate ffjson $GOFILE at the top of any files with the structs.

easyjson works very similarly to ffjson and claims to be more performant and behave better when run concurrently.

Deserializing Unstructured Data

jsonparser and gjson like ffjson and easyjson do not rely on reflection but what differentiates them from the structured JSON libraries is that instead of creating structs that defines the structure of the JSON to be parsed these libraries read the value at a specific path you want.

By only reading values at paths specified, you only need to parse the part of the data you are interested in. No need to parse the whole json eagerly. Users of lazy functional languages will recognize here the value of lazy reading. This allows these libraries to read even faster than ffjson and easyjson.

By not having to deserialize to a struct, these libraries can read directly from the byte buffer which allows values to be read much faster than other deserialization libraries.

Another advantage over "encoding/json", is that the api is much more ergonomic than unmarshalling the json into a map[string]interface{} since no casting is necessary and walking to the specific field you want to read is much easier. For example, here is how to read values using "encoding/json" with map[string]interface{} vs gjson.

// "encoding/json"
package main

import (
	"encoding/json"
)

const data = `{"name":{"first":"Janet","last":"Prichard"},"age":47}`

func main() {
	var values map[string]interface{}
	json.Unmarshal([]byte(data), &values)
	value := values["name"].(map[string]interface{})["last"].(string)
	printf(value)
}

// gjson
package main

import "github.com/tidwall/gjson"

const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}`

func main() {
    value := gjson.Get(json, "name.last")
    println(value.String())
}

The disadvantage of these libraries is that they only provide deserialization and not serialization. For fast serialization, you can rely on the structured data serializers.

Utilities and other helpful libraries

Convert JSON to a Go Struct

Instead of doing this job by hand, here are some utilities that can help you:

  • Online: https://mholt.github.io/json-to-go/
  • Locally: https://github.com/ChimeraCoder/gojson

Summary

Useful JSON libraries: For generating encoders and decoders:

For reading unstructured json at a specific path: