jsondecode.com logo

HomeChevronBlogChevronJSON in Go: Working with encoding/json

Blog post

JSON in Go: Working with encoding/json

Marshal and unmarshal JSON in Go — struct tags, custom marshalers, streaming, and common patterns with the encoding/json package.

author

Shashank Jain

Author

14/06/20261 minute 50 seconds read
JSON in Go: Working with encoding/jsonJSON in Go: Working with encoding/json

Article

Basic Marshal and Unmarshal

Go's encoding/json package is part of the standard library — no dependencies needed:

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

func main() {
    // Marshal (Go struct to JSON)
    user := User{ID: 1, Name: "Alice", Email: "alice@example.com"}
    data, err := json.Marshal(user)
    if err != nil { panic(err) }
    fmt.Println(string(data))
    // {"id":1,"name":"Alice","email":"alice@example.com"}

    // Unmarshal (JSON to Go struct)
    jsonStr := `{"id":2,"name":"Bob","email":"bob@example.com"}`
    var decoded User
    err = json.Unmarshal([]byte(jsonStr), &decoded)
    if err != nil { panic(err) }
    fmt.Println(decoded.Name) // Bob
}

Struct Tags

type Product struct {
    ID          int     `json:"id"`
    Name        string  `json:"name"`
    Price       float64 `json:"price"`
    Description string  `json:"description,omitempty"` // omit if zero value
    internal    string  `json:"-"`                     // always omit
    CreatedAt   string  `json:"created_at"`
}

Dynamic JSON with map and interface{}

// Unknown structure
var result map[string]interface{}
json.Unmarshal([]byte(jsonStr), &result)

name, ok := result["name"].(string)
if ok {
    fmt.Println(name)
}

// Any valid JSON
var raw interface{}
json.Unmarshal([]byte(jsonStr), &raw)

Pretty Printing

data, _ := json.MarshalIndent(user, "", "  ")
fmt.Println(string(data))

Streaming Large JSON (Decoder)

import (
    "encoding/json"
    "os"
)

func streamJSON(filename string) {
    f, _ := os.Open(filename)
    defer f.Close()

    decoder := json.NewDecoder(f)
    // Read opening bracket
    decoder.Token()

    for decoder.More() {
        var item map[string]interface{}
        decoder.Decode(&item)
        // Process item without loading full file
        fmt.Println(item["id"])
    }
}

Custom Marshaler

type Color int

const (Red Color = iota; Green; Blue)

func (c Color) MarshalJSON() ([]byte, error) {
    names := []string{"red", "green", "blue"}
    if int(c) >= len(names) {
        return nil, fmt.Errorf("unknown color: %d", c)
    }
    return json.Marshal(names[c])
}

func (c *Color) UnmarshalJSON(data []byte) error {
    var name string
    json.Unmarshal(data, &name)
    names := map[string]Color{"red": Red, "green": Green, "blue": Blue}
    *c = names[name]
    return nil
}

FAQ

Why does json.Unmarshal need a pointer?

Unmarshal needs to modify the variable you pass in. Go is pass-by-value, so without a pointer, modifications would affect a copy. Always pass &variable to Unmarshal.

How do I handle optional/nullable JSON fields in Go?

Use pointer types: *string, *int. A nil pointer marshals to null and a missing JSON field leaves the pointer nil on unmarshal.

What is json.RawMessage used for?

json.RawMessage lets you delay parsing of part of a JSON payload. Useful when different subtypes have different structures — unmarshal the discriminator field first, then parse the raw part based on its value.

Is encoding/json the fastest JSON library for Go?

No. Libraries like json-iterator, sonic, and go-json are 2-5x faster. For high-throughput services, consider replacing encoding/json with one of these drop-in alternatives.

How do I validate JSON in Go before unmarshaling?

Use json.Valid([]byte(data)) which returns a boolean without allocating. Or unmarshal into interface{} and inspect the result.

Keep reading

Recent blogs

View all

If jsondecode.com saved you time, share it with your team

Free forever. No ads. No sign-up. Help other developers find it.