JSON Formatter Go — دليل MarshalIndent()

·Systems Engineer·مراجعة بواسطةTobias Müller·نُشر

استخدم منسق ومُجمِّل 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.

After · json
Before · json
{
	"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" للأعمدة أو " " لمسافتين.

Go — مثال عمل بسيط
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 — أعمدة مقابل مسافات
// إزاحة بعلامات تبويب — مُفضَّلة في أدوات 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:"-" تستبعد الحقل كلياً — مفيد للكلمات السرية والمعرّفات الداخلية أو البيانات التي يجب ألا تتجاوز حدود الخدمة أبداً.

Go — علامات struct لاستجابة API
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:"-" هي الخيار الصحيح للحقول التي يجب استبعادها دون قيد أو شرط بغض النظر عن قيمتها — عادةً الأسرار وحقول التتبع الداخلية أو البيانات الصحيحة في الذاكرة لكن لا يجب تسلسلها إلى أي نظام خارجي.

Go — استبعاد الحقول الحساسة
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 الحالة وتنسيقات الوقت المخصصة أو أي نوع يخزن البيانات بشكل مختلف عما يجب تسلسله.

نوع مخصص — المال مع تحويل السنتات إلى الكسور العشرية

Go — MarshalJSON مخصص للمال
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 الحالة — التمثيل النصي

Go — MarshalJSON مخصص لـ 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.

Go 1.21+ — تسلسل UUID
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"
// }
ملاحظة:إذا خزّنت UUIDs كـ [16]byte بدون نوع مخصص، ستُرمّزها encoding/json كسلسلة base64 — مثلاً "VQ6EAOKbQdSnFkRmVUQAAA==". استخدم دائماً نوع UUID مناسباً أو نفّذ MarshalJSON() لإخراج تنسيق السلسلة الكنوني بالشرطات.

مرجع معاملات json.MarshalIndent()‎

توقيع الدالة func MarshalIndent(v any, prefix, indent string) ([]byte, error). كلٌّ من prefix و indent لغات حرفية من السلاسل النصية — لا يوجد اختصار رقمي مثل indent=4 في Python.

المعامل
Type
الوصف
v
any
القيمة المراد تحويلها — struct أو map أو slice أو primitive
prefix
string
سلسلة تُضاف في بداية كل سطر في المخرجات (عادةً "")
indent
string
سلسلة تُستخدم لكل مستوى من مستويات الإزاحة ("\t" أو " ")

خيارات علامات struct الشائعة:

Tag
التأثير
json:"name"
إعادة تسمية الحقل إلى name في مخرجات JSON
json:"name,omitempty"
إعادة تسمية + حذف إذا كانت القيمة صفرية (nil, "", 0, false)
json:"-"
استبعاد هذا الحقل دائماً من مخرجات JSON
json:",string"
ترميز رقم أو قيمة منطقية كقيمة سلسلة JSON

json.Indent() — إعادة تنسيق بايتات JSON الموجودة

عندما تمتلك بالفعل []byte من JSON — مثلاً من جسم استجابة HTTP، أو عمود jsonb في Postgres، أو ملف قرئ بـ os.ReadFile — لا تحتاج إلى تعريف struct وfetch قبل الطباعة الجميلة. json.Indent تُعيد تنسيق البايتات الخام مباشرةً بكتابة مخرجات مُزاحة في bytes.Buffer.

Go — json.Indent على البايتات الخام
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.

Go — json.Indent لتسجيل التصحيح
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، اكتب مجدداً أو سجّل.

قراءة ملف → تنسيق → إعادة كتابة

Go — تنسيق ملف 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 غير صالح في %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 → فك ترميز → طباعة جميلة لتسجيل التصحيح

Go — تنسيق استجابة 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)

Go — تنسيق جسم HTTP الخام بـ 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("قراءة الجسم: %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 أو الوصول إلى الحقول — إنه مجرد تحويل للعرض. يتعامل كلا النهجين بشكل صحيح مع أجسام الاستجابة الكبيرة طالما أن الجسم الكامل يتسع في الذاكرة.

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. هذه الأوامر المختصرة هي التي أحفظها عن ظهر قلب خلال التطوير والاستجابة للحوادث.

bash — تمرير JSON عبر الأنبوب إلى مُنسّق Python المدمج
echo '{"service":"payments","port":8443,"workers":4}' | python3 -m json.tool
# {
#     "service": "payments",
#     "port": 8443,
#     "workers": 4
# }
bash — تمرير عبر الأنبوب إلى jq للتنسيق والتصفية
# تنسيق فقط
cat api-response.json | jq .

# استخراج حقل متداخل
cat api-response.json | jq '.checks.database'

# تصفية مصفوفة
cat audit-log.json | jq '.[] | select(.severity == "error")'
bash — تمرير عبر الأنبوب إلى Go main.go مصغّر عبر stdin
# 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.go
ملاحظة:gofmt تُنسّق كود 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.

bash — تثبيت go-json
go get github.com/goccy/go-json
Go — استبدال encoding/json بـ go-json بتغيير import واحد
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

Go — بث مصفوفة 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("فتح %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 واحد لكل سطر

Go — معالجة تدفق سجل NDJSON سطراً بسطر
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)
	}
}
ملاحظة:انتقل إلى البث عندما يتجاوز ملف JSON لديك 100 MB أو عند معالجة تدفقات غير محدودة (مستهلكو Kafka، خطوط أنابيب السجلات، قراءات كائنات S3). تحميل ملف JSON بحجم 500 MB بـ os.ReadFile سيُخصص كامل تلك الذاكرة المؤقتة على الكومة، ويُحفّز ضغط GC، وقد يتسبب في OOM على الحاويات المحدودة الذاكرة.

الأخطاء الشائعة

تجاهل قيمة الخطأ المُعادة من MarshalIndent

المشكلة: التخلص من الخطأ بـ _ يعني أن قيمة غير قابلة للتسلسل (قناة، دالة، عدد مركب) تُنتج بصمت مخرجات nil أو تتسبب في panic أسفل المسار عند استدعاء string(nil).

الحل: تحقق دائماً من الخطأ. إذا كنت تُحوّل نوعاً يجب أن ينجح دائماً، فإن log.Fatalf المُسبب للـ panic أفضل من فقدان البيانات بصمت.

After · Go
Before · Go
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 بدلاً من os.Stdout.Write للمخرجات الثنائية

المشكلة: fmt.Println(string(data)) يُضيف محرف سطر جديد بعد JSON، مما يُفسد خطوط الأنابيب التي تعامل المخرجات كبايتات خام — مثلاً عند التمرير عبر الأنبوب إلى jq أو الكتابة إلى بروتوكول ثنائي.

الحل: استخدم os.Stdout.Write(data) للمخرجات النظيفة ثنائياً. إذا احتجت إلى سطر جديد للعرض البشري، أضفه صراحةً.

After · Go
Before · Go
data, _ := json.MarshalIndent(cfg, "", "  ")
os.Stdout.Write(data)
os.Stdout.Write([]byte("
")) // سطر جديد صريح فقط عند الحاجة
data, _ := json.MarshalIndent(cfg, "", "  ")
fmt.Println(string(data)) // يُضيف سطراً جديداً زائداً في النهاية
نسيان omitempty على حقول pointer

المشكلة: بدون omitempty، يُسلسل nil *string أو *int كـ "field": null. هذا يكشف أسماء الحقول الداخلية وقد يكسر متحققي مخطط JSON الصارمين على جانب المستهلك.

الحل: أضف omitempty لحقول pointer التي تريدها غائبة (لا null) في المخرجات. nil *T مع omitempty لا يُنتج أي مفتاح في JSON على الإطلاق.

After · Go
Before · Go
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]interface{} بدلاً من structs

المشكلة: إلغاء التسلسل إلى map[string]any يفقد معلومات النوع، يتطلب تأكيدات نوع يدوية، ويُنتج ترتيباً غير حتمي للمفاتيح — مما يُصعّب مقارنات JSON والسجلات.

الحل: عرّف struct مع علامات json مناسبة. الـ Structs آمنة النوع، أسرع في التحويل، تُنتج ترتيباً حتمياً للحقول يطابق تعريف الـ struct، وتجعل الكود موثقاً ذاتياً.

After · Go
Before · Go
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 صالح
أنواع مخصصة
البث
يتطلب تثبيتاً
json.MarshalIndent
✓ عبر MarshalJSON
لا (stdlib)
json.Indent
غير متاح (bytes فقط)
لا (stdlib)
json.Encoder
✗ (مضغوط)
✓ عبر MarshalJSON
لا (stdlib)
go-json
go get
sonic
go get (amd64/arm64)
jq (CLI)
غير متاح
تثبيت النظام

استخدم json.MarshalIndent لأي حالة تتحكم فيها في تعريف الـ struct وتحتاج إلى مخرجات منسقة — ملفات الإعداد وتسجيل التصحيح وحوامل الاختبار وتسجيل استجابات API. استخدم json.Indent عندما تمتلك بايتات خام بالفعل وتحتاج فقط إلى إضافة مسافات بيضاء دون رحلة ذهاب وإياب عبر أنواع Go. انتقل إلى go-json أو sonic فقط بعد أن يُؤكّد التحليل أن تحويل JSON هو عنق الزجاجة القابل للقياس — بالنسبة لمعظم الخدمات، المكتبة القياسية أكثر من كافية.

الأسئلة الشائعة

كيف أطبع JSON بشكل منسق في Go؟

استدعِ json.MarshalIndent(v, "", "\t") من حزمة encoding/json — الوسيطة الثانية هي بادئة السطر (عادةً فارغة) والثالثة هي محرف الإزاحة. مرر "\t" للأعمدة أو " " لمسافتين. لا تحتاج إلى مكتبة خارجية؛ encoding/json مدمجة في المكتبة القياسية لـ Go.

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 هي بايتات إخراج أكثر قليلاً وقدر ضئيل من وحدة المعالجة المركزية لإدراج المسافات البيضاء.

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

كيف أنسق []byte من JSON دون unmarshal للـ struct؟

استخدم json.Indent(&buf, src, "", "\t"). تأخذ هذه الدالة []byte موجودة من JSON وتكتب النسخة المُزاحة في bytes.Buffer — لا حاجة لتعريف struct أو تأكيد النوع أو رحلة ذهاب وإياب عبر أنواع Go. إنها الخيار الأسرع عندما تمتلك بايتات JSON خام بالفعل، مثل جسم استجابة HTTP أو عمود في قاعدة البيانات.

Go
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 لا يُسبب خطأ — تلك الحقول تُحذف ببساطة.

Go
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 دون الحاجة إلى أي علامة.

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                                 // غير مُصدَّر — مستبعد تلقائياً
}

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 ويُعيد التنسيق المطلوب.

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

الأدوات ذات الصلة

متاح أيضاً بـ: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.