Go: معالجة ملفات الإدخال/الإخراج (I/O) وملفات JSON
آخر تحديث: 2026-08-26
يُعد كل من إدخال/إخراج الملفات وJSON مهارتين أساسيتين في تطوير الباك إند بلغة Go — حيث توفر حزمة
osعمليات الملفات على مستوى استدعاءات النظام، بينما تتيح حزمةencoding/jsonالتواصل السلس بين لغة Go وعالم JavaScript.
صُممت عمليات الملفات ومعالجة JSON في المكتبة القياسية لـ Go باتباع نهج ذكي وموحد: فهي تستند جميعها إلى واجهتي io.Reader وio.Writer. في هذا الدرس، ستتقن جميع المهارات الأساسية المتعلقة بعمليات الإدخال/الإخراج للملفات ومعالجة JSON.
1. ستتعلم
- عمليات الملفات في نظام التشغيل (إنشاء/فتح/قراءة/كتابة)
os.ReadFile/os.WriteFile: القراءة والكتابة في عملية واحدة- القراءة/الكتابة المخزنة مؤقتًا باستخدام bufio (سطرًا تلو الآخر/الملفات الكبيرة)
encoding/jsonتنظيم / إلغاء التنظيم- معالجة تدفق البيانات باستخدام أداة تشفير/فك تشفير JSON
- تتحكم علامات JSON في سلوك التسلسل
- إنشاء أداة لتصدير قاعدة البيانات باستخدام عمليات الإدخال/الإخراج للملفات وJSON
2. القصة الحقيقية لمهندس متكامل
(1) المشكلة: ربط سلاسل JSON يدويًّا
تشارلي هو مهندس برمجيات متكامل يحتاج إلى تصدير بيانات المستخدمين من قاعدة البيانات إلى ملف JSON لاستخدامها في الواجهة الأمامية:
«لم أرغب في استخدام مكتبة خارجية، لذا قمت بإنشاء سلسلة JSON يدويًّا. وتبين لاحقًا أن حقل اسم المستخدم احتوى على الحرف
\"، مما أدى إلى إفساد تنسيق JSON تمامًا. ولم يظهر الخطأ إلا بعد أن كنت قد وصلت إلى منتصف عملية تصدير مليون صف من البيانات — واستغرق الأمر ساعتين إضافيتين لإلغاء التغييرات.»
فتح الكود الذي كان قد كتبه:
// Bad code: manually concatenating JSON strings
func exportUserJSON(users []User) string {
result := "["
for i, u := range users {
if i > 0 {
result += ","
}
// Manual concatenation, double quotes not escaped
result += "{\"name\":\"" + u.Name + "\",\"age\":" + string(u.Age) + "}"
}
result += "]"
return result
}
إذا احتوى u.Name على " أو \، فسيصبح ملف JSON الذي تم إنشاؤه تالفًا. علاوة على ذلك، يقوم string(u.Age) بتحويل الأرقام إلى رموز ASCII — فيصبح 28 هو \x1c.
(2) حل Go: encoding/json + عمليات الإدخال/الإخراج للملفات
// json_exporter.go
package main
import (
"encoding/json"
"fmt"
"os"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
City string `json:"city"`
}
func main() {
users := []User{
{Name: "Alice", Age: 28, City: "Shanghai"},
{Name: `Bob "The Builder"`, Age: 32, City: "Beijing"},
{Name: "Charlie", Age: 25, City: "Guangzhou"},
}
// JSON serialization to file (no manual concatenation needed)
file, _ := os.Create("users.json")
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
encoder.Encode(users)
fmt.Println("Export successful!")
// Verification: read it back
data, _ := os.ReadFile("users.json")
fmt.Println(string(data))
}
الناتج:
Export successful!
[
{
"name": "Alice",
"age": 28,
"city": "Shanghai"
},
{
"name": "Bob \"The Builder\"",
"age": 32,
"city": "Beijing"
},
{
"name": "Charlie",
"age": 25,
"city": "Guangzhou"
}
]
(3) المزايا: مقارنة بين طرق معالجة JSON
| الطريقة | الهروب من علامات الاقتباس المزدوجة | الأحرف الخاصة | الملفات الكبيرة | حجم الكود |
|---|---|---|---|---|
| التسلسل اليدوي | ❌ الترميز اليدوي | ❌ عرضة للأخطاء | ❌ نفاد الذاكرة (OOM) | حوالي 50 سطراً |
json.Marshal |
✅ تلقائي | ✅ تلقائي | ❌ الذاكرة ممتلئة | ~5 أسطر |
json.Encoder |
✅ تلقائي | ✅ تلقائي | ✅ قائم على التدفق | ~5 أسطر |
encoding/json. فهي تتولى تلقائيًّا عمليات الترميز الهروبي والترميز والتبويب، كما تتعامل بشكل صحيح مع أحرف UTF-8 في لغة Go.
3. عمليات الملفات في نظام التشغيل
(1) فتح الملفات وإنشاؤها
package main
import (
"fmt"
"os"
)
func main() {
// Create (or truncate) file
f, _ := os.Create("test.txt")
defer f.Close()
// Write string
f.WriteString("Hello, Go Files!\n")
f.Write([]byte("Second line\n"))
// Open in append mode
f2, _ := os.OpenFile("test.txt", os.O_APPEND|os.O_WRONLY, 0644)
defer f2.Close()
f2.WriteString("Appended line\n")
// Read file
data, _ := os.ReadFile("test.txt")
fmt.Print(string(data))
}
الناتج:
Hello, Go Files!
Second line
Appended line
(2) os.ReadFile / os.WriteFile (قراءة/كتابة لمرة واحدة)
package main
import (
"fmt"
"os"
)
func main() {
// Write (one-time)
content := []byte("line1\nline2\nline3\n")
err := os.WriteFile("data.txt", content, 0644)
if err != nil {
fmt.Printf("Write error: %v\n", err)
return
}
// Read (one-time)
data, err := os.ReadFile("data.txt")
if err != nil {
fmt.Printf("Read error: %v\n", err)
return
}
fmt.Printf("Read %d bytes:\n%s", len(data), data)
}
▶ مثال: نسخ ملف
package main
import (
"fmt"
"io"
"os"
)
func copyFile(src, dst string) (int64, error) {
sourceFile, err := os.Open(src)
if err != nil {
return 0, err
}
defer sourceFile.Close()
destFile, err := os.Create(dst)
if err != nil {
return 0, err
}
defer destFile.Close()
// io.Copy uses a default 32KB buffer
return io.Copy(destFile, sourceFile)
}
func main() {
// First write the source file
os.WriteFile("source.txt", []byte("Hello World!\n"), 0644)
n, err := copyFile("source.txt", "dest.txt")
if err != nil {
fmt.Printf("Copy error: %v\n", err)
return
}
fmt.Printf("Copied %d bytes\n", n)
data, _ := os.ReadFile("dest.txt")
fmt.Printf("Content: %s", data)
}
الناتج:
Copied 13 bytes
Content: Hello World!
os.ReadFile بقراءة الملف بأكمله في الذاكرة — وهو ما يناسب الملفات الصغيرة (< 100 ميغابايت). أما الملفات الكبيرة (مثل سجلات 1 جيجابايت أو أكثر) فيجب معالجتها باستخدام bufio أو io.Copy من أجل المعالجة التدفقية.
4. bufio: الإدخال/الإخراج المخزن مؤقتًا
(1) يقوم bufio بالقراءة سطراً سطراً
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
// First write a test file
content := ""
for i := 1; i <= 100; i++ {
content += fmt.Sprintf("Line %d\n", i)
}
os.WriteFile("large.txt", []byte(content), 0644)
// Read line by line
file, _ := os.Open("large.txt")
defer file.Close()
scanner := bufio.NewScanner(file)
lineCount := 0
for scanner.Scan() {
lineCount++
if lineCount <= 3 {
fmt.Println(scanner.Text())
}
}
fmt.Printf("...total %d lines\n", lineCount)
}
الناتج:
Line 1
Line 2
Line 3
...total 100 lines
▶ مثال: الكتابة إلى المخزن المؤقت bufio
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, _ := os.Create("buffered.txt")
defer file.Close()
writer := bufio.NewWriter(file)
writer.WriteString("First line\n")
writer.WriteString("Second line\n")
writer.WriteString("Third line\n")
fmt.Printf("Buffer size: %d\n", writer.Buffered())
// Important: flush the buffer to disk
writer.Flush()
data, _ := os.ReadFile("buffered.txt")
fmt.Printf("Content:\n%s", data)
}
(3) os مقابل bufio
| الميزة | القراءة/الكتابة المباشرة على نظام التشغيل | التخزين المؤقت Bufio |
|---|---|---|
| الآلية الداخلية | استدعاء النظام (استدعاء واحد لكل عملية قراءة/كتابة) | مخزن مؤقت في وضع المستخدم (يقلل من عدد استدعاءات النظام) |
| السيناريوهات المناسبة | الملفات الصغيرة، الوصول العشوائي | الملفات الكبيرة، القراءة/الكتابة التسلسلية |
| قراءة سطرًا سطرًا | غير مدعوم | bufio.Scanner |
| الأداء (الملفات الكبيرة) | بطيء (عدد كبير من استدعاءات النظام) | سريع (انخفاض بنسبة 90% في استدعاءات النظام) |
| المخزن المؤقت الافتراضي | غير متوفر | 4 كيلوبايت (قابل للتعديل) |
5. التسلسل باستخدام encoding/json
(1) json.Marshal / json.Unmarshal
package main
import (
"encoding/json"
"fmt"
)
type Product struct {
Name string `json:"name"`
Price float64 `json:"price"`
InStock bool `json:"in_stock"`
Tags []string `json:"tags,omitempty"`
Internal string `json:"-"` // not serialized
}
func main() {
p := Product{
Name: "Go Mug",
Price: 19.99,
InStock: true,
Tags: []string{"gift", "office"},
Internal: "secret123",
}
// Serialize
data, _ := json.Marshal(p)
fmt.Printf("Marshaled: %s\n", data)
// With indentation
pretty, _ := json.MarshalIndent(p, "", " ")
fmt.Printf("Pretty:\n%s\n", pretty)
// Deserialize
var p2 Product
json.Unmarshal([]byte(`{"name":"Go T-Shirt","price":29.99,"in_stock":false}`), &p2)
fmt.Printf("Unmarshaled: %+v\n", p2)
}
الناتج:
Marshaled: {"name":"Go Mug","price":19.99,"in_stock":true,"tags":["gift","office"]}
Pretty:
{
"name": "Go Mug",
"price": 19.99,
"in_stock": true,
"tags": [
"gift",
"office"
]
}
Unmarshaled: {Name:Go T-Shirt Price:29.99 InStock:false Tags:[] Internal:}
▶ مثال: تحليل JSON ديناميكي (interface{})
package main
import (
"encoding/json"
"fmt"
)
func main() {
data := `{"name":"Alice","age":28,"address":{"city":"Shanghai","zip":"200000"}}`
// Parse into map (no need to pre-define a struct)
var result map[string]interface{}
json.Unmarshal([]byte(data), &result)
fmt.Printf("name: %v\n", result["name"])
fmt.Printf("age: %v (type=%T)\n", result["age"], result["age"])
// Nested map requires type assertion (comma-ok safe pattern)
if addr, ok := result["address"].(map[string]interface{}); ok {
fmt.Printf("city: %v\n", addr["city"])
}
}
الناتج:
name: Alice
age: 28 (type=float64)
city: Shanghai
float64 بشكل افتراضي — لذا يُعرض age على أنه 28، لكن نوعه هو float64. إذا كنت ترغب في استخدام int، فإما أن تحدد النوع باستخدام بنية (struct)، أو تستخدم json.Decoder مع UseNumber().
6. معالجة تدفق البيانات باستخدام أداة التشفير/فك التشفير لـ JSON
▶ مثال: يقوم جهاز التشفير بالكتابة إلى ملف في الوقت الفعلي
package main
import (
"encoding/json"
"fmt"
"os"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
users := []User{
{Name: "Alice", Age: 28},
{Name: "Bob", Age: 32},
{Name: "Charlie", Age: 25},
}
// Stream write to file (no complete []byte constructed)
file, _ := os.Create("users.json")
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
for _, u := range users {
encoder.Encode(u) // write one by one
}
data, _ := os.ReadFile("users.json")
fmt.Println(string(data))
}
(2) وحدة فك الترميز: القراءة القائمة على الدفق (JSON سطراً سطراً)
package main
import (
"encoding/json"
"fmt"
"strings"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
// Simulate JSON Lines format (one JSON object per line)
input := `{"name":"Alice","age":28}
{"name":"Bob","age":32}
{"name":"Charlie","age":25}`
// Stream decode
decoder := json.NewDecoder(strings.NewReader(input))
for {
var u User
err := decoder.Decode(&u)
if err != nil {
break // EOF
}
fmt.Printf("Decoded: %s (%d)\n", u.Name, u.Age)
}
}
الناتج:
Decoded: Alice (28)
Decoded: Bob (32)
Decoded: Charlie (25)
(3) مارشال ضد إنكودر
| الخاصية | json.Marshal | json.Encoder |
|---|---|---|
| وجهة الإخراج | []byte (الذاكرة) |
io.Writer (أي وجهة) |
| استخدام الذاكرة | حجم البنية [] بايت | مخزن مؤقت الكتابة للدفق |
| الملفات الكبيرة | ❌ خطر نفاد الذاكرة | ✅ الأمان |
| التحكم في المسافات البادئة | MarshalIndent |
SetIndent() |
| السيناريوهات النموذجية | استجابات واجهة برمجة التطبيقات (API) / البيانات الصغيرة | كتابة الملفات / تدفقات الشبكة |
7. الدليل الشامل لعلامات JSON
(1) خيار العلامة (1)
type Config struct {
Name string `json:"name"` // field name mapping
Omit string `json:"omit,omitempty"` // omit on zero value
Skip string `json:"-"` // skip this field
String int `json:"string"` // number converted to string
}
(2) جميع الخيارات
| الخيار | الصيغة | التأثير |
|---|---|---|
| إعادة تسمية | json:"new_name" |
تغيير اسم حقل JSON إلى "new_name" |
| omitempty | json:"name,omitempty" |
تجاهل هذا الحقل إذا كانت القيمة صفرًا |
| تخطي | json:"-" |
عدم التسلسل/إلغاء التسلسل |
| سلسلة | json:"id,string" |
تحويل الأرقام إلى سلاسل (متوافق مع JavaScript) |
| التداخل | json:"-" بنية متوسطة |
التحكم في التسلسل المتداخل |
▶ مثال: استخدام omitempty + سلسلة في الممارسة العملية
package main
import (
"encoding/json"
"fmt"
)
type APIResponse struct {
Code int `json:"code"`
Message string `json:"message,omitempty"` // omitted when empty
Data any `json:"data,omitempty"` // omitted when nil
ID int64 `json:"id,string"` // int64 → string
}
func main() {
resp := APIResponse{
Code: 200,
Data: nil, // will be omitted
ID: 9876543210123,
}
data, _ := json.MarshalIndent(resp, "", " ")
fmt.Println(string(data))
}
الناتج:
{
"code": 200,
"id": "9876543210123"
}
8. مثال كامل: أداة تصدير قاعدة البيانات بتنسيق JSON
// db_exporter.go
package main
import (
"encoding/json"
"fmt"
"os"
"time"
)
// ---------- Data Model ----------
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
Stock int `json:"stock"`
Category string `json:"category,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
type ExportConfig struct {
FilePath string `json:"file_path"`
PrettyPrint bool `json:"pretty_print"`
IncludeMeta bool `json:"include_meta"`
}
// ---------- Simulated Database ----------
type Database struct {
products []Product
}
func NewDatabase() *Database {
return &Database{
products: []Product{
{ID: 1, Name: "Go Mug", Price: 19.99, Stock: 100, Category: "accessories",
CreatedAt: time.Now()},
{ID: 2, Name: `Go "Gopher" T-Shirt`, Price: 29.99, Stock: 50, Category: "clothing",
CreatedAt: time.Now()},
{ID: 3, Name: "Go Programming Book", Price: 49.99, Stock: 200, Category: "books",
CreatedAt: time.Now()},
},
}
}
func (db *Database) QueryAll() []Product {
return db.products
}
// ---------- Exporter ----------
type Exporter struct {
config ExportConfig
}
func NewExporter(config ExportConfig) *Exporter {
return &Exporter{config: config}
}
func (e *Exporter) ExportToFile(products []Product) error {
file, err := os.Create(e.config.FilePath)
if err != nil {
return fmt.Errorf("create file: %w", err)
}
defer file.Close()
encoder := json.NewEncoder(file)
if e.config.PrettyPrint {
encoder.SetIndent("", " ")
}
if e.config.IncludeMeta {
wrapper := map[string]interface{}{
"exported_at": time.Now().Format(time.RFC3339),
"total": len(products),
"products": products,
}
return encoder.Encode(wrapper)
}
return encoder.Encode(products)
}
func (e *Exporter) ExportToMemory(products []Product) ([]byte, error) {
if e.config.PrettyPrint {
return json.MarshalIndent(products, "", " ")
}
return json.Marshal(products)
}
// ---------- Importer (reads JSON file) ----------
type Importer struct{}
func NewImporter() *Importer {
return &Importer{}
}
func (imp *Importer) ImportFromFile(path string) ([]Product, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("open file: %w", err)
}
defer file.Close()
var products []Product
decoder := json.NewDecoder(file)
if err := decoder.Decode(&products); err != nil {
// Try the wrapped format: must rebuild decoder after Seek
file.Seek(0, 0)
decoder = json.NewDecoder(file)
var wrapped struct {
Products []Product `json:"products"`
}
if err2 := decoder.Decode(&wrapped); err2 != nil {
return nil, fmt.Errorf("decode: %w", err)
}
products = wrapped.Products
}
return products, nil
}
func main() {
db := NewDatabase()
products := db.QueryAll()
// 1. Stream JSON write to file
exporter := NewExporter(ExportConfig{
FilePath: "export.json",
PrettyPrint: true,
IncludeMeta: true,
})
if err := exporter.ExportToFile(products); err != nil {
fmt.Printf("Export error: %v\n", err)
return
}
fmt.Println("Exported to export.json")
// 2. Read back to verify
importer := NewImporter()
imported, err := importer.ImportFromFile("export.json")
if err != nil {
fmt.Printf("Import error: %v\n", err)
return
}
fmt.Printf("Imported %d products\n", len(imported))
for _, p := range imported {
fmt.Printf(" %d. %s ($%.2f)\n", p.ID, p.Name, p.Price)
}
// 3. Display file content
data, _ := os.ReadFile("export.json")
fmt.Printf("\nFile content:\n%s\n", data)
}
النتيجة المتوقعة:
Exported to export.json
Imported 3 products
1. Go Mug ($19.99)
2. Go "Gopher" T-Shirt ($29.99)
3. Go Programming Book ($49.99)
File content:
{
"exported_at": "2026-07-08T10:00:00Z",
"total": 3,
"products": [
{
"id": 1,
"name": "Go Mug",
"price": 19.99,
"stock": 100,
"category": "accessories",
"created_at": "2026-07-08T10:00:00Z"
},
...
]
}
flowchart LR
A[Go struct] --> B[json.Marshal]
A --> C[json.Encoder]
B --> D["[]byte (memory)"]
D --> E[os.WriteFile / HTTP Response]
C --> F[File / net.Conn / bytes.Buffer]
F --> G[Stream write]
H[JSON file/stream] --> I[json.Decoder]
I --> J[Go struct]
H --> K[json.Unmarshal]
K --> J
style A fill:#e1f5fe
style J fill:#e1f5fe
omitempty أثناء التصدير بتنسيق JSON مع الأوقات ذات القيمة الصفرية (time.Time{})—فـ time.Time{} ليس nil ولن يتم حذفه. إذا كنت بحاجة إلى حذف الأوقات ذات القيمة الصفرية، فاستخدم نوع المؤشر *time.Time.
❓ أسئلة شائعة
os.ReadFile وioutil.ReadFile؟os.ReadFile هو بديل تم تقديمه في الإصدار Go 1.16؛ وهو يعمل بنفس الطريقة. أصبح ioutil.ReadFile مهملًا بدءًا من الإصدار Go 1.19. يجب عليك استخدام os.ReadFile / os.WriteFile.os.File.Read فيؤدي دائمًا إلى استدعاء النظام (مع عبء إضافي يبلغ حوالي 1 µs)، في حين أن bufio يقرأ 4 كيلوبايت في المرة الواحدة، مما يقلل من استدعاءات النظام بنسبة 99.9%.json.Marshal وjson.Encoder؟Marshal؛ أما بالنسبة للكتابة إلى الملفات/تدفقات الشبكة أو الملفات الكبيرة → Encoder. ميزة Encoder هي أنها تعمل بطريقة التدفق ولا تستهلك الكثير من الذاكرة.struct tag في حقول JSON؟json:"name" يعيد تسمية الحقل؛ json:"name,omitempty" يحذف القيم الصفرية؛ json:"-" يتخطى الحقل؛ json:"id,string" يحول الأرقام إلى سلاسل نصية. افصل الخيارات المتعددة بفواصل.encoding/json في لغة Go بإخراج UTF-8 بشكل افتراضي — حيث يتم إخراج الأحرف غير ASCII كما هي. إذا كنت بحاجة إلى إخراج يقتصر على أحرف ASCII فقط (كما تتطلب بعض الأنظمة القديمة)، فاستخدم json.MarshalIndent أو SetEscapeHTML(false).json.Decoder لفك الترميز بالتدفق (decoder.Decode(&v) لفك ترميز كائن واحد في كل مرة)، أو تنسيق JSON Lines (كائن JSON واحد في كل سطر). لا تستخدم json.Unmarshal لقراءة الملف بأكمله.map[string]interface{} أو json.RawMessage (التحليل المؤجل). map[string]interface{} مرن ولكنه يتطلب تأكيدات الأنواع؛ أما json.RawMessage فيحتفظ بـ JSON الخام لتحليله لاحقًا.os.Create وos.OpenFile؟os.Create(f) تعادل os.OpenFile(f, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) — فهي تقوم باقتطاع (الكتابة فوق) الملف إذا كان موجودًا بالفعل. os.OpenFile يدعم المزيد من الأوضاع (الإضافة، القراءة فقط، إنشاء ملف جديد، إلخ).📖 ملخص
os.ReadFile/os.WriteFile: القراءة من الملفات الصغيرة والكتابة إليها في عملية واحدةbufio.Scannerيعالج الملفات الكبيرة سطراً سطراً، بينماbufio.Writerيخزن عمليات الكتابة مؤقتاً- تتولى حزمة
encoding/jsonتلقائيًا عمليات التسلسل وإلغاء التسلسل، مما يلغي الحاجة إلى إنشاء السلاسل يدويًّا - يقوم
json.Marshalبمعالجة البيانات في الذاكرة، بينما يقومjson.Encoderبكتابة البيانات إلى دفق json.Decoderيقرأ البيانات في شكل دفق، مما يجعله مناسبًا للملفات الكبيرة- علامة JSON:
omitempty/-/stringتتحكم في سلوك التسلسل - تم توحيد عمليات الإدخال/الإخراج للملفات وJSON من خلال واجهات
io.Reader/io.Writer
📝 تمارين
-
المسألة الأساسية (الصعوبة ⭐): اكتب برنامجًا يستخدم
os.ReadFileلقراءة ملف نصي، ويحسب عدد الأسطر والكلمات والأحرف، ويكتب النتائج في ملف آخر. يجب عليك استخدامstrings.Fieldsوbufio.Scannerوos.WriteFile. -
مشكلة متقدمة (درجة الصعوبة ⭐⭐): قم بتنفيذ مدير
TodoListيدعم إضافة العناصر وعرضها وإكمالها وحذفها، ويستخدمencoding/jsonلتسلسل البيانات إلى ملف من أجل الاحتفاظ بها. المتطلبات: استخدم علامات JSON للتحكم في أسماء الحقول، واستخدمomitemptyللتعامل مع الحقول الاختيارية. -
مشكلة التحدي (الصعوبة ⭐⭐⭐): قم بتنفيذ أداة تصدير متعددة التنسيقات تقوم بتصدير البيانات من مصدر البيانات
[]Productفي آن واحد بثلاثة تنسيقات: JSON، وJSON Lines (كائن JSON واحد في كل سطر)، وCSV. يجب عليك استخدامos.Create+json.Encoder(لـ JSON) /fmt.Fprintf(لـ CSV) وواجهةio.Writerمشتركة.