---
title: 'JSON Kodlama ve Çözme'
source: 'https://academia.sh/tr/kurslar/ios-veri-ve-ag/json-kodlama-ve-cozme'
course: 'Veri, Ağ ve Eşzamanlılık'
language: tr
updated: '2026-10-21T05:03:09+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Kodlanabilir ve çözülebilir bir tipin bir bayt dizisiyle nasıl gidip geldiğini ölçer'
    - 'Aynı kaydın iki sürümünü içeren iki yanıtın varış sırasının depoyu nasıl değiştirdiğini gösterir'
    - 'Ham metin eşitliği ile çözülmüş değer eşitliği arasındaki farkı ayırır'
    - 'Yinelemeyi çözen bir yazma kuralının sırayı çözmediğini ölçüme dayanarak söyler'
---

# JSON Kodlama ve Çözme

Bir ağ yanıtının tipli kayda çevrilmesi ve kaydın depoya yazılması; iki sürümlü bir kaydın hangi sırayla yazıldığının sonucu nasıl değiştirdiğinin ölçümü.

Önceki derste ağ istemcisinin üç parçasını ve altı işlemin dört duyarlılığını ölçtük. Ağdan dönen şey ise bir bayt dizisidir. Uygulama bu diziyi **kodlama ve çözme** (encoding and decoding) ile tipli bir kayda çevirir. Bu çevirinin kendisi güvenli görünür: tip uymuyorsa çözme hata verir, uyuyorsa kayıt hazırdır. Sorun çözmeden sonra başlar. Çözülen kayıt bir depoya yazılır ve iki yanıt aynı kaydın iki sürümünü taşıyorsa, hangisinin sonra yazıldığı depodaki değeri belirler.

Çekirdek aynıdır. Her derste olduğu gibi ilk ölçülen Swift bloğu `Replay.swift`'tir.

```swift
// Replay.swift — bir islemin yinelemeye ve siraya duyarliligini siniflandirir
struct Trial<State: Equatable & CustomStringConvertible> {
    let label: String
    let once: State
    let twice: State
    let forward: State
    let backward: State

    var sensitivity: String {
        switch (once != twice, forward != backward) {
        case (false, false): return "saglam"
        case (true, false): return "yinelemeye-duyarli"
        case (false, true): return "siraya-duyarli"
        case (true, true): return "cift-duyarli"
        }
    }

    func report() -> String {
        print("\(label) | bir=\(once) iki=\(twice) ab=\(forward) ba=\(backward) -> \(sensitivity)")
        return sensitivity
    }
}

func replay<State>(_ label: String, from start: State, _ a: (State) -> State, then b: (State) -> State) -> Trial<State> {
    Trial(label: label, once: a(start), twice: a(a(start)), forward: b(a(start)), backward: a(b(start)))
}

func countSensitivities(_ results: [String]) {
    for name in ["saglam", "yinelemeye-duyarli", "siraya-duyarli", "cift-duyarli"] {
        print("\(name): \(results.filter { $0 == name }.count)")
    }
}
```

Bu derste üç işlem ölçülecek: kaydı listeye eklemek, kaydı kimliğine göre depoya yazmak ve kaydı kodlayıp çözmek. Her biri bir kayıt tipi ve küçük bir raf üzerinde çalışır.

## Kayıt ve Raf

`Record` hem `Codable`'dır hem de ölçüm çıktısında okunabilsin diye kısa bir `description` taşır. `Shelf` kayıtların durumunu tutar; `description` kayıtları kimliğe göre sıralayarak yazar, çünkü sözlük sırası bilinmez.

```swift
// Records.swift — kayit, raf ve uc yazma bicimi
import Foundation

struct Record: Codable, Equatable, CustomStringConvertible {
    let id: Int
    let name: String
    let version: Int
    var description: String { "\(id):\(name):v\(version)" }
}

struct Shelf: Equatable, CustomStringConvertible {
    var records: [Record] = []
    var description: String { records.sorted { $0.id < $1.id }.map(\.description).joined(separator: ",") }
}

func decodeRecord(_ text: String) -> Record {
    try! JSONDecoder().decode(Record.self, from: Data(text.utf8))
}

func append(_ record: Record) -> (Shelf) -> Shelf {
    { shelf in Shelf(records: shelf.records + [record]) }
}

func upsert(_ record: Record) -> (Shelf) -> Shelf {
    { shelf in
        var next = shelf.records
        if let index = next.firstIndex(where: { $0.id == record.id }) {
            next[index] = record
        } else {
            next.append(record)
        }
        return Shelf(records: next)
    }
}

func roundTrip(_ record: Record) -> Record {
    let encoder = JSONEncoder()
    encoder.outputFormatting = [.sortedKeys]
    let data = try! encoder.encode(record)
    return try! JSONDecoder().decode(Record.self, from: data)
}

func rename(_ name: String) -> (Record) -> Record {
    { record in Record(id: record.id, name: name, version: record.version) }
}
```

Üç işlemi iki yanıtın metniyle sınarız. `first` ve `second` aynı kimliği taşıyan iki sürümdür.

```swift
// main.swift — iki surum, uc yazma bicimi
let first = decodeRecord(#"{"id":7,"name":"defter","version":1}"#)
let second = decodeRecord(#"{"id":7,"name":"defter-yeni","version":2}"#)

let listed = replay("listeye-ekle", from: Shelf(), append(first), then: append(second)).report()
let stored = replay("kimlige-gore-yaz", from: Shelf(), upsert(first), then: upsert(second)).report()
let travel = replay("kodla-coz", from: first, roundTrip, then: rename("defter-yeni")).report()

countSensitivities([listed, stored, travel])
```

```
listeye-ekle | bir=7:defter:v1 iki=7:defter:v1,7:defter:v1 ab=7:defter:v1,7:defter-yeni:v2 ba=7:defter-yeni:v2,7:defter:v1 -> cift-duyarli
kimlige-gore-yaz | bir=7:defter:v1 iki=7:defter:v1 ab=7:defter-yeni:v2 ba=7:defter:v1 -> siraya-duyarli
kodla-coz | bir=7:defter:v1 iki=7:defter:v1 ab=7:defter-yeni:v1 ba=7:defter-yeni:v1 -> saglam
saglam: 1
yinelemeye-duyarli: 0
siraya-duyarli: 1
cift-duyarli: 1
```

`listeye-ekle` çift duyarlıdır. Aynı yanıtı iki kez eklemek kaydı çoğaltır ve iki sürümün sırası listenin sırasını değiştirir. `kimlige-gore-yaz` yinelemeye karşı sağlamdır: `bir` ile `iki` aynıdır, çünkü aynı kimlikli kayıt yenisiyle değişir, eklenmez. Ama sıraya duyarlı kalır: `ab` ile `ba` farklıdır, çünkü son yazan sürüm kazanır. `kodla-coz` sağlamdır; kayıt kodlanıp çözülünce aynı kayıt döner ve yeniden adlandırmayla sırası değişse de sonuç aynıdır.

**AG4.** Ölçüm iki sürümün kimliğinin aynı olduğunu varsayar. Farklı kimlikli kayıtlar ayrı satırlara yazılır ve sırası yalnız sıralı gösterimde görünmez olur.

**AG5.** `rename` yalnız adı değiştirir ve sürüm numarasına dokunmaz. Sürümü de değiştiren bir işlem ayrıca ölçülmelidir.

## Ağdan Gelen Yanıt

Gerçek bir uygulamada bu iki metin iki ayrı yanıtın gövdesidir. İstemci yanıtı bir bayt dizisi olarak alır ve çözer.

```swift
import Foundation

func loadRecord(session: URLSession, url: URL) async throws -> Record {
    let (data, _) = try await session.data(from: url)
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    return try decoder.decode(Record.self, from: data)
}
```

Bu parça KAYNAK kalır; ağa çıkar. Yukarıdaki model, yazma işleminin duruma ne yaptığını kurar; ağın kendisi değildir. `convertFromSnakeCase` sunucunun `created_at` gibi anahtarlarını `createdAt` gibi alan adlarına eşler. Çözme hata fırlatabilir; bu yüzden çağrı `try` ile yapılır ve çağıran hatayı ya yayar ya yakalar.

İki yanıtın hangi sırayla geleceğini istemci seçemez. İki istek aynı anda yola çıkarsa ikinci gönderilen önce dönebilir. Bu yüzden `kimlige-gore-yaz` satırındaki sıra duyarlılığı bir kuram değil, ağın normal bir davranışıdır.

## Kodlamanın Neden Güvenli Olduğu

`kodla-coz` satırı sağlam çıktı, çünkü kodlama ile çözme birbirinin tersi olacak biçimde yazılır. `Record` üç alanı da sabit tiplerle taşır; kodlayıcı her alanı adıyla bir metne yazar, çözücü aynı adlarla geri okur. Araya giren bir karar yoktur: hangi alanın nereye gideceği tipten gelir, işlemin sırasından ya da kaç kez çalıştığından gelmez. Bu yüzden aynı kaydı iki kez kodlayıp çözmek aynı kaydı verir ve kaydın adını değiştirmekle gidiş dönüşün hangisinin önce geldiği sonucu etkilemez.

Bu güvenliğin iki koşulu vardır. Birincisi, kodlanan değerin kendisi kayıpsız olmalıdır. Bir `Double` alanı çok uzun bir ondalık kesir taşıyorsa metne yazılıp geri okunurken son basamaklar değişebilir; `Record`'un tam sayı ve metin alanları bu tehlikeyi taşımaz. İkincisi, çözücünün beklediği adlar kodlayıcının yazdığı adlarla aynı olmalıdır. Sunucu `created_at` yazıp istemci `createdAt` beklerse çözme ilk satırda hata fırlatır. Ölçüm ikisini de karşılanmış sayar; bu yüzden sağlam çıkması çevirinin kusursuz olduğunu değil, ölçülen kaydın çevirinin kolay olduğu bir kayıt olduğunu söyler.

## Gerçek Uygulamada Nerede Görülür

Bir not uygulaması her kaydı sunucudan JSON olarak alır. Kullanıcı bir notu açar, uygulama notu çözer ve ekrandaki listeyi günceller. Aynı not, kullanıcı ekranı yenilediğinde bir kez daha gelir. Listeye ekleyen kod bu ikinci yanıtı yeni bir not sanar ve ekranda aynı not iki satır olarak görünür. Ölçümdeki `listeye-ekle` satırı bunun ta kendisidir: `iki` sütununda aynı kayıt iki kez durur.

Kullanıcı bunu bir görsel kusur olarak görür, geliştirici ise çoğunlukla ağ katmanında bir şey aramaya başlar. Oysa ağ katmanı doğru çalışmıştır; iki yanıt da geçerli ve doğru çözülmüştür. Hata, çözülmüş kaydın depoya yazılma biçimindedir. Çözmenin sağlam, yazmanın duyarlı olması bu dersin bölünme çizgisidir: sorunu arayacağınız yer çevirinin iki yakası değil, çevirinin çıktısıyla yapılan işlemdir.

İkinci bir durum sıradır. Kullanıcı bir notu hızla iki kez düzenler ve her düzenleme ayrı bir kaydetme isteği gönderir. Sunucu iki isteği işler ve ardından uygulama notu iki yanıtla yeniden okur. Yanıtların varış sırası gönderim sırasıyla aynı olmak zorunda değildir. Eski sürüm sonra gelirse ekranda eski metin kalır ve kullanıcı yazdığı metnin kaybolduğunu düşünür. `kimlige-gore-yaz` satırının `ab` ile `ba` sütunları arasındaki fark tam olarak budur.

## Yanlış Kurulum ve Düzeltmesi

Yaygın yanlış kurulum, her yanıtı gelir gelmez depoya yazmaktır.

```swift
import Foundation

// Yanlış: gelen her yanıt, sürümüne bakılmadan kaydın üstüne yazılır
func receive(_ record: Record, into shelf: Shelf) -> Shelf {
    upsert(record)(shelf)
}
```

Bu parça KAYNAK kalır. Kimliğe göre yazmak yinelemeyi çözer, ama geç gelen eski bir yanıt yeni sürümün üstüne yazılır. Düzeltme, yalnız daha büyük sürümü kabul etmektir.

```swift
import Foundation

// Düzeltme: yalnız daha yeni sürüm depoya girer
func receiveNewer(_ record: Record, into shelf: Shelf) -> Shelf {
    if let current = shelf.records.first(where: { $0.id == record.id }), current.version >= record.version {
        return shelf
    }
    return upsert(record)(shelf)
}
```

Bu da KAYNAK kalır. İşlev, aynı kimlikli bir kayıt için depodaki sürümü yanıttakiyle karşılaştırır. Eşit ya da daha büyük sürüm depoda varsa gelen yanıt yok sayılır. Bu iki yanıtın sırasını ortadan kaldırır: hangi sırayla gelirlerse gelsinler depoda sürümü büyük olan kalır. Yineleme de çözülür, çünkü aynı sürümün ikinci gelişi yok sayılır. Ölçümde bu işlev yok; ölçülen `kimlige-gore-yaz` hâlâ sıraya duyarlıdır ve düzeltmenin etkisi yalnız bu açıklamada anlatılır.

Düzeltmenin bir koşulu vardır: sürüm numarasını sunucu vermelidir. İstemcinin kendi sayacı, iki istemci arasında karşılaştırılamaz. Sunucu sürüm vermiyorsa son değiştirilme zamanı benzer bir iş görür, ama saatlerin ayrışması bu yolu da kırılgan yapar.

## Anahtar ve Tarih Stratejileri

Sunucu ile istemcinin adlandırma alışkanlığı çoğu zaman farklıdır. Sunucu kelimeleri alt çizgiyle ayırır, Swift kodu büyük harfle. Çözücüye bir anahtar stratejisi vermek bu farkı tek yerde kapatır.

```swift
import Foundation

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
```

Bu parça KAYNAK kalır. Tarih stratejisi, sunucunun gönderdiği tarih metninin hangi biçimde olduğunu çözücüye bildirir. Biçim yanlış seçilirse çözme `DecodingError` fırlatır ve tüm yanıt okunamaz. Stratejiyi her çağrıda yeniden kurmak yerine ağ katmanının tek bir yerinde kurmak, iki ekranın aynı alanı farklı okumasını önler.

Eksik ve fazla alan da aynı yerde karar verilen bir konudur. Yanıtta tipte olmayan bir alan varsa çözücü onu yok sayar; bu, sunucunun yeni alan eklemesini istemciyi kırmadan mümkün kılar. Tipte olup yanıtta bulunmayan zorunlu bir alan ise `keyNotFound` hatası verir. Alanın gerçekten bazen eksik olabileceğini biliyorsanız tipte isteğe bağlı olarak tanımlarsınız. Bunu yapmadan hatayı yutmak, kaydı sessizce yarım bırakır.

## Ölçümün Gösterdiği ve Göstermediği

Üç satırın üç sınıfı, çevirinin nerede güvenli, nerede duyarlı olduğunu ayırır. Güvenli olan, metin ile kayıt arasındaki dönüşümdür; duyarlı olan, kaydın bir koleksiyona katılma biçimidir. Bu ayrım bir hatayı ararken zaman kazandırır. Çözme hatası alan bir uygulamada sorun yanıtın biçimindedir: bir alan eksik ya da tipi farklıdır. Çoğalan ya da eski değere dönen kayıt gören bir uygulamada ise çözme doğru çalışmıştır ve sorun yazma kuralındadır.

Ölçümün göstermediği şeyler de vardır. Kayıtta iç içe bir nesne ya da dizi olsaydı çözme daha fazla yerde başarısız olabilirdi. Tarih alanı olsaydı biçim seçimi sonucu değiştirirdi. Yanıtta gelen büyük sayılar, isteğe bağlı alanlar ve boş değerler ayrı davranışlar doğurur. Bu derste ölçülen kayıt bilerek küçük tutuldu, böylece duyarlılık sorusu çevirinin ayrıntılarına karışmadan görülebilsin. Gerçek bir uygulamada çözme hatası da bir hata sınıfıdır ve üçüncü derste anlatılan sınıflandırmanın içinde yer alır: çözülemeyen yanıtı yeniden denemek çoğunlukla aynı sonucu verir.

## Çözme Hatasıyla Karşılaşmak

Çözme hatası sessiz bırakılırsa en pahalı hatalardan biri olur. Sunucu bir alanın adını ya da tipini değiştirdiğinde tüm yanıt okunamaz hâle gelir ve uygulama boş bir ekran gösterir. Hatayı yakalayan kod, `DecodingError`'ın hangi alanda ve hangi yolda oluştuğunu günlüğe yazmalıdır; yalnız "çözülemedi" demek, sorunu bulmayı saatlere uzatır. Kullanıcıya ise ayrıntı değil, anlaşılır bir ileti gösterilir.

Hatanın yeniden denenip denenmeyeceği de bir karardır. Çözülemeyen bir yanıtı aynı sunucudan yeniden istemek çoğunlukla aynı sonucu verir; hata yanıtın biçimindedir, ağda değildir. Bu yüzden çözme hatası üçüncü dersteki sınıflandırmada denenemez hatalar arasına girer. Bir istisna, yanıtın yarım kesilmiş olduğu durumdur; o zaman gövde eksiktir ve yeniden istemek sonucu düzeltebilir. İkisini ayırt etmek için yanıtın durum kodu ve uzunluk bilgisi birlikte okunur.

## Sık Yapılan Hatalar

- Yanıtı çözüp doğrudan listeye eklemek. Aynı kayıt iki yanıtla gelince çoğalır.
- Ham metni karşılaştırarak iki yanıtı "aynı" saymak. Anahtar sırası farklı iki metin aynı kaydı taşıyabilir.
- Çözme hatasını yutmak. Sunucu bir alanı değiştirdiğinde uygulama sessizce eski değerle çalışır.
- Kayıtta sürüm numarası olmadığını varsayarak son gelen yanıtın en yeni olduğunu düşünmek.

## Ölçülen Sonucun Yorumu

Üç işlemin üç sınıfı vardı: çift duyarlı, sıraya duyarlı ve sağlam. Kodlama ile çözme sağlamdı; sorun çevirinin kendisinde değil, çevrilen kaydın depoya nasıl yazıldığındaydı. Kimliğe göre yazmak yinelemeyi çözdü; sıra sorunu kaldı. Sırayı çözmek için kayıtta bir sürüm numarası taşımak ve yalnız daha büyük sürümü yazmak gerekir. Bu kural ölçülmedi; dördüncü derste benzer bir saklama kuralı ölçülecek.

## Sınırlayıcı Ölçüm

Kural ham metne mi çözülmüş değere mi baktığını söylemez; hangisinin karşılaştırıldığı sonucu belirler. Aynı kaydı taşıyan, anahtar sırası farklı iki metin aşağıda iki biçimde karşılaştırılır.

```swift
// main.swift — ayni kayit, iki metin
import Foundation

let textA = #"{"id":7,"name":"defter","version":1}"#
let textB = #"{"version":1,"name":"defter","id":7}"#
print("ham metin esit mi: \(textA == textB)")
print("cozulmus kayit esit mi: \(decodeRecord(textA) == decodeRecord(textB))")
```

```
ham metin esit mi: false
cozulmus kayit esit mi: true
```

Ham metinler farklıdır, çözülmüş kayıtlar aynıdır. İki yanıtı ham metin olarak karşılaştıran bir istemci yinelenen yanıtı yeni sanar; çözülmüş değeri karşılaştıran istemci yok sayar. **Kural eşitsizliği görür, neyin karşılaştırıldığını görmez.**

**AG6.** Bu ölçüm anahtar sırasının farkını gösterir. Boşluk ya da sayı biçimi farkı ölçülmedi.

## Özet

- Kodlama ve çözme tipli bir kaydı bir bayt dizisine ve geri çevirir; gidiş dönüş ölçümde sağlam çıktı.
- Çözülen kaydı listeye eklemek hem yinelemeye hem sıraya duyarlıdır.
- Kimliğe göre yazmak yinelemeyi çözer, iki sürümün varış sırasını çözmez.
- İki yanıtın varış sırası ağın davranışıdır; istemci seçemez.
- Ham metin eşitliği ile çözülmüş değer eşitliği aynı kararı vermez.
- Bu dersin duyarlılığı: **sıraya duyarlı**.

## Sonraki Adım

Yanıt hiç gelmeyebilir ya da yarıda kesilebilir. Sıradaki derste Hata ve Yeniden Deneme, bir hatanın sınıflandırılmasını ve zaman aşımından sonra yeniden denenen bir siparişin neden ikinci kaydı doğurabildiğini ölçecek.
