Go’da JSON bilan ishlash
JSON — matn ko‘rinishidagi ma’lumot almashish formati. U web API, konfiguratsiya fayllari, servislar orasidagi ma’lumot almashinuvi va boshqa ko‘plab joylarda ishlatiladi.
Go’da JSON bilan ishlash uchun standart kutubxonadagi encoding/json paketi mavjud.
Bu paket ikki asosiy yo‘nalishda ishlaydi:
- JSON ma’lumotini Go qiymatiga aylantirish — decode qilish;
- Go qiymatini JSON ko‘rinishiga aylantirish — encode qilish.
Masalan, serverdan quyidagi JSON kelishi mumkin:
Go dasturi bu ma’lumotni struct, map, slice yoki boshqa mos turga dekodlashi mumkin.
Aksincha, Go ichidagi struct qiymatini JSON ko‘rinishiga aylantirib, HTTP response yoki faylga yozish ham mumkin.
JSON va Go turlari
JSON va Go turlari bir xil emas. Shuning uchun encoding/json JSON qiymatini mos Go turiga aylantiradi.
Odatda moslik quyidagicha bo‘ladi:
| JSON | Go’da odatiy tur |
|---|---|
| string | string |
| number | int, float64 yoki boshqa son turi |
| boolean | bool |
| array | slice yoki array |
| object | struct yoki map |
| null | pointer, slice, map yoki interface uchun nil |
Masalan, quyidagi JSON:
quyidagi Go structiga mos kelishi mumkin:
Bu yerda:
- JSON
stringqiymatistringga; - JSON
numberqiymatiintga; - JSON
booleanqiymatiboolga; - JSON array esa
[]stringslicega yoziladi.
Struct orqali dekodlashda muhim qoida bor: faqat export qilinadigan fieldlar ishlatiladi.
Go’da field nomi katta harf bilan boshlansa, u export qilingan hisoblanadi.
Masalan:
Bu structda Name eksport qilingan, age esa eksport qilinmagan.
Shuning uchun encoding/json Name bilan ishlay oladi, lekin age fieldiga qiymat yoza olmaydi va uni JSON’ga ham chiqarmaydi.
Struct tag
JSON kalitlari bilan Go field nomlari bir xil bo‘lishi shart emas. Buni boshqarish uchun struct tag ishlatiladi.
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
Password string `json:"-"`
Roles []string `json:"roles"`
}
Bu yerda har bir json:"..." yozuvi encoding/json paketiga field bilan qanday ishlash kerakligini aytadi.
JSON kalitini belgilash
Go fieldining nomi Name, JSON ichidagi kalit esa name bo‘ladi.
Masalan:
JSON’ga kodlanganda:
ko‘rinishida chiqadi.
Struct tag yozilmasa, paket odatda fieldning o‘z nomidan foydalanadi.
omitempty
omitempty fieldning qiymati bo‘sh hisoblanganda uni JSON natijasiga kiritmaslikni bildiradi.
Masalan:
kodlanganda email fieldi natijada bo‘lmasligi mumkin.
Bu API response ichida keraksiz bo‘sh fieldlarni yubormaslik uchun qulay.
Lekin omitemptyni ishlatishda zero value bilan “qiymat berilmagan” holatni ajratish kerak bo‘lishi mumkin.
Masalan, bool uchun false, int uchun 0 ham bo‘sh qiymat hisoblanadi. Agar false yoki 0 haqiqiy biznes qiymati bo‘lsa, pointer yoki boshqa model kerak bo‘lishi mumkin.
Fieldni butunlay chiqarib tashlash
json:"-" fieldni JSON bilan ishlashdan butunlay chiqaradi.
Bu field:
- JSON’ga kodlanmaydi;
- JSON’dan dekodlanmaydi.
Bu, masalan, ichki yoki maxfiy fieldlar uchun foydali.
Lekin faqat json:"-"ga tayanib xavfsizlikni to‘liq hal qilingan deb hisoblash to‘g‘ri emas. Maxfiy ma’lumot qayerda va qanday ishlatilayotganini alohida nazorat qilish kerak.
Marshal va Unmarshal
json.Marshal() va json.Unmarshal() xotirada to‘liq mavjud bo‘lgan JSON ma’lumoti bilan ishlash uchun qulay.
json.Marshal() Go qiymatini JSON ko‘rinishidagi []bytega aylantiradi.
json.Unmarshal() esa JSON saqlangan []byteni Go qiymatiga dekodlaydi.
data, err := json.Marshal(User{Name: "Ali", Roles: []string{"editor"}})
if err != nil {
return err
}
var user User
if err := json.Unmarshal(data, &user); err != nil {
return err
}
Birinchi qismni ko‘ramiz:
Bu yerda User qiymati JSON’ga kodlanadi.
Natija string emas, []byte ko‘rinishida qaytadi.
Taxminan quyidagi ma’lumot hosil bo‘ladi:
Keyin:
yangi User qiymati yaratiladi. Hozircha uning fieldlari zero value holatida.
So‘ng:
JSON shu struct ichiga yoziladi.
Bu yerda &user berilishining sababi muhim.
Unmarshal mavjud qiymatni o‘zgartirishi kerak. Shuning uchun unga structning o‘zi emas, yozish mumkin bo‘lgan pointer beriladi.
Bunday yozish noto‘g‘ri:
Chunki userning nusxasiga qiymat yozish orqali chaqiruvchidagi structni o‘zgartirib bo‘lmaydi.
To‘g‘ri variant:
Kichik va to‘liq xotirada mavjud JSON uchun Marshal va Unmarshal odatda eng sodda yechim bo‘ladi.
Masalan:
- database’dan olingan kichik JSON ustuni;
- test ichidagi JSON;
- kichik HTTP response tanasi oldindan
[]bytega o‘qilgan bo‘lsa.
Katta oqimlarda esa Encoder va Decoder ko‘proq mos kelishi mumkin.
Encoder va Decoder
json.Encoder va json.Decoder io.Writer hamda io.Reader bilan ishlaydi.
Bu ularni oqimlar bilan ishlashga qulay qiladi.
Masalan:
Bu yerda:
os.Stdoutga yozadigan encoder yaratadi.
os.Stdout io.Writer interface’ini bajaradi. Shuning uchun Encoder JSON natijasini bevosita terminalga yozishi mumkin.
Keyin:
user qiymatini JSON’ga aylantirib, natijani writerga yozadi.
Bunda alohida:
qilib []byte hosil qilish va keyin uni writerga yozish shart emas.
Encode yana bitta muhim xususiyatga ega: JSON qiymatidan keyin yangi qator qo‘shadi.
Masalan, natija:
ko‘rinishida yozilib, oxirida \n bo‘ladi.
Decoder
Decoder esa io.Readerdan JSON o‘qiydi.
Masalan:
Bu yerda r:
- fayl;
- HTTP request body;
- TCP connection;
strings.Reader;- boshqa
io.Reader
bo‘lishi mumkin.
Fayl, terminal yoki tarmoq ulanishi kabi oqim bilan ishlaganda Encoder va Decoder ortiqcha oraliq []byte yaratmaslikka yordam beradi.
Bu ayniqsa ma’lumot allaqachon reader yoki writer ko‘rinishida bo‘lsa qulay.
Qat’iy dekodlash
Oddiy json.Decoder JSON object ichidagi noma’lum fieldlarni odatda e’tiborsiz qoldiradi.
Ba’zi holatlarda bu qulay.
Lekin konfiguratsiya fayllarida bu xavfli bo‘lishi mumkin.
Masalan, konfiguratsiya structi:
foydalanuvchi esa xato qilib:
yozsa, debgu structda mavjud emas.
Agar noma’lum fieldlar e’tiborsiz qoldirilsa, dastur xato bermasdan ishlashi mumkin. Foydalanuvchi esa debug yoqildi deb o‘ylaydi.
Bunday holatda DisallowUnknownFields() foydali.
U JSON object ichida structga mos kelmaydigan field uchrasa, xato qaytaradi.
Bitta JSON qiymatidan keyin yana boshqa JSON qiymati kelmaganini ham tekshirish kerak.
func decodeConfig(r io.Reader) (Config, error) {
var cfg Config
dec := json.NewDecoder(r)
dec.DisallowUnknownFields()
if err := dec.Decode(&cfg); err != nil {
return Config{}, fmt.Errorf("JSON o‘qilmadi: %w", err)
}
if err := dec.Decode(&struct{}{}); err != io.EOF {
if err == nil {
return Config{}, errors.New("faqat bitta JSON qiymati kutilgan")
}
return Config{}, fmt.Errorf("ortiqcha JSON qiymati: %w", err)
}
return cfg, nil
}
Bu funksiya uchun quyidagi paketlar kerak:
Endi funksiyani bosqichma-bosqich ko‘ramiz.
Avval:
reader ustida ishlaydigan decoder yaratiladi.
Keyin:
noma’lum JSON fieldlarini xato qilish rejimi yoqiladi.
Birinchi Decode:
asosiy JSON qiymatini cfg ichiga yozadi.
Agar sintaksis noto‘g‘ri bo‘lsa yoki qiymat struct turiga mos kelmasa, shu yerda xato qaytishi mumkin.
Keyin ikkinchi Decode bajariladi:
Bu yangi biznes qiymatini o‘qish uchun emas. Maqsad — birinchi JSON qiymatidan keyin yana ma’lumot bor-yo‘qligini tekshirish.
Agar oqim tugagan bo‘lsa, Decode io.EOF qaytaradi.
Bu kerakli holat:
birinchi JSON qiymati o‘qildi
↓
yana Decode qilindi
↓
io.EOF qaytdi
↓
demak boshqa JSON qiymati yo‘q
Lekin input quyidagicha bo‘lsa:
birinchi Decode faqat birinchi objectni o‘qiydi.
Ikkinchi Decode esa ikkinchi objectni topadi. Natijada “faqat bitta JSON qiymati kutilgan” qoidasi buzilganini aniqlash mumkin.
Bu ayniqsa konfiguratsiya kabi qat’iy inputlarda muhim.
Xato turlari
encoding/json ayrim xatolarni maxsus turlar orqali qaytaradi.
Bu xatoning sababini aniqroq aniqlash imkonini beradi.
Noto‘g‘ri JSON sintaksisi *json.SyntaxError bo‘lishi mumkin.
Masalan:
Bu JSON yopilmagan.
JSON qiymati Go field turiga mos kelmasa, *json.UnmarshalTypeError qaytishi mumkin.
Masalan:
lekin JSON:
bo‘lsa, string qiymatini intga yozib bo‘lmaydi.
Xatoni errors.As orqali tekshirish mumkin:
var syntaxErr *json.SyntaxError
var typeErr *json.UnmarshalTypeError
switch {
case errors.As(err, &syntaxErr):
fmt.Printf("JSON sintaksisi xato, offset: %d\n", syntaxErr.Offset)
case errors.As(err, &typeErr):
fmt.Printf("%s field uchun tur noto‘g‘ri\n", typeErr.Field)
default:
fmt.Println("JSON o‘qilmadi")
}
Birinchi holatda:
xato zanjiri ichida *json.SyntaxError bor-yo‘qligini tekshiradi.
Agar bo‘lsa:
xato JSON oqimining taxminan qaysi byte pozitsiyasida yuz berganini ko‘rsatadi.
Ikkinchi holatda:
xato *json.UnmarshalTypeError ekanini aniqlaydi.
typeErr.Field struct fieldi haqida ma’lumot berishi mumkin.
Bu diagnostika loglarda foydali.
Lekin foydalanuvchiga aynan shu ichki xato matnini to‘liq qaytarish har doim ham yaxshi emas.
Masalan, log ichida:
- fayl yo‘li;
- ichki struct nomlari;
- maxfiy qiymatlar;
- implementation tafsilotlari
paydo bo‘lishi mumkin.
Shuning uchun ichki diagnostikani logda saqlab, tashqi foydalanuvchiga qisqa va xavfsiz xabar qaytarish odatda yaxshiroq.
Masalan:
API uchun esa mos HTTP status bilan umumiy xato qaytarilishi mumkin.
Konfiguratsiya misoli
Quyidagi misol Decoder va DisallowUnknownFields()ni birgalikda ishlatishni ko‘rsatadi.
package main
import (
"encoding/json"
"fmt"
"log"
"strings"
)
type Config struct {
Address string `json:"address"`
Debug bool `json:"debug"`
}
func main() {
input := `{"address":"localhost:8080","debug":true}`
var cfg Config
dec := json.NewDecoder(strings.NewReader(input))
dec.DisallowUnknownFields()
if err := dec.Decode(&cfg); err != nil {
log.Fatal("konfiguratsiya o‘qilmadi: ", err)
}
fmt.Printf("Manzil: %s, debug: %t\n", cfg.Address, cfg.Debug)
}
Bu misol ataylab fayl yoki HTTP requestga bog‘lanmagan.
Avval JSON oddiy string sifatida berilgan:
Keyin:
shu stringni io.Reader sifatida ko‘rish imkonini beradi.
Natijada json.Decoder stringni xuddi fayl yoki tarmoq oqimini o‘qigandek o‘qiy oladi.
Keyingi qator:
noma’lum fieldlarni taqiqlaydi.
Masalan, input xato yozilsa:
adress structdagi address fieldiga mos kelmaydi.
Shunda decoder xato qaytaradi.
Asosiy dekodlash:
JSON qiymatlarini cfg structiga yozadi.
Dekodlashdan keyin taxminan quyidagi qiymat hosil bo‘ladi:
Oxirida:
qiymatlar terminalga chiqariladi.
Natija:
Bu misoldagi asosiy qoida shuki, Decoder faqat fayl yoki HTTP bilan cheklanmaydi.
Unga io.Reader berilsa kifoya.
Shuning uchun bir xil dekodlash kodi:
os.File;http.Request.Body;strings.Reader;bytes.Reader;- network connection
kabi turli manbalar bilan ishlashi mumkin.