How to use JSON with Go [best practices]

yourbasic.org/golang

The JSON data-interchange format is easy for humans to read and write, and efficient for machines to parse and generate.

Default types

The default Go types for decoding and encoding JSON are

Additionally, time.Time and the numeric types in the math/big package can be automatically encoded as JSON strings.

Note that JSON doesn’t support basic integer types. They can often be approximated by floating-point numbers.

Since software that implements IEEE 754-2008 binary64 (double precision) numbers is generally available and widely used, good interoperability can be achieved by implementations that expect no more precision or range than these provide […]

Note that when such software is used, numbers that are integers and are in the range [-253 + 1, 253 - 1] are interoperable in the sense that implementations will agree exactly on their numeric values. RFC 7159: The JSON Data Interchange Format

Encode (marshal) struct to JSON

The json.Marshal function in package encoding/json generates JSON data.

type FruitBasket struct {
	Name    string
	Fruit   []string
	Id      int64  `json:"ref"`
	private string // An unexported field is not encoded.
	Created time.Time
}

basket := FruitBasket{
	Name:    "Standard",
	Fruit:   []string{"Apple", "Banana", "Orange"},
	Id:      999,
	private: "Second-rate",
	Created: time.Now(),
}

var jsonData []byte
jsonData, err := json.Marshal(basket)
if err != nil {
	log.Println(err)
}
fmt.Println(string(jsonData))

Output:

{"Name":"Standard","Fruit":["Apple","Banana","Orange"],"ref":999,"Created":"2018-04-09T23:00:00Z"}

Only data that can be represented as JSON will be encoded; see json.Marshal for the complete rules.

Pretty print

Replace json.Marshal with json.MarshalIndent in the example above to indent the JSON output.

jsonData, err := json.MarshalIndent(basket, "", "    ")

Output:

{
    "Name": "Standard",
    "Fruit": [
        "Apple",
        "Banana",
        "Orange"
    ],
    "ref": 999,
    "Created": "2018-04-09T23:00:00Z"
}

Decode (unmarshal) JSON to struct

The json.Unmarshal function in package encoding/json parses JSON data.

type FruitBasket struct {
	Name    string
	Fruit   []string
	Id      int64 `json:"ref"`
	Created time.Time
}

jsonData := []byte(`
{
    "Name": "Standard",
    "Fruit": [
        "Apple",
        "Banana",
        "Orange"
    ],
    "ref": 999,
    "Created": "2018-04-09T23:00:00Z"
}`)

var basket FruitBasket
err := json.Unmarshal(jsonData, &basket)
if err != nil {
	log.Println(err)
}
fmt.Println(basket.Name, basket.Fruit, basket.Id)
fmt.Println(basket.Created)

Output:

Standard [Apple Banana Orange] 999
2018-04-09 23:00:00 +0000 UTC

Note that Unmarshal allocated a new slice all by itself. This is how unmarshaling works for slices, maps and pointers.

For a given JSON key Foo, Unmarshal will attempt to match the struct fields in this order:

  1. an exported (public) field with a struct tag json:"Foo",
  2. an exported field named Foo, or
  3. an exported field named FOO, FoO, or some other case-insensitive match.

Only fields thar are found in the destination type will be decoded:

Arbitrary objects and arrays

The encoding/json package uses

It will unmarshal any valid JSON data into a plain interface{} value.

Consider this JSON data:

{
    "Name": "Eve",
    "Age": 6,
    "Parents": [
        "Alice",
        "Bob"
    ]
}

The json.Unmarshal function will parse it into a map whose keys are strings, and whose values are themselves stored as empty interface values:

map[string]interface{}{
	"Name": "Eve",
	"Age":  6.0,
	"Parents": []interface{}{
		"Alice",
		"Bob",
	},
}

We can iterate through the map with a range statement and use a type switch to access its values.

jsonData := []byte(`{"Name":"Eve","Age":6,"Parents":["Alice","Bob"]}`)

var v interface{}
json.Unmarshal(jsonData, &v)
data := v.(map[string]interface{})

for k, v := range data {
	switch v := v.(type) {
	case string:
		fmt.Println(k, v, "(string)")
	case float64:
		fmt.Println(k, v, "(float64)")
	case []interface{}:
		fmt.Println(k, "(array):")
		for i, u := range v {
			fmt.Println("    ", i, u)
		}
	default:
		fmt.Println(k, v, "(unknown)")
	}
}

Output:

Name Eve (string)
Age 6 (float64)
Parents (array):
     0 Alice
     1 Bob

JSON file example

The json.Decoder and json.Encoder types in package encoding/json offer support for reading and writing streams, e.g. files, of JSON data.

The code in this example

const jsonData = `
	{"Name": "Alice", "Age": 25}
	{"Name": "Bob", "Age": 22}
`
reader := strings.NewReader(jsonData)
writer := os.Stdout

dec := json.NewDecoder(reader)
enc := json.NewEncoder(writer)

for {
	// Read one JSON object and store it in a map.
	var m map[string]interface{}
	if err := dec.Decode(&m); err == io.EOF {
		break
	} else if err != nil {
		log.Fatal(err)
	}

	// Remove all key-value pairs with key == "Age" from the map.
	for k := range m {
		if k == "Age" {
			delete(m, k)
		}
	}

	// Write the map as a JSON object.
	if err := enc.Encode(&m); err != nil {
		log.Println(err)
	}
}

Output:

{"Name":"Alice"}
{"Name":"Bob"}

Further reading

Tutorials for beginners and experienced developers alike: best practices and production-quality code examples.

Share this page: