Skip to content

Go JSON — Encoding and Decoding JSON with encoding/json and Struct Tags

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Go JSON. We cover key concepts, practical examples, and best practices to help you master this topic.

Go JSON handling uses encoding/json for Marshal/Unmarshal with struct tags for field mapping and custom marshaling for complex data.

What You'll Learn

  • Marshaling and unmarshaling
  • Struct tags for JSON mapping
  • Reading/writing JSON files
  • Custom JSON marshaling

Why It Matters

JSON is the standard data format. Docker uses JSON for configs. Kubernetes uses JSON/YAML for API. DodaZIP uses JSON for API responses and configuration.

Real-World Use

REST API responses, configuration files, data export/import, service-to-service communication.

flowchart LR
    A["JSON"] --> B["Marshal"]
    A --> C["Unmarshal"]
    A --> D["Encoder/Decoder"]
    A --> E["Custom Marshal"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b

Basic Marshal/Unmarshal

type Person struct {
    Name  string `json:"name"`
    Age   int    `json:"age"`
    Email string `json:"email,omitempty"`
}

func main() {
    p := Person{Name: "Alice", Age: 30, Email: "alice@example.com"}

    jsonData, err := json.Marshal(p)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(jsonData))

    var p2 Person
    err = json.Unmarshal(jsonData, &p2)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%+v\n", p2)
}

Pretty Printing

func main() {
    p := Person{Name: "Bob", Age: 25}
    jsonData, _ := json.MarshalIndent(p, "", "  ")
    fmt.Println(string(jsonData))
}

Reading JSON from File

type Config struct {
    Port    int      `json:"port"`
    Host    string   `json:"host"`
    DB      DBConfig `json:"database"`
}

type DBConfig struct {
    URL  string `json:"url"`
    Pool int    `json:"pool"`
}

func main() {
    data, err := os.ReadFile("config.json")
    if err != nil { log.Fatal(err) }

    var config Config
    err = json.Unmarshal(data, &config)
    if err != nil { log.Fatal(err) }

    fmt.Printf("Running on %s:%d\n", config.Host, config.Port)
}

Writing JSON to File

func main() {
    config := Config{
        Port: 8080,
        Host: "localhost",
        DB: DBConfig{
            URL:  "postgres://localhost:5432/app",
            Pool: 10,
        },
    }

    file, _ := os.Create("config.json")
    defer file.Close()

    encoder := json.NewEncoder(file)
    encoder.SetIndent("", "  ")
    encoder.Encode(config)
}

Streaming JSON

func main() {
    // Decode JSON stream
    file, _ := os.Open("large.json")
    defer file.Close()

    decoder := json.NewDecoder(file)
    for {
        var item map[string]interface{}
        if err := decoder.Decode(&item); err == io.EOF {
            break
        } else if err != nil {
            log.Fatal(err)
        }
        fmt.Println(item)
    }
}

Custom Marshaling

type Color struct {
    R, G, B uint8
}

func (c Color) MarshalJSON() ([]byte, error) {
    hex := fmt.Sprintf("#%02x%02x%02x", c.R, c.G, c.B)
    return json.Marshal(hex)
}

func (c *Color) UnmarshalJSON(data []byte) error {
    var hex string
    if err := json.Unmarshal(data, &hex); err != nil {
        return err
    }
    fmt.Sscanf(hex, "#%02x%02x%02x", &c.R, &c.G, &c.B)
    return nil
}

func main() {
    c := Color{R: 255, G: 128, B: 0}
    jsonData, _ := json.Marshal(c)
    fmt.Println(string(jsonData))

    var c2 Color
    json.Unmarshal([]byte("\"#00ff00\""), &c2)
    fmt.Printf("R:%d G:%d B:%d\n", c2.R, c2.G, c2.B)
}

Common Mistakes

1. Unexported Fields

type Person struct {
    Name string  // Exported: marshals as "Name"
    age  int     // Unexported: silently skipped!
}

2. Wrong Struct Tags

type Person struct {
    Name string `json:name`  // Missing quotes! Won't compile
}

3. Pointer vs Value

type Config struct {
    Name *string `json:"name,omitempty"`
    // Use pointer to distinguish null from empty string
}

4. Not Handling Unknown Fields

decoder := json.NewDecoder(file)
decoder.DisallowUnknownFields()  // Strict parsing

5. Forgetting json tags for camelCase

Go expects PascalCase fields. Add json:"field_name" tags for camelCase or snake_case output.

Practice Questions

1. What does omitempty do? Omits the field from JSON if it has zero value. Useful for optional fields.

2. How do you handle nested JSON? Define nested struct types that match the JSON structure. json.Unmarshal handles nesting recursively.

3. What is json.RawMessage? A raw JSON value stored as []byte. Useful for deferred Parsing or heterogeneous data.

4. How do you parse dynamic JSON? Use map[string]interface{} or json.RawMessage. Prefer structs for type safety.

Challenge: Write a function that reads a JSON array of users and returns only active users.

Solution
type User struct {
    ID     int    `json:"id"`
    Name   string `json:"name"`
    Active bool   `json:"active"`
}

func filterActive(data []byte) ([]User, error) {
    var users []User
    if err := json.Unmarshal(data, &users); err != nil {
        return nil, err
    }
    var active []User
    for _, u := range users {
        if u.Active { active = append(active, u) }
    }
    return active, nil
}

FAQ

{{< faq question="What is the difference between Marshal and MarshalIndent?" >}} Marshal produces compact JSON. MarshalIndent adds indentation for human readability. {{< /faq >}}

{{< faq question="How do I convert JSON to map?" >}} Use var data map[string]interface{} with json.Unmarshal. Access values with type assertions. {{< /faq >}}

{{< faq question="How do I handle time.Time in JSON?" >}} time.Time marshals to RFC3339 by default. Use json:"timestamp" tag and custom format with MarshalJSON. {{< /faq >}}

{{< faq question="Can I JSON encode private fields?" >}} No. Only exported fields (PascalCase) are marshaled. Use getter/setter methods if needed. {{< /faq >}}

{{< faq question="What is json.Decoder vs json.Unmarshal?" >}} Decoder streams from io.Reader, useful for large data. Unmarshal works on []byte in memory. Decoder is more memory-efficient. {{< /faq >}}

Try It Yourself

package main

import (
    "encoding/json"
    "fmt"
)

type Product struct {
    Name  string  `json:"name"`
    Price float64 `json:"price"`
}

func main() {
    jsonData := `{"name": "Widget", "price": 9.99}`
    var p Product
    json.Unmarshal([]byte(jsonData), &p)
    fmt.Printf("%s: $%.2f\n", p.Name, p.Price)
}

Expected output:

Widget: $9.99

What's Next

Now that you understand JSON, explore building HTTP servers in Go.

Topic Description Link
Go HTTP Servers Building web servers {{< ref "27-http-server" >}}
Go File I/O Reading and writing files {{< ref "25-file-io" >}}
Go Database SQL database operations {{< ref "29-database-sql" >}}

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse Go