Working with JSON
Prerequisites: sync: WaitGroup & Mutex
The encoding/json package converts between Go values and JSON.
json.Marshal(v)turns a value into JSON bytes.json.Unmarshal(data, &v)fillsvfrom JSON bytes — note the pointer.- Struct tags such as
`json:"name"`choose the JSON key. Add,omitemptyto leave out zero values ("",0,false,nil, empty slices and maps), and use`json:"-"`to never encode a field. - Only exported (capitalised) fields are encoded or decoded.
- For streams such as files and HTTP bodies, use
json.NewEncoder(w)andjson.NewDecoder(r).
Syntax
type User struct {
ID int `json:"id"`
Email string `json:"email,omitempty"`
Token string `json:"-"`
}
data, err := json.Marshal(u)
err = json.Unmarshal(data, &u)
Example
package main
import (
"encoding/json"
"fmt"
"log"
)
type Address struct {
City string `json:"city"`
}
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"`
Tags []string `json:"tags"`
Address *Address `json:"address,omitempty"`
Password string `json:"-"`
}
func main() {
// Go -> JSON
u := User{ID: 1, Name: "Ada", Tags: []string{"admin"}, Password: "secret"}
data, err := json.Marshal(u)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
pretty, err := json.MarshalIndent(Address{City: "London"}, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(pretty))
// JSON -> Go (unknown keys like "extra" are ignored)
input := []byte(`{"id": 2, "name": "Linus", "email": "linus@example.com",
"address": {"city": "Helsinki"}, "extra": true}`)
var u2 User
if err := json.Unmarshal(input, &u2); err != nil {
log.Fatal(err)
}
fmt.Println(u2.Name, u2.Email, u2.Address.City, u2.Tags == nil)
// Unknown shape: decode into map[string]any
var generic map[string]any
if err := json.Unmarshal([]byte(`{"count": 3, "ok": true}`), &generic); err != nil {
log.Fatal(err)
}
fmt.Println(generic["count"], generic["ok"])
fmt.Printf("%T\n", generic["count"])
// Type mismatches are reported as errors.
err = json.Unmarshal([]byte(`{"id": "seven"}`), &u2)
fmt.Println(err)
}
Expected output
{"id":1,"name":"Ada","tags":["admin"]}
{
"city": "London"
}
Linus linus@example.com Helsinki true
3 true
float64
json: cannot unmarshal string into Go struct field User.id of type int
Email and Address were omitted by omitempty, and Password was never
encoded. When decoding into any, every JSON number becomes a float64.
Common mistakes
- Lowercase field names (
name string). They are unexported, soencoding/jsonsilently skips them and you get{}. - Passing a value instead of a pointer to
Unmarshal. It returns an error (json: Unmarshal(non-pointer main.User)) and fills nothing. - Expecting
omitemptyto omit an empty struct field. A struct value is never "empty" toencoding/json; use a pointer (*Address) if you want it omitted. - Ignoring the error from
UnmarshalorDecode. Malformed or mistyped input is common, especially from HTTP requests.
Practice
Decode {"title":"The Go Programming Language","pages":380,"in_stock":true}
into a Book struct with appropriate tags, print the title, then change
InStock to false and print the struct encoded back as JSON.
Show solution
package main
import (
"encoding/json"
"fmt"
"log"
)
type Book struct {
Title string `json:"title"`
Pages int `json:"pages"`
InStock bool `json:"in_stock"`
}
func main() {
input := `{"title":"The Go Programming Language","pages":380,"in_stock":true}`
var b Book
if err := json.Unmarshal([]byte(input), &b); err != nil {
log.Fatal(err)
}
fmt.Println(b.Title)
b.InStock = false
out, err := json.Marshal(b)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(out))
// {"title":"The Go Programming Language","pages":380,"in_stock":false}
}