JSON Formatter Go — دليل MarshalIndent()
استخدم منسق ومُجمِّل JSON المجاني مباشرةً في متصفحك — لا حاجة للتثبيت.
جرّب منسق ومُجمِّل JSON أونلاين ←حين أعمل على microservice بـ Go وأحتاج إلى فحص استجابة API أو ملف إعداد، يكون JSON المضغوط أول عقبة — سطر واحد بمئات الحقول المتداخلة لا يخبرني بشيء في نظرة واحدة. لـتنسيق JSON في Go، تمنحك المكتبة القياسية كل ما تحتاجه: json.MarshalIndent مدمجة في encoding/json، تأتي مع كل تثبيت Go، ولا تتطلب أي تبعيات خارجية. يغطي هذا الدليل الصورة الكاملة: struct tags، وتطبيقات MarshalJSON() المخصصة، و json.Indent لإعادة تنسيق البايتات الخام، والبث المتدفق للملفات الكبيرة بـ json.Decoder، ومتى تلجأ إلى go-json للمسارات عالية الإنتاجية، إضافة إلى أوامر CLI للتنسيق السريع في الطرفية. جميع الأمثلة تستخدم Go 1.21+.
- ✓json.MarshalIndent(v, "", "\t") مكتبة قياسية — صفر تبعيات، تأتي مع كل تثبيت Go.
- ✓علامات struct json:"field_name,omitempty" تتحكم في مفاتيح التسلسل وتحذف الحقول ذات القيمة الصفرية.
- ✓نفّذ MarshalJSON() على أي نوع للتحكم الكامل في تمثيله في JSON.
- ✓json.Indent() تُعيد تنسيق []byte المُحوَّلة مسبقاً دون إعادة تحليل الـ struct — أسرع للبايتات الخام.
- ✓للملفات الكبيرة (>100 MB): استخدم json.Decoder مع Token() للبث دون تحميل كل شيء في الذاكرة.
- ✓go-json بديل drop-in أسرع 3–5× من encoding/json لـ API عالية الإنتاجية.
ما هو تنسيق JSON؟
تنسيق JSON — يُسمى أيضاً الطباعة الجميلة — يحوّل سلسلة JSON مضغوطة ومصغّرة إلى تخطيط مقروء للإنسان مع إزاحة متسقة وفواصل أسطر. البيانات الأساسية متطابقة؛ المسافة البيضاء وحدها هي التي تتغير. JSON المضغوط مثالي لنقل الشبكة حيث تهم كل بايت؛ أما JSON المنسق فمثالي للتصحيح ومراجعة الكود وفحص السجلات وتأليف ملفات الإعداد. حزمة encoding/json في Go تتعامل مع كلا الوضعين بنداء دالة واحد — تبديل بين المخرجات المضغوطة والمُزاحة عبر الاختيار بين json.Marshal و json.MarshalIndent.
{
"service": "payments",
"port": 8443,
"workers": 4
}{"service":"payments","port":8443,"workers":4}json.MarshalIndent() — نهج المكتبة القياسية
json.MarshalIndent موجودة في حزمة encoding/json التي هي جزء من المكتبة القياسية لـ Go — لا go get مطلوب. توقيعها MarshalIndent(v any, prefix, indent string) ([]byte, error): سلسلة prefix تُضاف في بداية كل سطر مخرجات (تُترك فارغة دائماً تقريباً)، و indent تُكرَّر مرة واحدة لكل مستوى تداخل. مرر "\t" للأعمدة أو " " لمسافتين.
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"
// ]
// }الاختيار بين الأعمدة والمسافات هو في الغالب اتفاقية الفريق. تُفضّل كثير من مشاريع Go الأعمدة لأن gofmt (الذي يُنسّق كود Go) يستخدم الأعمدة. إزاحة مسافتين أو أربع مسافات شائعة عندما يكون JSON مُوجَّهاً لمستهلك JavaScript أو Python. إليك نفس الـ struct مع كلا أسلوبَي الإزاحة جنباً إلى جنب:
// إزاحة بعلامات تبويب — مُفضَّلة في أدوات Go الأصلية
data, _ := json.MarshalIndent(cfg, "", " ")
// {
// "host": "payments-api.internal",
// "port": 8443
// }
// إزاحة بمسافتين — شائعة لـ API مع مستهلكين JS/Python
data, _ = json.MarshalIndent(cfg, "", " ")
// {
// "host": "payments-api.internal",
// "port": 8443
// }
// إزاحة بأربع مسافات
data, _ = json.MarshalIndent(cfg, "", " ")
// {
// "host": "payments-api.internal",
// "port": 8443
// }json.Marshal(v) عندما تحتاج إلى مخرجات مضغوطة — حمولات الشبكة، قيم الذاكرة المؤقتة، أو أي مسار يهم فيه الحجم الثنائي. يقبل نفس وسيطة القيمة ولديه نفس دلالات الخطأ، لكنه يُنتج JSON في سطر واحد بدون أي مسافات بيضاء.علامات Struct — التحكم في أسماء الحقول وOmitempty
علامات struct في Go هي لغات حرفية توضع بعد إعلانات الحقول تُخبر encoding/jsonكيف تُسلسل كل حقل. ثلاث توجيهات رئيسية: 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"` // احذف إذا كان pointer نيل
IsAdmin bool `json:"is_admin,omitempty"` // احذف إذا كانت false
passwordHash string // غير مُصدَّر — مستبعد تلقائياً
}
// مستخدم بجميع الحقول الاختيارية مملوءة
full := UserProfile{
ID: "usr_7b3c", Email: "m.alomari@example.sa",
DisplayName: "محمد العمري", IsAdmin: true,
}
// {
// "id": "usr_7b3c",
// "email": "m.alomari@example.sa",
// "display_name": "محمد العمري",
// "is_admin": true
// }
// مستخدم بدون حقول اختيارية — تُحذف كلياً
minimal := UserProfile{ID: "usr_2a91", Email: "sara.alahmad@example.sa"}
// {
// "id": "usr_2a91",
// "email": "sara.alahmad@example.sa"
// }علامة 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("سري"), 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 تطبيق واجهة json.Marshaler بتعريف طريقة MarshalJSON() ([]byte, error). عندما تصادف encoding/json مثل هذا النوع، تستدعي الطريقة بدلاً من التسلسل الافتراضي القائم على الانعكاس. هذا هو النمط الكنوني في Go لأنواع النطاق التي تحتاج إلى تمثيل سلكي محدد — القيم النقدية وenums الحالة وتنسيقات الوقت المخصصة أو أي نوع يخزن البيانات بشكل مختلف عما يجب تسلسله.
نوع مخصص — المال مع تحويل السنتات إلى الكسور العشرية
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: "SAR"},
Tax: Money{Amount: 1592, Currency: "SAR"},
Total: Money{Amount: 21492, Currency: "SAR"},
}
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": "SAR", "display": "SAR 199.00" },
// "tax": { "amount": 15.92, "currency": "SAR", "display": "SAR 15.92" },
// "total": { "amount": 214.92, "currency": "SAR", "display": "SAR 214.92" }
// }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"
// }MarshalJSON() و UnmarshalJSON() معاً. إذا نفّذت التسلسل فقط، فإن رحلة الذهاب والإياب للنوع عبر JSON (تسلسل ← تخزين ← فك تسلسل) ستفقد البنية بصمت أو تُعيد النوع الخاطئ. الزوج يشكّل عقداً يضمن أن النوع يستطيع النجاة من رحلة ذهاب وإياب عبر JSON.UUID — التسلسل كسلسلة نصية
لا تمتلك المكتبة القياسية لـ Go نوع UUID. الخيار الأكثر شيوعاً هو github.com/google/uuid، الذي ينفّذ بالفعل MarshalJSON() ويُسلسل كسلسلة RFC 4122 بين علامتي اقتباس. إذا كنت تستخدم [16]byte خاماً أو نوع ID مخصصاً، نفّذ الواجهة بنفسك لتجنب كتل base64 ثنائية في مخرجات JSON.
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 لغات حرفية من السلاسل النصية — لا يوجد اختصار رقمي مثل indent=4 في Python.
خيارات علامات struct الشائعة:
json.Indent() — إعادة تنسيق بايتات JSON الموجودة
عندما تمتلك بالفعل []byte من JSON — مثلاً من جسم استجابة HTTP، أو عمود jsonb في Postgres، أو ملف قرئ بـ os.ReadFile — لا تحتاج إلى تعريف struct وfetch قبل الطباعة الجميلة. json.Indent تُعيد تنسيق البايتات الخام مباشرةً بكتابة مخرجات مُزاحة في bytes.Buffer.
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
)
func main() {
// محاكاة حمولة JSON خام من خدمة 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
// }نمط شائع أستخدمه في الخدمات المصغرة هو استدعاء json.Indent قبل الكتابة إلى السجلات المنظمة — يُضيف عبئاً ضئيلاً ويجعل إدخالات السجل أسهل بكثير في القراءة خلال حادثة. الدالة مفيدة بشكل خاص لتسجيل استجابات HTTP، والطباعة الجميلة لسلاسل JSON المخزّنة، وخطوط أنابيب format-on-read حيث لا يتوفر تعريف الـ struct.
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 قبل محاولة الإزاحة.تنسيق JSON من ملف واستجابة HTTP
من أكثر السيناريوهات الواقعية شيوعاً في خدمات Go: تنسيق JSON مقروء من ملف على القرص (ملفات الإعداد، بيانات الاختبار، بذور الهجرة) وطباعة جميلة لأجسام استجابة HTTP لتسجيل التصحيح أو تأكيدات الاختبار. كلاهما يتبع نفس النمط: اقرأ البايتات، استدعِ json.Indent أو unmarshal ثم 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("قراءة %s: %w", path, err)
}
if !json.Valid(data) {
return fmt.Errorf("JSON غير صالح في %s", path)
}
var buf bytes.Buffer
if err := json.Indent(&buf, data, "", " "); err != nil {
return fmt.Errorf("إزاحة %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("تم تنسيق config/database.json بنجاح")
}استجابة HTTP → فك ترميز → طباعة جميلة لتسجيل التصحيح
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("فك ترميز استجابة الصحة: %v", err)
}
pretty, err := json.MarshalIndent(result, "", " ")
if err != nil {
log.Fatalf("تحويل استجابة الصحة: %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
// }جسم الاستجابة الخام → 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("قراءة الجسم: %v", err)
}
var buf bytes.Buffer
if err := json.Indent(&buf, body, "", " "); err != nil {
log.Fatalf("indent: %v", err)
}
fmt.Println(buf.String())
}طباعة JSON بشكل جميل من استجابة HTTP في Go
النهجان أعلاه يغطيان الحالات الأكثر شيوعاً: فك ترميز إلى 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())
تنسيق JSON من سطر الأوامر في مشاريع Go
أحياناً تحتاج إلى تنسيق حمولة JSON في الطرفية مباشرةً دون كتابة برنامج Go. هذه الأوامر المختصرة هي التي أحفظها عن ظهر قلب خلال التطوير والاستجابة للحوادث.
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 — ستتسبب إما في خطأ أو مخرجات غير مقروءة. استخدم jq . أو python3 -m json.tool لملفات JSON.بديل عالي الأداء — go-json
بالنسبة للغالبية العظمى من خدمات Go، encoding/json سريعة بما يكفي. لكن إذا ظهر تحويل JSON في المحلل — الأمر الشائع في REST APIs عالية الإنتاجية أو الخدمات التي تُصدر سطور سجل منظمة كبيرة في كل طلب — مكتبة go-json بديل drop-in أسرع 3–5× بنفس سطح API.
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 هي أسرع مكتبة JSON في Go، لكنها تعمل فقط على amd64 و arm64 (تستخدم تجميع JIT). استخدم go-json عندما تحتاج إلى drop-in محمول؛ الجأ إلى sonic عندما تكون على معمارية معروفة وتحتاج كل ميكروثانية في مسار ساخن.
العمل مع ملفات JSON الكبيرة
تتطلب json.MarshalIndent و json.Indent كلتاهما تواجد الحمولة الكاملة في الذاكرة. للملفات التي تتجاوز 100 MB — تصديرات البيانات وسجلات التدقيق وحمولات مستهلك Kafka — استخدم json.Decoder لبث المدخلات ومعالجة السجلات واحداً تلو الآخر.
بث مصفوفة JSON كبيرة مع json.Decoder
package main
import (
"encoding/json"
"fmt"
"log"
"os"
)
type AuditEvent struct {
RequestID string `json:"request_id"`
UserID string `json:"user_id"`
Action string `json:"action"`
Severity string `json:"severity"`
DurationMs int `json:"duration_ms"`
}
func processAuditLog(path string) error {
file, err := os.Open(path)
if err != nil {
return fmt.Errorf("فتح %s: %w", path, err)
}
defer file.Close()
dec := json.NewDecoder(file)
// قراءة رمز الفتح '['
if _, err := dec.Token(); err != nil {
return fmt.Errorf("قراءة رمز الفتح: %w", err)
}
var processed int
for dec.More() {
var event AuditEvent
if err := dec.Decode(&event); err != nil {
return fmt.Errorf("فك ترميز الحدث %d: %w", processed, err)
}
// معالجة حدث واحد في كل مرة — استخدام ذاكرة ثابت
if event.Severity == "error" {
fmt.Printf("[خطأ] %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("معالجة سجل التدقيق: %v", err)
}
}NDJSON — كائن 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("فتح السجل: %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("مسح: %v", err)
}
}os.ReadFile سيُخصص كامل تلك الذاكرة المؤقتة على الكومة، ويُحفّز ضغط GC، وقد يتسبب في OOM على الحاويات المحدودة الذاكرة.الأخطاء الشائعة
المشكلة: التخلص من الخطأ بـ _ يعني أن قيمة غير قابلة للتسلسل (قناة، دالة، عدد مركب) تُنتج بصمت مخرجات nil أو تتسبب في panic أسفل المسار عند استدعاء string(nil).
الحل: تحقق دائماً من الخطأ. إذا كنت تُحوّل نوعاً يجب أن ينجح دائماً، فإن log.Fatalf المُسبب للـ panic أفضل من فقدان البيانات بصمت.
data, err := json.MarshalIndent(payload, "", " ")
if err != nil {
log.Fatalf("marshal payload: %v", err)
}
fmt.Println(string(data))data, _ := json.MarshalIndent(payload, "", " ") fmt.Println(string(data)) // سلسلة فارغة إذا فشل التحويل
المشكلة: fmt.Println(string(data)) يُضيف محرف سطر جديد بعد JSON، مما يُفسد خطوط الأنابيب التي تعامل المخرجات كبايتات خام — مثلاً عند التمرير عبر الأنبوب إلى jq أو الكتابة إلى بروتوكول ثنائي.
الحل: استخدم os.Stdout.Write(data) للمخرجات النظيفة ثنائياً. إذا احتجت إلى سطر جديد للعرض البشري، أضفه صراحةً.
data, _ := json.MarshalIndent(cfg, "", " ")
os.Stdout.Write(data)
os.Stdout.Write([]byte("
")) // سطر جديد صريح فقط عند الحاجةdata, _ := json.MarshalIndent(cfg, "", " ") fmt.Println(string(data)) // يُضيف سطراً جديداً زائداً في النهاية
المشكلة: بدون omitempty، يُسلسل nil *string أو *int كـ "field": null. هذا يكشف أسماء الحقول الداخلية وقد يكسر متحققي مخطط JSON الصارمين على جانب المستهلك.
الحل: أضف omitempty لحقول pointer التي تريدها غائبة (لا null) في المخرجات. nil *T مع omitempty لا يُنتج أي مفتاح في JSON على الإطلاق.
type WebhookPayload struct {
EventID string `json:"event_id"`
ErrorMsg *string `json:"error_msg,omitempty"` // يُحذف عندما يكون nil
}
// {"event_id":"evt_3c7f"}type WebhookPayload struct {
EventID string `json:"event_id"`
ErrorMsg *string `json:"error_msg"` // يظهر null عندما يكون nil
}
// {"event_id":"evt_3c7f","error_msg":null}المشكلة: إلغاء التسلسل إلى map[string]any يفقد معلومات النوع، يتطلب تأكيدات نوع يدوية، ويُنتج ترتيباً غير حتمي للمفاتيح — مما يُصعّب مقارنات JSON والسجلات.
الحل: عرّف struct مع علامات json مناسبة. الـ Structs آمنة النوع، أسرع في التحويل، تُنتج ترتيباً حتمياً للحقول يطابق تعريف الـ struct، وتجعل الكود موثقاً ذاتياً.
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 // مُحدَّد النوع، آمن، سريعvar result map[string]any json.Unmarshal(body, &result) port := result["port"].(float64) // تأكيد النوع مطلوب، panic إذا كان النوع خاطئاً
encoding/json مقابل البدائل — مقارنة سريعة
استخدم json.MarshalIndent لأي حالة تتحكم فيها في تعريف الـ struct وتحتاج إلى مخرجات منسقة — ملفات الإعداد وتسجيل التصحيح وحوامل الاختبار وتسجيل استجابات API. استخدم json.Indent عندما تمتلك بايتات خام بالفعل وتحتاج فقط إلى إضافة مسافات بيضاء دون رحلة ذهاب وإياب عبر أنواع Go. انتقل إلى go-json أو sonic فقط بعد أن يُؤكّد التحليل أن تحويل JSON هو عنق الزجاجة القابل للقياس — بالنسبة لمعظم الخدمات، المكتبة القياسية أكثر من كافية.
الأسئلة الشائعة
كيف أطبع JSON بشكل منسق في Go؟
استدعِ json.MarshalIndent(v, "", "\t") من حزمة encoding/json — الوسيطة الثانية هي بادئة السطر (عادةً فارغة) والثالثة هي محرف الإزاحة. مرر "\t" للأعمدة أو " " لمسافتين. لا تحتاج إلى مكتبة خارجية؛ encoding/json مدمجة في المكتبة القياسية لـ Go.
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
config := map[string]any{
"service": "payments-api",
"port": 8443,
"region": "me-south-1",
}
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
log.Fatalf("marshal: %v", err)
}
fmt.Println(string(data))
// {
// "port": 8443,
// "region": "me-south-1",
// "service": "payments-api"
// }
}ما الفرق بين json.Marshal وjson.MarshalIndent؟
تنتج json.Marshal مخرجات JSON مضغوطة في سطر واحد بدون مسافات بيضاء — مثالية لنقل الشبكة حيث تهم كل بايت. أما json.MarshalIndent فتأخذ معاملين إضافيين من السلاسل النصية (prefix وindent) وتنتج مخرجات مُزاحة مقروءة للإنسان. كلا الدالتين تقبل نفس أنواع القيم وتُعيدان ([]byte, error). التكلفة الوحيدة لـ MarshalIndent هي بايتات إخراج أكثر قليلاً وقدر ضئيل من وحدة المعالجة المركزية لإدراج المسافات البيضاء.
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
// }كيف أنسق []byte من JSON دون unmarshal للـ struct؟
استخدم json.Indent(&buf, src, "", "\t"). تأخذ هذه الدالة []byte موجودة من JSON وتكتب النسخة المُزاحة في bytes.Buffer — لا حاجة لتعريف struct أو تأكيد النوع أو رحلة ذهاب وإياب عبر أنواع Go. إنها الخيار الأسرع عندما تمتلك بايتات JSON خام بالفعل، مثل جسم استجابة HTTP أو عمود في قاعدة البيانات.
import (
"bytes"
"encoding/json"
"fmt"
"log"
)
raw := []byte(`{"endpoint":"/api/v2/faturat","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/faturat",
// "page": 1,
// "per_page": 50,
// "total": 312
// }لماذا تُعيد json.MarshalIndent خطأ؟
تُعيد encoding/json خطأ عندما تعذّر تمثيل القيمة كـ JSON. الأسباب الأكثر شيوعاً: تحويل قناة أو دالة أو عدد مركب (لا يوجد مكافئ JSON لها)؛ struct تُطبّق MarshalJSON() وتُعيد خطأ؛ أو map بمفاتيح غير نصية. والأهم: تحويل struct تحتوي على حقل غير مُصدَّر أو pointer بقيمة nil لا يُسبب خطأ — تلك الحقول تُحذف ببساطة.
import (
"encoding/json"
"fmt"
)
// هذا سيُعيد خطأ — القنوات غير قابلة للتسلسل في JSON
ch := make(chan int)
_, err := json.MarshalIndent(ch, "", " ")
fmt.Println(err)
// json: unsupported type: chan int
// هذا يعمل — حقول pointer النيل مع 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 محذوف، لا خطأكيف أستبعد حقلاً من مخرجات JSON في Go؟
ثلاث طرق. أولاً، استخدم علامة struct json:"-" — يُستبعد الحقل دائماً بصرف النظر عن قيمته. ثانياً، استخدم omitempty — يُستبعد الحقل فقط عندما يحمل القيمة الصفرية لنوعه (nil pointer، سلسلة فارغة، 0، false). ثالثاً، الحقول غير المُصدَّرة (بحرف صغير) تُستبعد تلقائياً بواسطة 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 لا تظهر في المخرجاتكيف أتعامل مع time.Time في تحويل JSON؟
تُحوّل encoding/json قيم time.Time بشكل افتراضي إلى تنسيق RFC3339Nano (مثلاً "2026-03-10T14:22:00Z")، وهو متوافق مع ISO 8601. إذا كنت بحاجة إلى تنسيق مختلف — كأعداد صحيحة Unix epoch لـ API قديم، أو سلسلة تاريخ مخصصة — نفّذ MarshalJSON() على نوع wrapper يتضمن time.Time ويُعيد التنسيق المطلوب.
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 timestamp كعدد صحيح
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.