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 و slice را نیز سریالسازی کند. دو مثال زیر نحوه استفاده از map[string]interface{}
و slice
را نشان میدهند:
// تبدیل map به JSON
myMap := map[string]interface{}{
"name": "Bob",
"age": 25,
}
jsonData, err := json.Marshal(myMap)
// ... بدست آوردن خطا و خروجی حذف شده است ...
// تبدیل slice به JSON
mySlice := []string{"سیب", "موز", "گیلاس"}
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: "گاج آفریقایی",
Description: "یک پستاندار بزرگ با خرطوم و دندانهای خیسلان.",
Tag: "متاثر شده از انقراض", // این فیلد به JSON سریالسازی نخواهد شد
}
jsonData, err := json.Marshal(animal)
if err != nil {
log.Fatalf("خطا در سریالسازی JSON: %s", err)
}
fmt.Println(string(jsonData)) // خروجی: {"species":"گاج آفریقایی","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 تجزیه پویا
گاهی اوقات ساختار JSONی که باید آن را تجزیه کنیم از قبل شناور نیست یا ساختار داده JSON ممکن است بهطور پویا تغییر کند. در چنین مواردی، میتوانیم از 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، آنها متناظر با slice ها میباشند.
به آرایه JSON زیر توجه کنید:
[
{"name": "دیوید", "age": 34},
{"name": "ایو", "age": 28}
]
در Go، ما ساختار متناظر و slice را به صورت زیر تعریف میکنیم:
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 به یک slice از ساختارهای 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":"نامعلوم"}`) // "age" باید یک عدد صحیح باشد، اما اینجا یک رشته ارائه شده است
var u User
err := json.Unmarshal(data, &u)
if err != nil {
log.Fatalf("تجزیه JSON ناموفق بود: %v", err)
}
fmt.Printf("%+v\n", u)
}
در این مثال کد، ما به طور ارادی نوع داده نادرست را برای فیلد age
(یک رشته به جای عدد صحیح مورد انتظار) فراهم کردیم که باعث میشود json.Unmarshal
یک خطا ایجاد کند. بنابراین، ما باید این موقف را بهطور مناسب رفع کنیم. روش متداول این است که پیام خطا را لاگ نموده و بسته به سناریو، احتمالاً یک شیء خالی، مقدار پیشفرض یا پیام خطا بازگردانده شود.
6.1 سفارشی کردن Marshal و Unmarshal
به طور پیشفرض، بسته 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 انواع Encoder
و Decoder
را فراهم کرده است که میتوانند دادههای 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("خطای رمزنگاری: %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("خطای رمزگشا: %v", err)
}
for _, u := range users {
fmt.Printf("%+v\n", u)
}
}
با پردازش داده با استفاده از رمزنگارها و رمزگشاها میتوانید در حین خواندن فرآیند رمزنگاری JSON را انجام دهید، مصرف حافظه را کاهش دهید و کارایی پردازش را بهبود بخشید که به ویژه برای مدیریت انتقالهای شبکه یا فایلهای بزرگ مفید است.