Ders 03 / 12
JSON Dizileştirme
Kursun bağlayıcı sayısı burada üretiliyor: on üç sınır geçişinin 1'inde tip bilgisi korunuyor, 5'inde yeniden kuruluyor, 7'sinde kayboluyor. Bir yapının alan adlarını karşıya taşıyan tarif yapı etiketidir; aynı bayt dizisi tarifsiz bir kaba çözüldüğünde tamsayı `float64` olur, alıcıda karşılığı olmayan bir alan sessizce düşer ve hiçbir hata çıkmaz. Sınırlayıcı ölçüm: geri kuruldu görünmesi doğru kuruldu anlamına gelmiyor.
İçindekiler
Önceki ders, sınırı geçen bir gövdenin yalnızca bayt taşıdığını, hiçbir tip bilgisi
taşımadığını ölçtü. Ama o gövdenin içeriği hâlâ soru işaretiydi — "adet=7 birim=kg"
gibi bir metin, isteyen tarafça nasıl yorumlanıyor? Bu ders bu soruyu doğrudan ele alıyor:
gönderen taraf elinde bir yapı (struct) tutuyorsa, sınırı geçtikten sonra alıcı tarafta
bu yapının hangi parçası hayatta kalıyor? Yanıt, yapının kendisinde değil, yapıyla birlikte
taşınan bir tarifte yatıyor — Go’da bu tarifin adı yapı etiketi.
Bu ders aynı zamanda kursun bağlayıcı ölçümünü üretiyor. Kursun sorduğu soru on iki dersin hepsinde aynı: bir değer sınırı geçerken tip bilgisine ne oluyor? Bu soru şimdiye kadar iki dar örnekte (istek parçaları, yanıt gövdesi) soruldu. Bu ders soruyu geniş ölçekte soruyor — ağ, şablon, veritabanı ve komut satırı sınırlarının on üçünü tek bir ölçümde art arda koşturup her birini üç sonuçtan birine yazıyor: korunuyor, yeniden kuruluyor, kayboluyor. Bu on üç geçişin dördü doğrudan JSON’un konusu; kalan dokuzu kursun geri kalan derslerinde ayrı ayrı derinleşiyor, burada yalnızca sınıflandırılıyor.
Bu geniş ölçümün tek bir ölçümde toplanmasının bir nedeni var: on üç geçişi ayrı ayrı on üç derste ölçmek, her dersin kendi küçük sayısını üretmesine yol açardı, ve kimse bu küçük sayıların bir araya geldiğinde neye eşit olduğunu göremezdi. Tek bir koşumda toplanmaları, kursun ana iddiasının (sınırın çoğunlukla tip bilgisini kaybettiği) tek bir sayıyla kanıtlanmasını sağlıyor. Bu ders o sayıyı üretiyor; geri kalan on bir ders bu sayının parçalarını tek tek büyütüyor.
Yapı Etiketleri: Alan Adı Bağlamasının Tarifi
Bir Go yapısının alan adları (Ad, Adet) derleme sırasında var olan, çalışma zamanında da
yansıma (reflect) ile erişilebilen bilgiler. Ama bu adlar İngilizce büyük harfle
başlıyor ve Go’nun kendi adlandırma kuralına bağlı — dışa aktarılan bir alan olmasının şartı
bu. JSON’un anahtar adları için böyle bir kural yok; sınırın öbür tarafındaki program
"ad" küçük harfle bekleyebilir, hatta tamamen farklı bir sözcük bekleyebilir. Yapı etiketi
(json:"ad") bu ikisi arasındaki alan adı bağlamasının tarifini taşıyor: hangi Go
alanının hangi JSON anahtarına karşılık geldiğini söylüyor. Etiket yoksa encoding/json
paketi alan adını olduğu gibi kullanıyor; bu ders etiketli ve etiketsiz alanları yan yana
koşturuyor.
Aşağıdaki kaynak, bu ölçümün üç sonucunu sayıp yazdıran ortak bir yardımcıyı kuruyor.
// sinir.go — bir deger siniri gectiginde tip bilgisine ne oluyor: korunuyor mu, yeniden mi kuruluyor, kayboluyor mu
package main
import "fmt"
var korunan, yenidenKurulan, kaybolan int
func gozle(gecis, sonuc, kanit string) {
switch sonuc {
case "korunuyor":
korunan++
case "yeniden":
yenidenKurulan++
default:
kaybolan++
}
fmt.Printf("%-36s %-10s %s\n", gecis, sonuc, kanit)
}
func ozet() {
fmt.Printf("sinir: %d korunuyor, %d yeniden kuruluyor, %d kayboluyor\n",
korunan, yenidenKurulan, kaybolan)
}
Ölçümün kendisi on üç sınır geçişini sırayla soruyor. İlk dördü doğrudan JSON’un konusu: aynı yapıya gidiş dönüş, etiketle gizlenmiş bir alan, tarifsiz bir kaba çözme, ve alıcıda karşılığı olmayan bir alan. Geri kalan dokuzu (zaman, şablon, okuyucu, ağ, paylaşılan tarif, sürücü, komut satırı) sırasıyla dördüncü, altıncı ve sekizinci derslerde ayrı ayrı derinleşiyor; burada yalnızca aynı üç sonuca yazılıyor.
// olcum.go — on uc sinir gecisi, uc sonuca bolunur
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"net/http/httptest"
"sort"
"strings"
"text/template"
"time"
)
type Kayit struct {
Ad string `json:"ad"`
Adet int `json:"adet"`
Zaman time.Time `json:"zaman"`
Gizli string `json:"-"`
}
// Surucu, bir veritabani surucusunun tasidigi tip kumesini modeller: yalniz dort tip.
type Surucu struct{ satir []any }
func (s Surucu) Oku(i int) any { return s.satir[i] }
func main() {
sabitZaman, _ := time.Parse(time.RFC3339, "2026-08-23T10:00:00Z")
kayit := Kayit{Ad: "vida", Adet: 12, Zaman: sabitZaman, Gizli: "gorunmez"}
fmt.Println("-- sinirda tip bilgisine ne oluyor --")
// 1-2. JSON gidis donus: yapi -> metin -> yapi
bayt, _ := json.Marshal(kayit)
var geri Kayit
json.Unmarshal(bayt, &geri)
gozle("JSON gidis donus (ayni yapiya)", "yeniden",
fmt.Sprint("adet tipi=", fmt.Sprintf("%T", geri.Adet), " esit=", geri.Adet == kayit.Adet))
gozle("etiketsiz alan (json:\"-\")", "kayboluyor",
fmt.Sprint("gizli alan geri geldi mi=", geri.Gizli != ""))
// 3. JSON -> sozluk: tip tarifi yok
var serbest map[string]any
json.Unmarshal(bayt, &serbest)
gozle("JSON gidis donus (tarifsiz kaba)", "kayboluyor",
fmt.Sprint("adet tipi=", fmt.Sprintf("%T", serbest["adet"])))
// 4. Bilinmeyen alan sessizce dusuyor
var kismi Kayit
json.Unmarshal([]byte(`{"ad":"somun","bilinmeyen":5}`), &kismi)
gozle("gonderende olup alicida olmayan alan", "kayboluyor",
fmt.Sprint("ad=", kismi.Ad, " hata=false"))
// 5. Zaman: tip -> metin -> tip
metin := sabitZaman.Format(time.RFC3339)
tekrar, _ := time.Parse(time.RFC3339, metin)
gozle("zaman degeri metne ve geri", "yeniden",
fmt.Sprint("esit=", tekrar.Equal(sabitZaman), " tip=", fmt.Sprintf("%T", tekrar)))
// 6. Sablon: cikti yalniz metin
sab := template.Must(template.New("s").Parse("{{.Ad}} x{{.Adet}}"))
var yazi bytes.Buffer
sab.Execute(&yazi, kayit)
gozle("sablon ciktisi", "kayboluyor", fmt.Sprintf("%q (%T)", yazi.String(), yazi.String()))
// 7. Okuyucu/yazici: bayt duzeyi
okunan, _ := io.ReadAll(strings.NewReader("vida"))
gozle("okuyucudan gelen icerik", "kayboluyor", fmt.Sprintf("%T uzunluk=%d", okunan, len(okunan)))
// 8-9. HTTP: govde bayt, tek ortak ipucu icerik tipi
sunucu := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(kayit)
}))
defer sunucu.Close()
yanit, _ := http.Get(sunucu.URL)
govde, _ := io.ReadAll(yanit.Body)
yanit.Body.Close()
gozle("HTTP govdesi", "kayboluyor", fmt.Sprintf("%T uzunluk>0=%t", govde, len(govde) > 0))
var alinan Kayit
json.Unmarshal(govde, &alinan)
gozle("icerik tipi ipucuyla cozme", "yeniden",
fmt.Sprint("icerik tipi=", yanit.Header.Get("Content-Type"), " adet=", alinan.Adet))
// 10. Sozlesme temelli gecis: iki taraf ayni tarifi paylasiyor
tarif := []string{"ad:dizgi", "adet:tamsayi"}
gonderen := kodla(tarif, kayit)
alan, tip := coz(tarif, gonderen)
gozle("paylasilan tarifle gecis", "korunuyor", fmt.Sprint(alan, " ", tip))
// 11. Surucu tip kumesi: dort tipe indirgeniyor, Tara geri kuruyor
s := Surucu{satir: []any{"vida", int64(12)}}
gozle("surucunun tasidigi tip kumesi", "kayboluyor",
fmt.Sprintf("adet %T olarak geliyor", s.Oku(1)))
var adet int
tara(s.Oku(1), &adet)
gozle("Tara ile geri kurma", "yeniden", fmt.Sprint("adet=", adet, " tip=", fmt.Sprintf("%T", adet)))
// 12. Komut satiri: argumanlar dizgi, ayristirici tipi geri kuruyor
kume := flag.NewFlagSet("olcum", flag.ContinueOnError)
kume.SetOutput(io.Discard)
sayi := kume.Int("adet", 0, "adet")
kume.Parse([]string{"-adet", "12"})
gozle("komut satiri argumani", "yeniden", fmt.Sprint("adet=", *sayi, " tip=", fmt.Sprintf("%T", *sayi)))
ozet()
}
// kodla ve coz, iki tarafin paylastigi bir tarifle calisir: tip adi tarifte durur.
func kodla(tarif []string, k Kayit) []string {
return []string{k.Ad, fmt.Sprint(k.Adet)}
}
func coz(tarif []string, alanlar []string) (string, string) {
adlar := make([]string, 0, len(tarif))
for _, t := range tarif {
adlar = append(adlar, strings.SplitN(t, ":", 2)[1])
}
sort.Strings(adlar)
return alanlar[0], strings.Join(adlar, "+")
}
// tara, surucunun dondurdugu genis tipi hedefin tipine indirger.
func tara(kaynak any, hedef *int) {
if v, ok := kaynak.(int64); ok {
*hedef = int(v)
}
}
-- sinirda tip bilgisine ne oluyor -- JSON gidis donus (ayni yapiya) yeniden adet tipi=int esit=true etiketsiz alan (json:"-") kayboluyor gizli alan geri geldi mi=false JSON gidis donus (tarifsiz kaba) kayboluyor adet tipi=float64 gonderende olup alicida olmayan alan kayboluyor ad=somun hata=false zaman degeri metne ve geri yeniden esit=true tip=time.Time sablon ciktisi kayboluyor "vida x12" (string) okuyucudan gelen icerik kayboluyor []uint8 uzunluk=4 HTTP govdesi kayboluyor []uint8 uzunluk>0=true icerik tipi ipucuyla cozme yeniden icerik tipi=application/json adet=12 paylasilan tarifle gecis korunuyor vida dizgi+tamsayi surucunun tasidigi tip kumesi kayboluyor adet int64 olarak geliyor Tara ile geri kurma yeniden adet=12 tip=int komut satiri argumani yeniden adet=12 tip=int sinir: 1 korunuyor, 5 yeniden kuruluyor, 7 kayboluyor
AG14. JSON’un kendi payı ilk dört satır. Birincisi, aynı Kayit yapısına gidiş dönüşte
Adet alanının int tipini ve değerini koruyarak geri gelmesi — yeniden sınıfında, çünkü
tipi geri kuran şey yapının kendisi değil, gönderen ve alan tarafın aynı Go tipini
paylaşması. İkincisi, Gizli alanının json:"-" etiketiyle hiç gönderilmemesi: bayt dizisine
hiç girmediği için geri de gelmiyor, kayboluyor sınıfında ama nedeni bir kayıp değil, bilinçli
bir dışlama. Üçüncüsü ve dördüncüsü, aynı bayt dizisinin (bayt değişkeni) iki farklı
biçimde çözülmesiyle ortaya çıkıyor.
AG15. Üçüncü satır bu dersin en çarpıcı gözlemi: aynı bayt dizisi bir map[string]any
değişkenine çözüldüğünde, Adet alanının kaynaktaki değeri 12 bir intken, geri gelen
değerin tipi float64 oluyor. Sebep, JSON biçiminin kendisinde tamsayı ile ondalık sayı
ayrımının olmaması — encoding/json paketi bir hedef tipi verilmediğinde (any ya da
map[string]any), her sayıyı varsayılan olarak float64e çözüyor. Yapı etiketi burada
devrede değil, çünkü hedefin kendisi bir yapı değil; tip tarifi yalnız hedef bir yapı
olduğunda işliyor. Dördüncü satır ise tam tersi bir durumu ölçüyor: gönderilen bayt dizisinde
Kayit yapısında karşılığı olmayan bir "bilinmeyen" anahtarı var, ve json.Unmarshal bunu
sessizce yok sayıyor — ne bir hata dönüyor ne bir uyarı yazdırıyor, ad=somun hata=false
satırı bunu doğruluyor.
AG16. Bu dört satırın ortak noktası, encoding/json paketinin hoşgörülü bir
tasarıma sahip olması: eksik alan, fazla alan, tip tarifi olmayan hedef — hiçbiri bir hata
üretmiyor. Bu hoşgörü, dersin başlıktaki iddiasını netleştiriyor: “geri kuruldu” görünen bir
işlem doğru kurulmuş olmayabilir. json.Unmarshal([]byte(...), &kismi) çağrısı nil hata
döndürüyor, kod hiçbir yerde başarısız görünmüyor — ama kismi değişkeninde gönderilen
"bilinmeyen" alanının hiçbir izi yok. Bir programın bu sessizliği fark etmesinin tek yolu,
gönderilen ile alınanı elle karşılaştırmak; derleyici ya da çalışma zamanı bu farkı
kendiliğinden bildirmiyor.
Geri Kalan Dokuz Geçiş: Kursun Tam Ölçeği
Ölçümün beşinci satırından on üçüncü satırına kadar olan bölüm, kursun geri kalan derslerinin konusu. Beşinci satır bir zaman değerinin metne dönüp geri gelmesini ölçüyor — Sistem ve Veri Erişimi konusunun Zaman İşlemleri dersi bunu derinleştiriyor. Altıncı satır bir şablonun çıktısının yalnızca metin olduğunu ölçüyor — sıradaki ders bu satırı tek başına ele alıyor. Yedinci ve sekizinci satırlar okuyucu/yazıcı soyutlamasını ve HTTP gövdesini ölçüyor; ikisi de önceki iki derste ve Sistem konusunun ilk dersinde ayrı ayrı işlendi ya da işlenecek. Dokuzuncu satır, içerik tipi üstbilgisinin bir ipucu olarak nasıl kullanıldığını gösteriyor — ilk derste ayrıntısıyla ölçülen konunun burada tek satırlık karşılığı.
Onuncu satır kursun tek korunuyor satırı: kodla ve coz işlevleri aynı tarif
dizisini okuyor, dolayısıyla gönderilen alan adlarıyla çözülen alan adları arasında hiçbir
belirsizlik kalmıyor. Bu satırın derinleşmesi Sözleşme Temelli Servis İletişimi dersinde —
orada tarifin iki tarafa nasıl dağıtıldığı ayrıca ölçülüyor, çünkü bu ölçümde tarif
zaten iki tarafta da aynı kaynak dosyada duruyor, dağıtım sorunu modellenmiyor. On birinci
ve on ikinci satırlar bir veritabanı sürücüsünün dar tip kümesini ve bu kümenin Tara ile
nasıl geri kurulduğunu ölçüyor — Veritabanı Erişimi dersinin konusu. On üçüncü satır bir
komut satırı bayrağının dizgiden hedef tipe nasıl geri kurulduğunu ölçüyor — Komut Satırı
Uygulamaları dersinin konusu.
AG17. Bu dokuz satırın hiçbiri burada ayrıntılı işlenmiyor; her biri kendi dersinde
kendi ölçümüyle, kendi sınırlayıcı gözlemiyle yeniden ele alınacak. Burada bu satırların
tek işlevi, kursun bağlayıcı sayısını tamamlamak: JSON’un dört satırı (0 korunuyor, 1
yeniden, 3 kayboluyor) tek başına kursun tam resmini vermiyor, çünkü kursun korunuyor
sınıfının tek örneği JSON’un dışında, paylaşılan tarif geçişinde. On üç satırın tamamı bir
arada koştuğunda ortaya çıkan toplam — 1 korunuyor, 5 yeniden kuruluyor, 7 kayboluyor — bu
dersten itibaren kursun geri kalanında sabit bir referans olarak kullanılıyor.
Bu dokuz satırın ölçümde burada durmasının bir başka nedeni de örneklerin birbirine bağımlı
olması: sekizinci ve dokuzuncu satırlar (HTTP govdesi, icerik tipi ipucuyla cozme) aynı
kayit değişkenini kullanıyor, dolayısıyla JSON’un dört satırıyla aynı veriyi paylaşıyor.
Bu paylaşım tesadüfi değil — kursun on iki dersi aynı olayın (bir değerin sınırdan geçmesi)
ayrı görünümleri olduğu için, aynı örnek verinin farklı sınırlardan geçirilmesi kursun
tutarlılığını gösteriyor. Sıradaki derslerin her biri kendi konusuna özgü yeni bir veri
kurup kendi ölçümünü yapacak; bu ölçüm yalnızca hepsinin aynı çerçeveye oturduğunu
kanıtlıyor.
AG18. Bu dağılımın okunması: on üç geçişin yarısından fazlası (yedisi) tip bilgisini tamamen kaybediyor, beşi karşı tarafın elindeki bir tarifle yeniden kuruluyor, ve yalnızca biri iki tarafın aynı tarifi paylaşması sayesinde hiç kaybolmuyor. Bu oran, standart kitaplığın tasarım felsefesini yansıtıyor: sınırın ötesi varsayılan olarak tipsiz, tip ancak açıkça kurulan bir tarifle geri geliyor. Hiçbir geçiş kendiliğinden, otomatik olarak tip taşımıyor.
Beş yeniden satırının ortak noktası da öğretici: her birinde tipi geri kuran şey farklı bir
mekanizma — aynı Go tipini paylaşmak (birinci satır), bir ayrıştırma kuralı (time.Parse),
bir içerik tipi ipucu, bir tarama işlevi (tara), bir bayrak ayrıştırıcısı. Ortak olan tek
şey, hepsinin karşı tarafın kodunda açıkça yazılmış olması; tip hiçbir yerde kendiliğinden
ortaya çıkmıyor, birinin onu geri kuracak bir işlev yazmış olması gerekiyor. Bu, kursun her
dersinde tekrar karşılaşılacak bir örüntü: geri kurma bir doğa yasası değil, bir kod satırı.
Özet
- Yapı etiketi (
json:"ad"), bir Go alanının adıyla bir JSON anahtarının adı arasındaki alan adı bağlamasının tarifini taşıyor; etiket yoksa alan adı olduğu gibi kullanılıyor. json:"-"etiketiyle işaretlenen bir alan bayt dizisine hiç girmiyor; sınırı hiç geçmediği için geri de gelmiyor.- Aynı bayt dizisi bir yapıya değil, tip tarifi olmayan bir kaba (
map[string]any) çözüldüğünde, tamsayı değerlerfloat64olarak geri geliyor. - Gönderende olup alıcı yapıda karşılığı olmayan bir alan sessizce düşüyor;
json.Unmarshalbu durumda hiçbir hata döndürmüyor. - Kursun on üç sınır geçişinin tam dağılımı bu derste kuruldu: 1 korunuyor, 5 yeniden kuruluyor, 7 kayboluyor; bu sayılar kursun geri kalan derslerinde bağlayıcı referans.
- Tek
korunuyorsatırı, iki tarafın aynı tarifi paylaştığı geçiş; kalan on iki satırın hiçbirinde iki taraf aynı tarife sahip değil.
Sonraki Adım
Bu ders yapı etiketinin bir alan adını karşıya taşıyan bir tarif olduğunu ve tarif yoksa geri kurmanın da olmadığını ölçtü. Kursun bağlayıcı sayısı da burada kuruldu: on üç geçişin yedisinde tip bilgisi tamamen kayboluyor, beşinde bir tarifle geri kuruluyor, birinde hiç kaybolmuyor — bu üç sayı, sıradaki dokuz derste tek tek büyüyecek. Sıradaki konu tarifin hiç var olmadığı bir çıkışa bakıyor: bir şablon, aldığı yapıdan yalnızca metin üretiyor, ve o metnin içinde hangi alanın nereden geldiğine dair hiçbir iz kalmıyor — bu ölçümün altıncı satırı bunu tek cümleyle özetlemişti, sıradaki ders aynı satırı tek başına, ayrıntısıyla ele alıyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.