JSON Formatter Go — MarshalIndent() Guide

·Systems Engineer·Granskad avTobias Müller·Publicerad

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.

Before · json
After · json
{"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.

Go — minimalt fungerande exempel
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:

Go — tabbar vs mellanslag
// 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
// }
Obs:Använd 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.

Go — struct-taggar för ett API-svar
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.

Go — utesluta känsliga fält
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 aldrig
Obs:Oexporterade (gemena) struct-fält utesluts alltid av encoding/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

Go — anpassad MarshalJSON för Money
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

Go — anpassad MarshalJSON för status-enum
type OrderStatus int

const (
	StatusPending OrderStatus = iota
	StatusPaid
	StatusShipped
	StatusCancelled
)

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

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

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

o := Order{ID: "ord_3c7f", Status: StatusShipped}
data, _ := json.MarshalIndent(o, "", "  ")
// {
//   "id": "ord_3c7f",
//   "status": "shipped"
// }
Obs:Implementera alltid både 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.

Go 1.21+ — UUID-serialisering
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"
// }
Obs:Om du lagrar UUID:er som [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.

Parameter
Typ
Beskrivning
v
any
Värdet som ska marshallas — struct, map, slice eller primitiv
prefix
string
Sträng som läggs till i början av varje utmatningsrad (oftast "")
indent
string
Sträng för varje indragnivå ("\t" eller " ")

Vanliga struct-taggalternativ:

Tag
Effekt
json:"name"
Byter namn på fältet i JSON-utmatningen
json:"name,omitempty"
Namnbyte + utelämning vid nollvärde (nil, "", 0, false)
json:"-"
Utesluter alltid detta fält från JSON-utmatningen
json:",string"
Kodar tal eller bool som ett JSON-strängvärde

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.

Go — json.Indent på råa bytes
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.

Go — json.Indent för debug-loggning
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())
}
Varning: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

Go — formatera JSON-fil på plats
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

Go — formatera HTTP-svar för debug-logg
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)

Go — formatera rå HTTP-kropp med io.ReadAll
package main

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

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

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

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

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.

Go — två mönster sida vid sida
// 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.

bash — skicka JSON till Pythons inbyggda formaterare
echo '{"service":"payments","port":8443,"workers":4}' | python3 -m json.tool
# {
#     "service": "payments",
#     "port": 8443,
#     "workers": 4
# }
bash — skicka till jq för fullständig formatering och filtrering
# 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")'
bash — skicka till minimal Go main.go via stdin
# 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.go
Obs:gofmt 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.

bash — installera go-json
go get github.com/goccy/go-json
Go — byt ut encoding/json mot go-json med en importändring
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

Go — strömma stor JSON-array utan att ladda in i minnet
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

Go — bearbeta NDJSON-loggström rad för 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)
	}
}
Obs:Byt till streaming när filen överstiger 100 MB eller vid bearbetning av obegränsade strömmar (Kafka-consumers, loggpipelines, S3-objektläsningar). Att ladda en 500 MB JSON-fil med os.ReadFile allokerar hela bufferten på heap:en, utlöser GC-tryck och kan orsaka OOM på minneskapacitetsbegränsade containrar.

Vanliga misstag

Ignorera felreturvärdet från MarshalIndent

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.

Before · Go
After · Go
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))
Använda fmt.Println istället för os.Stdout.Write för binär utmatning

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.

Before · Go
After · Go
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övs
Glömma omitempty på pekarfält

Problem: 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.

Before · Go
After · Go
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"}
Använda map[string]interface{} istället för struct

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.

Before · Go
After · Go
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, snabb

encoding/json vs alternativ — snabb jämförelse

Metod
Formaterad utmatning
Giltig JSON
Egna typer
Streaming
Kräver installation
json.MarshalIndent
✓ via MarshalJSON
Nej (stdlib)
json.Indent
N/A (bytes only)
Nej (stdlib)
json.Encoder
✗ (compact)
✓ via MarshalJSON
Nej (stdlib)
go-json
go get
sonic
go get (amd64/arm64)
jq (CLI)
N/A
Systeminstallation

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.

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

Go
import "encoding/json"

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

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

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

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

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.

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

Go
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 fel

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

Go
type PaymentMethod struct {
	ID           string  `json:"id"`
	Last4        string  `json:"last4"`
	ExpiryMonth  int     `json:"expiry_month"`
	ExpiryYear   int     `json:"expiry_year"`
	CVV          string  `json:"-"`                    // 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 utmatningen

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

Go
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

Finns även på:PythonJavaScriptBash
JO
James OkaforSystems Engineer

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

TM
Tobias MüllerTeknisk granskare

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.