JSON Formatter Go — Průvodce MarshalIndent()

·Systems Engineer·ZkontrolovánoTobias Müller·Publikováno

Používejte bezplatný JSON Formatter & Beautifier přímo v prohlížeči — bez instalace.

Vyzkoušet JSON Formatter & Beautifier online →

Když pracuji na Go mikroslužbě a potřebuji zkontrolovat odpověď API nebo konfigurační soubor, kompaktní JSON je první překážka — jeden řádek se stovkami vnořených polí mi toho na první pohled téměř nic neřekne. Aby bylo možné formátovat JSON v Go, standardní knihovna poskytuje vše potřebné: json.MarshalIndent je součástí encoding/json, dodává se s každou instalací Go a nevyžaduje žádné závislosti třetích stran. Tato příručka pokrývá celý obraz: struct tags, vlastní implementace MarshalJSON(), json.Indent pro přeformátování surových bajtů, streamování velkých souborů pomocí json.Decoder, a kdy sáhnout po go-json pro vysokovýkonné cesty — plus CLI příkazy pro rychlé formátování v terminálu. Všechny příklady používají Go 1.21+.

  • json.MarshalIndent(v, "", "\t") je součástí standardní knihovny — nulové závislosti, obsaženo v každé instalaci Go.
  • Struct tags json:"field_name,omitempty" řídí klíče serializace a vynechávají pole s nulovou hodnotou.
  • Implementujte MarshalJSON() na libovolném typu pro plnou kontrolu nad jeho JSON reprezentací.
  • json.Indent() přeformátuje již serializovaný []byte bez opětovného parsování struktury — rychlejší pro surové bajty.
  • Pro velké soubory (>100 MB): použijte json.Decoder s Token() pro streaming bez načítání všeho do paměti.
  • go-json je drop-in náhrada 3–5× rychlejší než encoding/json pro vysokovýkonná API.

Co je formátování JSON?

Formátování JSON — také nazývané pretty-printing — transformuje kompaktní, minifikovaný JSON řetězec na čitelné rozvržení s konzistentním odsazením a zalomením řádků. Podkladová data jsou identická; mění se pouze bílé znaky. Kompaktní JSON je optimální pro přenos po síti, kde záleží na každém bajtu; formátovaný JSON je optimální pro ladění, code review, inspekci logů a psaní konfiguračních souborů. Balíček encoding/json v Go zpracovává oba režimy jediným voláním funkce — přepínání mezi kompaktním a odsazeným výstupem volbou mezi json.Marshal a json.MarshalIndent.

Before · json
After · json
{"service":"payments","port":8443,"workers":4}
{
	"service": "payments",
	"port": 8443,
	"workers": 4
}

json.MarshalIndent() — přístup standardní knihovny

json.MarshalIndent je součástí balíčku encoding/json, který je součástí standardní knihovny Go — žádné go get není potřeba. Signatura je MarshalIndent(v any, prefix, indent string) ([]byte, error): řetězec prefix je přidán na začátek každého výstupního řádku (téměř vždy ponechán prázdný), a indent se opakuje jednou na každou úroveň vnořování. Předejte "\t" pro tabulátory nebo " " pro dvě mezery.

Go — minimální funkční příklad
package main

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

type ServiceConfig struct {
	Host       string   `json:"host"`
	Port       int      `json:"port"`
	Workers    int      `json:"workers"`
	TLSEnabled bool     `json:"tls_enabled"`
	AllowedIPs []string `json:"allowed_ips"`
}

func main() {
	cfg := ServiceConfig{
		Host:       "payments-api.internal",
		Port:       8443,
		Workers:    8,
		TLSEnabled: true,
		AllowedIPs: []string{"10.0.0.0/8", "172.16.0.0/12"},
	}

	data, err := json.MarshalIndent(cfg, "", "	")
	if err != nil {
		log.Fatalf("marshal config: %v", err)
	}
	fmt.Println(string(data))
}
// {
// 	"host": "payments-api.internal",
// 	"port": 8443,
// 	"workers": 8,
// 	"tls_enabled": true,
// 	"allowed_ips": [
// 		"10.0.0.0/8",
// 		"172.16.0.0/12"
// 	]
// }

Volba mezi tabulátory a mezerami je převážně týmová konvence. Mnoho Go projektů preferuje tabulátory, protože gofmt (formátovač Go zdrojového kódu) tabulátory používá. Dvou- nebo čtyřmezerové odsazení je běžné, když je JSON určen pro JavaScript nebo Python konzumenty. Zde je stejná struktura s oběma styly odsazení vedle sebe:

Go — tabulátory vs mezery
// Tabulátorové odsazení — preferováno v Go nativním toolingu
data, _ := json.MarshalIndent(cfg, "", "	")
// {
// 	"host": "payments-api.internal",
// 	"port": 8443
// }

// Dvě mezery — běžné pro API s JS/Python konzumenty
data, _ = json.MarshalIndent(cfg, "", "  ")
// {
//   "host": "payments-api.internal",
//   "port": 8443
// }

// Čtyři mezery
data, _ = json.MarshalIndent(cfg, "", "    ")
// {
//     "host": "payments-api.internal",
//     "port": 8443
// }
Poznámka:Použijte json.Marshal(v) když potřebujete kompaktní výstup — síťové payload, hodnoty cache nebo jakoukoliv cestu, kde záleží na velikosti dat. Přijímá stejný argument hodnoty a má stejnou sémantiku chyb, ale produkuje jednořádkový JSON bez bílých znaků.

Struct Tags — řízení názvů polí a omitempty

Go struct tags jsou řetězcové literály za deklaracemi polí, které říkajíencoding/jsonjak serializovat každé pole. Existují tři klíčové direktivy: json:"name" přejmenuje pole ve výstupu, omitempty vynechá pole s nulovou hodnotou, a json:"-" pole zcela vyloučí — užitečné pro hesla, interní identifikátory nebo pole, která nesmějí opustit hranici služby.

Go — struct tags pro odpověď API
type UserProfile struct {
	ID          string  `json:"id"`
	Email       string  `json:"email"`
	DisplayName string  `json:"display_name,omitempty"`  // vynechat pokud prázdný řetězec
	AvatarURL   *string `json:"avatar_url,omitempty"`    // vynechat pokud nil pointer
	IsAdmin     bool    `json:"is_admin,omitempty"`      // vynechat pokud false
	passwordHash string                                   // neexportované — auto vyloučení
}

// Uživatel se všemi volitelnými poli
full := UserProfile{
	ID: "usr_7b3c", Email: "j.novak@priklad.cz",
	DisplayName: "Jakub Novák", IsAdmin: true,
}
// {
//   "id": "usr_7b3c",
//   "email": "j.novak@priklad.cz",
//   "display_name": "Jakub Novák",
//   "is_admin": true
// }

// Uživatel bez volitelných polí — jsou zcela vynechána
minimal := UserProfile{ID: "usr_2a91", Email: "t.horackova@priklad.cz"}
// {
//   "id": "usr_2a91",
//   "email": "t.horackova@priklad.cz"
// }

Tag json:"-" je správnou volbou pro pole, která musí být bezpodmínečně vyloučena bez ohledu na hodnotu — typicky tajemství, interní tracking pole nebo data, která jsou v paměti správná, ale nesmějí být serializována do externích systémů.

Go — vyloučení citlivých polí
type AuthToken struct {
	TokenID      string `json:"token_id"`
	Subject      string `json:"sub"`
	IssuedAt     int64  `json:"iat"`
	ExpiresAt    int64  `json:"exp"`
	SigningKey    []byte `json:"-"`   // nikdy serializováno
	RefreshToken string `json:"-"`   // nikdy serializováno
}

tok := AuthToken{
	TokenID: "tok_8f2a", Subject: "usr_7b3c",
	IssuedAt: 1741614120, ExpiresAt: 1741617720,
	SigningKey: []byte("secret"), RefreshToken: "rt_9e4f",
}
data, _ := json.MarshalIndent(tok, "", "  ")
// {
//   "token_id": "tok_8f2a",
//   "sub": "usr_7b3c",
//   "iat": 1741614120,
//   "exp": 1741617720
// }
// SigningKey a RefreshToken se nikdy neobjeví
Poznámka:Neexportovaná (malá písmena) pole struktury jsou vždy vyloučena encoding/json bez ohledu na tagy. Není třeba přidávat json:"-" k neexportovaným polím — vyloučení je automatické a nelze ho přepsat.

Vlastní MarshalJSON() — práce s nestandardními typy

Libovolný Go typ může implementovat rozhraní json.Marshaler definováním metody MarshalJSON() ([]byte, error). Když encoding/json narazí na takový typ, zavolá tuto metodu místo výchozího reflektovaného marshallingu. Toto je kanonický Go vzor pro doménové typy, které potřebují specifickou wire reprezentaci — peněžní hodnoty, status enumerace, vlastní formáty času nebo jakýkoli typ, který ukládá data jinak, než by měl být serializován.

Vlastní typ — Money s převodem haléřů na koruny

Go — vlastní MarshalJSON pro Money
package main

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

type Money struct {
	Amount   int64  // uloženo v haléřích, aby se zabránilo nepřesnostem plovoucí desetinné čárky
	Currency string
}

func (m Money) MarshalJSON() ([]byte, error) {
	return json.Marshal(struct {
		Amount   float64 `json:"amount"`
		Currency string  `json:"currency"`
		Display  string  `json:"display"`
	}{
		Amount:   float64(m.Amount) / 100,
		Currency: m.Currency,
		Display:  fmt.Sprintf("%s %.2f", m.Currency, float64(m.Amount)/100),
	})
}

type Invoice struct {
	ID       string `json:"id"`
	Subtotal Money  `json:"subtotal"`
	Tax      Money  `json:"tax"`
	Total    Money  `json:"total"`
}

func main() {
	inv := Invoice{
		ID:       "inv_9a2f91bc",
		Subtotal: Money{Amount: 19900, Currency: "CZK"},
		Tax:      Money{Amount: 1592, Currency: "CZK"},
		Total:    Money{Amount: 21492, Currency: "CZK"},
	}
	data, err := json.MarshalIndent(inv, "", "  ")
	if err != nil {
		log.Fatalf("marshal invoice: %v", err)
	}
	fmt.Println(string(data))
}
// {
//   "id": "inv_9a2f91bc",
//   "subtotal": { "amount": 199, "currency": "CZK", "display": "CZK 199.00" },
//   "tax":      { "amount": 15.92, "currency": "CZK", "display": "CZK 15.92" },
//   "total":    { "amount": 214.92, "currency": "CZK", "display": "CZK 214.92" }
// }

Status Enum — řetězcová reprezentace

Go — vlastní MarshalJSON pro status enum
type OrderStatus int

const (
	StatusPending OrderStatus = iota
	StatusPaid
	StatusShipped
	StatusCancelled
)

var orderStatusNames = map[OrderStatus]string{
	StatusPending:   "pending",
	StatusPaid:      "paid",
	StatusShipped:   "shipped",
	StatusCancelled: "cancelled",
}

func (s OrderStatus) MarshalJSON() ([]byte, error) {
	name, ok := orderStatusNames[s]
	if !ok {
		return nil, fmt.Errorf("unknown order status: %d", s)
	}
	return json.Marshal(name)
}

type Order struct {
	ID     string      `json:"id"`
	Status OrderStatus `json:"status"`
}

o := Order{ID: "ord_3c7f", Status: StatusShipped}
data, _ := json.MarshalIndent(o, "", "  ")
// {
//   "id": "ord_3c7f",
//   "status": "shipped"
// }
Poznámka:Vždy implementujte MarshalJSON() a UnmarshalJSON() dohromady. Pokud implementujete pouze marshalling, round-trip typu přes JSON (serializace → uložení → deserializace) tiše ztratí strukturu nebo vrátí nesprávný typ. Dvojice tvoří kontrakt, že typ přežije JSON round-trip.

UUID — serializace jako řetězec

Standardní knihovna Go nemá typ UUID. Nejčastější volbou je github.com/google/uuid, který již implementuje MarshalJSON() a serializuje jako quoted RFC-4122 řetězec. Při použití surového [16]byte nebo vlastního ID typu implementujte rozhraní sami, abyste se vyhnuli base64 kódovaným binárním blobům ve výstupu JSON.

Go 1.21+ — serializace UUID
import (
    "encoding/json"
    "fmt"

    "github.com/google/uuid"
)

type AuditEvent struct {
    EventID   uuid.UUID `json:"event_id"`   // serializuje jako "550e8400-e29b-41d4-a716-446655440000"
    SessionID uuid.UUID `json:"session_id"`
    Action    string    `json:"action"`
    ActorID   string    `json:"actor_id"`
    OccuredAt string    `json:"occurred_at"`
}

event := AuditEvent{
    EventID:   uuid.New(),
    SessionID: uuid.MustParse("550e8400-e29b-41d4-a716-446655440000"),
    Action:    "user.password_changed",
    ActorID:   "usr_7f3a91bc",
    OccuredAt: "2026-03-10T14:22:00Z",
}

data, _ := json.MarshalIndent(event, "", "  ")
fmt.Println(string(data))
// {
//   "event_id": "a4b2c1d0-...",
//   "session_id": "550e8400-e29b-41d4-a716-446655440000",
//   "action": "user.password_changed",
//   "actor_id": "usr_7f3a91bc",
//   "occurred_at": "2026-03-10T14:22:00Z"
// }
Poznámka:Pokud ukládáte UUID jako [16]byte bez vlastního typu, encoding/json je zakóduje jako base64 řetězec — např. "VQ6EAOKbQdSnFkRmVUQAAA==". Vždy používejte správný UUID typ nebo implementujte MarshalJSON() pro výstup kanonického řetězce s pomlčkami.

Přehled parametrů json.MarshalIndent()

Signatura funkce je func MarshalIndent(v any, prefix, indent string) ([]byte, error). Jak prefix, tak indent jsou řetězcové literály — neexistuje žádná číselná zkratka jako u Pythonu's indent=4.

Parametr
Typ
Popis
v
any
Hodnota k marshalování — struct, map, slice nebo primitiv
prefix
string
Řetězec přidávaný na začátek každého řádku výstupu (zpravidla "")
indent
string
Řetězec pro každou úroveň odsazení ("\t" nebo " ")

Běžné volby struct tagů:

Tag
Účinek
json:"name"
Přejmenuje pole ve výstupu JSON
json:"name,omitempty"
Přejmenování + vynechání při nulové hodnotě (nil, "", 0, false)
json:"-"
Pole vždy vyloučí z výstupu JSON
json:",string"
Zakóduje číslo nebo bool jako řetězcovou JSON hodnotu

json.Indent() — přeformátování existujících JSON bajtů

Pokud již máte []byte s JSON — například z těla HTTP odpovědi, sloupce Postgres jsonb nebo souboru načteného pomocí os.ReadFile — nemusíte definovat strukturu a unmarshallovat, než budete moci provést pretty-print. json.Indent přímo přeformátuje surové bajty zápisem odsazeného výstupu do bytes.Buffer.

Go — json.Indent na surových bajtech
package main

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

func main() {
	// Simulace surového JSON payload od nadřazené služby
	raw := []byte(`{"trace_id":"tr_9a2f","service":"checkout","latency_ms":342,"error":null}`)

	var buf bytes.Buffer
	if err := json.Indent(&buf, raw, "", "	"); err != nil {
		log.Fatalf("indent: %v", err)
	}
	fmt.Println(buf.String())
}
// {
// 	"trace_id": "tr_9a2f",
// 	"service": "checkout",
// 	"latency_ms": 342,
// 	"error": null
// }

Vzor, který pravidelně používám v mikroslužbách: volat json.Indent před zápisem do strukturovaných logů — přidává zanedbatelnou režii a záznamy v logu jsou při incidentu mnohem lépe čitelné. Funkce je zvláště užitečná pro logování HTTP odpovědí, pretty-printing uložených JSON řetězců a pipeline s formátováním při čtení, kde definice struktury není k dispozici.

Go — json.Indent pro debug logování
func logResponse(logger *slog.Logger, statusCode int, body []byte) {
	var pretty bytes.Buffer
	if err := json.Indent(&pretty, body, "", "  "); err != nil {
		// Body není platný JSON — logujeme surově
		logger.Debug("upstream response", "status", statusCode, "body", string(body))
		return
	}
	logger.Debug("upstream response", "status", statusCode, "body", pretty.String())
}
Upozornění:json.Indent NEVALIDUJE JSON plně — pouze to, co je strukturně nutné pro vložení bílých znaků. Pro úplnou syntaktickou validaci nejprve zavolejte json.Valid(data) a ošetřete případ false před pokusem o odsazení.

Formátování JSON ze souboru a HTTP odpovědi

Dva nejběžnější reálné scénáře v Go službách jsou formátování JSON načteného ze souboru na disku (konfigurační soubory, fixture data, migration seeds) a pretty-printing těla HTTP odpovědí pro debug logování nebo testové aserce. Oba sledují stejný vzor: načíst bajty, zavolat json.Indent nebo unmarshallovat a pak json.MarshalIndent, zapsat zpět nebo logovat.

Načtení souboru → formátování → zápis zpět

Go — formátování JSON souboru na místě
package main

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

func formatJSONFile(path string) error {
	data, err := os.ReadFile(path)
	if err != nil {
		return fmt.Errorf("read %s: %w", path, err)
	}

	if !json.Valid(data) {
		return fmt.Errorf("invalid JSON in %s", path)
	}

	var buf bytes.Buffer
	if err := json.Indent(&buf, data, "", "	"); err != nil {
		return fmt.Errorf("indent %s: %w", path, err)
	}

	if err := os.WriteFile(path, buf.Bytes(), 0644); err != nil {
		return fmt.Errorf("write %s: %w", path, err)
	}
	return nil
}

func main() {
	if err := formatJSONFile("config/databaze.json"); err != nil {
		log.Fatalf("format config: %v", err)
	}
	fmt.Println("config/databaze.json úspěšně naformátován")
}

HTTP odpověď → Decode → Pretty-Print pro debug logování

Go — formátování HTTP odpovědi pro debug log
package main

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

type HealthResponse struct {
	Status    string            `json:"status"`
	Version   string            `json:"version"`
	Checks    map[string]string `json:"checks"`
	UptimeSec int64             `json:"uptime_seconds"`
}

func main() {
	resp, err := http.Get("https://api.payments.internal/v2/health")
	if err != nil {
		log.Fatalf("health check: %v", err)
	}
	defer resp.Body.Close()

	var result HealthResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		log.Fatalf("decode health response: %v", err)
	}

	pretty, err := json.MarshalIndent(result, "", "  ")
	if err != nil {
		log.Fatalf("marshal health response: %v", err)
	}
	fmt.Printf("Health check (%d):
%s
", resp.StatusCode, string(pretty))
}
// Health check (200):
// {
//   "status": "ok",
//   "version": "1.4.2",
//   "checks": {
//     "database": "ok",
//     "cache": "ok",
//     "queue": "degraded"
//   },
//   "uptime_seconds": 172800
// }

Surové tělo odpovědi → json.Indent (bez struktury)

Go — formátování surového HTTP těla pomocí io.ReadAll
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	resp, err := http.Get("https://api.payments.internal/v2/health")
	if err != nil {
		log.Fatalf("request: %v", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatalf("read body: %v", err)
	}

	var buf bytes.Buffer
	if err := json.Indent(&buf, body, "", "  "); err != nil {
		log.Fatalf("indent: %v", err)
	}
	fmt.Println(buf.String())
}

Formátovaný výstup JSON z HTTP odpovědi v Go

Oba výše popsané přístupy pokrývají nejběžnější případy: dekódování do typované struktury s následným voláním json.MarshalIndent (nejlepší, když potřebujete validovat nebo prozkoumat konkrétní pole), nebo načtení surových bajtů těla pomocí io.ReadAll a přímé volání json.Indent (nejlepší pro rychlé debug logování bez definice struktury). Přístup se surovými bajty je jednodušší, ale neposkytuje Go typovou bezpečnost ani přístup k polím — jde čistě o vizuální transformaci. Oba přístupy správně zpracovávají velká těla odpovědí, pokud se celé tělo vejde do paměti.

Go — dva vzory vedle sebe
// Vzor A: typované decode → MarshalIndent
// Použijte, když potřebujete zkontrolovat nebo validovat konkrétní pole
var result map[string]any
json.NewDecoder(resp.Body).Decode(&result)
pretty, _ := json.MarshalIndent(result, "", "  ")
fmt.Println(string(pretty))

// Vzor B: surové bajty → json.Indent
// Pro rychlé debug logování — definice struktury není potřeba
body, _ := io.ReadAll(resp.Body)
var buf bytes.Buffer
json.Indent(&buf, body, "", "  ")
fmt.Println(buf.String())

Formátování JSON v příkazové řádce v Go projektech

Někdy potřebujete naformátovat JSON payload přímo v terminálu bez psaní Go programu. Tyto příkazy mám zažité ve svalové paměti při vývoji a řešení incidentů.

bash — přesměrovat JSON do vestavěného formátovače Pythonu
echo '{"service":"payments","port":8443,"workers":4}' | python3 -m json.tool
# {
#     "service": "payments",
#     "port": 8443,
#     "workers": 4
# }
bash — přesměrovat do jq pro plnohodnotné formátování a filtrování
# Pouze formátování
cat api-response.json | jq .

# Extrahovat vnořené pole
cat api-response.json | jq '.checks.database'

# Filtrovat pole
cat audit-log.json | jq '.[] | select(.severity == "error")'
bash — přesměrovat do minimálního Go main.go přes stdin
# main.go: čte stdin, formátuje, zapisuje stdout
cat <<'EOF' > /tmp/fmt.go
package main
import ("bytes";"encoding/json";"io";"os")
func main() {
    b,_:=io.ReadAll(os.Stdin)
    var buf bytes.Buffer
    json.Indent(&buf,b,"","  ")
    os.Stdout.Write(buf.Bytes())
}
EOF
echo '{"port":8080,"debug":true}' | go run /tmp/fmt.go
Poznámka:gofmt formátuje Go zdrojový kód, nikoli JSON. Nepřesměrovávejte JSON soubory přes gofmt — buď to způsobí chybu, nebo produce nečitelný výstup. Pro JSON soubory použijte jq . nebo python3 -m json.tool.

Vysokovýkonná alternativa — go-json

Pro naprostou většinu Go služeb je encoding/json dostatečně rychlý. Ale pokud se JSON marshalling objeví ve vašem profileru — běžné u vysokovýkonných REST API nebo služeb generujících velké strukturované log řádky na každý požadavek — knihovna go-json je drop-in náhrada 3–5× rychlejší s identickým API.

bash — instalovat go-json
go get github.com/goccy/go-json
Go — nahradit encoding/json za go-json jednou změnou importu
package main

import (
	// Nahraďte toto:
	// "encoding/json"

	// Tímto — identické API, žádné další změny kódu:
	json "github.com/goccy/go-json"

	"fmt"
	"log"
)

type AuditEvent struct {
	RequestID string `json:"request_id"`
	UserID    string `json:"user_id"`
	Action    string `json:"action"`
	ResourceID string `json:"resource_id"`
	IPAddress  string `json:"ip_address"`
	DurationMs int    `json:"duration_ms"`
}

func main() {
	event := AuditEvent{
		RequestID: "req_7d2e91", UserID: "usr_4421",
		Action: "faktura.stazeni", ResourceID: "inv_9a2f",
		IPAddress: "203.0.113.45", DurationMs: 23,
	}
	data, err := json.MarshalIndent(event, "", "  ")
	if err != nil {
		log.Fatalf("marshal: %v", err)
	}
	fmt.Println(string(data))
	// Výstup je identický s encoding/json — liší se pouze rychlost
}

github.com/bytedance/sonic je nejrychlejší dostupná Go JSON knihovna, ale funguje pouze na amd64 a arm64 (používá JIT kompilaci). Použijte go-json když potřebujete přenosnou náhradu; sáhněte po sonic když jste na známé architektuře a potřebujete každou mikrosekundu na horkém místě.

Práce s velkými JSON soubory

json.MarshalIndent i json.Indent vyžadují, aby celý payload byl v paměti. Pro soubory nad 100 MB — datové exporty, audit logy, Kafka consumer payload — použijte json.Decoder pro streamování vstupu a zpracování záznamů po jednom.

Streamování velkého JSON pole s json.Decoder

Go — streamování velkého JSON pole bez načtení do paměti
package main

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

type AuditEvent struct {
	RequestID  string `json:"request_id"`
	UserID     string `json:"user_id"`
	Action     string `json:"action"`
	Severity   string `json:"severity"`
	DurationMs int    `json:"duration_ms"`
}

func processAuditLog(path string) error {
	file, err := os.Open(path)
	if err != nil {
		return fmt.Errorf("open %s: %w", path, err)
	}
	defer file.Close()

	dec := json.NewDecoder(file)

	// Přečíst úvodní '['
	if _, err := dec.Token(); err != nil {
		return fmt.Errorf("read opening token: %w", err)
	}

	var processed int
	for dec.More() {
		var event AuditEvent
		if err := dec.Decode(&event); err != nil {
			return fmt.Errorf("decode event %d: %w", processed, err)
		}
		// Zpracovat jednu událost najednou — konstantní využití paměti
		if event.Severity == "error" {
			fmt.Printf("[ERROR] %s: %s (%dms)
", event.UserID, event.Action, event.DurationMs)
		}
		processed++
	}
	fmt.Printf("Zpracováno %d audit událostí
", processed)
	return nil
}

func main() {
	if err := processAuditLog("audit-2026-03.json"); err != nil {
		log.Fatalf("process audit log: %v", err)
	}
}

NDJSON — jeden JSON objekt na řádek

Go — zpracování NDJSON log streamu řádek po řádku
package main

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

type LogLine struct {
	Timestamp  string `json:"ts"`
	Level      string `json:"level"`
	Service    string `json:"service"`
	Message    string `json:"msg"`
	TraceID    string `json:"trace_id"`
	DurationMs int    `json:"duration_ms,omitempty"`
}

func main() {
	file, err := os.Open("service-2026-03.ndjson")
	if err != nil {
		log.Fatalf("open log: %v", err)
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	scanner.Buffer(make([]byte, 1024*1024), 1024*1024) // 1 MB na řádek

	for scanner.Scan() {
		var line LogLine
		if err := json.Unmarshal(scanner.Bytes(), &line); err != nil {
			continue // přeskočit chybné řádky
		}
		if line.Level == "error" {
			fmt.Printf("%s [%s] %s trace=%s
",
				line.Timestamp, line.Service, line.Message, line.TraceID)
		}
	}
	if err := scanner.Err(); err != nil {
		log.Fatalf("scan: %v", err)
	}
}
Poznámka:Přepněte na streaming, když soubor překročí 100 MB nebo při zpracování neomezených streamů (Kafka consumers, log pipeline, čtení S3 objektů). Načtení 500 MB JSON souboru pomocí os.ReadFile alokuje celý buffer na haldě, způsobí tlak na GC a může vyvolat OOM na kontejnerech s omezenou pamětí.

Časté chyby

Ignorování chybového návratového hodnoty z MarshalIndent

Problém: Zahazování chyby pomocí _ znamená, že neserializovatelná hodnota (kanál, funkce, komplexní číslo) tiše produkuje nil výstup nebo způsobí paniku níže po kódu při volání string(nil).

Řešení: Vždy zkontrolujte chybu. Pokud marshalujete typ, který by měl vždy uspět, panikující log.Fatalf je lepší než tichá ztráta dat.

Before · Go
After · Go
data, _ := json.MarshalIndent(payload, "", "  ")
fmt.Println(string(data)) // prázdný řetězec pokud marshal selhal
data, err := json.MarshalIndent(payload, "", "  ")
if err != nil {
    log.Fatalf("marshal payload: %v", err)
}
fmt.Println(string(data))
Použití fmt.Println místo os.Stdout.Write pro binární výstup

Problém: fmt.Println(string(data)) přidá za JSON znak nového řádku, což poškozuje pipeline, které zpracovávají výstup jako surové bajty — například při přesměrování do jq nebo zápisu do binárního protokolu.

Řešení: Použijte os.Stdout.Write(data) pro binárně čistý výstup. Pokud potřebujete závěrečný nový řádek pro lidské zobrazení, přidejte jej explicitně.

Before · Go
After · Go
data, _ := json.MarshalIndent(cfg, "", "  ")
fmt.Println(string(data)) // přidá navíc nový řádek na konci
data, _ := json.MarshalIndent(cfg, "", "  ")
os.Stdout.Write(data)
os.Stdout.Write([]byte("
")) // explicitní nový řádek jen když je potřeba
Zapomenutý omitempty u pointer polí

Problém: Bez omitempty se nil *string nebo *int pointer serializuje jako "field": null. To odhaluje interní názvy polí a může rozbít přísné JSON schema validátory na straně konzumenta.

Řešení: Přidejte omitempty k pointer polím, která chcete mít ve výstupu chybějící (ne null). nil *T s omitempty vůbec nevytvoří klíč v JSON.

Before · Go
After · Go
type WebhookPayload struct {
    EventID   string  `json:"event_id"`
    ErrorMsg  *string `json:"error_msg"`  // objeví se jako null pokud nil
}
// {"event_id":"evt_3c7f","error_msg":null}
type WebhookPayload struct {
    EventID   string  `json:"event_id"`
    ErrorMsg  *string `json:"error_msg,omitempty"`  // vynecháno pokud nil
}
// {"event_id":"evt_3c7f"}
Použití map[string]interface{} místo struktur

Problém: Unmarshalling do map[string]any ztrácí informace o typech, vyžaduje manuální type assertions a produkuje nedeterministické pořadí klíčů — což ztěžuje JSON diff a porovnávání logů.

Řešení: Definujte strukturu se správnými json tagy. Struktury jsou typově bezpečné, marshallují se rychleji, produkují deterministické pořadí polí odpovídající definici struktury a dělají kód samo-dokumentujícím.

Before · Go
After · Go
var result map[string]any
json.Unmarshal(body, &result)
port := result["port"].(float64) // type assertion povinná, panika při špatném typu
type ServiceStatus struct {
    Service string `json:"service"`
    Port    int    `json:"port"`
    Healthy bool   `json:"healthy"`
}
var result ServiceStatus
json.Unmarshal(body, &result)
port := result.Port // typové, bezpečné, rychlé

encoding/json vs alternativy — rychlé srovnání

Metoda
Formátovaný výstup
Platný JSON
Vlastní typy
Streaming
Vyžaduje instalaci
json.MarshalIndent
✓ via MarshalJSON
Ne (stdlib)
json.Indent
N/A (bytes only)
Ne (stdlib)
json.Encoder
✗ (compact)
✓ via MarshalJSON
Ne (stdlib)
go-json
go get
sonic
go get (amd64/arm64)
jq (CLI)
N/A
Instalace systémem

Použijte json.MarshalIndent pro jakýkoli případ, kdy kontrolujete definici struktury a potřebujete formátovaný výstup — konfigurační soubory, debug logování, test fixture a logování odpovědí API. Použijte json.Indent když už máte surové bajty a jen potřebujete přidat bílé znaky bez okliků přes Go typy. Přejděte na go-json nebo sonic teprve poté, co profilování potvrdí, že JSON marshalling je měřitelným úzkým hrdlem — pro většinu služeb je standardní knihovna více než dostačující.

Často kladené otázky

Jak v Go formátovat JSON?

Zavolejte json.MarshalIndent(v, "", "\t") z balíčku encoding/json — druhý argument je prefix pro každý řádek (zpravidla prázdný) a třetí je odsazení na každou úroveň. Předejte "\t" pro tabulátory nebo " " pro dvě mezery. Externí knihovny nejsou potřeba; encoding/json je součástí standardní knihovny Go.

Go
package main

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

func main() {
	config := map[string]any{
		"service": "payments-api",
		"port":    8443,
		"region":  "eu-central-1",
	}
	data, err := json.MarshalIndent(config, "", "	")
	if err != nil {
		log.Fatalf("marshal: %v", err)
	}
	fmt.Println(string(data))
	// {
	// 	"port": 8443,
	// 	"region": "eu-central-1",
	// 	"service": "payments-api"
	// }
}

Jaký je rozdíl mezi json.Marshal a json.MarshalIndent?

json.Marshal produkuje kompaktní jednořádkový JSON bez mezer — ideální pro přenos po síti, kde záleží na každém bajtu. json.MarshalIndent přijímá dva další řetězcové parametry (prefix a indent) a produkuje odsazený, čitelný výstup. Obě funkce přijímají stejné typy hodnot a vracejí ([]byte, error). Jediná cena MarshalIndent je o něco větší výstup a zanedbatelné CPU náklady na vkládání bílých znaků.

Go
import "encoding/json"

type HealthCheck struct {
	Status  string `json:"status"`
	Version string `json:"version"`
	Uptime  int    `json:"uptime_seconds"`
}

h := HealthCheck{Status: "ok", Version: "1.4.2", Uptime: 86400}

compact, _ := json.Marshal(h)
// {"status":"ok","version":"1.4.2","uptime_seconds":86400}

pretty, _ := json.MarshalIndent(h, "", "  ")
// {
//   "status": "ok",
//   "version": "1.4.2",
//   "uptime_seconds": 86400
// }

Jak formátovat JSON []byte bez unmarshalling do struktury?

Použijte json.Indent(&buf, src, "", "\t"). Tato funkce přijme existující []byte s JSON a zapíše odsazenou verzi do bytes.Buffer — bez definice struktury, bez type assertion, bez okliků přes Go typy. Je to nejrychlejší varianta, když už máte surové JSON bajty, například z těla HTTP odpovědi nebo databázového sloupce.

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

raw := []byte(`{"endpoint":"/api/v2/faktury","page":1,"per_page":50,"total":312}`)

var buf bytes.Buffer
if err := json.Indent(&buf, raw, "", "	"); err != nil {
	log.Fatalf("indent: %v", err)
}
fmt.Println(buf.String())
// {
// 	"endpoint": "/api/v2/faktury",
// 	"page": 1,
// 	"per_page": 50,
// 	"total": 312
// }

Proč json.MarshalIndent vrací chybu?

encoding/json vrací chybu, když hodnotu nelze reprezentovat jako JSON. Nejčastější příčiny: marshalling kanálu, funkce nebo komplexního čísla (ty nemají JSON ekvivalent); struktura implementující MarshalJSON() vracející chybu; nebo mapa s ne-řetězcovými klíči. Důležité: marshalling struktury s neexportovaným nebo nil pointer polem chybu NEVYVOLÁ — ta jsou jednoduše vynechána.

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

// Toto vrátí chybu — kanály nejsou JSON-serializovatelné
ch := make(chan int)
_, err := json.MarshalIndent(ch, "", "	")
fmt.Println(err)
// json: unsupported type: chan int

// Toto je v pořádku — nil pointer pole s omitempty jsou tiše vynechána
type Profile struct {
	ID     string  `json:"id"`
	Avatar *string `json:"avatar,omitempty"`
}
p := Profile{ID: "usr_7b3c"}
data, _ := json.MarshalIndent(p, "", "  ")
// {"id": "usr_7b3c"}  — avatar vynechán, žádná chyba

Jak v Go vyloučit pole z výstupu JSON?

Existují tři způsoby. Za prvé, struct tag json:"-" — pole je vždy vyloučeno bez ohledu na hodnotu. Za druhé, omitempty — pole je vynecháno pouze tehdy, když obsahuje nulovou hodnotu pro svůj typ (nil pointer, prázdný řetězec, 0, false). Za třetí, neexportovaná (malá písmena) pole jsou automaticky vyloučena encoding/json bez jakéhokoli tagu.

Go
type PaymentMethod struct {
	ID           string  `json:"id"`
	Last4        string  `json:"last4"`
	ExpiryMonth  int     `json:"expiry_month"`
	ExpiryYear   int     `json:"expiry_year"`
	CVV          string  `json:"-"`                    // vždy vyloučeno
	BillingName  string  `json:"billing_name,omitempty"` // vynecháno pokud prázdné
	internalRef  string                                 // neexportované — auto vyloučení
}

pm := PaymentMethod{
	ID: "pm_9f3a", Last4: "4242",
	ExpiryMonth: 12, ExpiryYear: 2028,
	CVV: "123", internalRef: "stripe:pm_9f3a",
}
data, _ := json.MarshalIndent(pm, "", "  ")
// CVV, BillingName (prázdný) a internalRef se ve výstupu neobjeví

Jak pracovat s time.Time při JSON marshalingu?

encoding/json serializuje time.Time ve výchozím formátu RFC3339Nano (např. "2026-03-10T14:22:00Z"), kompatibilním s ISO 8601. Pokud potřebujete jiný formát — například Unix epoch jako celé číslo pro legacy API nebo vlastní formát data — implementujte MarshalJSON() na wrapper typu, který embedduje time.Time a vrací požadovaný formát.

Go
import (
	"encoding/json"
	"fmt"
	"time"
)

// Výchozí chování — RFC3339Nano, žádný vlastní kód není potřeba
type AuditEvent struct {
	Action    string    `json:"action"`
	OccurredAt time.Time `json:"occurred_at"`
}

e := AuditEvent{
	Action:    "faktura.zaplacena",
	OccurredAt: time.Date(2026, 3, 10, 14, 22, 0, 0, time.UTC),
}
data, _ := json.MarshalIndent(e, "", "  ")
// {
//   "action": "faktura.zaplacena",
//   "occurred_at": "2026-03-10T14:22:00Z"
// }

// Vlastní: Unix timestamp jako celé číslo
type UnixTime struct{ time.Time }

func (u UnixTime) MarshalJSON() ([]byte, error) {
	return []byte(fmt.Sprintf("%d", u.Unix())), nil
}

Související nástroje

Dostupné také v:PythonJavaScriptBash
JO
James OkaforSystems Engineer

James is a systems engineer and Go enthusiast who focuses on high-performance microservices, command-line tooling, and infrastructure automation. He enjoys the simplicity and explicitness of Go and writes about building fast, reliable backend systems. When not coding he explores distributed systems concepts and contributes to open-source Go libraries.

TM
Tobias MüllerTechnický recenzent

Tobias is a platform engineer who builds developer tooling and internal infrastructure in Go. He has authored several open-source CLI tools and contributes to the Go toolchain ecosystem. He writes about the cobra and urfave/cli frameworks, cross-platform binary distribution, configuration management, and the patterns that make Go an ideal language for building reliable, self-contained command-line utilities.