Using JSON in Go
Go can serialize JSON from/to structs or maps.
Only exported fields of a struct will be present in the JSON output. Additionally, structs can have tags attached to the fields that provide metadata to use when converting them. These tags can specify the name of the JSON key, to omit fields if they are empty, and to not include fields.
For example this struct:
var item = struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
CreatedAt time.Time `json:"created_at"`
SecretCode string `json:"-"`
}{
ID: "foo",
Name: "",
CreatedAt: time.Now(),
SecretCode: "qux",
}
Would result in the following JSON:
{
"id": "foo",
"created_at": "2009-11-10T23:00:00Z"
}
JSON conversion
Converting a struct into JSON:
func main() {
data := struct {
ID string `json:"id"`
Name string `json:"name"`
}{
ID: "qux",
Name: "foo",
}
payload, err := json.Marshal(data)
if err != nil {
log.Fatalln(err)
}
fmt.Println(string(payload))
}
// Output:
// {"id":"qux","name":"foo"}
To prettify the JSON output it can be used:
payload, err := json.MarshalIndent(item, "", " ")
Converting a map into JSON:
func main() {
data := map[string]string{"id": "qux", "name": "foo"}
payload, err := json.Marshal(data)
if err != nil {
log.Fatalln(err)
}
fmt.Println(string(payload))
}
// Output:
// {"id":"qux","name":"foo"}
Converting JSON into a struct:
func main() {
payload := `{"id":"qux","name":"foo"}`
data := struct {
ID string
Name string
}{}
err := json.Unmarshal([]byte(payload), &data)
if err != nil {
log.Fatalln(err)
}
fmt.Printf("%+v\n", data)
}
// Output:
// {ID:qux Name:foo}
Converting JSON into a map:
func main() {
payload := `{"id":"qux","name":"foo"}`
data := map[string]string{}
err := json.Unmarshal([]byte(payload), &data)
if err != nil {
log.Fatalln(err)
}
fmt.Println(data)
}
// Output:
// map[id:qux name:foo]
JSON payloads
When working with streams (for example in HTTP responses), json.Encoder
can be used for better performance, as it will directly write the JSON result into the response. However, if an error happens during the process, a partial response will have been sent.
func handler(w http.ResponseWriter, r *http.Request) {
data := struct {
ID string `json:"id"`
Name string `json:"name"`
}{
ID: "qux",
Name: "foo",
}
w.Header().Set("content-type", "application/json")
err := json.NewEncoder(w).Encode(data)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Println(err)
return
}
}
// Output:
// {"id":"qux","name":"foo"}
To make sure the entire response is correct before sending it, it can be marshaled first and then sent.
func handler(w http.ResponseWriter, r *http.Request) {
data := struct {
ID string `json:"id"`
Name string `json:"name"`
}{
ID: "qux",
Name: "foo",
}
payload, err := json.Marshal(data)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Println(err)
return
}
w.Header().Set("content-type", "application/json")
w.Write(payload)
}
// Output:
// {"id":"qux","name":"foo"}
A gotcha to take into account when sending empty slices: if a slice is declared with var
, like var data []string
, and is sent empty, it will be sent as null
instead of []
. This is due to the fact that it has been initialized as a nil
pointer, so it gets encoded as null
. To avoid it, it should be initialized as data := []string{}
.