JSON Formatter Go — Průvodce MarshalIndent()
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.
{"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.
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:
// 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
// }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.
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ů.
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í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
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
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"
// }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.
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"
// }[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.
Běžné volby struct tagů:
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.
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.
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())
}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
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í
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)
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.
// 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ů.
echo '{"service":"payments","port":8443,"workers":4}' | python3 -m json.tool
# {
# "service": "payments",
# "port": 8443,
# "workers": 4
# }# 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")'
# 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.gogofmt 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.
go get github.com/goccy/go-json
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
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
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)
}
}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
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.
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))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ě.
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řebaProblé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.
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"}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.
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í
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.
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ů.
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.
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.
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á chybaJak 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.
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.
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
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.
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.