JSON Formatter Go — MarshalIndent() Guide
Använd det kostnadsfria JSON Formatter & Beautifier direkt i webbläsaren — ingen installation krävs.
Prova JSON Formatter & Beautifier online →När jag arbetar på en Go-mikrotjänst och behöver inspektera ett API-svar eller en konfigurationsfil är kompakt JSON det första hindret — en enda rad med hundratals nästlade fält berättar nästan ingenting vid första anblicken. För att formatera JSON i Go ger standardbiblioteket allt som behövs: json.MarshalIndent ingår i encoding/json, levereras med varje Go-installation och kräver inga tredjepartsberoenden. Den här guiden täcker hela bilden: struct-taggar, anpassade MarshalJSON()-implementationer, json.Indent för omformatering av råa bytes, streaming av stora filer med json.Decoder, och när man bör välja go-json för högbelastade sökvägar — plus CLI-enrader för snabb formatering i terminalen. Alla exempel använder Go 1.21+.
- ✓json.MarshalIndent(v, "", "\t") är standardbibliotek — noll beroenden, ingår i varje Go-installation.
- ✓Struct-taggar json:"field_name,omitempty" styr serialiseringsnycklarna och utelämnar fält med nollvärden.
- ✓Implementera MarshalJSON() på valfri typ för full kontroll över dess JSON-representation.
- ✓json.Indent() omformaterar redan marshallad []byte utan att återanalysera struct:en — snabbare för råa bytes.
- ✓För stora filer (>100 MB): använd json.Decoder med Token() för streaming utan att ladda allt i minnet.
- ✓go-json är ett drop-in-utbyte 3–5× snabbare än encoding/json för högbelastade API:er.
Vad är JSON-formatering?
JSON-formatering — även kallad pretty-printing — omvandlar en kompakt, minifierad JSON-sträng till en lättläst layout med konsekvent indragning och radbrytningar. Underliggande data är identiska; bara blankstegen ändras. Kompakt JSON är optimalt för nätverksöverföring där varje byte räknas; formaterad JSON är optimalt för felsökning, kodgranskning, logginspektering och konfigurationsfiler. Paketet encoding/json i Go hanterar båda lägena med ett enda funktionsanrop — växla mellan kompakt och indragen utmatning genom att välja mellan json.Marshal och json.MarshalIndent.
{"service":"payments","port":8443,"workers":4}{
"service": "payments",
"port": 8443,
"workers": 4
}json.MarshalIndent() — standardbibliotekets tillvägagångssätt
json.MarshalIndent finns i paketet encoding/json, som ingår i Gos standardbibliotek — inget go get behövs. Signaturen är MarshalIndent(v any, prefix, indent string) ([]byte, error): strängen prefix läggs till i början av varje utmatningsrad (lämnas nästan alltid tom), och indent upprepas en gång per nästlingsnivå. Ange "\t" för tabbar eller " " för två mellanslag.
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"
// ]
// }Valet mellan tabbar och mellanslag är till stor del en teamkonvention. Många Go-projekt föredrar tabbar eftersom gofmt (som formaterar Go-källkod) använder tabbar. Två- eller fyramellanslagindragning är vanlig när JSON är avsedd för JavaScript- eller Python-konsumenter. Här är samma struct med båda indragstilarna sida vid sida:
// Tabbindragning — föredragen i Go-nativa verktyg
data, _ := json.MarshalIndent(cfg, "", " ")
// {
// "host": "payments-api.internal",
// "port": 8443
// }
// Två mellanslag — vanligt för API:er med JS/Python-konsumenter
data, _ = json.MarshalIndent(cfg, "", " ")
// {
// "host": "payments-api.internal",
// "port": 8443
// }
// Fyra mellanslag
data, _ = json.MarshalIndent(cfg, "", " ")
// {
// "host": "payments-api.internal",
// "port": 8443
// }json.Marshal(v) när du behöver kompakt utmatning — nätverks-payloads, cachevärden eller vilken sökväg som helst där binärstorleken spelar roll. Det tar samma värdeargument och har samma felssemantik men producerar enradig JSON utan blanksteg.Struct-taggar — styra fältnamn och omitempty
Go struct-taggar är strängliteraler efter fältdeklarationer som talar om förencoding/jsonhur varje fält ska serialiseras. Det finns tre nyckeldirektiv: json:"name" byter namn på fältet i utmatningen, omitempty utelämnar fältet när det innehåller nollvärdet för sin typ, och json:"-" utesluter fältet helt — användbart för lösenord, interna identifierare eller fält som aldrig får lämna servicens gräns.
type UserProfile struct {
ID string `json:"id"`
Email string `json:"email"`
DisplayName string `json:"display_name,omitempty"` // utelämna om tom sträng
AvatarURL *string `json:"avatar_url,omitempty"` // utelämna om nil-pekare
IsAdmin bool `json:"is_admin,omitempty"` // utelämna om false
passwordHash string // oexporterat — auto-utesluten
}
// Användare med alla valfria fält ifyllda
full := UserProfile{
ID: "usr_7b3c", Email: "e.lindqvist@exempel.se",
DisplayName: "Erik Lindqvist", IsAdmin: true,
}
// {
// "id": "usr_7b3c",
// "email": "e.lindqvist@exempel.se",
// "display_name": "Erik Lindqvist",
// "is_admin": true
// }
// Användare utan valfria fält — de utelämnas helt
minimal := UserProfile{ID: "usr_2a91", Email: "a.johansson@exempel.se"}
// {
// "id": "usr_2a91",
// "email": "a.johansson@exempel.se"
// }Taggen json:"-" är rätt val för fält som måste uteslutas ovillkorligt oavsett värde — typiskt hemligheter, interna spårningsfält eller data som är korrekt i minnet men aldrig får serialiseras till ett externt system.
type AuthToken struct {
TokenID string `json:"token_id"`
Subject string `json:"sub"`
IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"`
SigningKey []byte `json:"-"` // serialiseras aldrig
RefreshToken string `json:"-"` // serialiseras aldrig
}
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 och RefreshToken visas aldrigencoding/json oavsett taggar. Du behöver inte lägga till json:"-" på oexporterade fält — uteslutningen är automatisk och kan inte åsidosättas.Anpassad MarshalJSON() — hantera icke-standardtyper
Alla Go-typer kan implementera gränssnittet json.Marshaler genom att definiera en metod MarshalJSON() ([]byte, error). När encoding/json stöter på en sådan typ anropar det metoden istället för sin standardreflexionsbaserade marshalling. Detta är det kanoniska Go-mönstret för domäntyper som behöver en specifik wire-representation — monetära värden, status-enumerationer, anpassade tidsformat eller vilken typ som helst som lagrar data annorlunda än hur de ska serialiseras.
Anpassad typ — Money med öre-till-kronor-konvertering
package main
import (
"encoding/json"
"fmt"
"log"
)
type Money struct {
Amount int64 // lagras i ören för att undvika flyttalsfel
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: "SEK"},
Tax: Money{Amount: 1592, Currency: "SEK"},
Total: Money{Amount: 21492, Currency: "SEK"},
}
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": "SEK", "display": "SEK 199.00" },
// "tax": { "amount": 15.92, "currency": "SEK", "display": "SEK 15.92" },
// "total": { "amount": 214.92, "currency": "SEK", "display": "SEK 214.92" }
// }Status Enum — strängrepresentation
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() och UnmarshalJSON() tillsammans. Om du bara implementerar marshalling kommer en round-trip av typen genom JSON (serialisera → lagra → deserialisera) tyst att förlora struktur eller returnera fel typ. Paret bildar ett kontrakt att typen klarar en JSON-round-trip.UUID — serialisera som sträng
Gos standardbibliotek har ingen UUID-typ. Det vanligaste valet är github.com/google/uuid, som redan implementerar MarshalJSON() och serialiseras som en quoted RFC-4122-sträng. Om du använder en rå [16]byte eller en anpassad ID-typ, implementera gränssnittet själv för att undvika base64-kodade binära blobbar i JSON-utmatningen.
import (
"encoding/json"
"fmt"
"github.com/google/uuid"
)
type AuditEvent struct {
EventID uuid.UUID `json:"event_id"` // serialiseras som "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 utan en anpassad typ kodar encoding/json dem som en base64-sträng — t.ex. "VQ6EAOKbQdSnFkRmVUQAAA==". Använd alltid en ordentlig UUID-typ eller implementera MarshalJSON() för att mata ut det kanoniska streckformat.Parameterreferens för json.MarshalIndent()
Funktionssignaturen är func MarshalIndent(v any, prefix, indent string) ([]byte, error). Både prefix och indent är strängliteraler — det finns ingen numerisk genväg som Pythons indent=4.
Vanliga struct-taggalternativ:
json.Indent() — omformatera befintliga JSON-bytes
När du redan har en []byte med JSON — säg från ett HTTP-svarskropp, en Postgres- jsonb-kolumn eller en fil läst med os.ReadFile — behöver du inte definiera en struct och unmarshalla innan du kan pretty-printa. json.Indent omformaterar de råa bytes direkt genom att skriva indragen utmatning till en bytes.Buffer.
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
)
func main() {
// Simulerar en rå JSON-payload från en uppströmstjänst
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
// }Ett mönster jag använder regelbundet i mikrotjänster: anropa json.Indent innan skrivning till strukturerade loggar — det tillför försumbar overhead och gör loggposter mycket lättare att läsa under en incident. Funktionen är särskilt användbar för att logga HTTP-svar, pretty-printa lagrade JSON-strängar och format-on-read-pipelines där struct-definitionen inte är tillgänglig.
func logResponse(logger *slog.Logger, statusCode int, body []byte) {
var pretty bytes.Buffer
if err := json.Indent(&pretty, body, "", " "); err != nil {
// Body är inte giltig JSON — logga råt
logger.Debug("upstream response", "status", statusCode, "body", string(body))
return
}
logger.Debug("upstream response", "status", statusCode, "body", pretty.String())
}json.Indent validerar INTE JSON fullt ut — bara det som strukturellt krävs för att infoga blanksteg. För fullständig syntaxvalidering, anropa json.Valid(data) först och hantera false-fallet innan indragningsförsöket.Formatera JSON från en fil och ett HTTP-svar
Två av de vanligaste verkliga scenarierna i Go-tjänster är formatering av JSON läst från en fil på disk (konfigurationsfiler, fixture-data, migreringsfrön) och pretty-printing av HTTP-svarskroppar för debug-loggning eller testpåståenden. Båda följer samma mönster: läs bytes, anropa json.Indent eller unmarshalla och sedan json.MarshalIndent, skriv tillbaka eller logga.
Läs fil → formatera → skriv tillbaka
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/databas.json"); err != nil {
log.Fatalf("format config: %v", err)
}
fmt.Println("config/databas.json formaterades utan problem")
}HTTP-svar → Decode → Pretty-Print för debug-loggning
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
// }Rå svarskropp → json.Indent (ingen struct krävs)
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())
}Pretty-printa JSON från ett HTTP-svar i Go
De två tillvägagångssätten ovan täcker de vanligaste fallen: avkoda till en typad struct och anropa json.MarshalIndent (bäst när du behöver validera eller inspektera specifika fält), eller läs de råa kroppsbyten med io.ReadAll och anropa json.Indent direkt (bäst för snabb debug-loggning när ingen struct-definition finns tillgänglig). Raw-bytes-metoden är enklare men ger inte Go-typsäkerhet eller fälttillgång — det är en rent visuell transformation. Båda metoderna hanterar stora svarskroppar korrekt så länge hela kroppen ryms i minnet.
// Mönster A: typat decode → MarshalIndent // Använd när du behöver inspektera eller validera specifika fält var result map[string]any json.NewDecoder(resp.Body).Decode(&result) pretty, _ := json.MarshalIndent(result, "", " ") fmt.Println(string(pretty)) // Mönster B: råa bytes → json.Indent // För snabb debug-loggning — ingen struct-definition behövs body, _ := io.ReadAll(resp.Body) var buf bytes.Buffer json.Indent(&buf, body, "", " ") fmt.Println(buf.String())
JSON-formatering på kommandoraden i Go-projekt
Ibland behöver man formatera en JSON-payload direkt i terminalen utan att skriva ett Go-program. Det här är de enrader jag har i muskelminnet under utveckling och incidenthantering.
echo '{"service":"payments","port":8443,"workers":4}' | python3 -m json.tool
# {
# "service": "payments",
# "port": 8443,
# "workers": 4
# }# Enbart formatering cat api-response.json | jq . # Extrahera ett nästlat fält cat api-response.json | jq '.checks.database' # Filtrera en array cat audit-log.json | jq '.[] | select(.severity == "error")'
# main.go: läser stdin, formaterar, skriver 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 formaterar Go-källkod, inte JSON. Skicka inte JSON-filer genom gofmt — det ger antingen ett fel eller oläslig utmatning. Använd jq . eller python3 -m json.tool för JSON-filer.Högpresterande alternativ — go-json
För den stora majoriteten Go-tjänster är encoding/json tillräckligt snabbt. Men om JSON-marshalling dyker upp i din profilerare — vanligt i högbelastade REST API:er eller tjänster som sänder ut stora strukturerade loggrader vid varje förfrågan — är biblioteket go-json ett drop-in-utbyte som är 3–5× snabbare med identisk API-yta.
go get github.com/goccy/go-json
package main
import (
// Ersätt detta:
// "encoding/json"
// Med detta — identiskt API, inga andra kodändringar:
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.nedladdning", 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))
// Utmatningen är identisk med encoding/json — bara hastigheten skiljer sig
}github.com/bytedance/sonic är det snabbaste tillgängliga Go JSON-biblioteket, men fungerar bara på amd64 och arm64 (det använder JIT-kompilering). Använd go-json när du behöver ett portabelt drop-in; välj sonic när du är på en känd arkitektur och behöver varje mikrosekund på en varm kodstig.
Arbeta med stora JSON-filer
json.MarshalIndent och json.Indent kräver båda att hela payloaden finns i minnet. För filer över 100 MB — dataexporter, auditloggar, Kafka-consumer-payloads — använd json.Decoder för att strömma indata och bearbeta poster en i taget.
Strömma en stor JSON-array med 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)
// Läs inledande '['
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)
}
// Bearbeta en händelse i taget — konstant minnesanvändning
if event.Severity == "error" {
fmt.Printf("[ERROR] %s: %s (%dms)
", event.UserID, event.Action, event.DurationMs)
}
processed++
}
fmt.Printf("Bearbetade %d audithändelser
", processed)
return nil
}
func main() {
if err := processAuditLog("audit-2026-03.json"); err != nil {
log.Fatalf("process audit log: %v", err)
}
}NDJSON — ett JSON-objekt per rad
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 per rad
for scanner.Scan() {
var line LogLine
if err := json.Unmarshal(scanner.Bytes(), &line); err != nil {
continue // hoppa över felaktiga rader
}
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 allokerar hela bufferten på heap:en, utlöser GC-tryck och kan orsaka OOM på minneskapacitetsbegränsade containrar.Vanliga misstag
Problem: Att kasta felet med _ innebär att ett icke-serialiserbart värde (en kanal, en funktion, ett komplext tal) tyst producerar nil-utmatning eller orsakar panik nedströms när du anropar string(nil).
Lösning: Kontrollera alltid felet. Om du marshallär en typ som alltid borde lyckas är en panikande log.Fatalf bättre än tyst dataförlust.
data, _ := json.MarshalIndent(payload, "", " ") fmt.Println(string(data)) // tom sträng om marshal misslyckades
data, err := json.MarshalIndent(payload, "", " ")
if err != nil {
log.Fatalf("marshal payload: %v", err)
}
fmt.Println(string(data))Problem: fmt.Println(string(data)) lägger till ett radbytetecken efter JSON, vilket korrumperar pipelines som behandlar utmatningen som råa bytes — till exempel vid pipning till jq eller skrivning till ett binärt protokoll.
Lösning: Använd os.Stdout.Write(data) för binärren utmatning. Om du behöver ett avslutande radbryte för mänsklig visning, lägg till det explicit.
data, _ := json.MarshalIndent(cfg, "", " ") fmt.Println(string(data)) // lägger till extra radbryte i slutet
data, _ := json.MarshalIndent(cfg, "", " ")
os.Stdout.Write(data)
os.Stdout.Write([]byte("
")) // explicit radbryte bara när det behövsProblem: Utan omitempty serialiseras en nil *string eller *int-pekare som "field": null. Det exponerar interna fältnamn och kan bryta strikta JSON-schemavaliderare på konsumentsidan.
Lösning: Lägg till omitempty på pekarfält som du vill ska vara frånvarande (inte null) i utmatningen. En nil *T med omitempty producerar ingen nyckel alls i JSON.
type WebhookPayload struct {
EventID string `json:"event_id"`
ErrorMsg *string `json:"error_msg"` // visas som null om nil
}
// {"event_id":"evt_3c7f","error_msg":null}type WebhookPayload struct {
EventID string `json:"event_id"`
ErrorMsg *string `json:"error_msg,omitempty"` // utelämnad om nil
}
// {"event_id":"evt_3c7f"}Problem: Unmarshalling till map[string]any förlorar typinformation, kräver manuella type assertions och producerar icke-deterministisk nyckelordning — vilket gör JSON-diff och loggjämförelser svårare.
Lösning: Definiera en struct med rätt json-taggar. Struct:ar är typsäkra, marshallar snabbare, producerar deterministisk fältordning som matchar struct-definitionen och gör koden självdokumenterande.
var result map[string]any json.Unmarshal(body, &result) port := result["port"].(float64) // type assertion krävs, panik vid fel typ
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 // typad, säker, snabbencoding/json vs alternativ — snabb jämförelse
Använd json.MarshalIndent i alla fall där du kontrollerar struct-definitionen och behöver formaterad utmatning — konfigurationsfiler, debug-loggning, testfixtures och API-svarloggning. Använd json.Indent när du redan har råa bytes och bara behöver lägga till blanksteg utan omväg via Go-typer. Byt till go-json eller sonic bara efter att profilering har bekräftat att JSON-marshalling är ett mätbart flaskhals — för de flesta tjänster är standardbiblioteket mer än tillräckligt.
Vanliga frågor
Hur formaterar man JSON i Go?
Anropa json.MarshalIndent(v, "", "\t") från paketet encoding/json — det andra argumentet är ett prefix per rad (vanligtvis tomt) och det tredje är indraget per nivå. Ange "\t" för tabbar eller " " för två mellanslag. Inga externa bibliotek behövs; encoding/json ingår i Gos standardbibliotek.
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
config := map[string]any{
"service": "payments-api",
"port": 8443,
"region": "eu-north-1",
}
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
log.Fatalf("marshal: %v", err)
}
fmt.Println(string(data))
// {
// "port": 8443,
// "region": "eu-north-1",
// "service": "payments-api"
// }
}Vad är skillnaden mellan json.Marshal och json.MarshalIndent?
json.Marshal producerar kompakt JSON på en rad utan blanksteg — perfekt för nätverksöverföring där varje byte räknas. json.MarshalIndent tar två extra strängparametrar (prefix och indent) och producerar indragen, lättläst utmatning. Båda funktionerna tar samma värdetyper och returnerar ([]byte, error). Den enda kostnaden för MarshalIndent är något mer utmatningsbytes och en försumbar CPU-overhead för att infoga blanksteg.
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
// }Hur formaterar man en JSON []byte utan att unmarshalla till struct?
Använd json.Indent(&buf, src, "", "\t"). Funktionen tar en befintlig []byte med JSON och skriver den indragna versionen till en bytes.Buffer — utan struct-definition, utan type assertion, utan omväg via Go-typer. Det är det snabbaste alternativet när du redan har råa JSON-bytes, till exempel från ett HTTP-svarskropp eller en databaskolumn.
import (
"bytes"
"encoding/json"
"fmt"
"log"
)
raw := []byte(`{"endpoint":"/api/v2/fakturor","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/fakturor",
// "page": 1,
// "per_page": 50,
// "total": 312
// }Varför returnerar json.MarshalIndent ett fel?
encoding/json returnerar ett fel när värdet inte kan representeras som JSON. De vanligaste orsakerna är: marshalling av en kanal, en funktion eller ett komplext tal (dessa saknar JSON-motsvarighet); en struct som implementerar MarshalJSON() och returnerar ett fel; eller en map med icke-strängnycklar. Viktigt: marshalling av en struct med ett oexporterat eller nil-pekarfält orsakar INTE ett fel — de utelämnas helt enkelt.
import (
"encoding/json"
"fmt"
)
// Detta returnerar ett fel — kanaler är inte JSON-serialiserbara
ch := make(chan int)
_, err := json.MarshalIndent(ch, "", " ")
fmt.Println(err)
// json: unsupported type: chan int
// Detta är okej — nil-pekarfält med omitempty utelämnas tyst
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 utelämnad, inget felHur utesluter man ett fält från JSON-utmatningen i Go?
Det finns tre sätt. För det första, struct-taggen json:"-" — fältet utesluts alltid oavsett värde. För det andra, omitempty — fältet utelämnas bara när det innehåller nollvärdet för sin typ (nil-pekare, tom sträng, 0, false). För det tredje, oexporterade (gemena) fält utesluts automatiskt av encoding/json utan några taggar.
type PaymentMethod struct {
ID string `json:"id"`
Last4 string `json:"last4"`
ExpiryMonth int `json:"expiry_month"`
ExpiryYear int `json:"expiry_year"`
CVV string `json:"-"` // alltid utesluten
BillingName string `json:"billing_name,omitempty"` // utelämnad om tom
internalRef string // oexporterad — auto-utesluten
}
pm := PaymentMethod{
ID: "pm_9f3a", Last4: "4242",
ExpiryMonth: 12, ExpiryYear: 2028,
CVV: "123", internalRef: "stripe:pm_9f3a",
}
data, _ := json.MarshalIndent(pm, "", " ")
// CVV, BillingName (tom) och internalRef visas inte i utmatningenHur hanterar man time.Time vid JSON-marshalling?
encoding/json marshallär time.Time till RFC3339Nano-format som standard (t.ex. "2026-03-10T14:22:00Z"), vilket är ISO 8601-kompatibelt. Om du behöver ett annat format — till exempel Unix epoch-heltal för ett äldre API eller en anpassad datumsträng — implementera MarshalJSON() på en wrapper-typ som bäddar in time.Time och returnerar önskat format.
import (
"encoding/json"
"fmt"
"time"
)
// Standardbeteende — RFC3339Nano, ingen anpassad kod behövs
type AuditEvent struct {
Action string `json:"action"`
OccurredAt time.Time `json:"occurred_at"`
}
e := AuditEvent{
Action: "faktura.betald",
OccurredAt: time.Date(2026, 3, 10, 14, 22, 0, 0, time.UTC),
}
data, _ := json.MarshalIndent(e, "", " ")
// {
// "action": "faktura.betald",
// "occurred_at": "2026-03-10T14:22:00Z"
// }
// Anpassat: Unix-tidsstämpel som heltal
type UnixTime struct{ time.Time }
func (u UnixTime) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("%d", u.Unix())), nil
}Relaterade verktyg
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.