iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

encoding/json

Go has first-class JSON in encoding/json. The mapping is driven by struct tags: name, omitempty, and the - sentinel that excludes a field. For schemas you do not control, use json.RawMessage to defer parsing of a sub-tree, or decode into map[string]any when you truly need to take what comes.

Encode, decode, stream, and handle unknown fields

EXAMPLE
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strings"
	"time"
)

type Money struct {
	Amount   int    `json:"amount"`
	Currency string `json:"currency"`
}

type Order struct {
	ID       string    `json:"id"`
	Customer string    `json:"customer"`
	Total    Money     `json:"total"`
	Notes    string    `json:"notes,omitempty"`     // omitted when empty
	Secret   string    `json:"-"`                    // never serialized
	PaidAt   *time.Time `json:"paid_at,omitempty"`   // null when nil
}

func main() {
	// 1) Marshal
	t := time.Date(2026, 6, 11, 9, 30, 0, 0, time.UTC)
	o := Order{ID: "o1", Customer: "alice",
		Total: Money{4995, "AUD"}, PaidAt: &t, Secret: "do-not-leak"}
	b, _ := json.MarshalIndent(o, "", "  ")
	fmt.Println(string(b))

	// 2) Unmarshal
	raw := []byte(`{"id":"o2","customer":"bob","total":{"amount":0,"currency":"AUD"}}`)
	var got Order
	if err := json.Unmarshal(raw, &got); err != nil { panic(err) }
	fmt.Printf("%+v\n", got)

	// 3) Reject unknown fields (catches typos in API payloads)
	dec := json.NewDecoder(strings.NewReader(`{"id":"o3","custmer":"typo"}`))
	dec.DisallowUnknownFields()
	if err := dec.Decode(&got); err != nil {
		fmt.Println("rejected:", err)
	}

	// 4) Streaming a JSON Lines file (one object per line)
	src := strings.NewReader(`{"id":"a"}` + "\n" + `{"id":"b"}` + "\n")
	d := json.NewDecoder(src)
	for d.More() {
		var o Order
		if err := d.Decode(&o); err != nil { panic(err) }
		fmt.Println("stream:", o.ID)
	}

	// 5) Defer parsing of a polymorphic field with RawMessage
	type Envelope struct {
		Type    string          `json:"type"`
		Payload json.RawMessage `json:"payload"`
	}
	env := []byte(`{"type":"refund","payload":{"order":"o1","amount":1000}}`)
	var e Envelope
	_ = json.Unmarshal(env, &e)
	switch e.Type {
	case "refund":
		var refund struct{ Order string; Amount int }
		_ = json.Unmarshal(e.Payload, &refund)
		fmt.Println("refund:", refund)
	}

	// 6) Pretty-print arbitrary JSON for logs
	var pretty bytes.Buffer
	_ = json.Indent(&pretty, env, "", "  ")
	fmt.Println(pretty.String())
}

Why it matters

json.RawMessage is the unsung hero — it lets you parse the outer shape now and the inner shape later, without paying the cost of two full decode passes. Reach for it any time the same field can hold different schemas based on a type discriminator.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}
b, _ := json.Marshal(User{"Ada", 36})
fmt.Println(string(b))
Try it Yourself »

Discussion

Loading…