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 stringjson:"weather"
Temperature intjson:"temperature"
Celsius booljson:"celsius"
Date stringjson:"date"
TempForecast []intjson:"temp_forecast"
Wind struct { Direction stringjson:"direction"
Speed intjson:"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 stringjson:"weather"
Temperature intjson:"temperature"
Celsius booljson:"celsius"
Date stringjson:"date"
TempForecast []intjson:"temp_forecast"
Wind Windjson:"wind"
} type Wind struct { Direction stringjson:"direction"
Speed intjson:"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 intjson:"myName,omitempty"
// Field appears in JSON as key "Field" (the default), but // the field is skipped if empty. // Note the leading comma. Field intjson:",omitempty"
// Field is ignored by this package. Field intjson:"-"
// Field appears in JSON as key "-". Field intjson:"-,"
// The "string" option signals that a field is stored as JSON inside a JSON-encoded string. Int64String int64json:",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 intjson:"speed"
} type Weather struct { Location stringjson:"location"
Weather stringjson:"weather"
Temperature intjson:"temperature"
Celsius booljson:"celsius"
Date Datejson:"date"
TempForecast []intjson:"temp_forecast"
Wind Windjson:"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.