Blog

Go – JSON Encoder

30 Nov, 2018
Xebia Background Header Wave

In my last blog, Learning Go, we learned the Go programming language. Go is a modern systems programming language that is very easy to learn, runs natively on your operating system and does not need a runtime in order to operate. This makes Go very fast an memory efficient. Go has a module system that makes it easy to split, modularize and version code bases. Go supports rapid web development and has cloud support by means of the AWS SDK for Go. In this blog we are going to take a closer look at the JSON encoder that comes with Go.

Package json

Package json provides functions for encoding and decoding of JSON to Go values. The package provides the functions Marshal and Unmarshal.

Creating Structs

The json package works with Go structs. To generate Go structs from a JSON data payload we can use the online JSON-to-Go. When you enter your JSON payload, it will return the following:

Input:

{
  "location": "Amsterdam",
  "weather": "sunny",
  "temperature": 22,
  "celsius": true,
  "date": "2018-06-22T15:04:05Z",
  "temp_forecast": [25, 26, 24, 20, 21, 22],
  "wind": {
    "direction": "SE",
    "speed": 15
  }
}

Output:

type AutoGenerated struct {
    Location     string <code>json:"location"
    Weather      string json:"weather"
    Temperature  int    json:"temperature"
    Celsius      bool   json:"celsius"
    Date         string json:"date"
    TempForecast []int  json:"temp_forecast"
    Wind         struct {
        Direction string json:"direction"
        Speed     int    json:"speed"
    } json:"wind"
}

With some copy-and-pasting we can clean it up a bit. The website saves us a lot of typing:

type Weather struct {
    Location     string <code>json:"location"
    Weather      string json:"weather"
    Temperature  int    json:"temperature"
    Celsius      bool   json:"celsius"
    Date         string json:"date"
    TempForecast []int  json:"temp_forecast"
    Wind         Wind   json:"wind"
}
type Wind struct {
    Direction string json:"direction"
    Speed     int    json:"speed"
}

Marshal Function

To marshal ie. serialize a Go struct to JSON, we call the Marshal function with a copy of the struct. The function returns the JSON encoding of the struct as a byte array and an optional error.

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
)

// serialize to json
bytes, _ := json.Marshal(GetWeatherData())
fmt.Println(string(bytes))

Unmarshal function

To unmarshal ie. deserialize JSON to a Go struct, we call the Unmarshal function. We must prepare a struct first, and pass a pointer to the struct to the unmarshal function. The function will then update (mutate) the struct and return any errors as a result value.

// read JSON from a file
data, _ := ioutil.ReadFile("weather.json") 
// create a struct
weather := Weather{}
// pass the struct pointer to the unmarshal function
json.Unmarshal(data, &weather)
fmt.Print(weather)

Json Encoder Configuration

The encoding of struct fields can be customized by the format string stored under the “json” key in the struct field’s tag. In the example, the struct tags is the String that follows the struct fields eg. json:location. The struct fields tags a The format string gives the name of the field, possibly followed by a comma-separated list of options. The following options are available:

// Field appears in JSON as key "myName".
Field int <code>json:"myName"

// Field appears in JSON as key "myName" and
// the field is omitted from the object if its value is empty,
// as defined above.
Field int json:"myName,omitempty"

// Field appears in JSON as key "Field" (the default), but
// the field is skipped if empty.
// Note the leading comma.
Field int json:",omitempty"

// Field is ignored by this package.
Field int json:"-"

// Field appears in JSON as key "-".
Field int json:"-,"

// The "string" option signals that a field is stored as JSON inside a JSON-encoded string.
Int64String int64 json:",string"

Custom Types

Lets say we want to have the date parsed as a custom type. We are going to introduce a new struct called Date that will be serialized and deserialized from a field with the field name date:

type Date struct {
    value time.Time
}

We also need to update the Weather struct:

type Wind struct {
    Direction string <code>json:"direction"
    Speed     int    json:"speed"
}

type Weather struct {
    Location     string json:"location"
    Weather      string json:"weather"
    Temperature  int    json:"temperature"
    Celsius      bool   json:"celsius"
    Date         Date    json:"date"
    TempForecast []int  json:"temp_forecast"
    Wind          Wind  json:"wind"
}

We will create two new functions that will fit the interface of the JSON Encoder of Go. The encoder functions extend our Date struct. In effect this system works as type classes for the type Date.

func (w *Date) UnmarshalJSON(b []byte) error {
    x := string("")
    e := json.Unmarshal(b, &x)
    if e != nil {
        return e
    }
    t, err := time.Parse(time.RFC3339, x)
    if err != nil {
        return err
    }
    w.value = t
    return nil
}

func (w Date) MarshalJSON() ([]byte, error) {
    return json.Marshal(w.value.Format(time.RFC3339))
}

Go has enough information to serialize and deserialize the whole weather struct. We have added the necessary transformation functions for the Date field.

Conclusion

It is easy to serialize and deserialize JSON data structures to and from Go structs. The JSON encoder is configurable, and with tags we can configure the encoder. The json package supports creating new serializers and deserializers for custom types. We have created a custom serializer and deserializer for the type Date by implementing just two functions.

Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts