معالجة JSON
الدرس 21: معالجة JSON
تشبيه من الحياة
تخيل أنك مترجم. JSON هي "اللغة العالمية" الأكثر استخداماً في العالم. عندما يحتاج برنامج Go للتواصل مع أنظمة أخرى (الواجهة الأمامية، API، قاعدة بيانات)، تحتاج إلى:
- تسلسل (Marshal): "ترجمة" هياكل Go إلى صيغة JSON لإرسالها
- فك التسلسل (Unmarshal): "ترجمة" JSON المستلم إلى هياكل Go للاستخدام
تماماً كما يحتاج المترجم لفهم قواعد وأعراف اللغتين، فإن حزمة encoding/json في Go هي أداتك القوية للتعامل مع JSON.
المفاهيم الأساسية
| المفهوم | الوصف |
|---|---|
| التسلسل (Marshal) | تحويل هياكل بيانات Go إلى شرائح بايت JSON |
| فك التسلسل (Unmarshal) | تحليل شرائح بايت JSON إلى هياكل بيانات Go |
| وسوم الهيكل (Struct Tag) | بيانات وصفية تتحكم في أسماء حقول JSON والسلوك |
| التدفق (Streaming) | استخدام Decoder/Encoder للبيانات الكبيرة أو تدفق الشبكة |
| التسلسل المخصص | تنفيذ واجهات Marshaler/Unmarshaler لمنطق تحويل مخصص |
الصياغة الأساسية والاستخدام
1. استيراد الحزمة
import "encoding/json"
2. التسلسل: هيكل ← JSON
// تعريف هيكل
type User struct {
Name string
Age int
Email string
}
user := User{Name: "Alice", Age: 28, Email: "alice@example.com"}
// التسلسل
data, err := json.Marshal(user)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
// المخرجات: {"Name":"Alice","Age":28,"Email":"alice@example.com"}
json.Marshal تعيد []byte، تحتاج تحويل string() لطباعة JSON مقروء.
3. فك التسلسل: JSON ← هيكل
jsonStr := `{"Name":"Bob","Age":32,"Email":"bob@example.com"}`
var user User
err := json.Unmarshal([]byte(jsonStr), &user)
if err != nil {
log.Fatal(err)
}
fmt.Printf("الاسم: %s, العمر: %d\n", user.Name, user.Age)
// المخرجات: الاسم: Bob, العمر: 32
Unmarshal يجب أن يكون مؤشراً، وإلا لن تؤثر التغييرات.
4. وسوم الهيكل
type Product struct {
ID int `json:"id"` // تحديد اسم حقل JSON
Name string `json:"name"` // التسمية بحروف صغيرة أكثر توافقاً مع JSON
Price float64 `json:"price"`
Desc string `json:"description,omitempty"` // حذف عند الفراغ
internal string `json:"-"` // تجاهل هذا الحقل تماماً
}
omitempty: عندما يكون الحقل قيمة صفرية، سيُحذف من مخرجات JSON-: هذا الحقل لن يظهر أبداً في JSON- أسماء الوسوم لها أولوية على أسماء الحقول
5. خرائط الأنواع الشائعة
| نوع Go | نوع JSON |
|---|---|
string |
string |
int, float64 |
number |
bool |
boolean |
nil |
null |
[]T |
array |
map[string]T |
object |
struct |
object |
أمثلة
مثال: تسلسل وفك تسلسل JSON أساسي (الصعوبة ⭐)
package main
import (
"encoding/json"
"fmt"
"log"
)
// هيكل Book
type Book struct {
Title string `json:"title"`
Author string `json:"author"`
Pages int `json:"pages"`
Tags []string `json:"tags"`
InStock bool `json:"in_stock"`
}
func main() {
// === التسلسل ===
book := Book{
Title: "Go in Action",
Author: "John Smith",
Pages: 350,
Tags: []string{"Programming", "Go", "Backend"},
InStock: true,
}
// طباعة منسقة (مع مسافات بادئة)
jsonData, err := json.MarshalIndent(book, "", " ")
if err != nil {
log.Fatal("فشل التسلسل:", err)
}
fmt.Println("=== نتيجة التسلسل ===")
fmt.Println(string(jsonData))
// === فك التسلسل ===
jsonStr := `{
"title": "Mastering Go",
"author": "Jane Doe",
"pages": 480,
"tags": ["Go", "Advanced", "Concurrency"],
"in_stock": false
}`
var newBook Book
err = json.Unmarshal([]byte(jsonStr), &newBook)
if err != nil {
log.Fatal("فشل فك التسلسل:", err)
}
fmt.Println("\n=== نتيجة فك التسلسل ===")
fmt.Printf("العنوان: %s\n", newBook.Title)
fmt.Printf("المؤلف: %s\n", newBook.Author)
fmt.Printf("العلامات: %v\n", newBook.Tags)
fmt.Printf("متوفر: %v\n", newBook.InStock)
}
المخرجات:
=== نتيجة التسلسل ===
{
"title": "Go in Action",
"author": "John Smith",
"pages": 350,
"tags": [
"Programming",
"Go",
"Backend"
],
"in_stock": true
}
=== نتيجة فك التسلسل ===
العنوان: Mastering Go
المؤلف: Jane Doe
العلامات: [Go Advanced Concurrency]
متوفر: false
مثال: JSON المتداخل والتعامل مع Map (الصعوبة ⭐⭐)
package main
import (
"encoding/json"
"fmt"
"log"
)
// هيكل العنوان
type Address struct {
City string `json:"city"`
Street string `json:"street"`
ZipCode string `json:"zip_code"`
}
// معلومات الاتصال
type Contact struct {
Phone string `json:"phone"`
Email string `json:"email"`
}
// هيكل الموظف (مع تداخل)
type Employee struct {
Name string `json:"name"`
Age int `json:"age"`
Address Address `json:"address"` // هيكل متداخل
Contact Contact `json:"contact"` // هيكل متداخل
Skills []string `json:"skills"` // شريحة
Metadata map[string]string `json:"metadata"` // حقول ديناميكية
}
func main() {
// بناء بيانات متداخلة
emp := Employee{
Name: "Alice",
Age: 35,
Address: Address{
City: "Beijing",
Street: "88 Jianguo Road, Chaoyang District",
ZipCode: "100022",
},
Contact: Contact{
Phone: "13800138000",
Email: "alice@example.com",
},
Skills: []string{"Go", "Python", "Docker"},
Metadata: map[string]string{
"department": "Engineering",
"level": "P7",
"joined": "2020-03-15",
},
}
// التسلسل
data, err := json.MarshalIndent(emp, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println("=== تسلسل JSON متداخل ===")
fmt.Println(string(data))
// التعامل مع JSON ديناميكي (باستخدام map)
dynamicJSON := `{
"event": "user_login",
"timestamp": 1700000000,
"data": {
"user_id": 12345,
"ip": "192.168.1.100",
"browser": "Chrome"
},
"tags": ["web", "auth"]
}`
var result map[string]interface{}
err = json.Unmarshal([]byte(dynamicJSON), &result)
if err != nil {
log.Fatal(err)
}
fmt.Println("\n=== تحليل JSON ديناميكي ===")
fmt.Printf("الحدث: %s\n", result["event"])
fmt.Printf("الوقت: %.0f\n", result["timestamp"])
// الوصول إلى الخريطة المتداخلة
if data, ok := result["data"].(map[string]interface{}); ok {
fmt.Printf("معرف المستخدم: %.0f\n", data["user_id"])
fmt.Printf("عنوان IP: %s\n", data["ip"])
}
// الوصول إلى المصفوفة
if tags, ok := result["tags"].([]interface{}); ok {
fmt.Print("العلامات: ")
for _, tag := range tags {
fmt.Printf("%s ", tag)
}
fmt.Println()
}
}
المخرجات:
=== تسلسل JSON متداخل ===
{
"name": "Alice",
"age": 35,
"address": {
"city": "Beijing",
"street": "88 Jianguo Road, Chaoyang District",
"zip_code": "100022"
},
"contact": {
"phone": "13800138000",
"email": "alice@example.com"
},
"skills": [
"Go",
"Python",
"Docker"
],
"metadata": {
"department": "Engineering",
"joined": "2020-03-15",
"level": "P7"
}
}
=== تحليل JSON ديناميكي ===
الحدث: user_login
الوقت: 1700000000
معرف المستخدم: 12345
عنوان IP: 192.168.1.100
العلامات: web auth
مثال: تسلسل مخصص وتدفق (الصعوبة ⭐⭐⭐)
package main
import (
"encoding/json"
"fmt"
"log"
"strings"
"time"
)
// CustomTime نوع وقت مخصص
type CustomTime struct {
time.Time
}
// تنفيذ واجهة json.Marshaler
func (ct CustomTime) MarshalJSON() ([]byte, error) {
// صيغة المخرجات: 2006-01-02 15:04:05
formatted := ct.Format("2006-01-02 15:04:05")
return json.Marshal(formatted)
}
// تنفيذ واجهة json.Unmarshaler
func (ct *CustomTime) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
// دعم تحليل صيغ متعددة
formats := []string{
"2006-01-02 15:04:05",
"2006-01-02T15:04:05",
"2006/01/02",
}
for _, format := range formats {
t, err := time.Parse(format, s)
if err == nil {
ct.Time = t
return nil
}
}
return fmt.Errorf("غير قادر على تحليل الوقت: %s", s)
}
// Status نوع تعداد مخصص
type Status int
const (
StatusActive Status = iota // 0
StatusInactive // 1
StatusBanned // 2
)
// خريطة Status إلى نص
var statusNames = map[Status]string{
StatusActive: "active",
StatusInactive: "inactive",
StatusBanned: "banned",
}
// خريطة نص إلى Status
var statusValues = map[string]Status{
"active": StatusActive,
"inactive": StatusInactive,
"banned": StatusBanned,
}
// MarshalJSON تسلسل مخصص
func (s Status) MarshalJSON() ([]byte, error) {
name, ok := statusNames[s]
if !ok {
return json.Marshal("unknown")
}
return json.Marshal(name)
}
// UnmarshalJSON فك تسلسل مخصص
func (s *Status) UnmarshalJSON(data []byte) error {
var name string
if err := json.Unmarshal(data, &name); err != nil {
return err
}
val, ok := statusValues[name]
if !ok {
return fmt.Errorf("حالة غير معروفة: %s", name)
}
*s = val
return nil
}
// EventLog إدخال سجل الأحداث
type EventLog struct {
Event string `json:"event"`
Timestamp CustomTime `json:"timestamp"`
Status Status `json:"status"`
Details string `json:"details,omitempty"`
}
func main() {
// === عرض التسلسل المخصص ===
logEntry := EventLog{
Event: "user_register",
Timestamp: CustomTime{time.Date(2024, 1, 15, 14, 30, 0, 0, time.Local)},
Status: StatusActive,
Details: "تم تسجيل مستخدم جديد بنجاح",
}
data, _ := json.MarshalIndent(logEntry, "", " ")
fmt.Println("=== تسلسل مخصص ===")
fmt.Println(string(data))
// === عرض فك التسلسل المخصص ===
jsonStr := `{
"event": "user_login",
"timestamp": "2024/01/15",
"status": "inactive"
}`
var entry EventLog
err := json.Unmarshal([]byte(jsonStr), &entry)
if err != nil {
log.Fatal(err)
}
fmt.Printf("\nنتيجة التحليل: الحدث=%s, الوقت=%s, الحالة=%d\n",
entry.Event,
entry.Timestamp.Format("2006-01-02 15:04:05"),
entry.Status,
)
// === عرض التدفق ===
fmt.Println("\n=== Decoder بالتدفق ===")
// محاكاة تدفق JSON مستلم من الشبكة
jsonStream := `[
{"name": "Alice", "score": 95},
{"name": "Bob", "score": 87},
{"name": "Charlie", "score": 92}
]`
decoder := json.NewDecoder(strings.NewReader(jsonStream))
// قراءة رمز البداية
token, err := decoder.Token()
if err != nil {
log.Fatal(err)
}
fmt.Printf("رمز البداية: %v\n", token)
// قراءة عناصر المصفوفة واحداً تلو الآخر
type Student struct {
Name string `json:"name"`
Score int `json:"score"`
}
var students []Student
for decoder.More() {
var s Student
if err := decoder.Decode(&s); err != nil {
log.Fatal(err)
}
students = append(students, s)
}
for _, s := range students {
fmt.Printf("الطالب: %s, الدرجة: %d\n", s.Name, s.Score)
}
// === عرض Encoder بالتدفق ===
fmt.Println("\n=== Encoder بالتدفق ===")
var buf strings.Builder
encoder := json.NewEncoder(&buf)
encoder.SetIndent("", " ")
// تشفير كائنات فردية
for _, s := range students {
if err := encoder.Encode(s); err != nil {
log.Fatal(err)
}
}
fmt.Println(buf.String())
}
المخرجات:
=== تسلسل مخصص ===
{
"event": "user_register",
"timestamp": "2024-01-15 14:30:00",
"status": "active",
"details": "تم تسجيل مستخدم جديد بنجاح"
}
نتيجة التحليل: الحدث=user_login, الوقت=2024-01-15 00:00:00, الحالة=1
=== Decoder بالتدفق ===
رمز البداية: [
الطالب: Alice, الدرجة: 95
الطالب: Bob, الدرجة: 87
الطالب: Charlie, الدرجة: 92
=== Encoder بالتدفق ===
{
"name": "Alice",
"score": 95
}
{
"name": "Bob",
"score": 87
}
{
"name": "Charlie",
"score": 92
}
سيناريوهات التطبيق
السيناريو 1: غلاف استجابة API
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
// APIResponse هيكل استجابة API موحد
type APIResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
// SuccessResponse استجابة نجاح
func SuccessResponse(w http.ResponseWriter, data interface{}) {
resp := APIResponse{
Code: 200,
Message: "success",
Data: data,
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(resp)
}
// ErrorResponse استجابة خطأ
func ErrorResponse(w http.ResponseWriter, statusCode int, errMsg string) {
resp := APIResponse{
Code: statusCode,
Message: "error",
Error: errMsg,
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(resp)
}
// UserHandler يعالج طلبات المستخدم
func UserHandler(w http.ResponseWriter, r *http.Request) {
// بيانات مستخدم محاكاة
users := []map[string]interface{}{
{"id": 1, "name": "Alice", "role": "admin"},
{"id": 2, "name": "Bob", "role": "user"},
{"id": 3, "name": "Charlie", "role": "user"},
}
SuccessResponse(w, users)
}
func main() {
// محاكاة استجابة API
fmt.Println("=== محاكاة استجابة API ===")
// استجابة نجاح
successResp := APIResponse{
Code: 200,
Message: "success",
Data: map[string]interface{}{
"id": 1,
"name": "Alice",
},
}
data, _ := json.MarshalIndent(successResp, "", " ")
fmt.Println("استجابة النجاح:")
fmt.Println(string(data))
// استجابة خطأ
errorResp := APIResponse{
Code: 404,
Message: "error",
Error: "المستخدم غير موجود",
}
data, _ = json.MarshalIndent(errorResp, "", " ")
fmt.Println("\nاستجابة الخطأ:")
fmt.Println(string(data))
_ = log.Fatal // تجنب تحذير غير مستخدم
}
المخرجات:
=== محاكاة استجابة API ===
استجابة النجاح:
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"name": "Alice"
}
}
استجابة الخطأ:
{
"code": 404,
"message": "error",
"error": "المستخدم غير موجود"
}
السيناريو 2: قراءة والتحقق من ملف التكوين
package main
import (
"encoding/json"
"fmt"
"log"
"os"
)
// DatabaseConfig تكوين قاعدة البيانات
type DatabaseConfig struct {
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
DBName string `json:"dbname"`
}
// ServerConfig تكوين الخادم
type ServerConfig struct {
Host string `json:"host"`
Port int `json:"port"`
ReadTimeout int `json:"read_timeout"`
WriteTimeout int `json:"write_timeout"`
AllowOrigins []string `json:"allow_origins"`
}
// AppConfig تكوين التطبيق
type AppConfig struct {
AppName string `json:"app_name"`
Debug bool `json:"debug"`
Server ServerConfig `json:"server"`
Database DatabaseConfig `json:"database"`
}
// Validate يتحقق من صحة التكوين
func (c *AppConfig) Validate() error {
if c.AppName == "" {
return fmt.Errorf("app_name لا يمكن أن يكون فارغاً")
}
if c.Server.Port <= 0 || c.Server.Port > 65535 {
return fmt.Errorf("server.port يجب أن يكون بين 1-65535")
}
if c.Database.Host == "" {
return fmt.Errorf("database.host لا يمكن أن يكون فارغاً")
}
return nil
}
func main() {
// محاكاة محتوى ملف التكوين
configJSON := `{
"app_name": "GoWebApp",
"debug": true,
"server": {
"host": "0.0.0.0",
"port": 8080,
"read_timeout": 30,
"write_timeout": 30,
"allow_origins": ["http://localhost:3000", "https://example.com"]
},
"database": {
"host": "localhost",
"port": 3306,
"username": "root",
"password": "secret123",
"dbname": "myapp"
}
}`
// تحليل التكوين
var config AppConfig
err := json.Unmarshal([]byte(configJSON), &config)
if err != nil {
log.Fatalf("فشل تحليل التكوين: %v", err)
}
// التحقق من التكوين
if err := config.Validate(); err != nil {
log.Fatalf("فشل التحقق من التكوين: %v", err)
}
// طباعة معلومات التكوين
fmt.Printf("اسم التطبيق: %s\n", config.AppName)
fmt.Printf("وضع التصحيح: %v\n", config.Debug)
fmt.Printf("عنوان الخادم: %s:%d\n", config.Server.Host, config.Server.Port)
fmt.Printf("اتصال قاعدة البيانات: %s:%d/%s\n",
config.Database.Host,
config.Database.Port,
config.Database.DBName,
)
fmt.Printf("المصادر المسموحة: %v\n", config.Server.AllowOrigins)
// كتابة مثال (حفظ التكوين المعدّل)
config.Debug = false
config.Server.Port = 9090
output, err := json.MarshalIndent(config, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println("\n=== التكوين المعدّل ===")
fmt.Println(string(output))
// في مشروع حقيقي، ستكتب في ملف:
// os.WriteFile("config.json", output, 0644)
_ = os.WriteFile // تجنب تحذير غير مستخدم
}
المخرجات:
اسم التطبيق: GoWebApp
وضع التصحيح: true
عنوان الخادم: 0.0.0.0:8080
اتصال قاعدة البيانات: localhost:3306/myapp
المصادر المسموحة: [http://localhost:3000 https://example.com]
=== التكوين المعدّل ===
{
"app_name": "GoWebApp",
"debug": false,
"server": {
"host": "0.0.0.0",
"port": 9090,
"read_timeout": 30,
"write_timeout": 30,
"allow_origins": [
"http://localhost:3000",
"https://example.com"
]
},
"database": {
"host": "localhost",
"port": 3306,
"username": "root",
"password": "secret123",
"dbname": "myapp"
}
}
❓ أسئلة شائعة
س1: لماذا أسماء حقول JSON بأحرف كبيرة؟
السبب: Go يصدر فقط الحقول التي يبدأ حرفها الأول بأحرف كبيرة، و json.Marshal يستخدم اسم الحقل كمفتاح JSON افتراضياً.
الحل: استخدم وسوم الهيكل لتحديد أسماء بأحرف صغيرة:
type User struct {
Name string `json:"name"` // "name" في JSON
Age int `json:"age"` // "age" في JSON
Email string `json:"email"` // "email" في JSON
}
س2: كيف أتجاهل الحقول ذات القيم الفارغة؟
استخدم الوسم omitempty:
type Request struct {
Name string `json:"name"`
Email string `json:"email,omitempty"` // حذف عند نص فارغ
Age int `json:"age,omitempty"` // حذف عند 0
Items []string `json:"items,omitempty"` // حذف عند nil أو شريحة فارغة
}
// اختبار
req := Request{Name: "Alice"}
data, _ := json.Marshal(req)
fmt.Println(string(data))
// المخرجات: {"name":"alice"} — email, age, items محذوفة جميعاً
س3: كيف أتعامل مع مشاكل دقة الأرقام في JSON؟
json.Unmarshal في Go تحلل أرقام JSON كـ `float64 افتراضياً، مما يفقد الدقة للأعداد الصحيحة الكبيرة:
// مثال على المشكلة
var result map[string]interface{}
json.Unmarshal([]byte(`{"id": 12345678901234567}`), &result)
fmt.Printf("%.0f\n", result["id"]) // المخرجات: 12345678901234568 (فقدان الدقة!)
// الحل: استخدام json.Number
decoder := json.NewDecoder(strings.NewReader(`{"id": 12345678901234567}`))
decoder.UseNumber()
decoder.Decode(&result)
id, _ := result["id"].(json.Number).Int64()
fmt.Println(id) // المخرجات: 12345678901234567 (صحيح)
س4: كيف أتعامل مع JSON ذو بنية غير معروفة؟
استخدم map[string]interface{} أو json.RawMessage:
// الطريقة 1: استخدام map
var data map[string]interface{}
json.Unmarshal(jsonBytes, &data)
// الطريقة 2: استخدام json.RawMessage للتأخير في التحليل
type Message struct {
Type string `json:"type"`
Payload json.RawMessage `json:"payload"` // تأخير التحليل
}
// تحديد كيفية تحليل Payload بناءً على حقل Type
switch msg.Type {
case "user":
var user User
json.Unmarshal(msg.Payload, &user)
case "order":
var order Order
json.Unmarshal(msg.Payload, &order)
}
📖 ملخص
غطى هذا الدرس المحتوى الأساسي لمعالجة JSON في Go:
- العمليات الأساسية:
json.Marshalللتسلسل وjson.Unmarshalلفك التسلسل - وسوم الهيكل: استخدام
json:"name"للتحكم في أسماء الحقول،omitemptyلحذف القيم الفارغة،-لتجاهل الحقول - التعامل مع التداخل: تداخل الهياكل،
map[string]interface{}لـ JSON الديناميكي - التسلسل المخصص: تنفيذ واجهات
Marshaler/Unmarshaler - التدفق:
json.Decoderوjson.Encoderلبيانات التدفق - التطبيقات العملية: أغلفة استجابة API، إدارة ملفات التكوين
- تحقق دائماً من معالجة الأخطاء
- مرر مؤشرات لفك التسلسل
- استخدم وسوم الهيكل للحفاظ على اصطلاحات تسمية JSON
- استخدم التدفق لكميات البيانات الكبيرة
📝 تمارين
التمرين 1: تطبيق أساسي
اكتب برنامجاً يحدد هيكل Student (بالاسم والعمر وقائمة درجات)، وينفذ:
- إنشاء 3 كائنات طلاب
- التسلسل إلى مصفوفة JSON مع طباعة منسقة
- فك التسلسل إلى هياكل وطباعة المعلومات
التمرين 2: تطبيق متوسط
نفذ مدير تكوين JSON بسيطاً:
1.حدد هيكل تكوين التطبيق (يحتوي على خادم وقاعدة بيانات وسجلات إلخ)
2. نفذ دالة LoadConfig(filename) لقراءة التكوين من ملف
3. نفذ دالة SaveConfig(filename, config) لحفظ التكوين في ملف
4. نفذ التحقق من التكوين
التمرين 3: تطبيق متقدم
نفذ معالج رسائل JSON-RPC:
1.حدد هياكل الطلبات والاستجابات
2. استخدم json.RawMessage لتأخير التحليل
3. وجه إلى دوال معالجة مختلفة بناءً على اسم طريقة الطلب
4. ادعم معالجة الطلبات بالدفعة
// تلميح: صيغة طلب JSON-RPC
type RPCRequest struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params json.RawMessage `json:"params"`
ID interface{} `json:"id"`
}
type RPCResponse struct {
JSONRPC string `json:"jsonrpc"`
Result interface{} `json:"result,omitempty"`
Error *RPCError `json:"error,omitempty"`
ID interface{} `json:"id"`
}
الدرس التالي
بعد إكمال هذا الدرس، يرجى المتابعة إلى الدرس 22: خدمات HTTP، حيث سنتعلم كيفية استخدام Go لبناء خوادم وعملاء HTTP.



