ToolDeck

JSON Formatter Go — คู่มือ MarshalIndent()

·Systems Engineer·ตรวจสอบโดยTobias Müller·เผยแพร่เมื่อ

ใช้ จัดรูปแบบและตกแต่ง JSON ฟรีโดยตรงในเบราว์เซอร์ของคุณ — ไม่ต้องติดตั้ง

ลอง จัดรูปแบบและตกแต่ง JSON ออนไลน์ →

เมื่อทำงานกับ Go microservice และต้องการตรวจสอบ API response หรือไฟล์ config JSON แบบ compact มักเป็นอุปสรรคแรกเสมอ — บรรทัดเดียวที่มี field ซ้อนกันหลายร้อย ตัวแทบไม่บอกอะไรได้เลยเมื่อมองผ่านๆ สำหรับการ จัดรูปแบบ JSON ใน Go standard library มีทุกอย่างที่คุณต้องการ: json.MarshalIndent มีอยู่ใน encoding/json แล้ว มาพร้อมกับการติดตั้ง Go ทุกครั้ง และไม่ต้องใช้ dependency ของบุคคลที่สาม คู่มือนี้ครอบคลุมทุกอย่าง: struct tags, custom MarshalJSON() implementations, json.Indent สำหรับ reformat raw bytes, streaming ไฟล์ขนาดใหญ่ด้วย json.Decoder, ว่าเมื่อไหรควรใช้ go-json สำหรับ high-throughput paths และ CLI one-liners สำหรับ format เร็วๆ ใน terminal ตัวอย่างทั้งหมดใช้ Go 1.21+

  • json.MarshalIndent(v, "", "\t") คือ standard library — ไม่มี dependency มาพร้อมการติดตั้ง Go ทุกครั้ง
  • Struct tags json:"field_name,omitempty" ควบคุม serialization key และละเว้น field ที่มี zero value จาก output
  • Implement MarshalJSON() บน type ใดก็ได้เพื่อควบคุม JSON representation อย่างสมบูรณ์
  • json.Indent() reformat []byte ที่ marshal แล้วโดยไม่ต้อง parse struct ใหม่ — เร็วกว่าสำหรับ raw bytes
  • สำหรับไฟล์ขนาดใหญ่ (>100 MB): ใช้ json.Decoder กับ Token() เพื่อ stream โดยไม่โหลดทั้งหมดเข้า memory
  • go-json คือ drop-in replacement ที่เร็วกว่า 3–5× จาก encoding/json สำหรับ API throughput สูง

JSON Formatting คืออะไร?

JSON formatting — หรือเรียกว่า pretty-printing — แปลง JSON string แบบ compact ที่ minified ให้เป็น layout ที่อ่านได้สำหรับมนุษย์พร้อม indentation และการขึ้นบรรทัดใหม่ ที่สอดคล้องกัน ข้อมูลพื้นฐานเหมือนกัน มีแค่ whitespace ที่เปลี่ยนแปลง JSON แบบ compact เหมาะสำหรับการส่งข้อมูลผ่านเครือข่ายที่ทุก byte สำคัญ ส่วน JSON ที่จัด รูปแบบแล้วเหมาะสำหรับ debugging, code review, การตรวจสอบ log และการเขียนไฟล์ config Package encoding/json ของ Go จัดการทั้งสองโหมดด้วยการเรียกฟังก์ชันเดียว — สลับระหว่าง compact และ indented output โดยเลือกระหว่าง json.Marshal และ json.MarshalIndent

Before · json
After · json
{"service":"payments","port":8443,"workers":4}
{
	"service": "payments",
	"port": 8443,
	"workers": 4
}

json.MarshalIndent() — วิธีการของ Standard Library

json.MarshalIndent อยู่ใน package encoding/json ซึ่งเป็นส่วนหนึ่งของ Go standard library — ไม่ต้องใช้ go getSignature คือ MarshalIndent(v any, prefix, indent string) ([]byte, error): string prefix จะถูกเพิ่มต้นทุกบรรทัดของ output (มักเว้นว่างเสมอ) และ indent จะถูกทำซ้ำหนึ่งครั้งต่อระดับ nesting ใส่ "\t" สำหรับ tab หรือ " " สำหรับสองช่องว่าง

Go — ตัวอย่างที่ทำงานได้แบบ minimal
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"
// 	]
// }

การเลือกระหว่าง tab และ space ส่วนใหญ่เป็นเรื่องของ convention ทีม หลายโปรเจกต์ Go ชอบ tab เพราะ gofmt (ซึ่ง format source code Go) ใช้ tab Indentation สองหรือสี่ช่องว่างพบได้ทั่วไป เมื่อ JSON ถูกส่งให้ consumer ที่เป็น JavaScript หรือ Python นี่คือ struct เดียวกัน กับทั้งสองรูปแบบ indent วางเคียงข้างกัน:

Go — tab กับ space
// Tab indent — นิยมใน Go-native tooling
data, _ := json.MarshalIndent(cfg, "", "	")
// {
// 	"host": "payments-api.internal",
// 	"port": 8443
// }

// Two-space indent — พบบ่อยสำหรับ API ที่มี consumer JS/Python
data, _ = json.MarshalIndent(cfg, "", "  ")
// {
//   "host": "payments-api.internal",
//   "port": 8443
// }

// Four-space indent
data, _ = json.MarshalIndent(cfg, "", "    ")
// {
//     "host": "payments-api.internal",
//     "port": 8443
// }
หมายเหตุ:ใช้ json.Marshal(v) เมื่อต้องการ compact output — network payload, cache value หรือเส้นทางใดก็ตาม ที่ขนาด binary สำคัญ รับ argument ค่าเดิมและมี error semantics เดียวกัน แต่ สร้าง JSON บรรทัดเดียวโดยไม่มี whitespace ใดๆ

Struct Tags — ควบคุมชื่อ Field และ Omitempty

Struct tags ใน Go คือ string literal ที่วางหลัง field declaration เพื่อบอก encoding/jsonวิธี serialize แต่ละ field มี directive หลักสามอย่าง: json:"name" เปลี่ยนชื่อ field ใน output, omitempty ละเว้น field เมื่อมี zero value ของ type นั้น และ json:"-" ยกเว้น field อย่างสมบูรณ์ — มีประโยชน์สำหรับ password, internal identifier หรือ field ที่ไม่ควรออกจาก service boundary

Go — struct tags สำหรับ API response
type UserProfile struct {
	ID          string  `json:"id"`
	Email       string  `json:"email"`
	DisplayName string  `json:"display_name,omitempty"`  // ละเว้นถ้าเป็น string ว่าง
	AvatarURL   *string `json:"avatar_url,omitempty"`    // ละเว้นถ้าเป็น nil pointer
	IsAdmin     bool    `json:"is_admin,omitempty"`      // ละเว้นถ้าเป็น false
	passwordHash string                                   // unexported — ยกเว้นอัตโนมัติ
}

// ผู้ใช้ที่มี optional field ทั้งหมดถูกกรอก
full := UserProfile{
	ID: "usr_7b3c", Email: "somchai.j@example.th",
	DisplayName: "สมชาย ใจดี", IsAdmin: true,
}
// {
//   "id": "usr_7b3c",
//   "email": "somchai.j@example.th",
//   "display_name": "สมชาย ใจดี",
//   "is_admin": true
// }

// ผู้ใช้ที่ไม่มี optional field — ถูกละเว้นทั้งหมด
minimal := UserProfile{ID: "usr_2a91", Email: "somying.r@example.th"}
// {
//   "id": "usr_2a91",
//   "email": "somying.r@example.th"
// }

Tag json:"-" เป็นตัวเลือกที่ถูกต้องสำหรับ field ที่ต้องถูกยกเว้นอย่างไม่มีเงื่อนไขโดยไม่คำนึง ถึงค่า — มักเป็น secret, internal tracking field หรือข้อมูลที่ถูกต้องใน memory แต่ไม่ควร serialize ไปยังระบบภายนอกใดๆ

Go — ยกเว้น field ที่ sensitive
type AuthToken struct {
	TokenID      string `json:"token_id"`
	Subject      string `json:"sub"`
	IssuedAt     int64  `json:"iat"`
	ExpiresAt    int64  `json:"exp"`
	SigningKey    []byte `json:"-"`   // ไม่เคย serialize
	RefreshToken string `json:"-"`   // ไม่เคย serialize
}

tok := AuthToken{
	TokenID: "tok_8f2a", Subject: "usr_7b3c",
	IssuedAt: 1741614120, ExpiresAt: 1741617720,
	SigningKey: []byte("ลับ"), RefreshToken: "rt_9e4f",
}
data, _ := json.MarshalIndent(tok, "", "  ")
// {
//   "token_id": "tok_8f2a",
//   "sub": "usr_7b3c",
//   "iat": 1741614120,
//   "exp": 1741617720
// }
// SigningKey และ RefreshToken ไม่ปรากฏเลย
หมายเหตุ:Field unexported (ตัวพิมพ์เล็ก) จะถูกยกเว้นเสมอโดย encoding/json โดยไม่คำนึงถึง tag ใดๆ คุณไม่ต้อง เพิ่ม json:"-" ใน field unexported — การยกเว้นเป็น อัตโนมัติและไม่สามารถ override ได้

MarshalJSON() แบบกำหนดเอง — จัดการ Type ที่ไม่ใช่มาตรฐาน

Type ใดๆ ใน Go สามารถ implement interface json.Marshaler โดยนิยาม method MarshalJSON() ([]byte, error)เมื่อ encoding/json พบ type เช่นนั้น จะเรียก method แทนที่จะใช้ marshaling แบบ reflection เริ่มต้น นี่คือ pattern Go แบบ canonical สำหรับ domain type ที่ต้องการ wire representation เฉพาะ — ค่าเงิน, status enum, format เวลาแบบกำหนดเอง หรือ type ใดก็ตามที่เก็บ ข้อมูลต่างจากวิธีที่ควร serialize

Custom Type — เงินพร้อมการแปลง Cents เป็น Decimal

Go — custom MarshalJSON สำหรับ Money
package main

import (
	"encoding/json"
	"fmt"
	"log"
)

type Money struct {
	Amount   int64  // เก็บเป็นสตางค์เพื่อหลีกเลี่ยง floating-point drift
	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: 199000, Currency: "THB"},
		Tax:      Money{Amount: 15920, Currency: "THB"},
		Total:    Money{Amount: 214920, Currency: "THB"},
	}
	data, err := json.MarshalIndent(inv, "", "  ")
	if err != nil {
		log.Fatalf("marshal invoice: %v", err)
	}
	fmt.Println(string(data))
}
// {
//   "id": "inv_9a2f91bc",
//   "subtotal": { "amount": 1990, "currency": "THB", "display": "THB 1990.00" },
//   "tax":      { "amount": 159.2, "currency": "THB", "display": "THB 159.20" },
//   "total":    { "amount": 2149.2, "currency": "THB", "display": "THB 2149.20" }
// }

Status Enum — String Representation

Go — custom MarshalJSON สำหรับ 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("สถานะคำสั่งซื้อที่ไม่รู้จัก: %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"
// }
หมายเหตุ:Implement ทั้ง MarshalJSON() และ UnmarshalJSON() เสมอ ถ้า implement แค่ marshaling การ round-trip ของ type ผ่าน JSON (serialize → เก็บ → deserialize) อาจสูญเสีย structure อย่างเงียบๆ หรือ return type ที่ผิด คู่นี้สร้าง contract ว่า type สามารถผ่าน JSON round-trip ได้

UUID — Serialize เป็น String

Go standard library ไม่มี UUID type ตัวเลือกที่พบบ่อยที่สุดคือ github.com/google/uuidซึ่ง implement MarshalJSON() แล้วและ serialize เป็น quoted RFC 4122 string ถ้าใช้ raw [16]byte หรือ custom ID type ให้ implement interface เองเพื่อหลีกเลี่ยง base64-encoded binary blob ใน JSON output

Go 1.21+ — UUID serialization
import (
    "encoding/json"
    "fmt"

    "github.com/google/uuid"
)

type AuditEvent struct {
    EventID   uuid.UUID `json:"event_id"`   // serialize เป็น "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"
// }
หมายเหตุ:ถ้าเก็บ UUID เป็น [16]byte โดยไม่มี custom type, encoding/json จะ encode เป็น base64 string — เช่น "VQ6EAOKbQdSnFkRmVUQAAA==" ใช้ UUID type ที่ เหมาะสมเสมอหรือ implement MarshalJSON() เพื่อส่งออก format string แบบ canonical ที่มีขีดกลาง

เอกสารอ้างอิง Parameter ของ json.MarshalIndent()

Signature ฟังก์ชันคือ func MarshalIndent(v any, prefix, indent string) ([]byte, error)ทั้ง prefix และ indent คือ string literal — ไม่มี shorthand ตัวเลขแบบ indent=4 ของ Python

Parameter
Type
คำอธิบาย
v
any
ค่าที่ต้องการ marshal — struct, map, slice หรือ primitive
prefix
string
สตริงที่เพิ่มต้นทุกบรรทัดใน output (มักเป็น "")
indent
string
สตริงที่ใช้สำหรับแต่ละระดับการย่อหน้า ("\t" หรือ " ")

ตัวเลือก struct tag ที่พบบ่อย:

Tag
ผล
json:"name"
เปลี่ยนชื่อ field เป็น name ใน JSON output
json:"name,omitempty"
เปลี่ยนชื่อ + ละเว้นถ้าเป็น zero value (nil, "", 0, false)
json:"-"
ยกเว้น field นี้จาก JSON output เสมอ
json:",string"
เข้ารหัสตัวเลขหรือ bool เป็น JSON string value

json.Indent() — Reformat JSON Bytes ที่มีอยู่

เมื่อคุณมี []byte ของ JSON อยู่แล้ว — เช่น จาก HTTP response body, คอลัมน์ jsonb ของ Postgres หรือไฟล์ที่อ่านด้วย os.ReadFile — คุณ ไม่ต้องนิยาม struct และ unmarshal ก่อน pretty-print json.Indent reformat raw bytes โดยตรงโดยเขียน indented output ลงใน bytes.Buffer

Go — json.Indent บน raw bytes
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
)

func main() {
	// จำลอง raw JSON payload จาก upstream service
	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
// }

Pattern ที่ฉันใช้บ่อยใน microservice คือการเรียก json.Indent ก่อนเขียนลง structured log — เพิ่ม overhead เล็กน้อยมากและทำให้ log entry อ่านง่าย ขึ้นมากในช่วงที่เกิดปัญหา ฟังก์ชันนี้มีประโยชน์เป็นพิเศษสำหรับ log HTTP response, pretty-print JSON string ที่เก็บไว้ และ pipeline แบบ format-on-read ที่ไม่มี struct definition

Go — json.Indent สำหรับ debug logging
func logResponse(logger *slog.Logger, statusCode int, body []byte) {
	var pretty bytes.Buffer
	if err := json.Indent(&pretty, body, "", "  "); err != nil {
		// Body ไม่ใช่ JSON ที่ valid — log แบบ raw
		logger.Debug("upstream response", "status", statusCode, "body", string(body))
		return
	}
	logger.Debug("upstream response", "status", statusCode, "body", pretty.String())
}
คำเตือน:json.Indent ไม่ validate JSON อย่างสมบูรณ์นอกเหนือ จากที่จำเป็นเชิงโครงสร้างสำหรับการแทรก whitespace สำหรับการ validate syntax อย่างสมบูรณ์ ให้เรียก json.Valid(data) ก่อนและจัดการกรณี false ก่อน พยายาม indent

Format JSON จาก File และ HTTP Response

สองสถานการณ์จริงที่พบบ่อยที่สุดใน Go service คือการ format JSON ที่อ่านจากไฟล์ บนดิสก์ (config file, fixture data, migration seed) และ pretty-print HTTP response body เพื่อ debug log หรือ test assertion ทั้งสองตามรูปแบบเดียวกัน: อ่าน bytes, เรียก json.Indent หรือ unmarshal แล้ว json.MarshalIndent, เขียนกลับหรือ log

อ่านไฟล์ → Format → เขียนกลับ

Go — format ไฟล์ JSON ในที่เดิม
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("อ่าน %s: %w", path, err)
	}

	if !json.Valid(data) {
		return fmt.Errorf("JSON ไม่ valid ใน %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("เขียน %s: %w", path, err)
	}
	return nil
}

func main() {
	if err := formatJSONFile("config/database.json"); err != nil {
		log.Fatalf("format config: %v", err)
	}
	fmt.Println("format config/database.json สำเร็จ")
}

HTTP Response → Decode → Pretty-Print สำหรับ Debug Log

Go — format HTTP response สำหรับ 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 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
// }

Raw Response Body → json.Indent (ไม่ต้องการ Struct)

Go — format raw HTTP body ด้วย 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("อ่าน 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 จาก HTTP Response ใน Go

สองแนวทางข้างต้นครอบคลุมกรณีที่พบบ่อยที่สุด: decode เป็น typed struct แล้วเรียก json.MarshalIndent (ดีที่สุดเมื่อต้องการ validate หรือตรวจสอบ field เฉพาะ) หรืออ่าน raw body bytes ด้วย io.ReadAll และเรียก json.Indent โดยตรง (ดีที่สุดสำหรับ debug log แบบเร็วเมื่อไม่มี struct definition) แนวทาง raw-bytes ง่ายกว่าแต่ไม่ให้ Go type safety หรือการเข้าถึง field — เป็นแค่การ แปลงการแสดงผลล้วนๆ ทั้งสองแนวทางจัดการ response body ขนาดใหญ่ได้อย่างถูกต้อง ตราบใดที่ body ทั้งหมดใส่ในหน่วยความจำได้

Go — สองรูปแบบวางเคียงกัน
// Pattern A: decode มี type → MarshalIndent
// ใช้เมื่อต้องการตรวจสอบหรือ validate field เฉพาะ
var result map[string]any
json.NewDecoder(resp.Body).Decode(&result)
pretty, _ := json.MarshalIndent(result, "", "  ")
fmt.Println(string(pretty))

// Pattern B: raw bytes → json.Indent
// ใช้สำหรับ debug log แบบเร็ว — ไม่ต้องนิยาม struct
body, _ := io.ReadAll(resp.Body)
var buf bytes.Buffer
json.Indent(&buf, body, "", "  ")
fmt.Println(buf.String())

การ Format JSON ผ่าน Command-Line ในโปรเจกต์ Go

บางครั้งคุณต้อง format JSON payload ใน terminal โดยตรงโดยไม่ต้องเขียนโปรแกรม Go นี่คือ one-liner ที่ฉันจำไว้ใช้ตลอดระหว่างการพัฒนาและการรับมือกับปัญหา

bash — pipe JSON ไปยัง formatter ที่ built-in ของ Python
echo '{"service":"payments","port":8443,"workers":4}' | python3 -m json.tool
# {
#     "service": "payments",
#     "port": 8443,
#     "workers": 4
# }
bash — pipe ไปยัง jq สำหรับ formatting และ filtering ครบครัน
# Format เท่านั้น
cat api-response.json | jq .

# ดึง nested field
cat api-response.json | jq '.checks.database'

# Filter array
cat audit-log.json | jq '.[] | select(.severity == "error")'
bash — pipe ไปยัง Go main.go แบบ minimal ผ่าน stdin
# main.go: อ่าน stdin, format, เขียน 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
หมายเหตุ:gofmt format source code Go ไม่ใช่ JSON อย่า pipe ไฟล์ JSON ผ่าน gofmt — จะ error หรือสร้าง output ที่จำไม่ได้ ใช้ jq . หรือ python3 -m json.tool สำหรับไฟล์ JSON

ทางเลือกประสิทธิภาพสูง — go-json

สำหรับ Go service ส่วนใหญ่ encoding/json เร็วพอ แต่ถ้า JSON marshaling ปรากฏใน profiler ของคุณ — พบได้บ่อยใน REST API throughput สูงหรือ service ที่ส่ง structured log line ขนาดใหญ่ทุก request — library go-json คือ drop-in replacement ที่เร็วกว่า 3–5× ด้วย API surface ที่เหมือนกัน

bash — ติดตั้ง go-json
go get github.com/goccy/go-json
Go — แทนที่ encoding/json ด้วย go-json ด้วยการเปลี่ยน import เดียว
package main

import (
	// แทนที่สิ่งนี้:
	// "encoding/json"

	// ด้วยสิ่งนี้ — API เหมือนกัน ไม่ต้องเปลี่ยน code อื่น:
	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 เหมือนกับ encoding/json — แค่ความเร็วต่างกัน
}

github.com/bytedance/sonic คือ Go JSON library ที่เร็วที่สุดที่มีอยู่ แต่รันได้บน amd64 และ arm64 เท่านั้น (ใช้ JIT compilation) ใช้ go-json เมื่อต้องการ portable drop-in; ใช้ sonic เมื่ออยู่บน architecture ที่รู้จักและต้องการทุก microsecond ใน hot path

การทำงานกับไฟล์ JSON ขนาดใหญ่

json.MarshalIndent และ json.Indent ทั้งคู่ต้องการ payload ทั้งหมดอยู่ในหน่วยความจำ สำหรับไฟล์ขนาดเกิน 100 MB — data export, audit log, Kafka consumer payload — ใช้ json.Decoder เพื่อ stream input และประมวลผล record ทีละตัว

Streaming JSON Array ขนาดใหญ่ด้วย json.Decoder

Go — stream JSON array ขนาดใหญ่โดยไม่โหลดเข้า memory
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("เปิด %s: %w", path, err)
	}
	defer file.Close()

	dec := json.NewDecoder(file)

	// อ่าน opening token '['
	if _, err := dec.Token(); err != nil {
		return fmt.Errorf("อ่าน 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)
		}
		// ประมวลผล event ทีละตัว — การใช้ memory คงที่
		if event.Severity == "error" {
			fmt.Printf("[ERROR] %s: %s (%dms)
", event.UserID, event.Action, event.DurationMs)
		}
		processed++
	}
	fmt.Printf("ประมวลผล audit event %d รายการ
", processed)
	return nil
}

func main() {
	if err := processAuditLog("audit-2026-03.json"); err != nil {
		log.Fatalf("ประมวลผล audit log: %v", err)
	}
}

NDJSON — หนึ่ง JSON Object ต่อบรรทัด

Go — ประมวลผล NDJSON log stream ทีละบรรทัด
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("เปิด log: %v", err)
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	scanner.Buffer(make([]byte, 1024*1024), 1024*1024) // 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)
	}
}
หมายเหตุ:เปลี่ยนไปใช้ streaming เมื่อไฟล์ JSON เกิน 100 MB หรือเมื่อประมวลผล stream ที่ ไม่จำกัด (Kafka consumer, log pipeline, การอ่าน S3 object) การโหลดไฟล์ JSON ขนาด 500 MB ด้วย os.ReadFile จะ allocate buffer ทั้งหมดบน heap, trigger GC pressure และอาจทำให้เกิด OOM บน container ที่จำกัด memory

ข้อผิดพลาดทั่วไป

ละเว้น error return จาก MarshalIndent

ปัญหา: การทิ้ง error ด้วย _ หมายความว่า value ที่ไม่สามารถ serialize ได้ (channel, function, complex number) สร้าง nil output อย่างเงียบๆ หรือ panic downstream เมื่อเรียก string(nil)

วิธีแก้: ตรวจสอบ error เสมอ ถ้า marshal type ที่ควรสำเร็จเสมอ log.Fatalf ที่ทำให้ panic ดีกว่าการสูญเสียข้อมูลอย่างเงียบๆ

Before · Go
After · Go
data, _ := json.MarshalIndent(payload, "", "  ")
fmt.Println(string(data)) // string ว่างถ้า marshal ล้มเหลว
data, err := json.MarshalIndent(payload, "", "  ")
if err != nil {
    log.Fatalf("marshal payload: %v", err)
}
fmt.Println(string(data))
ใช้ fmt.Println แทน os.Stdout.Write สำหรับ binary output

ปัญหา: fmt.Println(string(data)) เพิ่ม newline character หลัง JSON ซึ่งทำให้ pipeline ที่ถือว่า output เป็น raw bytes เสียหาย — เช่น เมื่อ pipe ไปยัง jq หรือเขียนไปยัง binary protocol

วิธีแก้: ใช้ os.Stdout.Write(data) สำหรับ binary-clean output ถ้าต้องการ trailing newline สำหรับการแสดงผลสำหรับมนุษย์ ให้เพิ่มอย่างชัดเจน

Before · Go
After · Go
data, _ := json.MarshalIndent(cfg, "", "  ")
fmt.Println(string(data)) // เพิ่ม newline พิเศษต่อท้าย
data, _ := json.MarshalIndent(cfg, "", "  ")
os.Stdout.Write(data)
os.Stdout.Write([]byte("
")) // newline ที่ชัดเจนเฉพาะเมื่อต้องการ
ลืม omitempty บน pointer field

ปัญหา: ไม่มี omitempty, nil *string หรือ *int pointer จะ serialize เป็น "field": null ซึ่ง expose ชื่อ field ภายในและอาจทำให้ JSON schema validator ที่เข้มงวดด้าน consumer เสียหาย

วิธีแก้: เพิ่ม omitempty ใน pointer field ที่ต้องการให้ไม่มี (ไม่ใช่ null) ใน output nil *T ที่มี omitempty จะไม่สร้าง key ใดๆ ใน JSON เลย

Before · Go
After · Go
type WebhookPayload struct {
    EventID   string  `json:"event_id"`
    ErrorMsg  *string `json:"error_msg"`  // ปรากฏเป็น null เมื่อ nil
}
// {"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]interface{} แทน struct

ปัญหา: Unmarshal เป็น map[string]any สูญเสีย type information ต้องการ type assertion แบบ manual และสร้างลำดับ key ที่ไม่แน่นอน — ทำให้ JSON diff และการเปรียบเทียบ log ยากขึ้น

วิธีแก้: นิยาม struct ด้วย json tag ที่เหมาะสม Struct มี type-safe, marshal เร็วกว่า สร้างลำดับ field ที่แน่นอนตรงกับ struct definition และทำให้ code อธิบายตัวเอง

Before · Go
After · Go
var result map[string]any
json.Unmarshal(body, &result)
port := result["port"].(float64) // ต้องการ type assertion, panic ถ้า type ผิด
type ServiceStatus struct {
    Service string `json:"service"`
    Port    int    `json:"port"`
    Healthy bool   `json:"healthy"`
}
var result ServiceStatus
json.Unmarshal(body, &result)
port := result.Port // มี type, ปลอดภัย, เร็ว

encoding/json กับทางเลือก — เปรียบเทียบด่วน

วิธีการ
Output สวยงาม
JSON ถูกต้อง
ประเภทกำหนดเอง
Streaming
ต้องติดตั้ง
json.MarshalIndent
✓ ผ่าน MarshalJSON
ไม่ (stdlib)
json.Indent
ไม่มี (bytes เท่านั้น)
ไม่ (stdlib)
json.Encoder
✗ (compact)
✓ ผ่าน MarshalJSON
ไม่ (stdlib)
go-json
go get
sonic
go get (amd64/arm64)
jq (CLI)
ไม่มี
ติดตั้งระบบ

ใช้ json.MarshalIndent สำหรับกรณีที่คุณควบคุม struct definition และต้องการ formatted output — config file, debug logging, test fixture และ API response logging ใช้ json.Indent เมื่อมี raw bytes อยู่แล้วและต้องการแค่เพิ่ม whitespace โดยไม่ต้อง round-trip ผ่าน Go type เปลี่ยนไปใช้ go-json หรือ sonic หลังจาก profiling ยืนยันว่า JSON marshaling เป็น bottleneck ที่วัดได้เท่านั้น — สำหรับ service ส่วนใหญ่ standard library เพียงพอมากกว่า

คำถามที่พบบ่อย

จะ pretty print JSON ใน Go ได้อย่างไร?

เรียก json.MarshalIndent(v, "", "\t") จาก package encoding/json — argument ที่สองคือ prefix ต่อบรรทัด (มักว่างเปล่า) และที่สามคือตัวอักษรย่อหน้าต่อระดับ ใช้ "\t" สำหรับ tab หรือ " " สำหรับสองช่องว่าง ไม่จำเป็นต้องใช้ library ภายนอก encoding/json มาพร้อมกับ Go 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"
	// }
}

ความแตกต่างระหว่าง json.Marshal และ json.MarshalIndent คืออะไร?

json.Marshal สร้าง JSON แบบ compact ในบรรทัดเดียวโดยไม่มี whitespace — เหมาะสำหรับการส่งผ่านเครือข่ายที่ทุก byte สำคัญ json.MarshalIndent รับ string parameter พิเศษ 2 ตัว (prefix และ indent) และสร้าง output แบบ indented ที่อ่านได้สำหรับมนุษย์ ทั้งสองฟังก์ชันรับ value type เดียวกันและ return ([]byte, error) ต้นทุนเดียวของ MarshalIndent คือ bytes output มากขึ้นเล็กน้อยและ CPU ที่ไม่มีนัยสำคัญสำหรับการแทรก 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
// }

จะ format []byte JSON โดยไม่ต้อง unmarshal struct ได้อย่างไร?

ใช้ json.Indent(&buf, src, "", "\t") ฟังก์ชันนี้รับ []byte ของ JSON ที่มีอยู่แล้วและเขียน version ที่ indent แล้วลงใน bytes.Buffer — ไม่ต้องนิยาม struct ไม่ต้อง type assertion ไม่ต้อง round-trip ผ่าน Go types เป็นตัวเลือกที่เร็วที่สุดเมื่อคุณมี raw JSON bytes อยู่แล้ว เช่น จาก HTTP response body หรือ database column

Go
import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
)

raw := []byte(`{"endpoint":"/api/v2/invoice","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/invoice",
// 	"page": 1,
// 	"per_page": 50,
// 	"total": 312
// }

ทำไม json.MarshalIndent ถึง return error?

encoding/json return error เมื่อ value ไม่สามารถแสดงเป็น JSON ได้ สาเหตุที่พบบ่อยที่สุด: การ marshal channel, function หรือตัวเลขซับซ้อน (ไม่มี JSON equivalent); struct ที่ implement MarshalJSON() และ return error; หรือ map ที่มี key ที่ไม่ใช่ string สิ่งสำคัญ: การ marshal struct ที่มี unexported หรือ nil pointer field ไม่ก่อให้เกิด error — สิ่งเหล่านี้จะถูกละเว้นเท่านั้น

Go
import (
	"encoding/json"
	"fmt"
)

// นี่จะ return error — channel ไม่สามารถ serialize เป็น JSON
ch := make(chan int)
_, err := json.MarshalIndent(ch, "", "	")
fmt.Println(err)
// json: unsupported type: chan int

// นี่ไม่มีปัญหา — nil pointer field ที่มี omitempty จะถูกละเว้นอย่างเงียบ
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 ถูกละเว้น ไม่มี error

จะยกเว้น field จาก JSON output ใน Go ได้อย่างไร?

มีสามวิธี แรก ใช้ struct tag json:"-" — field จะถูกยกเว้นเสมอโดยไม่คำนึงถึงค่า สอง ใช้ omitempty — field จะถูกยกเว้นเฉพาะเมื่อมี zero value ของ type นั้น (nil pointer, string ว่าง, 0, false) สาม field unexported (ตัวพิมพ์เล็ก) จะถูกยกเว้นโดยอัตโนมัติโดย encoding/json โดยไม่ต้องมี tag ใดๆ

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:"-"`                    // ยกเว้นเสมอ
	BillingName  string  `json:"billing_name,omitempty"` // ยกเว้นถ้าว่าง
	internalRef  string                                 // unexported — ยกเว้นอัตโนมัติ
}

pm := PaymentMethod{
	ID: "pm_9f3a", Last4: "4242",
	ExpiryMonth: 12, ExpiryYear: 2028,
	CVV: "123", internalRef: "stripe:pm_9f3a",
}
data, _ := json.MarshalIndent(pm, "", "  ")
// CVV, BillingName (ว่าง) และ internalRef ไม่ปรากฏใน output

จะจัดการ time.Time ใน JSON marshaling ได้อย่างไร?

encoding/json marshal time.Time เป็น format RFC3339Nano โดยค่าเริ่มต้น (เช่น "2026-03-10T14:22:00Z") ซึ่งเข้ากันได้กับ ISO 8601 ถ้าต้องการ format อื่น — เช่น Unix epoch integers สำหรับ legacy API หรือ custom date-only string — ให้ implement MarshalJSON() บน wrapper type ที่ embed time.Time และ return format ที่ต้องการ

Go
import (
	"encoding/json"
	"fmt"
	"time"
)

// พฤติกรรมเริ่มต้น — RFC3339Nano ไม่ต้องใช้ custom code
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"
// }

// Custom: Unix timestamp เป็น integer
type UnixTime struct{ time.Time }

func (u UnixTime) MarshalJSON() ([]byte, error) {
	return []byte(fmt.Sprintf("%d", u.Unix())), nil
}

เครื่องมือที่เกี่ยวข้อง

มีให้ในภาษาอื่นด้วย: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üllerผู้ตรวจสอบทางเทคนิค

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.