encoding: JSON, CSV, XML, base64

Updated

September 8, 2026

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

Struct tags

type User struct {
    ID    int64  `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email,omitempty"`
    Internal string `json:"-"` // never serialized
}

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.Number

Dynamic 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.