Skip to content
AgileLogics
7 min read

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) fills v from JSON bytes — note the pointer.
  • Struct tags such as `json:"name"` choose the JSON key. Add ,omitempty to 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) and json.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, so encoding/json silently 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 omitempty to omit an empty struct field. A struct value is never "empty" to encoding/json; use a pointer (*Address) if you want it omitted.
  • Ignoring the error from Unmarshal or Decode. 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}
}