Free — no signup required

Parsing JSON with Structs

2 min read

Typed APIs: Safer Than Dictionaries

Ops engineers deal with JSON constantly — AWS API responses, Kubernetes object manifests, Prometheus query results, Terraform state files. In Python, you load JSON into a dictionary and access fields by string key. If you typo the key name, you get None at runtime, possibly hours later in production.

In Go, you define a struct that describes the shape of the JSON, and the compiler enforces that you access only fields that exist. Typos become compile errors, not runtime surprises.

The mapping between JSON keys and struct fields is controlled by struct tags — metadata annotations in backticks after each field.

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

// PodStatus mirrors the relevant fields from a Kubernetes Pod JSON response
type PodStatus struct {
    Phase     string `json:"phase"`
    HostIP    string `json:"hostIP"`
    PodIP     string `json:"podIP"`
    StartTime string `json:"startTime"`
}

type Pod struct {
    Name      string    `json:"name"`
    Namespace string    `json:"namespace"`
    Status    PodStatus `json:"status"`
}

func main() {
    // Simulated JSON from kubectl get pod -o json (simplified)
    raw := []byte(`{
        "name": "nginx-7d9f8b5c4-xk2p9",
        "namespace": "production",
        "status": {
            "phase": "Running",
            "hostIP": "10.0.1.15",
            "podIP": "172.16.0.42",
            "startTime": "2024-01-15T08:23:11Z"
        }
    }`)

    var pod Pod
    if err := json.Unmarshal(raw, &pod); err != nil {
        log.Fatalf("Failed to parse pod JSON: %v", err)
    }

    fmt.Printf("Pod: %s/%s\n", pod.Namespace, pod.Name)
    fmt.Printf("Status: %s\n", pod.Status.Phase)
    fmt.Printf("Running on node: %s\n", pod.Status.HostIP)
}

Output:

Pod: production/nginx-7d9f8b5c4-xk2p9
Status: Running
Running on node: 10.0.1.15

Marshalling (Go → JSON)

The reverse operation — converting a Go struct to JSON — uses json.Marshal:

alert := map[string]interface{}{
    "severity": "critical",
    "message":  "Disk usage above 90%",
    "host":     "db-01.prod",
}

data, err := json.Marshal(alert)
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(data))
// {"host":"db-01.prod","message":"Disk usage above 90%","severity":"critical"}

Handling unknown or optional fields

If a JSON field might be absent, use a pointer type or omitempty in the struct tag:

type Server struct {
    Hostname string  `json:"hostname"`
    Region   string  `json:"region,omitempty"` // omitted from output if empty
    Tags     []string `json:"tags,omitempty"`
}

omitempty means: when marshalling to JSON, skip this field if it has its zero value (empty string, nil slice, 0, false). This keeps your JSON output clean.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free