1. نظرة عامة على مكتبة encoding/json
القياسية
لغة Go توفر مكتبة قوية بإسم encoding/json
للتعامل مع تنسيق البيانات JSON. باستخدام هذه المكتبة، يمكنك بسهولة تحويل أنواع بيانات Go إلى تنسيق JSON (تسلسل) أو تحويل بيانات JSON إلى أنواع بيانات Go (فك تسلسل). توفر هذه المكتبة العديد من الوظائف مثل التشفير، فك التشفير، تدفق الإدخال/الإخراج، والدعم لمنطق تحليل JSON مخصص.
أهم أنواع البيانات والوظائف في هذه المكتبة تشمل:
-
Marshal
وMarshalIndent
: تُستخدم لتسلسل أنواع بيانات Go إلى سلاسل JSON. -
Unmarshal
: تُستخدم لفك تسلسل سلاسل JSON إلى أنواع بيانات Go. -
Encoder
وDecoder
: تُستخدم لتدفق الإدخال/الإخراج لبيانات JSON. -
Valid
: تُستخدم للتحقق مما إذا كانت سلسلة معينة بتنسيق JSON صالحة.
سنتعلم على وجه الخصوص استخدام هذه الوظائف والأنواع في الفصول القادمة.
2. تسلسل هياكل بيانات Go إلى JSON
2.1 استخدام json.Marshal
json.Marshal
هي وظيفة تقوم بتسلسل أنواع بيانات Go إلى سلاسل JSON. تأخذ أنواع بيانات من لغة Go كإدخال، تحولها إلى تنسيق JSON، وتُرجع شريحة بايت مع إمكانية وجود أخطاء.
فيما يلي مثال بسيط يوضح كيفية تحويل هيكل بيانات Go إلى سلسلة JSON:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
person := Person{"Alice", 30}
jsonData, err := json.Marshal(person)
if err != nil {
log.Fatalf("فشل تسلسل JSON: %s", err)
}
fmt.Println(string(jsonData)) // الناتج: {"name":"Alice","age":30}
}
بالإضافة إلى الهياكل، يمكن لوظيفة json.Marshal
أيضًا تسلسل أنواع بيانات أخرى مثل الخريطة والقطعة. فيما يلي أمثلة باستخدام map[string]interface{}
و slice
:
// تحويل الخريطة إلى JSON
myMap := map[string]interface{}{
"name": "Bob",
"age": 25,
}
jsonData, err := json.Marshal(myMap)
// ... تجاهل معالجة الأخطاء والناتج ...
// تحويل القطعة إلى JSON
mySlice := []string{"Apple", "Banana", "Cherry"}
jsonData, err := json.Marshal(mySlice)
// ... تجاهل معالجة الأخطاء والناتج ...
2.2 العلامات التصنيفية للهياكل
في لغة Go، تُستخدم العلامات التصنيفية لتوفير البيانات الوصفية لحقول الهيكل، للتحكم في سلوك تسلسل JSON. أشيع استخدامات العلامات تشمل إعادة تسمية الحقول، تجاهل الحقول، وتسلسل مشروط.
على سبيل المثال، يمكنك استخدام العلامة json:"<اسم>"
لتحديد اسم الحقل في JSON:
type Animal struct {
SpeciesName string `json:"species"`
Description string `json:"desc,omitempty"`
Tag string `json:"-"` // إضافة علامة "-" تشير إلى أن هذا الحقل لن يتم تسلسله
}
في المثال أعلاه، تشير العلامة json:"-"
أمام حقل Tag
إلى تجاهله بواسطة json.Marshal
. الخيار omitempty
لحقل Description
يشير إلى أنه إذا كان الحقل فارغًا (قيمة صفرية، مثل سلسلة فارغة)، فلن يُضمن في JSON المتسلسل.
فيما يلي مثال كامل باستخدام العلامات التصنيفية:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Animal struct {
SpeciesName string `json:"species"`
Description string `json:"desc,omitempty"`
Tag string `json:"-"`
}
func main() {
animal := Animal{
SpeciesName: "African Elephant",
Description: "ثديي كبير ذو خرطوم ونابات.",
Tag: "مهدد بالانقراض", // لن يتم تسلسل هذا الحقل إلى JSON
}
jsonData, err := json.Marshal(animal)
if err != nil {
log.Fatalf("فشل تسلسل JSON: %s", err)
}
fmt.Println(string(jsonData)) // الناتج: {"species":"African Elephant","desc":"ثديي كبير ذو خرطوم ونابات."}
}
بهذه الطريقة، يمكنك ضمان هيكل بيانات واضح بينما تتحكم في تمثيل JSON، مما يسمح بمرونة في التعامل مع مختلف احتياجات التسلسل.
3. فك تسلسل JSON إلى هيكل بيانات Go
3.1 استخدام json.Unmarshal
تسمح لنا الدالة json.Unmarshal
بتحليل سلاسل JSON إلى هياكل بيانات Go مثل الهياكل، والخرائط، إلخ. لاستخدام json.Unmarshal
، نحتاج أولاً إلى تعريف هيكل بيانات Go يتطابق مع بيانات JSON.
فتخيل أن لدينا البيانات JSON التالية:
{
"name": "Alice",
"age": 25,
"emails": ["[email protected]", "[email protected]"]
}
لتحليل هذه البيانات إلى هيكل Go، نحتاج إلى تعريف هيكل مطابق:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Emails []string `json:"emails"`
}
الآن يمكننا استخدام json.Unmarshal
لعملية التحليل السلسلة:
import (
"encoding/json"
"fmt"
)
func main() {
jsonData := `{
"name": "Alice",
"age": 25,
"emails": ["[email protected]", "[email protected]"]
}`
var user User
err := json.Unmarshal([]byte(jsonData), &user)
if err != nil {
fmt.Println("Error unmarshalling JSON:", err)
return
}
fmt.Printf("User: %+v\n", user)
}
في المثال أعلاه، استخدمنا الوسوم مثل json:"name"
لإعلام دالة json.Unmarshal
بتعيين حقول JSON إلى حقول الهيكل.
3.2 التحليل الديناميكي
في بعض الأحيان، الهيكل الذي نحتاج لتحليله ليس معروفاً مسبقاً، أو قد يتغير بشكل ديناميكي. في مثل هذه الحالات، يمكننا استخدام interface{}
أو json.RawMessage
للتحليل.
استخدام interface{}
يسمح لنا بالتحليل دون معرفة هيكل JSON:
func main() {
jsonData := `{
"name": "Alice",
"details": {
"age": 25,
"job": "Engineer"
}
}`
var result map[string]interface{}
json.Unmarshal([]byte(jsonData), &result)
fmt.Println(result)
// تأكيد الأنواع، التأكد من تطابق النوع قبل الاستخدام
name := result["name"].(string)
fmt.Println("Name:", name)
details := result["details"].(map[string]interface{})
age := details["age"].(float64) // ملحوظة: المتغيرات من نوع interface{} تعامل عادة ك float64
fmt.Println("Age:", age)
}
استخدام json.RawMessage
يسمح لنا بالاحتفاظ بالجزء الأصلي من JSON مع تحليل أجزاء منه بشكل انتقائي:
type UserDynamic struct {
Name string `json:"name"`
Details json.RawMessage `json:"details"`
}
func main() {
jsonData := `{
"name": "Alice",
"details": {
"age": 25,
"job": "Engineer"
}
}`
var user UserDynamic
json.Unmarshal([]byte(jsonData), &user)
var details map[string]interface{}
json.Unmarshal(user.Details, &details)
fmt.Println("Name:", user.Name)
fmt.Println("Age:", details["age"])
fmt.Println("Job:", details["job"])
}
هذا النهج مفيد للتعامل مع هياكل JSON حيث قد تحتوي بعض الحقول على أنواع مختلفة من البيانات، ويسمح بمرونة في التعامل مع البيانات.
4 التعامل مع الهياكل المدمجة والمصفوفات
4.1 كائنات JSON مدمجة
غالباً ما تكون بيانات JSON الشائعة ليست مسطحة، ولكنها تحتوي على هياكل مدمجة. في Go، يمكننا التعامل مع هذا الوضع من خلال تعريف هياكل مدمجة.
لنفترض أن لدينا الJSON المدمج التالي:
{
"name": "Bob",
"contact": {
"email": "[email protected]",
"address": "123 Main St"
}
}
يمكننا تعريف هيكل Go كما يلي:
type ContactInfo struct {
Email string `json:"email"`
Address string `json:"address"`
}
type UserWithContact struct {
Name string `json:"name"`
Contact ContactInfo `json:"contact"`
}
عملية التحليل تشبه عمليات التحليل للهياكل غير المدمجة:
func main() {
jsonData := `{
"name": "Bob",
"contact": {
"email": "[email protected]",
"address": "123 Main St"
}
}`
var user UserWithContact
err := json.Unmarshal([]byte(jsonData), &user)
if err != nil {
fmt.Println("Error unmarshalling JSON:", err)
}
fmt.Printf("%+v\n", user)
}
4.2 مصفوفات JSON
في JSON، تعتبر المصفوفات هيكل بيانات شائع. في Go، تتوافق مع القطع.
فكر في المصفوفة JSON التالية:
[
{"name": "دايف", "age": 34},
{"name": "إيف", "age": 28}
]
في Go، نقوم بتعريف الهيكل المقابل والقطع على النحو التالي:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
jsonData := `[
{"name": "دايف", "age": 34},
{"name": "إيف", "age": 28}
]`
var people []Person
json.Unmarshal([]byte(jsonData), &people)
for _, person := range people {
fmt.Printf("%+v\n", person)
}
}
بهذه الطريقة، يمكننا فك تسلسل كل عنصر في مصفوفة JSON إلى قطع من نوع Go لمعالجتها والوصول إليها.
5 التعامل مع الأخطاء
عند التعامل مع بيانات JSON، سواء كان التسلسل (تحويل البيانات المنظمة إلى تنسيق JSON) أو الفك تسلسل (تحويل JSON إلى بيانات منظمة)، قد تحدث أخطاء. فيما يلي، سنناقش الأخطاء الشائعة وكيفية التعامل معها.
5.1 التعامل مع أخطاء التسلسل
تحدث الأخطاء في التسلسل عادةً أثناء عملية تحويل هيكل أو أنواع بيانات أخرى إلى سلسلة JSON. على سبيل المثال، إذا تم محاولة تسلسل هيكل يحتوي على حقول غير قانونية (مثل نوع القناة أو دالة لا يمكن تمثيلها في JSON)، سيُعيد json.Marshal
خطأ.
import (
"encoding/json"
"fmt"
"log"
)
type User struct {
Name string
Age int
// نفترض أن هناك حقلًا هنا لا يمكن تسليسله
// Data chan struct{} // القنوات لا يمكن تمثيلها في JSON
}
func main() {
u := User{
Name: "آليس",
Age: 30,
// Data: make(chan struct{}),
}
bytes, err := json.Marshal(u)
if err != nil {
log.Fatalf("فشل تسلسل JSON: %v", err)
}
fmt.Println(string(bytes))
}
في المثال أعلاه، قمنا بتعليق الحقل Data
عمدًا. إذا تم إلغاء التعليق، سيفشل التسلسل، وسيُسجل البرنامج الخطأ ويتوقف التنفيذ. ينطوي التعامل مع مثل هذه الأخطاء عادةً على التحقق من الأخطاء وتنفيذ استراتيجيات معالجة الأخطاء المقابلة (مثل تسجيل الأخطاء، وإرجاع البيانات الافتراضية، وما إلى ذلك).
5.2 التعامل مع أخطاء الفك تسلسل
قد تحدث أخطاء الفك تسلسل أثناء عملية تحويل سلسلة JSON إلى هيكل من نوع Go أو نوع بيانات آخر. على سبيل المثال، إذا كان تنسيق سلسلة JSON غير صحيح أو غير متوافق مع النوع المستهدف، سيعيد json.Unmarshal
خطأ.
import (
"encoding/json"
"fmt"
"log"
)
func main() {
var data = []byte(`{"name":"آليس","age":"غير معروف"}`) // "العمر" يجب أن يكون عددًا، لكن تم توفير سلسلة هنا
var u User
err := json.Unmarshal(data, &u)
if err != nil {
log.Fatalf("فشل فك تسلسل JSON: %v", err)
}
fmt.Printf("%+v\n", u)
}
في هذا المثال، قمنا بتوفير نوع بيانات خاطئ لحقل "العمر" (سلسلة بدلاً من العدد المتوقع)، مما تسبب في رمي json.Unmarshal
لخطأ. لذلك، نحتاج إلى التعامل مع هذا الوضع بشكل مناسب. الممارسة الشائعة هي تسجيل رسالة الخطأ وربما إرجاع كائن فارغ أو قيمة افتراضية أو رسالة خطأ تبعًا للسيناريو.
6 الميزات المتقدمة وتحسين الأداء
6.1 التخصيص في عمليات التسلسل والإزالة
بشكل افتراضي، يقوم الحزمة encoding/json
في لغة البرمجة Go بتسلسل وإزالة JSON عبر التأمل. ومع ذلك، يمكننا تخصيص هذه العمليات من خلال تنفيذ واجهات json.Marshaler
و json.Unmarshaler
.
import (
"encoding/json"
"fmt"
)
type Color struct {
Red uint8
Green uint8
Blue uint8
}
func (c Color) MarshalJSON() ([]byte, error) {
hex := fmt.Sprintf("\"#%02x%02x%02x\"", c.Red, c.Green, c.Blue)
return []byte(hex), nil
}
func (c *Color) UnmarshalJSON(data []byte) error {
_, err := fmt.Sscanf(string(data), "\"#%02x%02x%02x\"", &c.Red, &c.Green, &c.Blue)
return err
}
func main() {
c := Color{Red: 255, Green: 99, Blue: 71}
jsonColor, _ := json.Marshal(c)
fmt.Println(string(jsonColor))
var newColor Color
json.Unmarshal(jsonColor, &newColor)
fmt.Println(newColor)
}
هنا، قمنا بتعريف نوع Color
وتنفيذ الأساليب MarshalJSON
و UnmarshalJSON
لتحويل الألوان RGB إلى سلاسل عشرية ومن ثم إلى ألوان RGB مرة أخرى.
6.2 المشفرات والفكريات
عند التعامل مع بيانات JSON كبيرة، قد يؤدي استخدام json.Marshal
و json.Unmarshal
مباشرة إلى استهلاك زائد للذاكرة أو عمليات إدخال/إخراج غير كفئة. لذا، توفر حزمة encoding/json
في لغة البرمجة Go أنواع مشفرة وفكريّة، والتي يمكن أن تعالج بيانات JSON بشكل تدفقي.
6.2.1 استخدام json.Encoder
json.Encoder
يمكنه كتابة بيانات JSON مباشرة إلى أي كائن ينفذ واجهة io.Writer، مما يعني أنه يمكنك ترميز بيانات JSON مباشرة إلى ملف، اتصال شبكة، الخ.
import (
"encoding/json"
"os"
)
func main() {
users := []User{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
}
file, _ := os.Create("users.json")
defer file.Close()
encoder := json.NewEncoder(file)
if err := encoder.Encode(users); err != nil {
log.Fatalf("Encoding error: %v", err)
}
}
6.2.2 استخدام json.Decoder
json.Decoder
يمكنه قراءة بيانات JSON مباشرة من أي كائن ينفذ واجهة io.Reader، بحثًا عن وتحليل كائنات JSON ومصفوفاتها.
import (
"encoding/json"
"os"
)
func main() {
file, _ := os.Open("users.json")
defer file.Close()
var users []User
decoder := json.NewDecoder(file)
if err := decoder.Decode(&users); err != nil {
log.Fatalf("Decoding error: %v", err)
}
for _, u := range users {
fmt.Printf("%+v\n", u)
}
}
من خلال معالجة البيانات باستخدام المشفرات والفكّرات، يمكنك تنفيذ معالجة JSON أثناء القراءة، مما يقلل من استخدام الذاكرة ويحسن من كفاءة المعالجة، مما يكون خاصة مفيدًا لمعالجة عمليات النقل عبر الشبكة أو الملفات الكبيرة.