The Language APIs Speak
APIs exchange data as JSON — text like {"id": 1, "name": "Ana"}. Go's encoding/json package translates in both directions: Marshal turns a struct into JSON, Unmarshal fills a struct from JSON.
Struct tags connect the two worlds: the tag `json:"name"` maps Go's exported Name field to the lowercase JSON key. Only exported fields (capital first letter) are translated at all.
From Struct to JSON
json.Marshal produces compact JSON for machines; json.MarshalIndent produces readable JSON for humans and logs. The omitempty tag option drops fields that hold their zero value.
Marshal, pretty and compact
Ana has no email, and omitempty removes the empty field from her JSON entirely. Ben's full record prints indented.
Output: Go program and captured output
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"`
}
func main() {
ana := User{ID: 1, Name: "Ana"}
compact, _ := json.Marshal(ana)
fmt.Println(string(compact))
ben := User{ID: 2, Name: "Ben", Email: "[email protected]"}
pretty, _ := json.MarshalIndent(ben, "", " ")
fmt.Println(string(pretty))
}
From JSON to Struct
json.Unmarshal parses JSON into a struct you provide (as a pointer). Unknown JSON keys are simply ignored; missing keys leave the zero value. The returned error is the only signal that the JSON was bad — always check it.
Unmarshal and the ignored extra
The JSON carries an extra key that the struct does not have — no error, it is skipped. Broken JSON, on the other hand, returns a real error.
Output: Go program and captured output
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
data := []byte(`{"id": 7, "name": "Cleo", "extra": "ignored"}`)
var u User
if err := json.Unmarshal(data, &u); err != nil {
fmt.Println("bad JSON:", err)
return
}
fmt.Printf("%+v\n", u)
var broken User
err := json.Unmarshal([]byte(`{"id": `), &broken)
fmt.Println("error:", err)
}
Nested Data
Real API payloads nest: an order carries its user and a list of items. Structs inside structs and slices map to JSON objects and arrays automatically — no special handling needed.
An order with a user inside
The nested User struct becomes a nested JSON object, the items slice becomes a JSON array — the shape of the Go data is the shape of the JSON.
Output: Go program and captured output
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
type Order struct {
ID int `json:"id"`
Items []string `json:"items"`
User User `json:"user"`
}
func main() {
order := Order{
ID: 1042,
Items: []string{"keyboard", "mouse"},
User: User{ID: 1, Name: "Ana"},
}
data, _ := json.MarshalIndent(order, "", " ")
fmt.Println(string(data))
}