JSON Formatter Go β MarshalIndent() Handleiding
Gebruik de gratis JSON Formatter & Beautifier direct in je browser β geen installatie nodig.
JSON Formatter & Beautifier online uitproberen βWanneer ik aan een Go-microservice werk en een API-respons of configuratiebestand wil inspecteren, is gecomprimeerde JSON het eerste obstakel β een enkele regel met honderden geneste velden vertelt me op het eerste gezicht bijna niets. Om JSON te formatteren in Go geeft de standaardbibliotheek je alles wat je nodig hebt: json.MarshalIndent zit ingebakken in encoding/json, wordt meegeleverd met elke Go-installatie en vereist nul externe afhankelijkheden. Deze gids behandelt het volledige plaatje: struct tags, aangepaste MarshalJSON()-implementaties, json.Indent voor het herformatteren van ruwe bytes, streamen van grote bestanden met json.Decoder, wanneer je go-json inzet voor hoge-doorvoerpaden, plus CLI-oneliners voor snel formatteren in de terminal. Alle voorbeelden gebruiken Go 1.21+.
- βjson.MarshalIndent(v, "", "\t") is standaardbibliotheek β nul afhankelijkheden, meegeleverd met elke Go-installatie.
- βStruct tags json:"field_name,omitempty" bepalen serialisatiesleutels en laten zero-value-velden weg uit de uitvoer.
- βImplementeer MarshalJSON() op elk type om de JSON-representatie volledig te beheersen.
- βjson.Indent() herformatteert al gemarshalede []byte zonder de struct opnieuw te parsen β sneller voor ruwe bytes.
- βVoor grote bestanden (>100 MB): gebruik json.Decoder met Token() om te streamen zonder alles in geheugen te laden.
- βgo-json is een drop-in vervanging 3β5Γ sneller dan encoding/json voor hoge-doorvoer-API's.
Wat is JSON-formattering?
JSON-formattering β ook wel pretty-printing genoemd β transformeert een compacte, geminimaliseerde JSON-string naar een voor mensen leesbare indeling met consistente inspringing en regelafbrekingen. De onderliggende data is identiek; alleen de witruimte verandert. Compacte JSON is optimaal voor netwerkoverdracht waarbij elke byte telt; geformatteerde JSON is optimaal voor debuggen, code review, loginspectie en het schrijven van configuratiebestanden. Het encoding/json-pakket van Go verwerkt beide modi met één functieaanroep β schakel tussen compact en ingesprongen uitvoer door te kiezen tussen json.Marshal en json.MarshalIndent.
{"service":"payments","port":8443,"workers":4}{
"service": "payments",
"port": 8443,
"workers": 4
}json.MarshalIndent() β De standaardbibliotheek-aanpak
json.MarshalIndent bevindt zich in het encoding/json-pakket, dat deel uitmaakt van de Go-standaardbibliotheek β geen go get nodig. De signatuur is MarshalIndent(v any, prefix, indent string) ([]byte, error): de prefix-string wordt voor elke uitvoeregel geplaatst (bijna altijd leeg gelaten) en indent wordt eenmaal per nestingniveau herhaald. Geef "\t" voor tabs of " " voor twee spaties.
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"
// ]
// }De keuze tussen tabs en spaties is vooral een teamconventie. Veel Go-projecten geven de voorkeur aan tabs omdat gofmt (dat Go-broncode opmaakt) ook tabs gebruikt. Twee of vier spaties zijn gebruikelijk wanneer de JSON bestemd is voor een JavaScript- of Python-consumer. Hier is dezelfde struct met beide inspring-stijlen naast elkaar:
// Tabinspringing β voorkeur in Go-native tooling
data, _ := json.MarshalIndent(cfg, "", " ")
// {
// "host": "payments-api.internal",
// "port": 8443
// }
// Twee spaties β gangbaar voor API's met JS/Python-consumers
data, _ = json.MarshalIndent(cfg, "", " ")
// {
// "host": "payments-api.internal",
// "port": 8443
// }
// Vier spaties
data, _ = json.MarshalIndent(cfg, "", " ")
// {
// "host": "payments-api.internal",
// "port": 8443
// }json.Marshal(v) wanneer je compacte uitvoer nodig hebt β netwerklading, cachewaarden, of elk pad waarbij de binaire grootte telt. Het accepteert hetzelfde waardeargument en heeft dezelfde foutsemantieken, maar produceert JSON op één regel zonder witruimte.Struct Tags β Veldnamen en Omitempty beheren
Go struct tags zijn stringliteralen die achter velddeclaraties worden geplaatst enencoding/jsonvertellen hoe elk veld geserialiseerd moet worden. Er zijn drie sleutelrichtlijnen: json:"name" hernoemt het veld in de uitvoer, omitempty laat het veld weg wanneer het de nulwaarde voor zijn type heeft, en json:"-" sluit het veld volledig uit β handig voor wachtwoorden, interne identificatoren, of velden die de servicegrens nooit mogen oversteken.
type UserProfile struct {
ID string `json:"id"`
Email string `json:"email"`
DisplayName string `json:"display_name,omitempty"` // weglaten indien lege string
AvatarURL *string `json:"avatar_url,omitempty"` // weglaten indien nil-pointer
IsAdmin bool `json:"is_admin,omitempty"` // weglaten indien false
passwordHash string // unexported β automatisch uitgesloten
}
// Gebruiker met alle optionele velden ingevuld
full := UserProfile{
ID: "usr_7b3c", Email: "ops@example.com",
DisplayName: "Jan de Vries", IsAdmin: true,
}
// {
// "id": "usr_7b3c",
// "email": "ops@example.com",
// "display_name": "Jan de Vries",
// "is_admin": true
// }
// Gebruiker zonder optionele velden β volledig weggelaten
minimal := UserProfile{ID: "usr_2a91", Email: "dev@example.com"}
// {
// "id": "usr_2a91",
// "email": "dev@example.com"
// }De json:"-" tag is de juiste keuze voor velden die onvoorwaardelijk uitgesloten moeten worden, ongeacht hun waarde β doorgaans geheimen, interne volgvelden, of data die in geheugen correct is maar nooit naar een extern systeem geserialiseerd mag worden.
type AuthToken struct {
TokenID string `json:"token_id"`
Subject string `json:"sub"`
IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"`
SigningKey []byte `json:"-"` // nooit geserialiseerd
RefreshToken string `json:"-"` // nooit geserialiseerd
}
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 en RefreshToken verschijnen nooitencoding/json, ongeacht eventuele tags. Je hoeft json:"-" niet aan unexported velden toe te voegen β de uitsluiting is automatisch en kan niet worden overschreven.Aangepaste MarshalJSON() β Niet-standaard typen verwerken
Elk Go-type kan de json.Marshaler-interface implementeren door een MarshalJSON() ([]byte, error)-methode te definiΓ«ren. Wanneer encoding/json zo'n type tegenkomt, roept het de methode aan in plaats van zijn standaard reflectie-gebaseerde marshaling. Dit is het canonieke Go-patroon voor domeintypen die een specifieke wire-representatie nodig hebben β monetaire waarden, status-enums, aangepaste tijdformaten, of elk type dat data anders opslaat dan hoe het geserialiseerd moet worden.
Aangepast type β Money met centen-naar-decimalen-omrekening
package main
import (
"encoding/json"
"fmt"
"log"
)
type Money struct {
Amount int64 // opgeslagen in centen om drijvende-komma-drift te vermijden
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: "EUR"},
Tax: Money{Amount: 1592, Currency: "EUR"},
Total: Money{Amount: 21492, Currency: "EUR"},
}
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": "EUR", "display": "EUR 199.00" },
// "tax": { "amount": 15.92, "currency": "EUR", "display": "EUR 15.92" },
// "total": { "amount": 214.92, "currency": "EUR", "display": "EUR 214.92" }
// }Status-enum β stringrepresentatie
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() als UnmarshalJSON() samen. Als je alleen marshaling implementeert, zal het rondsturen van het type via JSON (serialiseren β opslaan β deserialiseren) stilletjes structuur verliezen of het verkeerde type teruggeven. Het paar vormt een contract dat het type een JSON-rondreis kan overleven.UUID β serialiseren als string
Go's standaardbibliotheek heeft geen UUID-type. De meest gebruikte keuze is github.com/google/uuid, dat al MarshalJSON() implementeert en serialiseert als een geciteerde RFC 4122-string. Als je een ruwe [16]byte of een aangepast ID-type gebruikt, implementeer dan zelf de interface om base64-gecodeerde binaire blobs in je JSON-uitvoer te vermijden.
import (
"encoding/json"
"fmt"
"github.com/google/uuid"
)
type AuditEvent struct {
EventID uuid.UUID `json:"event_id"` // serialiseert als "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 zonder aangepast type, codeert encoding/json ze als base64-string β bijv. "VQ6EAOKbQdSnFkRmVUQAAA==". Gebruik altijd een correct UUID-type of implementeer MarshalJSON() om het canonieke koppelteken-formaat te produceren.json.MarshalIndent() Parameters referentie
De functiesignatuur is func MarshalIndent(v any, prefix, indent string) ([]byte, error). Zowel prefix als indent zijn stringliteralen β er is geen numerieke afkorting zoals Python's indent=4.
Veelgebruikte struct tag-opties:
json.Indent() β Bestaande JSON-bytes herformatteren
Wanneer je al een []byte van JSON hebt β zeg van een HTTP-responsebody, een Postgres jsonb-kolom, of een bestand ingelezen met os.ReadFile β hoef je geen struct te definiΓ«ren en te unmarshalen voordat je mooi kunt afdrukken. json.Indent herformatteert de ruwe bytes direct door ingesprongen uitvoer te schrijven naar een bytes.Buffer.
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
)
func main() {
// Een ruwe JSON-payload van een upstream-dienst simuleren
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
// }Een patroon dat ik vaak gebruik in microservices is json.Indent aanroepen voordat ik naar gestructureerde logs schrijf β het voegt verwaarloosbare overhead toe en maakt logvermeldingen veel gemakkelijker te lezen tijdens een incident. De functie is bijzonder nuttig voor het loggen van HTTP-responsen, het mooi afdrukken van opgeslagen JSON-strings, en format-on-read-pijplijnen waarbij de structdefinitie niet beschikbaar is.
func logResponse(logger *slog.Logger, statusCode int, body []byte) {
var pretty bytes.Buffer
if err := json.Indent(&pretty, body, "", " "); err != nil {
// Body is geen geldige JSON β log ruw
logger.Debug("upstream response", "status", statusCode, "body", string(body))
return
}
logger.Debug("upstream response", "status", statusCode, "body", pretty.String())
}json.Indent valideert de JSON NIET volledig voorbij wat structureel nodig is om witruimte in te voegen. Voor volledige syntaxvalidatie, roep eerst json.Valid(data) aan en verwerk het false-geval voordat je probeert te indenteren.JSON formatteren vanuit een bestand en HTTP-respons
Twee van de meest voorkomende realistische scenario's in Go-services zijn het formatteren van JSON gelezen uit een bestand op schijf (configuratiebestanden, fixture-data, migratiezaad) en het mooi afdrukken van HTTP-responsbodies voor debuglogboekregistratie of testbeweringen. Beide volgen hetzelfde patroon: bytes lezen, aanroepen json.Indent of unmarshalen dan json.MarshalIndent, terugschrijven of loggen.
Bestand lezen β Formatteren β Terugschrijven
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/database.json"); err != nil {
log.Fatalf("format config: %v", err)
}
fmt.Println("config/database.json succesvol opgemaakt")
}HTTP-respons β Decoderen β Pretty-print voor debuglog
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("Gezondheidscontrole (%d):
%s
", resp.StatusCode, string(pretty))
}
// Gezondheidscontrole (200):
// {
// "status": "ok",
// "version": "1.4.2",
// "checks": {
// "database": "ok",
// "cache": "ok",
// "queue": "degraded"
// },
// "uptime_seconds": 172800
// }Ruwe responsebody β json.Indent (geen Struct vereist)
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())
}JSON mooi afdrukken vanuit een HTTP-respons in Go
De twee bovenstaande benaderingen dekken de meest voorkomende gevallen: decoderen naar een getypeerde struct en dan json.MarshalIndent aanroepen (het beste wanneer je specifieke velden moet valideren of inspecteren), of de ruwe body-bytes lezen met io.ReadAll en json.Indent direct aanroepen (het beste voor snel debugloggen wanneer je geen structdefinitie bij de hand hebt). De ruwe-bytes-benadering is eenvoudiger maar geeft je geen Go-typeveiligheid of veldtoegang β het is puur een weergavetransformatie. Beide benaderingen verwerken grote responsbodies correct zolang de volledige body in het geheugen past.
// Patroon A: getypeerde decodering β MarshalIndent // Gebruik dit wanneer je specifieke velden moet inspecteren of valideren var result map[string]any json.NewDecoder(resp.Body).Decode(&result) pretty, _ := json.MarshalIndent(result, "", " ") fmt.Println(string(pretty)) // Patroon B: ruwe bytes β json.Indent // Gebruik voor snel debugloggen β geen structdefinitie vereist body, _ := io.ReadAll(resp.Body) var buf bytes.Buffer json.Indent(&buf, body, "", " ") fmt.Println(buf.String())
Opdrachtregelformattering van JSON in Go-projecten
Soms moet je een JSON-payload direct in de terminal formatteren zonder een Go-programma te schrijven. Dit zijn de oneliners die ik uit mijn hoofd ken tijdens ontwikkeling en incidentrespons.
echo '{"service":"payments","port":8443,"workers":4}' | python3 -m json.tool
# {
# "service": "payments",
# "port": 8443,
# "workers": 4
# }# Alleen formatteren cat api-response.json | jq . # Een genest veld extraheren cat api-response.json | jq '.checks.database' # Een array filteren cat audit-log.json | jq '.[] | select(.severity == "error")'
# main.go: leest stdin, formatteert, schrijft 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 formatteert Go-broncode, niet JSON. Pipe JSON-bestanden niet door gofmt β het zal ofwel een fout geven of onherkenbare uitvoer produceren. Gebruik jq . of python3 -m json.tool voor JSON-bestanden.Hoogperformant alternatief β go-json
Voor de overgrote meerderheid van Go-services is encoding/json snel genoeg. Maar als JSON-marshaling opduikt in je profiler β veelvoorkomend in hoge-doorvoer REST API's of services die per verzoek grote gestructureerde logregels uitstoten β is de go-json-bibliotheek een drop-in vervanging die 3β5Γ sneller is met een identiek API-oppervlak.
go get github.com/goccy/go-json
package main
import (
// Vervang dit:
// "encoding/json"
// Door dit β identieke API, geen andere codewijzigingen:
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: "invoice.download", 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))
// Uitvoer is identiek aan encoding/json β alleen de snelheid verschilt
}github.com/bytedance/sonic is de snelste Go JSON-bibliotheek beschikbaar, maar draait alleen op amd64 en arm64 (het gebruikt JIT-compilatie). Gebruik go-json wanneer je een draagbare drop-in nodig hebt; grijp naar sonic wanneer je op een bekende architectuur draait en elke microseconde in een hot path nodig hebt.
Werken met grote JSON-bestanden
Zowel json.MarshalIndent als json.Indent vereisen dat de volledige payload in het geheugen zit. Voor bestanden boven 100 MB β data-exports, auditlogs, Kafka-consumerpayloads β gebruik json.Decoder om de invoer te streamen en records één voor één te verwerken.
Een grote JSON-array streamen met 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)
// Lees de openende '['
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)
}
// Verwerk één event tegelijk β constant geheugengebruik
if event.Severity == "error" {
fmt.Printf("[ERROR] %s: %s (%dms)
", event.UserID, event.Action, event.DurationMs)
}
processed++
}
fmt.Printf("%d auditgebeurtenissen verwerkt
", processed)
return nil
}
func main() {
if err := processAuditLog("audit-2026-03.json"); err != nil {
log.Fatalf("process audit log: %v", err)
}
}NDJSON β één JSON-object per regel
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 regel
for scanner.Scan() {
var line LogLine
if err := json.Unmarshal(scanner.Bytes(), &line); err != nil {
continue // sla misvormde regels over
}
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 wijst die volledige buffer toe op de heap, veroorzaakt GC-druk en kan OOM veroorzaken op geheugenbeperkte containers.Veelgemaakte fouten
Probleem: De fout verwijderen met _ betekent dat een niet-serialiseerbare waarde (een kanaal, functie, complex getal) stilletjes nil-uitvoer produceert of downstream een panic veroorzaakt wanneer je string(nil) aanroept.
Oplossing: Controleer altijd de fout. Als je een type marshaalt dat altijd moet slagen, is een panikerend log.Fatalf beter dan stil gegevensverlies.
data, _ := json.MarshalIndent(payload, "", " ") fmt.Println(string(data)) // lege string als marshal mislukte
data, err := json.MarshalIndent(payload, "", " ")
if err != nil {
log.Fatalf("marshal payload: %v", err)
}
fmt.Println(string(data))Probleem: fmt.Println(string(data)) voegt een newline-teken toe na de JSON, wat pijplijnen corrumpeert die de uitvoer als ruwe bytes behandelen β bijvoorbeeld bij pipen naar jq of schrijven naar een binair protocol.
Oplossing: Gebruik os.Stdout.Write(data) voor binair-schone uitvoer. Als je een afsluitende newline nodig hebt voor menselijke weergave, voeg die dan expliciet toe.
data, _ := json.MarshalIndent(cfg, "", " ") fmt.Println(string(data)) // voegt een extra newline aan het einde toe
data, _ := json.MarshalIndent(cfg, "", " ")
os.Stdout.Write(data)
os.Stdout.Write([]byte("
")) // expliciete newline alleen wanneer nodigProbleem: Zonder omitempty serialiseert een nil *string of *int-pointer als "field": null. Dit legt interne veldnamen bloot en kan strenge JSON-schemavalidatoren aan de consumentenzijde breken.
Oplossing: Voeg omitempty toe aan pointervelden die je wilt weglaten (niet null) in de uitvoer. Een nil *T met omitempty produceert helemaal geen sleutel in de JSON.
type WebhookPayload struct {
EventID string `json:"event_id"`
ErrorMsg *string `json:"error_msg"` // verschijnt als null wanneer nil
}
// {"event_id":"evt_3c7f","error_msg":null}type WebhookPayload struct {
EventID string `json:"event_id"`
ErrorMsg *string `json:"error_msg,omitempty"` // weggelaten wanneer nil
}
// {"event_id":"evt_3c7f"}Probleem: Unmarshalen naar map[string]any verliest type-informatie, vereist handmatige type-assertions en produceert niet-deterministische sleutelvolgorde β waardoor JSON-diffs en logvergelijkingen moeilijker worden.
Oplossing: Definieer een struct met de juiste json-tags. Structs zijn typeveilig, marshalen sneller, produceren deterministische veldvolgorde die overeenkomt met de structdefinitie en maken de code zelfdocumenterend.
var result map[string]any json.Unmarshal(body, &result) port := result["port"].(float64) // type-assertion vereist, paniceert bij verkeerd type
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 // getypeerd, veilig, snelencoding/json vs. alternatieven β Snelle vergelijking
Gebruik json.MarshalIndent voor elk geval waarbij je de structdefinitie beheert en geformatteerde uitvoer nodig hebt β configuratiebestanden, debuglogboekregistratie, testfixtures en API-responslogboekregistratie. Gebruik json.Indent wanneer je al ruwe bytes hebt en alleen witruimte wilt toevoegen zonder een rondreis via Go-typen. Schakel pas over naar go-json of sonic nadat profilering bevestigt dat JSON-marshaling een meetbare knelpunt is β voor de meeste services is de standaardbibliotheek meer dan voldoende.
Veelgestelde vragen
Hoe print ik JSON mooi af in Go?
Roep json.MarshalIndent(v, "", "\t") aan uit het encoding/json-pakket β het tweede argument is een regelprefix (meestal leeg) en het derde is de inspringing per niveau. Geef "\t" voor tabs of " " voor twee spaties. Er is geen externe bibliotheek nodig; encoding/json zit in de standaardbibliotheek van Go.
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
config := map[string]any{
"service": "payments-api",
"port": 8443,
"region": "eu-west-1",
}
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
log.Fatalf("marshal: %v", err)
}
fmt.Println(string(data))
// {
// "port": 8443,
// "region": "eu-west-1",
// "service": "payments-api"
// }
}Wat is het verschil tussen json.Marshal en json.MarshalIndent?
json.Marshal produceert compacte JSON op één regel zonder witruimte β ideaal voor netwerkoverdracht waarbij elke byte telt. json.MarshalIndent neemt twee extra stringparameters (prefix en indent) en produceert ingesprongen, voor mensen leesbare uitvoer. Beide functies accepteren dezelfde waardetypen en retourneren ([]byte, error). De enige extra kosten van MarshalIndent zijn iets meer uitvoerbytes en een verwaarloosbare hoeveelheid extra CPU voor het invoegen van witruimte.
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
// }Hoe formatteer ik een JSON []byte zonder de struct te unmarshalen?
Gebruik json.Indent(&buf, src, "", "\t"). Deze functie neemt een bestaande []byte van JSON en schrijft de ingesprongen versie naar een bytes.Buffer β geen structdefinitie nodig, geen type-assertions, geen heen-en-weer via Go-typen. Dit is de snelste optie als je al ruwe JSON-bytes hebt, zoals van een HTTP-responsebody of een databasekolom.
import (
"bytes"
"encoding/json"
"fmt"
"log"
)
raw := []byte(`{"endpoint":"/api/v2/invoices","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/invoices",
// "page": 1,
// "per_page": 50,
// "total": 312
// }Waarom retourneert json.MarshalIndent een fout?
encoding/json retourneert een fout wanneer de waarde niet als JSON kan worden weergegeven. De meest voorkomende oorzaken zijn: het marshalen van een kanaal, functie of complex getal (deze hebben geen JSON-equivalent); een struct die MarshalJSON() implementeert en een fout retourneert; of een map met niet-stringsleutels. Belangrijk: het marshalen van een struct met een unexported of nil-pointerveld veroorzaakt GEEN fout β die worden simpelweg weggelaten.
import (
"encoding/json"
"fmt"
)
// Dit geeft een fout β kanalen zijn niet JSON-serialiseerbaar
ch := make(chan int)
_, err := json.MarshalIndent(ch, "", " ")
fmt.Println(err)
// json: unsupported type: chan int
// Dit is prima β nil-pointervelden met omitempty worden stil weggelaten
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 weggelaten, geen foutHoe sluit ik een veld uit van JSON-uitvoer in Go?
Er zijn drie manieren. Ten eerste, gebruik de json:"-" struct tag β het veld wordt altijd uitgesloten, ongeacht de waarde. Ten tweede, gebruik omitempty β het veld wordt alleen uitgesloten wanneer het de nulwaarde voor zijn type heeft (nil-pointer, lege string, 0, false). Ten derde worden unexported (kleine letter) velden automatisch uitgesloten door encoding/json, zonder dat er een tag nodig is.
type PaymentMethod struct {
ID string `json:"id"`
Last4 string `json:"last4"`
ExpiryMonth int `json:"expiry_month"`
ExpiryYear int `json:"expiry_year"`
CVV string `json:"-"` // altijd uitgesloten
BillingName string `json:"billing_name,omitempty"` // uitgesloten indien leeg
internalRef string // unexported β automatisch uitgesloten
}
pm := PaymentMethod{
ID: "pm_9f3a", Last4: "4242",
ExpiryMonth: 12, ExpiryYear: 2028,
CVV: "123", internalRef: "stripe:pm_9f3a",
}
data, _ := json.MarshalIndent(pm, "", " ")
// CVV, BillingName (leeg) en internalRef verschijnen niet in de uitvoerHoe ga ik om met time.Time bij JSON-marshaling?
encoding/json marshaalt time.Time standaard naar RFC3339Nano-formaat (bijv. "2026-03-10T14:22:00Z"), wat ISO 8601-compatibel is. Als je een ander formaat nodig hebt β zoals Unix-epoch-integers voor een legacy-API, of een aangepaste datum-string β implementeer dan MarshalJSON() op een wrapper-type dat time.Time insluit en het gewenste formaat retourneert.
import (
"encoding/json"
"fmt"
"time"
)
// Standaardgedrag β RFC3339Nano, geen aangepaste code nodig
type AuditEvent struct {
Action string `json:"action"`
OccurredAt time.Time `json:"occurred_at"`
}
e := AuditEvent{
Action: "invoice.paid",
OccurredAt: time.Date(2026, 3, 10, 14, 22, 0, 0, time.UTC),
}
data, _ := json.MarshalIndent(e, "", " ")
// {
// "action": "invoice.paid",
// "occurred_at": "2026-03-10T14:22:00Z"
// }
// Aangepast: Unix-tijdstempel als integer
type UnixTime struct{ time.Time }
func (u UnixTime) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("%d", u.Unix())), nil
}Gerelateerde tools
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.