JSON Formatter Go — MarshalIndent() ガイド
無料の JSON整形ツール をブラウザで直接使用 — インストール不要。
JSON整形ツール をオンラインで試す →GoのマイクロサービスでAPIレスポンスや設定ファイルを調査するとき、 圧縮されたJSONは最初の障壁になります——数百のネストされたフィールドが1行に詰め込まれていても、 一見しただけでは何もわかりません。Goで JSONをフォーマットするには、 標準ライブラリが必要なものをすべて提供してくれます: json.MarshalIndent は encoding/json に組み込まれており、すべてのGoインストールに付属していて、サードパーティの依存関係は不要です。 このガイドでは全体像を網羅します:struct tags、カスタム MarshalJSON() の実装、生のバイトを再フォーマットするための json.Indent、json.Decoder を使った大きなファイルのストリーミング、高スループットパスでの go-json の使い所、そしてターミナルでのクイックフォーマット用CLIワンライナーまで。 すべてのサンプルは Go 1.21+ を対象としています。
- ✓json.MarshalIndent(v, "", "\t") は標準ライブラリ——ゼロ依存で、すべてのGoインストールに付属。
- ✓struct tags json:"field_name,omitempty" でシリアライズキーを制御し、ゼロ値フィールドを出力から省略できる。
- ✓任意の型にMarshalJSON()を実装することで、JSON表現を完全にコントロールできる。
- ✓json.Indent() はstructの再解析なしで既にマーシャリングされた[]byteを再フォーマット——生バイトに対してより高速。
- ✓大きなファイル(>100 MB)の場合:Token()を使ったjson.Decoderでストリーミング処理し、全てをメモリに読み込まない。
- ✓go-jsonはencoding/jsonのドロップイン代替品で、高スループットAPIでは3〜5倍高速。
JSONフォーマットとは?
JSONフォーマット(pretty-printingとも呼ばれる)は、コンパクトに圧縮されたJSON文字列を、 一貫したインデントと改行を持つ人間が読みやすいレイアウトに変換します。 基礎となるデータは同一で、変わるのは空白だけです。コンパクトなJSONはバイト数が重要な ネットワーク転送に最適で、フォーマットされたJSONはデバッグ、コードレビュー、 ログの検査、設定ファイルの作成に最適です。Goの encoding/json パッケージは1回の関数呼び出しで両方のモードを処理します—— json.Marshal と json.MarshalIndent を切り替えるだけで、コンパクトとインデント出力の間を自在に移動できます。
{"service":"payments","port":8443,"workers":4}{
"service": "payments",
"port": 8443,
"workers": 4
}json.MarshalIndent() — 標準ライブラリのアプローチ
json.MarshalIndent はGoの標準ライブラリの一部である encoding/json パッケージに含まれており、 go get は不要です。関数シグネチャは MarshalIndent(v any, prefix, indent string) ([]byte, error):prefix 文字列はすべての出力行の先頭に追加され(ほぼ常に空のまま)、 indent はネストレベルごとに1回繰り返されます。タブには "\t"、 2スペースには " " を渡します。
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"
// ]
// }タブとスペースの選択は主にチームの慣習によります。 gofmt (Goのソースコードをフォーマットするツール)がタブを使用するため、 多くのGoプロジェクトはタブを好みます。JSONがJavaScriptやPythonのコンシューマ向けの場合は 2スペースや4スペースのインデントが一般的です。同じstructを2つのインデントスタイルで 並べた例を示します:
// タブインデント——Go標準ツールチェーンで好まれる
data, _ := json.MarshalIndent(cfg, "", " ")
// {
// "host": "payments-api.internal",
// "port": 8443
// }
// 2スペースインデント——JS/Pythonコンシューマ向けAPIで一般的
data, _ = json.MarshalIndent(cfg, "", " ")
// {
// "host": "payments-api.internal",
// "port": 8443
// }
// 4スペースインデント
data, _ = json.MarshalIndent(cfg, "", " ")
// {
// "host": "payments-api.internal",
// "port": 8443
// }json.Marshal(v) を使用してください。同じ値引数を受け取り、同じエラーセマンティクスを持ちますが、 空白なしの1行JSONを生成します。Struct Tags — フィールド名とOmitemptyの制御
Go struct tagsはフィールド宣言の後に置かれる文字列リテラルで、encoding/jsonに各フィールドのシリアライズ方法を指示します。3つの主要なディレクティブがあります: json:"name" は出力でフィールドをリネームし、 omitempty はフィールドがその型のゼロ値を保持している場合にそのフィールドを省略し、 json:"-" はフィールドを完全に除外します——パスワード、内部識別子、 サービスの境界を越えてはならないフィールドに有用です。
type UserProfile struct {
ID string `json:"id"`
Email string `json:"email"`
DisplayName string `json:"display_name,omitempty"` // 空文字列の場合に省略
AvatarURL *string `json:"avatar_url,omitempty"` // nilポインタの場合に省略
IsAdmin bool `json:"is_admin,omitempty"` // falseの場合に省略
passwordHash string // エクスポートなし——自動除外
}
// オプションフィールドがすべて設定されたユーザー
full := UserProfile{
ID: "usr_7b3c", Email: "ops@example.com",
DisplayName: "田中太郎", IsAdmin: true,
}
// {
// "id": "usr_7b3c",
// "email": "ops@example.com",
// "display_name": "田中太郎",
// "is_admin": true
// }
// オプションフィールドのないユーザー——完全に省略される
minimal := UserProfile{ID: "usr_2a91", Email: "dev@example.com"}
// {
// "id": "usr_2a91",
// "email": "dev@example.com"
// }json:"-" タグは、値に関わらず無条件に除外する必要があるフィールドに適しています—— 通常はシークレット、内部トラッキングフィールド、 またはメモリ上では正しいが外部システムにシリアライズしてはならないデータです。
type AuthToken struct {
TokenID string `json:"token_id"`
Subject string `json:"sub"`
IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"`
SigningKey []byte `json:"-"` // シリアライズされない
RefreshToken string `json:"-"` // シリアライズされない
}
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とRefreshTokenは決して現れないencoding/json によって除外されます。エクスポートされていないフィールドに json:"-" を追加する必要はありません——除外は自動的で上書きできません。カスタムMarshalJSON() — 非標準型の処理
任意のGo型は MarshalJSON() ([]byte, error) メソッドを定義することで json.Marshaler インターフェースを実装できます。 encoding/json がそのような型に遭遇すると、デフォルトのリフレクションベースのマーシャリングの代わりにそのメソッドを呼び出します。 これは特定のwire表現が必要なドメイン型——通貨値、ステータス列挙型、 カスタム時刻フォーマット、データの保存方法とシリアライズ方法が異なる型——に対する Goの標準的なパターンです。
カスタム型——Money(銭から円への変換)
package main
import (
"encoding/json"
"fmt"
"log"
)
type Money struct {
Amount int64 // 浮動小数点のずれを避けるために銭単位で保存
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: "JPY"},
Tax: Money{Amount: 1592, Currency: "JPY"},
Total: Money{Amount: 21492, Currency: "JPY"},
}
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": "JPY", "display": "JPY 199.00" },
// "tax": { "amount": 15.92, "currency": "JPY", "display": "JPY 15.92" },
// "total": { "amount": 214.92, "currency": "JPY", "display": "JPY 214.92" }
// }ステータス列挙型——文字列表現
type OrderStatus int
const (
StatusPending OrderStatus = iota
StatusPaid
StatusShipped
StatusCancelled
)
var orderStatusNames = map[OrderStatus]string{
StatusPending: "pending",
StatusPaid: "paid",
StatusShipped: "shipped",
StatusCancelled: "cancelled",
}
func (s OrderStatus) MarshalJSON() ([]byte, error) {
name, ok := orderStatusNames[s]
if !ok {
return nil, fmt.Errorf("unknown order status: %d", s)
}
return json.Marshal(name)
}
type Order struct {
ID string `json:"id"`
Status OrderStatus `json:"status"`
}
o := Order{ID: "ord_3c7f", Status: StatusShipped}
data, _ := json.MarshalIndent(o, "", " ")
// {
// "id": "ord_3c7f",
// "status": "shipped"
// }MarshalJSON() と UnmarshalJSON() を一緒に実装してください。マーシャリングだけを実装した場合、 JSONを通じた型のラウンドトリップ(シリアライズ→保存→デシリアライズ)で 構造が静かに失われるか、間違った型が返されます。 この2つのメソッドが合わさって、型がJSONのラウンドトリップに耐えられるという契約を形成します。UUID——文字列としてのシリアライズ
Goの標準ライブラリにはUUID型がありません。最も一般的な選択は github.com/google/uuid で、すでに MarshalJSON() を実装し、引用符付きのRFC 4122文字列としてシリアライズします。生の [16]byte やカスタムID型を使用する場合は、JSON出力にbase64エンコードされたバイナリが現れないよう 自分でインターフェースを実装してください。
import (
"encoding/json"
"fmt"
"github.com/google/uuid"
)
type AuditEvent struct {
EventID uuid.UUID `json:"event_id"` // "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 として保存すると、 encoding/json はそれをbase64文字列——例えば "VQ6EAOKbQdSnFkRmVUQAAA=="——としてエンコードします。 常に適切なUUID型を使用するか、正規のハイフン付き文字列形式を出力する MarshalJSON() を実装してください。json.MarshalIndent() パラメータリファレンス
関数シグネチャは func MarshalIndent(v any, prefix, indent string) ([]byte, error)。prefix と indent はどちらも文字列リテラルです——Pythonの indent=4 のような数値の省略形はありません。
よく使うstruct tagオプション:
json.Indent() — 既存のJSONバイトの再フォーマット
HTTPレスポンスボディ、Postgresの jsonb カラム、または os.ReadFile で読み込んだファイルなど、すでに []byte のJSONを持っている場合、pretty-printする前にstructを定義してアンマーシャルする必要はありません。 json.Indent は生のバイトを直接再フォーマットし、インデントされた出力を bytes.Buffer に書き込みます。
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
)
func main() {
// 上流サービスからの生のJSONペイロードをシミュレート
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
// }マイクロサービスでよく使うパターンは、構造化ログに書き込む前に json.Indent を呼び出すことです——オーバーヘッドはごくわずかですが、インシデント対応時のログエントリの 可読性が大幅に向上します。この関数はHTTPレスポンスのログ記録、 保存済みのJSON文字列のpretty-print、struct定義が利用できないフォーマット-on-readパイプラインに特に有用です。
func logResponse(logger *slog.Logger, statusCode int, body []byte) {
var pretty bytes.Buffer
if err := json.Indent(&pretty, body, "", " "); err != nil {
// ボディが有効なJSONでない——生のまま記録
logger.Debug("upstream response", "status", statusCode, "body", string(body))
return
}
logger.Debug("upstream response", "status", statusCode, "body", pretty.String())
}json.Indent は空白を挿入するために必要な構造的なものを超えてJSONを完全に検証しません。 完全な構文検証には、まず json.Valid(data) を呼び出し、falseの場合を処理してからインデントを試みてください。ファイルとHTTPレスポンスからJSONをフォーマット
Goサービスで最も一般的な2つの実際のシナリオは、ディスク上のファイルから読み込んだJSON (設定ファイル、フィクスチャデータ、マイグレーションシード)のフォーマットと、 デバッグログやテストアサーション用のHTTPレスポンスボディのpretty-printです。 どちらも同じパターンに従います:バイトを読み込み、 json.Indent を呼び出すかアンマーシャルしてから json.MarshalIndent を呼び出し、書き戻すかログに記録します。
ファイルを読み込んで→フォーマットして→書き戻す
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"os"
)
func formatJSONFile(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read %s: %w", path, err)
}
if !json.Valid(data) {
return fmt.Errorf("invalid JSON in %s", path)
}
var buf bytes.Buffer
if err := json.Indent(&buf, data, "", " "); err != nil {
return fmt.Errorf("indent %s: %w", path, err)
}
if err := os.WriteFile(path, buf.Bytes(), 0644); err != nil {
return fmt.Errorf("write %s: %w", path, err)
}
return nil
}
func main() {
if err := formatJSONFile("config/database.json"); err != nil {
log.Fatalf("format config: %v", err)
}
fmt.Println("config/database.json のフォーマットが完了しました")
}HTTPレスポンス → デコード → デバッグログ用のpretty-print
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("ヘルスチェック (%d):
%s
", resp.StatusCode, string(pretty))
}
// ヘルスチェック (200):
// {
// "status": "ok",
// "version": "1.4.2",
// "checks": {
// "database": "ok",
// "cache": "ok",
// "queue": "degraded"
// },
// "uptime_seconds": 172800
// }生レスポンスボディ → json.Indent(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("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())
}GoでHTTPレスポンスからJSONをpretty printする
上記の2つのアプローチは最も一般的なケースをカバーします:型付きstructにデコードしてから json.MarshalIndent を呼び出す(特定のフィールドを検証または検査する必要がある場合に最適)、あるいは io.ReadAll で生のボディバイトを読み込んで json.Indent を直接呼び出す(struct定義がない場合のクイックデバッグログに最適)。 生バイトのアプローチはより単純ですが、Goの型安全性やフィールドアクセスは提供されません—— 純粋に表示変換です。完全なレスポンスボディがメモリに収まる限り、 どちらのアプローチも大きなレスポンスボディを正しく処理します。
// パターンA:型付きデコード → MarshalIndent // 特定のフィールドを検査または検証する必要がある場合に使用 var result map[string]any json.NewDecoder(resp.Body).Decode(&result) pretty, _ := json.MarshalIndent(result, "", " ") fmt.Println(string(pretty)) // パターンB:生バイト → json.Indent // クイックデバッグログに使用——struct定義不要 body, _ := io.ReadAll(resp.Body) var buf bytes.Buffer json.Indent(&buf, body, "", " ") fmt.Println(buf.String())
GoプロジェクトでのコマンドラインJSONフォーマット
Goプログラムを書かずにターミナルで直接JSONペイロードをフォーマットしたい場合があります。 これらのワンライナーは開発中やインシデント対応時に体で覚えているものです。
echo '{"service":"payments","port":8443,"workers":4}' | python3 -m json.tool
# {
# "service": "payments",
# "port": 8443,
# "workers": 4
# }# フォーマットのみ cat api-response.json | jq . # ネストされたフィールドを抽出 cat api-response.json | jq '.checks.database' # 配列をフィルタリング cat audit-log.json | jq '.[] | select(.severity == "error")'
# main.go: stdinを読み込み、フォーマットして、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 はGoのソースコードをフォーマットするもので、JSONではありません。 JSONファイルを gofmt にパイプしないでください——エラーになるか、認識できない出力になります。 JSONファイルには jq . または python3 -m json.tool を使用してください。高性能な代替手段 — go-json
大多数のGoサービスでは、 encoding/json は十分に高速です。しかし、JSONマーシャリングがプロファイラーに現れる場合—— 高スループットのREST APIや、リクエストごとに大きな構造化ログ行を出力するサービスで一般的—— go-json ライブラリは同一のAPIで3〜5倍高速なドロップイン代替品です。
go get github.com/goccy/go-json
package main
import (
// これを置き換える:
// "encoding/json"
// これに——同一のAPI、他のコード変更不要:
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))
// 出力はencoding/jsonと同一——速度だけが異なる
}github.com/bytedance/sonic は最速のGoのJSONライブラリですが、 amd64 と arm64 でのみ動作します(JITコンパイルを使用)。ポータブルなドロップイン代替品が必要な場合は go-json を使用し、既知のアーキテクチャでホットパスの最後の1マイクロ秒が必要な場合は sonic に手を伸ばしてください。
大きなJSONファイルの処理
json.MarshalIndent と json.Indent はどちらもペイロード全体をメモリに読み込む必要があります。 データエクスポート、監査ログ、Kafkaコンシューマのペイロードなど、 100 MBを超えるファイルには json.Decoder を使って入力をストリーミングし、レコードを1つずつ処理してください。
json.Decoderで大きなJSON配列をストリーミング
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)
// 開始の '[' を読み込む
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)
}
// 1件ずつ処理——メモリ使用量は一定
if event.Severity == "error" {
fmt.Printf("[ERROR] %s: %s (%dms)
", event.UserID, event.Action, event.DurationMs)
}
processed++
}
fmt.Printf("%d件の監査イベントを処理しました
", processed)
return nil
}
func main() {
if err := processAuditLog("audit-2026-03.json"); err != nil {
log.Fatalf("process audit log: %v", err)
}
}NDJSON — 1行に1つのJSONオブジェクト
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行につき1 MB
for scanner.Scan() {
var line LogLine
if err := json.Unmarshal(scanner.Bytes(), &line); err != nil {
continue // 不正な行をスキップ
}
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 で500 MBのJSONファイルを読み込むとヒープにそのバッファ全体が確保され、 GCプレッシャーを引き起こし、メモリ制約のあるコンテナでOOMが発生する可能性があります。よくある間違い
問題: _でエラーを破棄すると、シリアライズできない値(チャンネル、関数、複素数)が静かにnil出力を生成するか、後続でstring(nil)を呼び出したときにパニックが発生します。
解決策: 常にエラーをチェックしてください。常に成功するはずの型をマーシャリングしている場合、パニックするlog.Fatalはサイレントデータロスよりも優れています。
data, _ := json.MarshalIndent(payload, "", " ") fmt.Println(string(data)) // マーシャリングが失敗した場合は空文字列
data, err := json.MarshalIndent(payload, "", " ")
if err != nil {
log.Fatalf("marshal payload: %v", err)
}
fmt.Println(string(data))問題: fmt.Println(string(data))はJSONの後に改行文字を追加し、出力を生のバイトとして扱うパイプラインを壊します——例えば、jqにパイプしたりバイナリプロトコルに書き込む場合です。
解決策: バイナリクリーンな出力にはos.Stdout.Write(data)を使用してください。人間が見るための末尾の改行が必要な場合は明示的に追加してください。
data, _ := json.MarshalIndent(cfg, "", " ") fmt.Println(string(data)) // 末尾に余分な改行を追加
data, _ := json.MarshalIndent(cfg, "", " ")
os.Stdout.Write(data)
os.Stdout.Write([]byte("
")) // 必要な場合のみ明示的な改行問題: omitemptyなしでは、nil *stringや*intポインタが "field": nullとしてシリアライズされます。これは内部フィールド名を露出させ、コンシューマ側の厳格なJSONスキーマバリデータを壊す可能性があります。
解決策: 出力に存在してほしくない(nullではなく)ポインタフィールドにomitemptyを追加してください。omitemptyを持つnil *TはJSONにキーを全く生成しません。
type WebhookPayload struct {
EventID string `json:"event_id"`
ErrorMsg *string `json:"error_msg"` // nilのときnullとして現れる
}
// {"event_id":"evt_3c7f","error_msg":null}type WebhookPayload struct {
EventID string `json:"event_id"`
ErrorMsg *string `json:"error_msg,omitempty"` // nilのとき省略される
}
// {"event_id":"evt_3c7f"}問題: map[string]anyにアンマーシャリングすると型情報が失われ、手動の型アサーションが必要になり、非決定論的なキー順序が生じ——JSONの差分比較やログの比較が難しくなります。
解決策: 適切なjson tagsを持つstructを定義してください。Structは型安全で、マーシャリングが高速で、structの定義に合致した決定論的なフィールド順序を生成し、コードが自己文書化されます。
var result map[string]any json.Unmarshal(body, &result) port := result["port"].(float64) // 型アサーションが必要で、型が違うとパニック
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 // 型付き、安全、高速encoding/json と代替手段の比較
struct定義をコントロールしてフォーマットされた出力が必要なすべての場合——設定ファイル、 デバッグログ、テストフィクスチャ、APIレスポンスログ——には json.MarshalIndent を使用してください。既に生のバイトを持っていてGoの型を経由したラウンドトリップなしに 空白を追加したいだけの場合は json.Indent を使用してください。プロファイリングでJSONマーシャリングが測定可能なボトルネックであることが 確認された後にのみ、 go-json や sonic に切り替えてください——ほとんどのサービスでは標準ライブラリで十分すぎるほどです。
よくある質問
GoでJSONをpretty printするには?
encoding/jsonパッケージのjson.MarshalIndent(v, "", "\t")を呼び出します——第2引数は行ごとのプレフィックス(通常は空)、第3引数はレベルごとのインデントです。タブには "\t"、2スペースには " " を渡します。外部ライブラリは不要で、encoding/jsonはGoの標準ライブラリに含まれています。
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
config := map[string]any{
"service": "payments-api",
"port": 8443,
"region": "ap-northeast-1",
}
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
log.Fatalf("marshal: %v", err)
}
fmt.Println(string(data))
// {
// "port": 8443,
// "region": "ap-northeast-1",
// "service": "payments-api"
// }
}json.Marshalとjson.MarshalIndentの違いは何ですか?
json.Marshalは空白なしのコンパクトな1行JSONを生成します——バイト数が重要なネットワーク転送に最適です。json.MarshalIndentは2つの追加文字列パラメータ(prefixとindent)を受け取り、インデントされた人間が読みやすい出力を生成します。どちらも同じ値の型を受け取り、([]byte, error)を返します。MarshalIndentのコストは出力バイト数がわずかに増えることと、空白挿入のためのごくわずかな追加CPU時間だけです。
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
// }structをアンマーシャルせずにJSON []byteをフォーマットするには?
json.Indent(&buf, src, "", "\t")を使用します。この関数は既存のJSON []byteを受け取り、インデントされたバージョンをbytes.Bufferに書き込みます——struct定義不要、型アサション不要、Goの型を経由したラウンドトリップも不要です。HTTPレスポンスボディやデータベースのカラムなど、すでに生のJSONバイトを持っている場合に最も高速な選択肢です。
import (
"bytes"
"encoding/json"
"fmt"
"log"
)
raw := []byte(`{"endpoint":"/api/v2/invoices","page":1,"per_page":50,"total":312}`)
var buf bytes.Buffer
if err := json.Indent(&buf, raw, "", " "); err != nil {
log.Fatalf("indent: %v", err)
}
fmt.Println(buf.String())
// {
// "endpoint": "/api/v2/invoices",
// "page": 1,
// "per_page": 50,
// "total": 312
// }json.MarshalIndentがエラーを返すのはなぜですか?
値をJSONとして表現できない場合、encoding/jsonはエラーを返します。最も一般的な原因は:チャンネル、関数、または複素数のマーシャリング(これらはJSONに相当するものがない)、エラーを返すMarshalJSON()を実装したstruct、または文字列以外のキーを持つmap。重要なのは、エクスポートされていないフィールドやnilポインタフィールドを持つstructのマーシャリングではエラーが発生しない点です——それらは単純に省略されます。
import (
"encoding/json"
"fmt"
)
// これはエラーを返す——チャンネルはJSONシリアライズできない
ch := make(chan int)
_, err := json.MarshalIndent(ch, "", " ")
fmt.Println(err)
// json: unsupported type: chan int
// これは問題なし——omitemptyを持つnilポインタフィールドは静かに省略される
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は省略、エラーなしGoでJSONの出力からフィールドを除外するには?
3つの方法があります。第1に、json:"-" struct tagを使用する——フィールドの値に関わらず常に除外されます。第2に、omitemptyを使用する——フィールドがその型のゼロ値(nilポインタ、空文字列、0、false)の場合のみ除外されます。第3に、エクスポートされていない(小文字で始まる)フィールドは、タグなしでencoding/jsonによって自動的に除外されます。
type PaymentMethod struct {
ID string `json:"id"`
Last4 string `json:"last4"`
ExpiryMonth int `json:"expiry_month"`
ExpiryYear int `json:"expiry_year"`
CVV string `json:"-"` // 常に除外
BillingName string `json:"billing_name,omitempty"` // 空の場合に除外
internalRef string // エクスポートなし——自動除外
}
pm := PaymentMethod{
ID: "pm_9f3a", Last4: "4242",
ExpiryMonth: 12, ExpiryYear: 2028,
CVV: "123", internalRef: "stripe:pm_9f3a",
}
data, _ := json.MarshalIndent(pm, "", " ")
// CVV、BillingName(空)、internalRefは出力に現れないJSONマーシャリングでtime.Timeをどう扱いますか?
encoding/jsonはデフォルトでtime.TimeをRFC3339Nano形式(例:"2026-03-10T14:22:00Z")にマーシャリングします。これはISO 8601互換です。レガシーAPIのUnixエポック整数やカスタム日付文字列など別の形式が必要な場合は、time.Timeを埋め込んで必要なフォーマットを返すラッパー型にMarshalJSON()を実装します。
import (
"encoding/json"
"fmt"
"time"
)
// デフォルト動作——RFC3339Nano、カスタムコード不要
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"
// }
// カスタム:Unix タイムスタンプ(整数)
type UnixTime struct{ time.Time }
func (u UnixTime) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("%d", u.Unix())), nil
}関連ツール
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.