JSON Formatter Go โ Panduan MarshalIndent()
Gunakan JSON Formatter & Beautifier gratis langsung di browser Anda โ tidak perlu instalasi.
Coba JSON Formatter & Beautifier Online โSaat mengerjakan microservice Go dan perlu memeriksa respons API atau file konfigurasi, JSON kompak selalu jadi hambatan pertama โ satu baris dengan ratusan field bersarang tidak memberikan banyak informasi sekilas. Untuk memformat JSON di Go, standard library sudah menyediakan semua yang dibutuhkan: json.MarshalIndent sudah ada di dalam encoding/json, disertakan bersama setiap instalasi Go, dan tidak memerlukan dependensi pihak ketiga. Panduan ini mencakup gambaran lengkap: struct tags, implementasi MarshalJSON() kustom, json.Indent untuk memformat ulang bytes mentah, streaming file besar dengan json.Decoder, kapan menggunakan go-json untuk jalur throughput tinggi, plus perintah CLI satu baris untuk formatting cepat di terminal. Semua contoh menggunakan Go 1.21+.
- โjson.MarshalIndent(v, "", "\t") adalah standard library โ nol dependensi, disertakan di setiap instalasi Go.
- โStruct tags json:"field_name,omitempty" mengontrol kunci serialisasi dan menghilangkan field bernilai nol dari output.
- โImplementasikan MarshalJSON() pada tipe apa pun untuk mengontrol penuh representasi JSON-nya.
- โjson.Indent() memformat ulang []byte yang sudah di-marshal tanpa mem-parse ulang struct โ lebih cepat untuk bytes mentah.
- โUntuk file besar (>100 MB): gunakan json.Decoder dengan Token() untuk streaming tanpa memuat semua ke memori.
- โgo-json adalah pengganti drop-in 3โ5ร lebih cepat dari encoding/json untuk API throughput tinggi.
Apa itu Pemformatan JSON?
Pemformatan JSON โ juga disebut pretty-printing โ mengubah string JSON yang kompak dan diminifikasi menjadi tata letak yang mudah dibaca manusia dengan indentasi dan jeda baris yang konsisten. Data yang mendasarinya identik; hanya whitespace yang berubah. JSON kompak optimal untuk transfer jaringan di mana setiap byte penting; JSON terformat optimal untuk debugging, tinjauan kode, inspeksi log, dan penulisan file konfigurasi. Paket encoding/json di Go menangani kedua mode dengan satu pemanggilan fungsi โ beralih antara output kompak dan berindentasi dengan memilih antara json.Marshal dan json.MarshalIndent.
{"service":"payments","port":8443,"workers":4}{
"service": "payments",
"port": 8443,
"workers": 4
}json.MarshalIndent() โ Pendekatan Standard Library
json.MarshalIndent ada di paket encoding/json yang merupakan bagian dari standard library Go โ tidak perlu go get. Signature-nya adalah MarshalIndent(v any, prefix, indent string) ([]byte, error): string prefix ditambahkan ke setiap baris output (hampir selalu dikosongkan), dan indent diulang sekali per level nesting. Gunakan "\t" untuk tab atau " " untuk dua spasi.
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"
// ]
// }Pilihan antara tab dan spasi sebagian besar adalah konvensi tim. Banyak proyek Go lebih memilih tab karena gofmt (yang memformat kode sumber Go) menggunakan tab. Indentasi dua atau empat spasi umum ketika JSON ditujukan untuk konsumen JavaScript atau Python. Berikut struct yang sama dengan kedua gaya indentasi secara berdampingan:
// Indentasi tab โ disukai di tooling native Go
data, _ := json.MarshalIndent(cfg, "", " ")
// {
// "host": "payments-api.internal",
// "port": 8443
// }
// Indentasi dua spasi โ umum untuk API dengan konsumen JS/Python
data, _ = json.MarshalIndent(cfg, "", " ")
// {
// "host": "payments-api.internal",
// "port": 8443
// }
// Indentasi empat spasi
data, _ = json.MarshalIndent(cfg, "", " ")
// {
// "host": "payments-api.internal",
// "port": 8443
// }json.Marshal(v) ketika Anda membutuhkan output kompak โ payload jaringan, nilai cache, atau jalur apa pun di mana ukuran biner penting. Menerima argumen nilai yang sama dan memiliki semantik error yang sama, tetapi menghasilkan JSON satu baris tanpa whitespace apa pun.Struct Tags โ Mengontrol Nama Field dan Omitempty
Struct tags Go adalah literal string yang ditempatkan setelah deklarasi field yang memberi tahu encoding/jsoncara menserialisasi setiap field. Ada tiga direktif utama: json:"name" mengganti nama field di output, omitempty menghilangkan field ketika menyimpan nilai nol untuk tipenya, dan json:"-" mengecualikan field sepenuhnya โ berguna untuk password, identifier internal, atau field yang tidak boleh melewati batas service.
type UserProfile struct {
ID string `json:"id"`
Email string `json:"email"`
DisplayName string `json:"display_name,omitempty"` // hilangkan jika string kosong
AvatarURL *string `json:"avatar_url,omitempty"` // hilangkan jika pointer nil
IsAdmin bool `json:"is_admin,omitempty"` // hilangkan jika false
passwordHash string // unexported โ otomatis dikecualikan
}
// Pengguna dengan semua field opsional terisi
full := UserProfile{
ID: "usr_7b3c", Email: "b.santoso@contoh.id",
DisplayName: "Budi Santoso", IsAdmin: true,
}
// {
// "id": "usr_7b3c",
// "email": "b.santoso@contoh.id",
// "display_name": "Budi Santoso",
// "is_admin": true
// }
// Pengguna tanpa field opsional โ semuanya dihilangkan
minimal := UserProfile{ID: "usr_2a91", Email: "s.rahayu@contoh.id"}
// {
// "id": "usr_2a91",
// "email": "s.rahayu@contoh.id"
// }Tag json:"-" adalah pilihan tepat untuk field yang harus dikecualikan tanpa syarat terlepas dari nilainya โ biasanya secret, field pelacakan internal, atau data yang benar di memori tetapi tidak boleh diserialisasi ke sistem eksternal mana pun.
type AuthToken struct {
TokenID string `json:"token_id"`
Subject string `json:"sub"`
IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"`
SigningKey []byte `json:"-"` // tidak pernah diserialisasi
RefreshToken string `json:"-"` // tidak pernah diserialisasi
}
tok := AuthToken{
TokenID: "tok_8f2a", Subject: "usr_7b3c",
IssuedAt: 1741614120, ExpiresAt: 1741617720,
SigningKey: []byte("rahasia"), RefreshToken: "rt_9e4f",
}
data, _ := json.MarshalIndent(tok, "", " ")
// {
// "token_id": "tok_8f2a",
// "sub": "usr_7b3c",
// "iat": 1741614120,
// "exp": 1741617720
// }
// SigningKey dan RefreshToken tidak pernah munculencoding/json terlepas dari tag apa pun. Anda tidak perlu menambahkan json:"-" ke field unexported โ pengecualian bersifat otomatis dan tidak dapat di-override.MarshalJSON() Kustom โ Menangani Tipe Non-Standar
Tipe Go apa pun dapat mengimplementasikan antarmuka json.Marshaler dengan mendefinisikan metode MarshalJSON() ([]byte, error). Ketika encoding/json menemukan tipe seperti itu, ia memanggil metode tersebut alih-alih marshaling berbasis refleksi defaultnya. Ini adalah pola Go kanonik untuk tipe domain yang membutuhkan representasi wire tertentu โ nilai moneter, enum status, format waktu kustom, atau tipe apa pun yang menyimpan data secara berbeda dari cara seharusnya diserialisasi.
Tipe Kustom โ Uang dengan Konversi Sen ke Desimal
package main
import (
"encoding/json"
"fmt"
"log"
)
type Money struct {
Amount int64 // disimpan dalam sen untuk menghindari drift floating-point
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: 19900000, Currency: "IDR"},
Tax: Money{Amount: 1990000, Currency: "IDR"},
Total: Money{Amount: 21890000, Currency: "IDR"},
}
data, err := json.MarshalIndent(inv, "", " ")
if err != nil {
log.Fatalf("marshal invoice: %v", err)
}
fmt.Println(string(data))
}
// {
// "id": "inv_9a2f91bc",
// "subtotal": { "amount": 199000, "currency": "IDR", "display": "IDR 199000.00" },
// "tax": { "amount": 19900, "currency": "IDR", "display": "IDR 19900.00" },
// "total": { "amount": 218900, "currency": "IDR", "display": "IDR 218900.00" }
// }Enum Status โ Representasi String
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("status pesanan tidak dikenal: %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() dan UnmarshalJSON() bersama-sama. Jika Anda hanya mengimplementasikan marshaling, round-tripping tipe melalui JSON (serialize โ simpan โ deserialize) akan kehilangan struktur secara diam-diam atau mengembalikan tipe yang salah. Keduanya membentuk kontrak bahwa tipe dapat bertahan melewati round-trip JSON.UUID โ Serialisasi sebagai String
Standard library Go tidak memiliki tipe UUID. Pilihan paling umum adalah github.com/google/uuid, yang sudah mengimplementasikan MarshalJSON() dan melakukan serialisasi sebagai string RFC 4122 yang dikutip. Jika Anda menggunakan raw [16]byte atau tipe ID kustom, implementasikan antarmuka sendiri untuk menghindari blob biner yang di-encode base64 dalam output JSON Anda.
import (
"encoding/json"
"fmt"
"github.com/google/uuid"
)
type AuditEvent struct {
EventID uuid.UUID `json:"event_id"` // serialisasi sebagai "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 tanpa tipe kustom, encoding/json akan meng-encode-nya sebagai string base64 โ mis. "VQ6EAOKbQdSnFkRmVUQAAA==". Selalu gunakan tipe UUID yang tepat atau implementasikan MarshalJSON() untuk menghasilkan format string bertiret yang kanonik.Referensi Parameter json.MarshalIndent()
Signature fungsi adalah func MarshalIndent(v any, prefix, indent string) ([]byte, error). Baik prefix maupun indent adalah literal string โ tidak ada singkatan numerik seperti indent=4 milik Python.
Opsi struct tag yang umum:
json.Indent() โ Memformat Ulang Bytes JSON yang Ada
Ketika Anda sudah memiliki []byte dari JSON โ misalnya dari body respons HTTP, kolom jsonb Postgres, atau file yang dibaca dengan os.ReadFile โ Anda tidak perlu mendefinisikan struct dan unmarshal sebelum pretty-print. json.Indent langsung memformat ulang bytes mentah dengan menulis output berindentasi ke dalam bytes.Buffer.
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
)
func main() {
// Mensimulasikan payload JSON mentah dari layanan upstream
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
// }Pola yang sering saya gunakan di microservice adalah memanggil json.Indent sebelum menulis ke structured log โ menambah overhead yang dapat diabaikan dan membuat entri log jauh lebih mudah dibaca saat insiden. Fungsi ini sangat berguna untuk logging respons HTTP, pretty-printing string JSON yang tersimpan, dan pipeline format-on-read di mana definisi struct tidak tersedia.
func logResponse(logger *slog.Logger, statusCode int, body []byte) {
var pretty bytes.Buffer
if err := json.Indent(&pretty, body, "", " "); err != nil {
// Body bukan JSON valid โ log mentah
logger.Debug("respons upstream", "status", statusCode, "body", string(body))
return
}
logger.Debug("respons upstream", "status", statusCode, "body", pretty.String())
}json.Indent TIDAK memvalidasi JSON secara penuh melebihi apa yang secara struktural diperlukan untuk menyisipkan whitespace. Untuk validasi sintaks penuh, panggil json.Valid(data) terlebih dahulu dan tangani kasus false sebelum mencoba indent.Format JSON dari File dan Respons HTTP
Dua skenario dunia nyata yang paling umum di service Go adalah memformat JSON yang dibaca dari file di disk (file konfigurasi, data fixture, migration seed) dan pretty-printing body respons HTTP untuk debug logging atau test assertion. Keduanya mengikuti pola yang sama: baca bytes, panggil json.Indent atau unmarshal lalu json.MarshalIndent, tulis kembali atau log.
Baca File โ Format โ Tulis Kembali
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("baca %s: %w", path, err)
}
if !json.Valid(data) {
return fmt.Errorf("JSON tidak valid di %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("tulis %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 berhasil diformat")
}Respons HTTP โ Decode โ Pretty-Print untuk Debug Logging
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 respons health: %v", err)
}
pretty, err := json.MarshalIndent(result, "", " ")
if err != nil {
log.Fatalf("marshal respons health: %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
// }Body Respons Mentah โ json.Indent (Tanpa Struct)
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("baca 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 Print JSON dari Respons HTTP di Go
Dua pendekatan di atas mencakup kasus paling umum: decode ke struct bertype lalu panggil json.MarshalIndent (terbaik ketika Anda perlu memvalidasi atau memeriksa field), atau baca bytes body mentah dengan io.ReadAll dan panggil json.Indent langsung (terbaik untuk debug logging cepat ketika definisi struct tidak tersedia). Pendekatan raw-bytes lebih sederhana tetapi tidak memberikan type safety Go atau akses field โ ini murni transformasi tampilan. Kedua pendekatan menangani body respons besar dengan benar selama body penuh muat di memori.
// Pola A: decode bertipe โ MarshalIndent // Gunakan ketika perlu memeriksa atau memvalidasi field tertentu var result map[string]any json.NewDecoder(resp.Body).Decode(&result) pretty, _ := json.MarshalIndent(result, "", " ") fmt.Println(string(pretty)) // Pola B: bytes mentah โ json.Indent // Gunakan untuk debug logging cepat โ tidak perlu definisi struct body, _ := io.ReadAll(resp.Body) var buf bytes.Buffer json.Indent(&buf, body, "", " ") fmt.Println(buf.String())
Pemformatan JSON Command-Line di Proyek Go
Terkadang Anda perlu memformat payload JSON langsung di terminal tanpa menulis program Go. Perintah satu baris ini adalah yang saya simpan dalam memori otot selama pengembangan dan penanganan insiden.
echo '{"service":"payments","port":8443,"workers":4}' | python3 -m json.tool
# {
# "service": "payments",
# "port": 8443,
# "workers": 4
# }# Hanya format cat api-response.json | jq . # Ekstrak field bersarang cat api-response.json | jq '.checks.database' # Filter array cat audit-log.json | jq '.[] | select(.severity == "error")'
# main.go: baca stdin, format, tulis 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 memformat kode sumber Go, bukan JSON. Jangan pipe file JSON melalui gofmt โ akan menghasilkan error atau output yang tidak dapat dikenali. Gunakan jq . atau python3 -m json.tool untuk file JSON.Alternatif Performa Tinggi โ go-json
Untuk sebagian besar service Go, encoding/json sudah cukup cepat. Tetapi jika JSON marshaling muncul di profiler Anda โ umum di REST API throughput tinggi atau service yang mengeluarkan baris log terstruktur besar di setiap request โ library go-json adalah pengganti drop-in yang 3โ5ร lebih cepat dengan API surface yang identik.
go get github.com/goccy/go-json
package main
import (
// Ganti ini:
// "encoding/json"
// Dengan ini โ API identik, tidak ada perubahan kode lain:
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))
// Output identik dengan encoding/json โ hanya kecepatan yang berbeda
}github.com/bytedance/sonic adalah library JSON Go tercepat yang tersedia, tetapi hanya berjalan di amd64 dan arm64 (menggunakan kompilasi JIT). Gunakan go-json saat Anda membutuhkan drop-in yang portabel; gunakan sonic saat berada di arsitektur yang diketahui dan membutuhkan setiap mikrodetik di jalur panas.
Bekerja dengan File JSON Besar
json.MarshalIndent dan json.Indent keduanya mengharuskan seluruh payload ada di memori. Untuk file di atas 100 MB โ ekspor data, audit log, payload konsumen Kafka โ gunakan json.Decoder untuk streaming input dan memproses record satu per satu.
Streaming Array JSON Besar dengan 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("buka %s: %w", path, err)
}
defer file.Close()
dec := json.NewDecoder(file)
// Baca token pembuka '['
if _, err := dec.Token(); err != nil {
return fmt.Errorf("baca token pembuka: %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)
}
// Proses satu event sekaligus โ penggunaan memori konstan
if event.Severity == "error" {
fmt.Printf("[ERROR] %s: %s (%dms)
", event.UserID, event.Action, event.DurationMs)
}
processed++
}
fmt.Printf("Memproses %d audit event
", processed)
return nil
}
func main() {
if err := processAuditLog("audit-2026-03.json"); err != nil {
log.Fatalf("proses audit log: %v", err)
}
}NDJSON โ Satu Objek JSON Per Baris
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("buka log: %v", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Buffer(make([]byte, 1024*1024), 1024*1024) // 1 MB per baris
for scanner.Scan() {
var line LogLine
if err := json.Unmarshal(scanner.Bytes(), &line); err != nil {
continue // lewati baris yang tidak valid
}
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 akan mengalokasikan seluruh buffer tersebut di heap, memicu tekanan GC, dan mungkin menyebabkan OOM di container yang memorinya terbatas.Kesalahan Umum
Masalah: Membuang error dengan _ berarti nilai yang tidak dapat diserialisasi (channel, fungsi, bilangan kompleks) secara diam-diam menghasilkan output nil atau panic downstream ketika Anda memanggil string(nil).
Solusi: Selalu periksa errornya. Jika Anda meng-marshal tipe yang seharusnya selalu berhasil, log.Fatalf yang menyebabkan panic lebih baik daripada kehilangan data secara diam-diam.
data, _ := json.MarshalIndent(payload, "", " ") fmt.Println(string(data)) // string kosong jika marshal gagal
data, err := json.MarshalIndent(payload, "", " ")
if err != nil {
log.Fatalf("marshal payload: %v", err)
}
fmt.Println(string(data))Masalah: fmt.Println(string(data)) menambahkan karakter newline setelah JSON, yang merusak pipeline yang memperlakukan output sebagai bytes mentah โ misalnya saat piping ke jq atau menulis ke protokol biner.
Solusi: Gunakan os.Stdout.Write(data) untuk output yang bersih secara biner. Jika Anda membutuhkan newline di akhir untuk tampilan manusia, tambahkan secara eksplisit.
data, _ := json.MarshalIndent(cfg, "", " ") fmt.Println(string(data)) // menambahkan newline ekstra di akhir
data, _ := json.MarshalIndent(cfg, "", " ")
os.Stdout.Write(data)
os.Stdout.Write([]byte("
")) // newline eksplisit hanya ketika diperlukanMasalah: Tanpa omitempty, nil *string atau *int pointer diserialisasi sebagai "field": null. Ini mengekspos nama field internal dan dapat merusak validator JSON schema yang ketat di sisi konsumen.
Solusi: Tambahkan omitempty ke field pointer yang Anda inginkan tidak ada (bukan null) di output. Nil *T dengan omitempty tidak menghasilkan key sama sekali di JSON.
type WebhookPayload struct {
EventID string `json:"event_id"`
ErrorMsg *string `json:"error_msg"` // muncul sebagai null ketika nil
}
// {"event_id":"evt_3c7f","error_msg":null}type WebhookPayload struct {
EventID string `json:"event_id"`
ErrorMsg *string `json:"error_msg,omitempty"` // dihilangkan ketika nil
}
// {"event_id":"evt_3c7f"}Masalah: Unmarshal ke map[string]any kehilangan informasi tipe, memerlukan type assertion manual, dan menghasilkan urutan key yang non-deterministik โ membuat JSON diff dan perbandingan log lebih sulit.
Solusi: Definisikan struct dengan json tag yang tepat. Struct bersifat type-safe, marshal lebih cepat, menghasilkan urutan field deterministik yang sesuai definisi struct, dan membuat kode mendokumentasikan dirinya sendiri.
var result map[string]any json.Unmarshal(body, &result) port := result["port"].(float64) // type assertion diperlukan, panic jika tipe salah
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 // bertipe, aman, cepatencoding/json vs Alternatif โ Perbandingan Cepat
Gunakan json.MarshalIndent untuk kasus apa pun di mana Anda mengontrol definisi struct dan membutuhkan output terformat โ file konfigurasi, debug logging, test fixture, dan logging respons API. Gunakan json.Indent ketika Anda sudah memiliki bytes mentah dan hanya perlu whitespace ditambahkan tanpa round-trip melalui tipe Go. Beralih ke go-json atau sonic hanya setelah profiling mengkonfirmasi bahwa JSON marshaling adalah bottleneck yang terukur โ untuk sebagian besar service, standard library sudah lebih dari cukup.
Pertanyaan yang Sering Diajukan
Bagaimana cara mencetak JSON dengan indentasi di Go?
Panggil json.MarshalIndent(v, "", "\t") dari paket encoding/json โ argumen kedua adalah awalan per baris (biasanya kosong) dan ketiga adalah karakter indentasi per level. Gunakan "\t" untuk tab atau " " untuk dua spasi. Tidak perlu library eksternal; encoding/json sudah disertakan bersama standard library Go.
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
config := map[string]any{
"service": "payments-api",
"port": 8443,
"region": "ap-southeast-1",
}
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
log.Fatalf("marshal: %v", err)
}
fmt.Println(string(data))
// {
// "port": 8443,
// "region": "ap-southeast-1",
// "service": "payments-api"
// }
}Apa perbedaan antara json.Marshal dan json.MarshalIndent?
json.Marshal menghasilkan JSON kompak satu baris tanpa whitespace โ ideal untuk transfer jaringan di mana setiap byte penting. json.MarshalIndent menerima dua parameter string tambahan (prefix dan indent) dan menghasilkan output yang berindentasi dan mudah dibaca manusia. Kedua fungsi menerima tipe nilai yang sama dan mengembalikan ([]byte, error). Satu-satunya biaya MarshalIndent adalah sedikit lebih banyak byte output dan CPU tambahan yang tidak berarti untuk menyisipkan whitespace.
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
// }Bagaimana cara memformat []byte JSON tanpa unmarshal struct?
Gunakan json.Indent(&buf, src, "", "\t"). Fungsi ini menerima []byte JSON yang ada dan menulis versi berindentasi ke bytes.Buffer โ tidak perlu definisi struct, type assertion, atau round-trip melalui tipe Go. Ini adalah opsi tercepat ketika Anda sudah memiliki bytes JSON mentah, seperti dari body respons HTTP atau kolom database.
import (
"bytes"
"encoding/json"
"fmt"
"log"
)
raw := []byte(`{"endpoint":"/api/v2/tagihan","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/tagihan",
// "page": 1,
// "per_page": 50,
// "total": 312
// }Mengapa json.MarshalIndent mengembalikan error?
encoding/json mengembalikan error ketika nilai tidak dapat direpresentasikan sebagai JSON. Penyebab paling umum: meng-marshal channel, fungsi, atau bilangan kompleks (tidak ada padanan JSON-nya); struct yang mengimplementasi MarshalJSON() dan mengembalikan error; atau map dengan kunci non-string. Yang penting: meng-marshal struct dengan field unexported atau pointer nil TIDAK menyebabkan error โ field tersebut hanya diabaikan saja.
import (
"encoding/json"
"fmt"
)
// Ini akan mengembalikan error โ channel tidak bisa diserialisasi ke JSON
ch := make(chan int)
_, err := json.MarshalIndent(ch, "", " ")
fmt.Println(err)
// json: unsupported type: chan int
// Ini baik-baik saja โ field pointer nil dengan omitempty diabaikan secara diam-diam
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 diabaikan, tidak ada errorBagaimana cara mengecualikan field dari output JSON di Go?
Ada tiga cara. Pertama, gunakan struct tag json:"-" โ field selalu dikecualikan terlepas dari nilainya. Kedua, gunakan omitempty โ field dikecualikan hanya ketika menyimpan nilai nol untuk tipenya (nil pointer, string kosong, 0, false). Ketiga, field unexported (huruf kecil) secara otomatis dikecualikan oleh encoding/json tanpa tag apa pun.
type PaymentMethod struct {
ID string `json:"id"`
Last4 string `json:"last4"`
ExpiryMonth int `json:"expiry_month"`
ExpiryYear int `json:"expiry_year"`
CVV string `json:"-"` // selalu dikecualikan
BillingName string `json:"billing_name,omitempty"` // dikecualikan jika kosong
internalRef string // unexported โ otomatis dikecualikan
}
pm := PaymentMethod{
ID: "pm_9f3a", Last4: "4242",
ExpiryMonth: 12, ExpiryYear: 2028,
CVV: "123", internalRef: "stripe:pm_9f3a",
}
data, _ := json.MarshalIndent(pm, "", " ")
// CVV, BillingName (kosong), dan internalRef tidak muncul di outputBagaimana cara menangani time.Time dalam marshaling JSON?
encoding/json meng-marshal time.Time ke format RFC3339Nano secara default (mis. "2026-03-10T14:22:00Z"), yang kompatibel dengan ISO 8601. Jika Anda membutuhkan format berbeda โ seperti integer Unix epoch untuk API lama, atau string tanggal-saja kustom โ implementasikan MarshalJSON() pada tipe wrapper yang menyematkan time.Time dan mengembalikan format yang dibutuhkan.
import (
"encoding/json"
"fmt"
"time"
)
// Perilaku default โ RFC3339Nano, tidak perlu kode kustom
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"
// }
// Kustom: Unix timestamp sebagai integer
type UnixTime struct{ time.Time }
func (u UnixTime) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("%d", u.Unix())), nil
}Alat Terkait
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.