İçeriğe geç
academia.sh

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ğerler float64 olarak geri geliyor.
  • Gönderende olup alıcı yapıda karşılığı olmayan bir alan sessizce düşüyor; json.Unmarshal bu 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 korunuyor satı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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat