Go ile JSON İşlemleri ve Veri Serileştirme

Modern yazılımlar artık tek başına yaşayan küçük adacıklar değil; API’ler, mikroservisler, mobil uygulamalar ve üçüncü parti sistemlerle sürekli konuşan sosyal kelebekler. Go tarafında bu sohbetin en popüler dili JSON’dır. Bir Go yapısını JSON’a çevirmek, valize kıyafet yerleştirmeye benzer: veriyi dış dünyaya taşınabilir, okunabilir ve standart bir forma sokarız. Gelen JSON’u tekrar Go struct’ına çözmek ise valizi açıp eşyaları doğru çekmecelere yerleştirmektir. ``

JSON ve serileştirmenin teorisi

Serileştirme, bellekte duran bir veri yapısını aktarılabilir bir biçime dönüştürme işlemidir. Go’da buna çoğunlukla marshal, tersine ise unmarshal denir. Matematiksel düşünürsek, serileştirmeyi bir dönüşüm fonksiyonu gibi görebiliriz: $f: GoStruct \rightarrow JSON$. Ters işlem ise $f^{-1}: JSON \rightarrow GoStruct$ şeklindedir. Elbette pratikte $f^{-1}$ her zaman mükemmel değildir; eksik alanlar, tip uyuşmazlıkları veya beklenmeyen değerler yüzünden hata alabiliriz.

JSON’un avantajı basit olmasıdır: string, number, boolean, array, object ve null. Go ise daha zengin tiplere sahiptir. Bu yüzden iki dünya arasında çeviri yaparken tip eşleşmelerini iyi bilmek gerekir.

JSON Tipi Go Karşılığı Not
string string Tarihler genellikle string taşınır
number int, float64 Varsayılan interface{} çözümünde float64 olur
boolean bool Doğrudan eşleşir
array slice Örn: []string
object struct veya map API modellerinde struct tercih edilir
null pointer, sql.Null* Yokluğu temsil etmek için kullanılır

Struct tag: JSON pasaport kontrolü

Go’da alan adları dışa açılmak için büyük harfle başlamalıdır. Ancak JSON alan isimleri genellikle küçük harfli veya snake_case olur. İşte struct tag burada devreye girer.

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    ID       int    `json:"id"`
    FullName string `json:"full_name"`
    Email    string `json:"email,omitempty"`
}

func main() {
    user := User{ID: 7, FullName: "Ada Lovelace"}

    data, err := json.MarshalIndent(user, "", "  ")
    if err != nil {
        panic(err)
    }

    fmt.Println(string(data))
}

Bu örnekte MarshalIndent, JSON’u okunabilir biçimde üretir. omitempty ise alan boşsa JSON’a yazma anlamına gelir. Yani e-posta yoksa gereksiz bir email alanı gönderilmez. API tüketen sistemlerin “Bu niye boş geldi?” diye trip atmasını engeller.

Gelen JSON’u Go yapısına çözmek

Dış sistemden veri geldiğinde json.Unmarshal kullanırız. Burada önemli detay, hedef değişkenin adresini vermektir; çünkü Go’nun o değişkenin içini doldurması gerekir.

package main

import (
    "encoding/json"
    "fmt"
)

type Product struct {
    Code  string  `json:"code"`
    Price float64 `json:"price"`
    Stock int     `json:"stock"`
}

func main() {
    raw := []byte(`{"code":"KB-42","price":129.90,"stock":15}`)

    var product Product
    err := json.Unmarshal(raw, &product)
    if err != nil {
        fmt.Println("JSON hatası:", err)
        return
    }

    fmt.Printf("%s ürünü %.2f TL\n", product.Code, product.Price)
}

Buradaki fiyat alanı için float64 kullandık. Para işlemlerinde kayan nokta hassasiyetine dikkat etmek gerekir. Örneğin $0.1 + 0.2 \neq 0.3$ gibi görünen sonuçlar bilgisayar temsilinden kaynaklanabilir. Finansal uygulamalarda kuruşu int olarak saklamak daha güvenlidir.

Struct mı map mi?

Her JSON için struct yazmak zorunda değilsiniz. Dinamik veya şeması belirsiz verilerde map[string]interface{} kullanılabilir. Ancak bu esneklik, tip güvenliğinden ödün verir.

Yaklaşım Avantaj Dezavantaj
struct Tip güvenli, okunabilir, IDE dostu Şema değişirse güncelleme ister
map[string]interface{} Esnek, hızlı prototiplenir Tip kontrolü zahmetlidir
json.RawMessage Parçalı çözümleme sağlar Daha ileri seviye yönetim ister

Hata yönetimi ve sözleşme bilinci

JSON işlemlerinde en büyük hata, “Karşı taraf doğru gönderir ya” rahatlığıdır. Göndermez. Alan eksik gelir, sayı string gelir, null gelir, hatta bazen HTML hata sayfası JSON sanılıp parse edilmeye çalışılır. Bu yüzden her Marshal ve Unmarshal çağrısında hata kontrolü yapılmalıdır.

Ayrıca JSON, sistemler arası bir sözleşmedir. Bu sözleşmenin maliyetini basitçe şöyle düşünebiliriz: $Toplam\ Risk = Veri\ Belirsizliği \times Entegrasyon\ Sayısı$. Entegrasyon arttıkça küçük tip hataları bile büyük problemlere dönüşür.

Sonuç

Go’nun encoding/json paketi, dış sistemlerle konuşmak için sade ama güçlü bir araçtır. Struct tag’leriyle alan adlarını kontrol eder, Marshal ile veriyi dışarı yollar, Unmarshal ile gelen veriyi güvenli şekilde içeri alırız. İyi modellenmiş struct’lar, dikkatli hata yönetimi ve tip farkındalığı sayesinde JSON iletişimi kaotik bir mesajlaşma grubu olmaktan çıkar, düzenli bir protokole dönüşür.

Yorumlar