encoding: JSON, CSV, XML, base64
encoding: JSON, CSV, XML, base64
Overview
The encoding/* packages turn Go values into bytes and back. JSON dominates APIs; CSV dominates data interchange; base64/hex appear in tokens and digests.
| Package | Common use |
|---|---|
encoding/json |
HTTP APIs, config (v1 API; v2 engine in Go 1.27+) |
encoding/json/v2 |
New JSON API: options, stricter UTF-8/duplicates |
encoding/csv |
Exports, bulk import |
encoding/xml |
Legacy integrations |
encoding/base64 |
Tokens, data URLs |
encoding/hex |
Digests, debug |
encoding/gob |
Go-only RPC caches (avoid for public APIs) |
JSON
Encode / decode
b, err := json.Marshal(user)
err = json.Unmarshal(b, &user)
// Streams (preferred for large or HTTP bodies)
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
err = enc.Encode(user) // adds trailing newline
dec := json.NewDecoder(r)
dec.DisallowUnknownFields() // strict APIs
err = dec.Decode(&user)Useful options
dec.UseNumber() // keep numbers as json.NumberDynamic JSON
Prefer typed structs. When needed:
var m map[string]any
json.Unmarshal(b, &m)Custom marshalers
func (id UserID) MarshalText() ([]byte, error) { /* ... */ }
func (id *UserID) UnmarshalText(b []byte) error { /* ... */ }Or MarshalJSON / UnmarshalJSON for full control.
encoding/json/v2 (Go 1.27+)
v1 remains supported. v2 is the API you want for new strict JSON:
import jsonv2 "encoding/json/v2"
b, err := jsonv2.Marshal(user)
err = jsonv2.Unmarshal(b, &user, jsonv2.RejectUnknownMembers(true))
err = jsonv2.UnmarshalRead(r, &user)
err = jsonv2.MarshalWrite(w, user)v2 rejects invalid UTF-8 and duplicate object names by default, and matches struct fields case-sensitively. The v1 package is now implemented on top of v2, so existing json.Marshal / json.Unmarshal calls get the faster unmarshal path without an import change. Compatibility problems: GOEXPERIMENT=nojsonv2.
CSV
r := csv.NewReader(f)
r.FieldsPerRecord = 3
records, err := r.ReadAll()
w := csv.NewWriter(f)
_ = w.Write([]string{"name", "age"})
_ = w.Write([]string{"ada", "36"})
w.Flush()
return w.Error()Always check w.Error() after Flush.
XML (when you must)
type Note struct {
XMLName xml.Name `xml:"note"`
To string `xml:"to"`
Body string `xml:",chardata"`
}
b, err := xml.MarshalIndent(note, "", " ")base64 and hex
s := base64.StdEncoding.EncodeToString(raw)
raw, err = base64.StdEncoding.DecodeString(s)
s = base64.RawURLEncoding.EncodeToString(raw) // JWT-style
h := hex.EncodeToString(sum[:])Error handling pattern
if err := json.NewDecoder(r).Decode(&in); err != nil {
var syn *json.SyntaxError
var typeErr *json.UnmarshalTypeError
switch {
case errors.As(err, &syn):
return fmt.Errorf("json syntax at %d: %w", syn.Offset, err)
case errors.As(err, &typeErr):
return fmt.Errorf("json field %s: %w", typeErr.Field, err)
default:
return fmt.Errorf("json decode: %w", err)
}
}Runnable example
go mod init example
go run .package main
import (
"bytes"
"encoding/base64"
"encoding/csv"
"encoding/hex"
"encoding/json"
"fmt"
"strings"
)
type Item struct {
SKU string `json:"sku"`
Qty int `json:"qty"`
Note string `json:"note,omitempty"`
}
func main() {
it := Item{SKU: "ABC", Qty: 2, Note: "rush"}
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
_ = enc.Encode(it)
fmt.Print("json:", buf.String())
var back Item
_ = json.NewDecoder(strings.NewReader(buf.String())).Decode(&back)
fmt.Printf("decoded=%+v\n", back)
var cbuf strings.Builder
cw := csv.NewWriter(&cbuf)
_ = cw.Write([]string{"sku", "qty"})
_ = cw.Write([]string{it.SKU, fmt.Sprintf("%d", it.Qty)})
cw.Flush()
fmt.Print("csv:\n", cbuf.String())
raw := []byte("hello")
fmt.Println("b64:", base64.StdEncoding.EncodeToString(raw))
fmt.Println("hex:", hex.EncodeToString(raw))
}Expected output:
json:{"sku":"ABC","qty":2,"note":"rush"}
decoded={SKU:ABC Qty:2 Note:rush}
csv:
sku,qty
ABC,2
b64: aGVsbG8=
hex: 68656c6c6f
What to notice: - Encode adds a newline; handy for NDJSON-style logs/APIs. - CSV writer buffers — Flush + Error are mandatory. - Pick base64 variant deliberately (std vs URL vs raw).
Try next: Decode JSON with DisallowUnknownFields and show the error when an extra field is present.