Marshal وUnmarshal
تحوّل json.Marshal قيمة Go إلى بايتات JSON. وتملأ json.Unmarshal قيمة Go من بايتات JSON. وتختار وسوم البنى أسماء حقول JSON.
يغيب Email عن المخرجات الأولى بسبب omitempty ونص فارغ. تحتاج Unmarshal مؤشرًا (&back)؛ وتمرير القيمة نفسها يعيد InvalidUnmarshalError.
وسوم البنى
صيغة الوسم هي json:"name,option,option".
| الوسم | الأثر |
|---|---|
json:"user_id" | استخدام user_id مفتاحًا |
json:"email,omitempty" | الإسقاط عند false أو 0 أو "" أو nil أو شريحة أو خريطة فارغة |
json:",omitzero" (Go 1.24) | الإسقاط عندما تكون القيمة صفرية، أو يعيد IsZero() القيمة true |
json:"-" | عدم ترميز هذا الحقل أو فكّ ترميزه أبدًا |
json:"-," | استخدام المفتاح الحرفي - |
json:"count,string" | ترميز رقم أو قيمة منطقية كنص JSON ("42") |
| بلا وسم | المفتاح هو اسم الحقل في Go، UserID |
لا تغادر كلمة المرور البنية أبدًا، ويوضع الرصيد بين علامتي اقتباس، ويختفي deleted_at بينما يطبع created_at الوقت الصفري 0001-01-01T00:00:00Z. هذا الفرق هو سبب إضافة omitzero: لم يعمل omitempty مع البنى قط، وهي مفاجأة قديمة مع time.Time. في Go 1.23 وما قبله استخدم *time.Time مع omitempty للحصول على الأثر نفسه.
تنتج MarshalIndent(v, prefix, indent) مخرجات مقروءة. استخدمها لملفات الإعدادات وتتبّع الأخطاء؛ أما الواجهات البرمجية فترسل JSON مضغوطًا عادة.
الحقول المُصدَّرة فقط
تستخدم encoding/json الانعكاس (reflection) ولا ترى إلا الحقول المُصدَّرة. هذا أشيع خطأ في JSON في Go:
type point struct {
x, y int // lowercase: json.Marshal(point{1, 2}) gives {}
}
لا خطأ ولا تحذير، فقط {} عند الإخراج وحقول باقية على الصفر عند الإدخال. اجعل الحروف الأولى كبيرة وأضف وسومًا للمفاتيح ذات الحروف الصغيرة.
كيف يطابق فكّ الترميز الحقول
عند فكّ الترميز إلى بنية:
- تُطابق المفاتيح مع اسم الوسم، أو مع اسم الحقل، دون تمييز حالة الأحرف. يملأ
{"NAME": "x"}حقلًا موسومًا بـjson:"name". - المفاتيح التي لا حقل مطابقًا لها تُتجاهل بصمت.
- الحقول التي لا مفتاح مطابقًا لها تحتفظ بقيمتها الحالية. لا تعيد Unmarshal ضبطها، ففكّ الترميز إلى بنية فيها بيانات يدمج فيها.
- عدم تطابق النوع (نص حيث في البنية
int) يعيد*json.UnmarshalTypeError، لكن الحقول الأخرى تُملأ مع ذلك.
لرفض المفاتيح غير المتوقّعة، مثلًا في واجهة برمجية صارمة أو ملف إعدادات فيه أخطاء كتابة، استخدم Decoder مع DisallowUnknownFields:
بنية مجهولة: map[string]any
عندما لا تعرف الشكل مسبقًا، فكّ الترميز إلى map[string]any أو any. التحويل ثابت:
| JSON | Go |
|---|---|
| كائن | map[string]any |
| مصفوفة | []any |
| نص | string |
| رقم | float64 |
| true / false | bool |
| null | nil |
يظهر فخّان في المخرجات. كل رقم float64، فتفشل m["stock"].(int). والأعداد الصحيحة الأكبر من 2^53 تفقد دقتها كـ float64: المعرّف 9007199254740993 يعود بالشكل ...992. يتجنّب UseNumber الأمرين.
إذا كنت تعرف جزءًا من البنية، ففكّ ترميز ذلك الجزء إلى بنية واستخدم json.RawMessage للباقي. يحمل البايتات الخام لحقل لتفكّ ترميزه لاحقًا، بعد أن تعرف نوعه.
الشرائح والخرائط والمؤشرات وnil
| قيمة Go | JSON |
|---|---|
شريحة nil (var s []int) | null |
شريحة فارغة ([]int{}) | [] |
خريطة nil | null |
مؤشر nil | null |
[]byte | نص بترميز base64 |
map[string]T | كائن، بمفاتيح مرتّبة |
map[int]T | كائن بمفاتيح صحيحة مكتوبة كنصوص |
الفرق بين الشريحة nil والشريحة الفارغة مهم لعملاء الواجهات البرمجية الذين يتوقّعون مصفوفة. هيّئ بـ []T{} أو make([]T, 0) عندما يجب أن يكون الحقل [].
استخدم حقل مؤشر (*int أو *bool) عندما تحتاج التمييز بين "غائب" و"صفر" في المدخلات. بعد فكّ الترميز يعني المؤشر nil أن المفتاح مفقود أو null؛ والمؤشر إلى 0 يعني أن العميل أرسل 0.
التدفّقات: Encoder وDecoder
تعمل json.Marshal وUnmarshal على شرائح بايتات كاملة. أما لـ io.Reader أو io.Writer (جسم HTTP، أو ملف، أو stdin) فاستخدم json.NewDecoder وjson.NewEncoder. ويستطيع الـ Decoder أيضًا قراءة سلسلة من قيم JSON واحدة تلو الأخرى:
يكتب Encoder.Encode سطرًا جديدًا بعد كل قيمة. افتراضيًا يهرّب كل من Marshal وEncoder الرموز < و> و& إلى \u003c و\u003e و\u0026، فيصبح JSON آمنًا للتضمين في HTML. ويعطّل SetEscapeHTML(false) ذلك. في معالجات HTTP يكون json.NewDecoder(r.Body).Decode(&v) وjson.NewEncoder(w).Encode(v) الثنائي المعتاد.
ترميز مخصّص
يستطيع النوع التحكّم في JSON الخاص به بتطبيق json.Marshaler وjson.Unmarshaler. حالة شائعة هي تعداد iota يجب أن يظهر كاسم لا كرقم:
لـ MarshalJSON مستقبِل قيمة فتعمل مع القيم والمؤشرات؛ أما UnmarshalJSON فتحتاج مستقبِل مؤشر لأنها تعدّل القيمة. الأنواع التي تحتاج شكلًا نصيًا فقط يمكنها تطبيق encoding.TextMarshaler (MarshalText) بدلًا من ذلك، وهذا يجعلها صالحة أيضًا كمفاتيح خرائط.
أخطاء شائعة
- أسماء حقول بحروف صغيرة. تُتخطّى بصمت.
- تمرير قيمة إلى
Unmarshal. تحتاج مؤشرًا. - تجاهل الخطأ. JSON المشوّه وعدم تطابق الأنواع لا يُبلَّغ عنهما إلا عبره.
- افتراض أن الأرقام في
map[string]anyمن النوعint. إنهاfloat64. - توقّع أن يُسقط
omitemptyبنية فارغة أوtime.Timeصفريًا. استخدمomitzeroفي Go 1.24، أو مؤشرًا. - الاعتماد على ترتيب الحقول في الخرائط. تُرتَّب مفاتيح الخرائط عند الإخراج؛ وتحتفظ حقول البنى بترتيب تعريفها.
الأسئلة الشائعة
كيف أحوّل بنية إلى JSON في Go؟
استدعِ json.Marshal(v)، التي تعيد []byte وخطأ. لا تُضمَّن إلا الحقول المُصدَّرة (الأسماء التي تبدأ بحرف كبير). استخدم وسم بنية مثل json:"name" على الحقل للتحكّم في مفتاحه في JSON. وللمخرجات المزاحة استخدم json.MarshalIndent(v, "", " ").
لماذا تختفي حقول بنيتي من مخرجات JSON؟
لا ترى encoding/json إلا الحقول المُصدَّرة. الحقل المسمّى name (بحرف صغير) غير مرئي لها، عند الترميز وعند فكّ الترميز. اجعل الحرف الأول كبيرًا واضبط مفتاح JSON بوسم مثل json:"name".
ماذا يفعل omitempty في JSON في Go؟
يُسقط omitempty الحقل من المخرجات عندما يحمل قيمة فارغة: false أو 0 أو "" أو مؤشرًا أو واجهة nil أو شريحة أو خريطة فارغة. ولا يعامل البنية أو time.Time كفارغة. أضافت Go 1.24 الخيار omitzero، الذي يُسقط أي قيمة تساوي القيمة الصفرية لنوعها (أو يعيد تابعها IsZero() القيمة true)، بما في ذلك البنى وtime.Time.
كيف أحلّل JSON ذا بنية مجهولة في Go؟
فكّ الترميز إلى map[string]any (أو any). تصبح الكائنات map[string]any، والمصفوفات []any، والنصوص string، والقيم المنطقية bool، وكل رقم float64. استخدم تأكيدات النوع لقراءة القيم، وDecoder.UseNumber إذا وجب أن تبقى الأعداد الصحيحة الكبيرة دقيقة.