JSON Formatter Go โ€” Panduan MarshalIndent()

ยทSystems EngineerยทDitinjau olehTobias MรผllerยทDiterbitkan

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.

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

Go โ€” contoh minimal yang berfungsi
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:

Go โ€” tab vs spasi
// 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
// }
Catatan:Gunakan 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.

Go โ€” struct tags untuk respons API
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.

Go โ€” mengecualikan field sensitif
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 muncul
Catatan:Field unexported (huruf kecil) selalu dikecualikan oleh encoding/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

Go โ€” MarshalJSON kustom untuk Uang
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

Go โ€” MarshalJSON kustom untuk enum status
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"
// }
Catatan:Selalu implementasikan 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.

Go 1.21+ โ€” serialisasi UUID
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"
// }
Catatan:Jika Anda menyimpan UUID sebagai [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.

Parameter
Type
Deskripsi
v
any
Nilai yang akan di-marshal โ€” struct, map, slice, atau primitive
prefix
string
String yang ditambahkan di awal setiap baris output (biasanya "")
indent
string
String yang digunakan untuk setiap level indentasi ("\t" atau " ")

Opsi struct tag yang umum:

Tag
Efek
json:"name"
Mengganti nama field menjadi name di output JSON
json:"name,omitempty"
Ganti nama + hilangkan jika nilai nol (nil, "", 0, false)
json:"-"
Selalu kecualikan field ini dari output JSON
json:",string"
Encode angka atau bool sebagai nilai string JSON

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.

Go โ€” json.Indent pada bytes mentah
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.

Go โ€” json.Indent untuk debug logging
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())
}
Peringatan: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

Go โ€” format file JSON di tempat
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

Go โ€” format respons HTTP untuk debug log
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)

Go โ€” format body HTTP mentah dengan 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("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.

Go โ€” dua pola berdampingan
// 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.

bash โ€” pipe JSON ke formatter bawaan Python
echo '{"service":"payments","port":8443,"workers":4}' | python3 -m json.tool
# {
#     "service": "payments",
#     "port": 8443,
#     "workers": 4
# }
bash โ€” pipe ke jq untuk formatting dan filtering lengkap
# 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")'
bash โ€” pipe ke Go main.go minimal via stdin
# 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.go
Catatan:gofmt 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.

bash โ€” instal go-json
go get github.com/goccy/go-json
Go โ€” ganti encoding/json dengan go-json dengan satu perubahan import
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

Go โ€” stream array JSON besar tanpa memuat ke memori
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

Go โ€” proses stream log NDJSON baris 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)
	}
}
Catatan:Beralih ke streaming ketika file JSON melebihi 100 MB atau saat memproses stream tak terbatas (konsumen Kafka, pipeline log, pembacaan objek S3). Memuat file JSON 500 MB dengan os.ReadFile akan mengalokasikan seluruh buffer tersebut di heap, memicu tekanan GC, dan mungkin menyebabkan OOM di container yang memorinya terbatas.

Kesalahan Umum

โŒ Mengabaikan nilai error yang dikembalikan MarshalIndent

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.

Before ยท Go
After ยท Go
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))
โŒ Menggunakan fmt.Println alih-alih os.Stdout.Write untuk output biner

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.

Before ยท Go
After ยท Go
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 diperlukan
โŒ Lupa omitempty pada field pointer

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

Before ยท Go
After ยท Go
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"}
โŒ Menggunakan map[string]interface{} alih-alih struct

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.

Before ยท Go
After ยท Go
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, cepat

encoding/json vs Alternatif โ€” Perbandingan Cepat

Metode
Output Rapi
JSON Valid
Tipe Kustom
Streaming
Perlu Instalasi
json.MarshalIndent
โœ“
โœ“
โœ“ via MarshalJSON
โœ—
Tidak (stdlib)
json.Indent
โœ“
โœ“
T/A (bytes saja)
โœ—
Tidak (stdlib)
json.Encoder
โœ— (kompak)
โœ“
โœ“ via MarshalJSON
โœ“
Tidak (stdlib)
go-json
โœ“
โœ“
โœ“
โœ“
go get
sonic
โœ“
โœ“
โœ“
โœ“
go get (amd64/arm64)
jq (CLI)
โœ“
โœ“
T/A
โœ“
Instal sistem

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.

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.

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
// }

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.

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

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

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

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:"-"`                    // 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 output

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

Go
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

Tersedia juga dalam: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รผllerPeninjau teknis

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.