سحر JSON في Go: لماذا بعض الحقول مرئية والبعض الآخر لا؟
\nهل سبق لك أن واجهت موقفًا حيث يرسل هيكل Go الخاص بك في استجابة JSON ما لا يجب على العميل رؤيته على الإطلاق؟ على سبيل المثال، كلمة مرور المستخدم، أو معرف قاعدة البيانات الداخلي، أو مفتاح سري. أو العكس، تحتاج إلى إظهار حقل فقط في ظل شرط معين (على سبيل المثال، إذا لم يكن فارغًا).
\nهذه المشكلة هي واحدة من أكثر المشكلات شيوعًا على Stack Overflow، وليس من دون سبب. العمل مع JSON في Go هو فن التوازن بين صرامة الأنواع ومرونة التمثيل. اليوم سنقوم بتحليل جميع الطرق: من الوسم البسيط json:"-" إلى الحلول المخصصة المعقدة باستخدام الواجهات. هيا بنا!
1. الطريقة الأبسط: الوسم json:"-"
\nيتيح لك Go التحكم في تسلسل الهيكل من خلال وسوم خاصة. إذا كنت تريد إخفاء حقل بشكل دائم من JSON، فقط أضف json:"-".
type User struct {\n Name string `json:"name"`\n Password string `json:"-"`\n Age int `json:"age"`\n}\n\nfunc main() {\n u := User{Name: "أليس", Password: "supersecret", Age: 30}\n data, _ := json.Marshal(u)\n fmt.Println(string(data))\n // الإخراج: {"name":"أليس","age":30}\n}\nاختفى حقل Password كما لو لم يكن موجودًا. مثالي للبيانات السرية التي لا يجب أن تغادر الخادم أبدًا.
2. الإخفاء الشرطي: omitempty
\nأحيانًا تحتاج إلى إخفاء حقل فقط إذا كان فارغًا. لهذا الغرض، تُستخدم الخاصية omitempty (اختصار لـ "omit if empty").
type Product struct {\n Name string `json:"name"`\n Description string `json:"description,omitempty"`\n Price float64 `json:"price,omitempty"`\n}\n\nfunc main() {\n p1 := Product{Name: "كتاب", Price: 500.0}\n data1, _ := json.Marshal(p1)\n fmt.Println(string(data1)) // {"name":"كتاب","price":500}\n\n p2 := Product{Name: "دورة"}\n data2, _ := json.Marshal(p2)\n fmt.Println(string(data2)) // {"name":"دورة"}\n}\nهام: تعمل omitempty فقط مع القيم "الصفرية": ""، 0، false، nil (للمصفوفات، الخرائط، المؤشرات).
مأزق الحقول الرقمية
\nكن حذرًا: إذا كان Price يساوي 0، فسيتم إخفاؤه أيضًا. في بعض منطقيات الأعمال، هذا غير مقبول (المنتج المجاني هو أيضًا سعر). في مثل هذه الحالات، من الأفضل استخدام المؤشرات.
3. استخدام المؤشرات (*) والواجهات
\nالمؤشرات هي أفضل صديق لك عندما تحتاج إلى التمييز بين "القيمة غير محددة" و"القيمة تساوي صفرًا".
\ntype UserProfile struct {\n Name string `json:"name"`\n Age *int `json:"age,omitempty"` // مؤشر إلى int\n Bio *string `json:"bio,omitempty"`\n}\n\nfunc main() {\n // العمر غير محدد - سيتم إخفاء الحقل\n u1 := UserProfile{Name: "إيفان"}\n data1, _ := json.Marshal(u1)\n fmt.Println(string(data1)) // {"name":"إيفان"}\n\n // العمر = 0 - هذه قيمة ذات معنى\n zero := 0\n u2 := UserProfile{Name: "ماريا", Age: &zero}\n data2, _ := json.Marshal(u2)\n fmt.Println(string(data2)) // {"name":"ماريا","age":0}\n}\nغالبًا ما يُستخدم هذا الأسلوب في REST API، حيث تحتاج إلى تحديث الحقول المرسلة فقط (التحديث الجزئي، طلبات PATCH).
\n\n4. التسلسل المخصص: الواجهة json.Marshaler
\nإذا لم تكن الوسوم القياسية كافية، يمكنك التحكم الكامل في العملية. قم بتنفيذ الواجهة json.Marshaler واكتب منطقك الخاص.
type DynamicUser struct {\n ID int\n Name string\n ShowEmail bool\n Email string\n}\n\nfunc (u DynamicUser) MarshalJSON() ([]byte, error) {\n // إنشاء هيكل مجهول للتسلسل فقط\n m := map[string]interface{}{\n "id": u.ID,\n "name": u.Name,\n }\n if u.ShowEmail {