Response struct formatlash
API’dan foydalanadigan dasturchi har bir endpointning javobini o‘qib, unga moslashadi. Bir endpoint kitobni {"id":1,...} ko‘rinishida, boshqasi {"book":{...}} ko‘rinishida, uchinchisi ro‘yxatni {"items":[...]}, to‘rtinchisi esa to‘g‘ridan-to‘g‘ri [...] ko‘rinishida qaytarsa, klient kodi har bir holat uchun alohida yoziladi. Bu noqulay va xatolarga olib keladi.
Yaxshi API’da javoblar izchil bo‘ladi: bir xil vaziyatda bir xil tuzilma qaytadi. Bu darsda javoblarni tartibli formatlash usullarini ko‘ramiz.
1. Bitta writeJSON funksiyasi
Barcha JSON javoblar bitta funksiya orqali yozilsa, format ham bitta joyda boshqariladi:
func writeJSON(w http.ResponseWriter, status int, data any) error {
body, err := json.Marshal(data)
if err != nil {
return err
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
_, err = w.Write(append(body, '\n'))
return err
}
Bu yerda json.NewEncoder(w).Encode emas, json.Marshal ishlatildi. Sababi — xato holati. Marshal JSON’ni avval xotirada to‘liq tayyorlaydi. Xato bo‘lsa, klientga hali hech narsa yuborilmagan bo‘ladi va handler 500 qaytara oladi. Encoder esa yozishni darhol boshlaydi. Yarim yo‘lda xato chiqsa, klient 200 status bilan buzilgan JSON oladi.
JSON’ga aylantirib bo‘lmaydigan qiymatlar kam uchraydi: kanal, funksiya yoki NaN kabi suzuvchi nuqtali son. Lekin ular uchraganda to‘g‘ri xato javobini qaytarish muhim.
2. Ichki model va javob modeli (DTO)
Database’dagi foydalanuvchi modeli taxminan shunday bo‘ladi:
type User struct {
ID int64
Email string
PasswordHash string
Role string
FailedLogins int
CreatedAt time.Time
DeletedAt *time.Time
}
Uni to‘g‘ridan-to‘g‘ri JSON’ga aylantirish xavfli. Javobga PasswordHash, FailedLogins kabi ichki maydonlar ham tushib qoladi. json:"-" tagi yordam beradi, lekin yangi maydon qo‘shilganda tag unutilishi mumkin. Bundan tashqari, ichki modeldagi har bir o‘zgarish API javobini ham o‘zgartirib yuboradi.
Ishonchliroq yo‘l — javob uchun alohida tur yaratish. Bunday tur DTO (Data Transfer Object) deb ataladi:
type UserResponse struct {
ID int64 `json:"id"`
Email string `json:"email"`
Role string `json:"role"`
CreatedAt time.Time `json:"created_at"`
}
func NewUserResponse(u User) UserResponse {
return UserResponse{
ID: u.ID,
Email: u.Email,
Role: u.Role,
CreatedAt: u.CreatedAt,
}
}
Endi javobga faqat aniq ko‘rsatilgan maydonlar tushadi. Userga yangi maxfiy maydon qo‘shilsa ham, u tasodifan tashqariga chiqmaydi. Ichki model va API shartnomasi bir-biridan mustaqil o‘zgaradi.
Tip
Qoida sifatida esda tuting: database modelini hech qachon to‘g‘ridan-to‘g‘ri klientga qaytarmang. Ro‘yxat qaytarilganda ham har bir element DTO’ga aylantiriladi.
3. Envelope: javobni o‘rash
Javob qanday ko‘rinishda bo‘lishi haqida ikki keng tarqalgan yondashuv bor.
To‘g‘ridan-to‘g‘ri — resursning o‘zi qaytariladi:
Envelope (konvert) — ma’lumot data kaliti ichiga o‘raladi:
Envelope qo‘shimcha ma’lumot joylash uchun joy qoldiradi. Masalan, ro‘yxat javobida sahifalash ma’lumotini ma’lumotlarning o‘zidan ajratib berish mumkin:
{
"data": [{"id": 1, "title": "Sarob"}, {"id": 2, "title": "Ufq"}],
"meta": {"page": 1, "per_page": 20, "total": 42}
}
Ro‘yxat to‘g‘ridan-to‘g‘ri massiv sifatida qaytarilsa, keyinchalik unga meta qo‘shib bo‘lmaydi. Buning uchun API’ni buzadigan o‘zgarish qilish kerak bo‘ladi. Shu sababli ro‘yxatlarni doim obyekt ichida qaytarish tavsiya etiladi.
Bu darsda quyidagi qoidadan foydalanamiz:
- muvaffaqiyatli javob —
{"data": ...}, ro‘yxat bo‘lsametaham qo‘shiladi; - xato —
{"error": {...}}. Uning tuzilishi keyingi darslardan birida ko‘riladi.
Go’da bu shunday ifodalanadi:
type envelope struct {
Data any `json:"data"`
Meta *pageMeta `json:"meta,omitempty"`
}
type pageMeta struct {
Page int `json:"page"`
PerPage int `json:"per_page"`
Total int `json:"total"`
TotalPages int `json:"total_pages"`
}
Meta ko‘rsatkich va omitempty bilan belgilangan. Bitta resurs qaytarilganda u nil bo‘ladi va JSON’ga yozilmaydi.
4. Status kodlarni to‘g‘ri tanlash
| Holat | Status | Tana |
|---|---|---|
| Ma’lumot qaytarildi | 200 OK |
resurs yoki ro‘yxat |
| Yangi resurs yaratildi | 201 Created |
yaratilgan resurs + Location sarlavhasi |
| So‘rov qabul qilindi, keyinroq bajariladi | 202 Accepted |
vazifa holati |
| Muvaffaqiyatli, qaytariladigan ma’lumot yo‘q | 204 No Content |
bo‘sh |
204 javobida tana bo‘lishi mumkin emas. Unga JSON yozishga urinmang:
To‘liq misol
| main.go | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | |
{"data":{"id":2,"title":"Kecha va kunduz","author":"Cho‘lpon","price":"39500.50","in_stock":false,"created_at":"2026-02-03T12:30:00Z"}}
{"data":[{"id":3,"title":"Sarob","author":"Abdulla Qahhor","price":"41000.00","in_stock":true,"created_at":"2026-03-21T08:15:00Z"}],"meta":{"page":2,"per_page":2,"total":3,"total_pages":2}}
Misolda e’tibor berishga arziydigan jihatlar:
- Pul miqdori tiyinda, butun son sifatida saqlanadi.
float64bilan pul hisoblash yaxlitlash xatolariga olib keladi. Masalan,0.1 + 0.2natijasi0.30000000000000004bo‘ladi. Klientga esa narx aniq formatlangan satr sifatida yuboriladi. Stocko‘rnigain_stockqaytarildi. Omborda aniq nechta kitob borligi ichki ma’lumot bo‘lishi mumkin. Klientga faqat kerakli xulosa beriladi.SupplierIDjavobga tushmadi. DTO’da bu maydon yo‘q.- Mavjud bo‘lmagan sahifa uchun
[]qaytdi,nullemas.itemsmakebilan yaratilgani uchun u hech qachonnilbo‘lmaydi. total_pagesklientga sahifalash tugmalarini chizish imkonini beradi.
Chiroyli formatlangan JSON
Ishlab chiqish paytida JSON’ni o‘qish oson bo‘lishi uchun chekinish bilan yozish mumkin:
Production’da bu keraksiz baytlar qo‘shadi. Terminalda chiroyli ko‘rish uchun esa jq yordamchi dasturi qulay:
Xulosa
- Barcha javoblarni bitta
writeJSONfunksiyasi orqali yozing.json.Marshalxatoni javob yuborilishidan oldin aniqlash imkonini beradi. - Ichki modelni to‘g‘ridan-to‘g‘ri qaytarmang. Javob uchun alohida DTO yarating.
- Ro‘yxatlarni obyekt ichida qaytaring. Shunda keyinchalik
metaqo‘shish mumkin bo‘ladi. - Ro‘yxat bo‘sh bo‘lsa,
nullemas,[]qaytaring. - Pulni butun sonda saqlang va formatlangan holda yuboring.
204javobida tana bo‘lmaydi.
Keyingi darsda so‘rov ma’lumotlarini qo‘lda yozilgan iflar o‘rniga go-playground/validator kutubxonasi bilan tekshirishni o‘rganamiz.