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…