encoding/json asoslari
API klient bilan JSON orqali gaplashadi. So‘rov JSON bo‘lib keladi, javob JSON bo‘lib ketadi. Go’da bu ishni standart kutubxonadagi encoding/json paketi bajaradi.
Paketning asosiy imkoniyatlari — Marshal, Unmarshal, struct taglar, Encoder va Decoder — Go’da JSON bilan ishlash darsida ko‘rib chiqilgan. Bu dars o‘sha bilimlarni API nuqtai nazaridan davom ettiradi. API yozishda tez-tez uchraydigan savollarga javob beramiz: maydon yuborilmaganini nulldan qanday ajratamiz? Vaqt qanday formatda ketadi? Katta ID’lar JavaScript’da nega buziladi?
Qisqa takrorlash
json:"title"— JSON’dagi kalit nomi.omitempty— qiymat bo‘sh bo‘lsa, maydon JSON’ga yozilmaydi.json:"-"— maydon JSON’da umuman ishtirok etmaydi.- Kichik harf bilan boshlangan (eksport qilinmagan) maydonlar ham JSON’ga tushmaydi.
API’da ikkinchi va uchinchi qoida xavfsizlik uchun muhim. Masalan, foydalanuvchi strukturasidagi PasswordHash maydoni json:"-" bilan belgilanmasa, parol xeshi javobda klientga ketib qolishi mumkin.
Nomlash uslubi
JSON kalitlari uchun ikki uslub keng tarqalgan: snake_case (created_at) va camelCase (createdAt). Go’ning o‘zi hech qaysisini majburlamaydi. Muhimi — butun API bo‘ylab bitta uslubni izchil ishlatish.
Tag yozilmasa, Go maydon nomining o‘zini ishlatadi: CreatedAt → "CreatedAt". API’da bu kamdan-kam ma’qul keladi, shuning uchun har bir maydonga tag yozing.
Ixtiyoriy maydonlar: yo‘q, null va nol qiymat
Bu API’dagi eng ko‘p chalkashlik tug‘diradigan mavzu. Klient kitobni qisman yangilash uchun PATCH /books/7 so‘rovini yuboradi:
Server bu so‘rovni qanday tushunishi kerak? "Yilni 0 qil" demoqchimi yoki klient yilni umuman yubormaganmi? Oddiy int maydonda bu ikki holat bir xil ko‘rinadi, chunki ikkalasida ham qiymat 0.
Yechim — ko‘rsatkich (pointer) turlari:
| Kelgan JSON | Year qiymati |
|---|---|
{} |
nil — maydon yuborilmagan |
{"year": null} |
nil — maydon null |
{"year": 0} |
0 ga ko‘rsatkich |
{"year": 1937} |
1937 ga ko‘rsatkich |
Misol
Klient faqat title yubordi. Year nil bo‘lib qoldi va kitob yili o‘zgarmadi.
Info
Standart encoding/json "maydon yuborilmagan" va "null yuborilgan" holatlarini ajratmaydi, ikkalasida ham nil bo‘ladi. Aksariyat API’lar uchun bu yetarli. Ajratish zarur bo‘lsa, maydonni json.RawMessage sifatida o‘qib, keyin qo‘lda tekshirish mumkin.
Javobda null va []
Javob yozishda ham shunga o‘xshash nozik jihat bor:
var a []string // nil slice
b := []string{} // bo‘sh slice
json.Marshal(a) // null
json.Marshal(b) // []
Klient ro‘yxat kutayotgan joyda null olsa, JavaScript kodi data.items.length qatorida xato beradi. Shuning uchun ro‘yxat qaytaradigan joylarda bo‘sh slice’ni aniq yarating:
Vaqt: time.Time
time.Time JSON’ga RFC 3339 formatida yoziladi:
type Event struct {
At time.Time `json:"at"`
}
json.Marshal(Event{At: time.Date(2026, 9, 30, 14, 5, 0, 0, time.UTC)})
// {"at":"2026-09-30T14:05:00Z"}
Bu format xalqaro standart. Uni JavaScript, Python va boshqa tillar muammosiz tushunadi. API’da vaqtni shu formatda, UTC’da yuborish odatiy amaliyot. Mahalliy vaqtga o‘tkazish esa klient zimmasida.
Kiruvchi JSON’dagi vaqt ham shu formatda bo‘lishi kerak. "30.09.2026" kabi boshqa formatni time.Time qabul qilmaydi va xato qaytaradi.
Katta sonlar va string opsiyasi
JavaScript’da barcha sonlar 64 bitli suzuvchi nuqtali (float64) turda saqlanadi. U butun sonlarni faqat 2⁵³ gacha aniq ifodalay oladi. Go’dagi int64 esa bundan ancha katta bo‘lishi mumkin:
Katta ID’larni, masalan Snowflake ID’larini, JSON’da satr sifatida yuborish kerak. Buning uchun string tag opsiyasi bor:
type Order struct {
ID int64 `json:"id,string"`
}
json.Marshal(Order{ID: 1234567890123456789})
// {"id":"1234567890123456789"}
Go tomonida maydon int64 bo‘lib qoladi, JSON’da esa qo‘shtirnoq ichida yoziladi. Unmarshal ham "id":"123" ko‘rinishidagi qiymatni to‘g‘ri o‘qiydi.
map[string]any va sonlar
Tuzilishi oldindan noma’lum JSON’ni map[string]anyga o‘qish mumkin. Lekin bunda barcha sonlar float64 bo‘lib keladi:
var m map[string]any
json.Unmarshal([]byte(`{"id": 42}`), &m)
fmt.Printf("%T\n", m["id"]) // float64
Bu katta sonlarda aniqlik yo‘qolishiga olib keladi. Decoderning UseNumber metodi sonlarni json.Number turida, ya’ni satr ko‘rinishida saqlaydi:
dec := json.NewDecoder(r.Body)
dec.UseNumber()
dec.Decode(&m)
n := m["id"].(json.Number)
id, err := n.Int64()
Umuman olganda, API’da map[string]any o‘rniga aniq struct’lardan foydalaning. Struct turi hujjat vazifasini ham bajaradi va xatolarni kamaytiradi.
json.RawMessage — keyinroq o‘qish
Ba’zan JSON’ning bir qismi turga qarab turlicha tuzilgan bo‘ladi. Masalan, webhook hodisalari:
{"type": "payment", "data": {"amount": 50000, "currency": "UZS"}}
{"type": "refund", "data": {"payment_id": "p_123", "reason": "qaytarildi"}}
data maydonining tuzilishi typega bog‘liq. json.RawMessage bu qismni o‘qimasdan, xom baytlar holida saqlab turadi:
Avval faqat type o‘qiladi. Keyin Data ichidagi xom JSON kerakli turga o‘qiladi.
Maxsus MarshalJSON va UnmarshalJSON
Tur json.Marshaler yoki json.Unmarshaler interfeysini amalga oshirsa, encoding/json standart qoidalar o‘rniga shu metodlarni chaqiradi. Bu, masalan, Go’dagi enum qiymatlarini JSON’da tushunarli satr ko‘rinishida yuborish uchun qulay:
Go ichida status int sifatida saqlanadi va tez solishtiriladi. JSON’da esa klient tushunadigan "published" ko‘rinishida chiqadi. Noto‘g‘ri qiymat kelganda UnmarshalJSON xato qaytaradi va handler 400 javobini beradi.
Warning
MarshalJSON qiymat (value) qabul qiluvchiga, UnmarshalJSON esa ko‘rsatkich qabul qiluvchiga yoziladi. UnmarshalJSON qiymatni o‘zgartirishi kerak, shuning uchun unga ko‘rsatkich zarur. MarshalJSON ko‘rsatkichga yozilsa, struct qiymat sifatida marshal qilinganda metod chaqirilmay qolishi mumkin.
Marshal yoki Encoder?
| Holat | Tanlov |
|---|---|
JSON’ni http.ResponseWriterga yozish |
json.NewEncoder(w).Encode(v) |
| So‘rov tanasini o‘qish | json.NewDecoder(r.Body).Decode(&v) |
| JSON’ni baytlar sifatida olish (keshga, bazaga, logga) | json.Marshal(v) |
| Xotiradagi baytlardan o‘qish | json.Unmarshal(data, &v) |
Encoder natijani to‘g‘ridan-to‘g‘ri oqimga yozadi va oxiriga \n qo‘shadi. Marshal esa butun natijani avval xotirada yig‘adi.
Bir farqni yodda tuting: Encode yozishni boshlagandan keyin xato yuz bersa, klientga yarim JSON va 200 status ketib qoladi. Marshal bilan avval baytlarni tayyorlab, keyin status yozilsa, xatoni 500 sifatida qaytarish mumkin. Bu yondashuv Response struct formatlash darsida qo‘llanadi.
HTML belgilari
Standartda encoding/json <, > va & belgilarini <, >, & ko‘rinishida kodlaydi. Bu JSON HTML sahifa ichiga joylansa, XSS hujumidan himoya qiladi. API uchun bu zararsiz: har qanday JSON parser ularni to‘g‘ri o‘qiydi. Kodlashni o‘chirish zarur bo‘lsa, enc.SetEscapeHTML(false) ishlatiladi.
Xulosa
- Har bir maydonga JSON tag yozing va butun API’da bitta nomlash uslubini ishlating.
- Maxfiy maydonlarni
json:"-"bilan yashiring. - Ixtiyoriy maydonlar uchun ko‘rsatkich turlarini ishlating:
nil— yuborilmagan, aks holda qiymat bor. - Ro‘yxat uchun
nilemas, bo‘sh slice qaytaring:nullo‘rniga[]chiqadi. - Vaqt RFC 3339 formatida yuboriladi. UTC ishlatish tavsiya etiladi.
- Katta
int64ID’larni,stringopsiyasi bilan satr ko‘rinishida yuboring. - Tuzilishi turga bog‘liq qism uchun
json.RawMessage, maxsus format uchunMarshalJSONvaUnmarshalJSONishlatiladi.
Keyingi darsda so‘rov tanasini xavfsiz va to‘g‘ri o‘qishni, jumladan xatolarni tushunarli xabarlarga aylantirishni o‘rganamiz.