مقدمة عن العمل مع JSON في Go
JSON (JavaScript Object Notation) هو تنسيق خفيف لتبادل البيانات يُستخدم على نطاق واسع في تطبيقات الويب وواجهات برمجة التطبيقات (APIs) والخدمات المصغرة. توفر لغة Go (Golang) مكتبة مدمجة قوية encoding/json تجعل من السهل تحليل البيانات (إلغاء التسلسل) وتسلسلها. في هذه المقالة، سنفحص بالتفصيل كيفية العمل مع JSON في Go، مع تغطية الوظائف الرئيسية والهياكل وأفضل الممارسات.
أساسيات التسلسل وإلغاء التسلسل
التسلسل (Serialization) هو عملية تحويل بنية بيانات Go (مثل هيكل (struct) أو شريحة (slice)) إلى سلسلة JSON. إلغاء التسلسل (Deserialization) هو العملية العكسية — تحويل JSON إلى بنية Go.
التسلسل (Marshal)
للتسلسل، تُستخدم الدالة json.Marshal(). تأخذ قيمة Go وتُرجع شريحة بايت ([]byte) تحتوي على تمثيل JSON.
package main
import ( "encoding/json" "fmt")
type User struct { Name string `json:"name"` Age int `json:"age"` Email string `json:"email,omitempty"`}
func main() { user := User{Name: "Alexey", Age: 30, Email: "alex@example.com"} jsonData, err := json.Marshal(user) if err != nil { fmt.Println("Serialization error:", err) return } fmt.Println(string(jsonData)) // Output: {"name":"Alexey","age":30,"email":"alex@example.com"}لاحظ الوسوم json:"field_name". تسمح لك بالتحكم في أسماء المفاتيح في JSON، بالإضافة إلى إضافة خيارات مثل omitempty (تخطي الحقل إذا كان فارغًا).
إلغاء التسلسل (Unmarshal)
لتحليل JSON إلى بنية Go، تُستخدم الدالة json.Unmarshal(). تأخذ شريحة بايت ومؤشرًا إلى الهيكل الذي يجب كتابة البيانات فيه.
func main() { jsonString := `{"name":"Maria","age":25,"email":"maria@example.com"}` var user User err := json.Unmarshal([]byte(jsonString), &user) if err != nil { fmt.Println("Parsing error:", err) return } fmt.Printf("Name: %s, Age: %d, Email: %s", user.Name, user.Age, user.Email)}مهم: يجب أن يكون الهيكل محددًا مسبقًا، ويجب أن تتوافق حقوله مع مفاتيح JSON (مع مراعاة الوسوم).
العمل مع كائنات JSON غير محددة
أحيانًا يكون بنية JSON غير معروفة مسبقًا أو معقدة للغاية. في هذه الحالات، يمكنك استخدام map[string]interface{} أو interface{}.
استخدام map[string]interface{}ج3>func main() { jsonString := `{"name":"Ivan","age":35,"address":{"city":"Moscow","street":"Lenina"}}` var result map[string]interface{} err := json.Unmarshal([]byte(jsonString), &result) if err != nil { fmt.Println("Error:", err) return } fmt.Println(result["name"]) // Ivan fmt.Println(result["age"]) // 35 (type float64) // For nested objects, type assertion is needed address := result["address"].(map[string]interface{}) fmt.Println(address["city"]) // Moscow}
func main() { jsonString := `{"name":"Ivan","age":35,"address":{"city":"Moscow","street":"Lenina"}}` var result map[string]interface{} err := json.Unmarshal([]byte(jsonString), &result) if err != nil { fmt.Println("Error:", err) return } fmt.Println(result["name"]) // Ivan fmt.Println(result["age"]) // 35 (type float64) // For nested objects, type assertion is needed address := result["address"].(map[string]interface{}) fmt.Println(address["city"]) // Moscow}عيب هذا الأسلوب هو الحاجة إلى تأكيد النوع يدويًا (.(map[string]interface{}))، والذي قد يتسبب في حالة ذعر (panic) إذا لم تتطابق الأنواع.
استخدام json.RawMessage
إذا كنت بحاجة إلى تأجيل تحليل جزء من JSON، استخدم json.RawMessage. يتيح لك هذا تحليل الهيكل الرئيسي أولاً ثم معالجة البيانات المتداخلة بشكل منفصل.
type Response struct { Status string `json:"status"` Data json.RawMessage `json:"data"`}
func main() { jsonString := `{"status":"ok","data":{"user":"Peter","score":100}}` var resp Response json.Unmarshal([]byte(jsonString), &resp)
// Now Data can be parsed separately var userData map[string]interface{} json.Unm