Mert Özen Her Satırda Daha İleri
Frontend JavaScript Temel — #13

JavaScript JSON: JSON.stringify, JSON.parse, try/catch ve Derin Kopya

Mert Özen 3 Kas 2026 8 dk 4 görüntülenme
JavaScript JSON: JSON.stringify, JSON.parse, try/catch ve Derin Kopya

JSON'un syntax kurallarını, JSON.stringify ile girinti ve replacer'ı, hangi değerlerin kaybolduğunu, JSON.parse ve reviver'ı, bozuk JSON'u try/catch ile yakalamayı ve derin kopyada structuredClone'u öğreniyoruz.

Sürüm notu: Bu yazıdaki kodlar ES2026 ve güncel tarayıcılar (Chrome, Edge, Firefox, Safari) içindir.

Önce Tahmin Et

Bir önceki yazıda object'leri öğrendik. Şimdi o object'leri metne çevirmeyi göreceğiz. Şu object'e ve tek satırlık dönüşüme bak:

const data = {
  name: "Ada",
  age: undefined,
  greet: function () { return "Hello"; },
  joined: new Date("2026-01-15T10:00:00Z"),
  score: NaN
};

console.log(JSON.stringify(data));

Beş property var. Konsola yazılan metinde kaçı, hangi hâliyle yer alır? Okumaya devam etmeden önce tahminini yap. Cevap, JSON'un neleri taşıyamadığını konuştuğumuz bölümde.

JSON Nedir?

JSON, JavaScript Object Notation'ın kısaltması. Adında JavaScript geçse de aslında dilden bağımsız, düz metin tabanlı bir veri formatı. Bir sunucu sana ürün listesini gönderirken, bir uygulama ayarlarını bir dosyaya yazarken ya da tarayıcı bir veriyi saklarken büyük ihtimalle JSON kullanılıyor. Python, Java, Go, PHP, aklına gelen her dil JSON okuyup yazabiliyor.

Bir object'i bir evdeki eşyalar gibi düşün. Taşınırken eşyaları olduğu gibi kamyona koyamazsın; paketler, kutulara etiket yapıştırırsın. JSON o kutulardır. Object bellekte yaşayan canlı bir yapı; JSON ise onun kâğıda dökülmüş, taşınabilir hâli. Karşı taraf kutuları açıp eşyaları kendi evine yerleştirir. Bu iki yönlü işin JavaScript'teki karşılığı iki method: object'i metne çeviren JSON.stringify ve metni object'e çeviren JSON.parse.

JSON'un Syntax Kuralları

JSON, object literal'e çok benzer ama çok daha katıdır. Bir JSON metni şöyle görünür; bunu tarayıcının konsoluna yapıştırıp dene:

const text = `{
  "name": "Ada",
  "age": 28,
  "isAdmin": false,
  "skills": ["HTML", "CSS"],
  "address": { "city": "Izmir" },
  "phone": null
}`;

const user = JSON.parse(text);
console.log(user.address.city); // "Izmir"

Kuralları kısaca sayalım. Key'ler her zaman çift tırnak içinde olmalı. String'ler de yalnızca çift tırnakla yazılır; tek tırnak ve backtick geçersiz. Son element'ten sonra virgül olmaz. Yorum satırı yazılamaz. Değer olarak yalnızca altı şey kullanılabilir: string, sayı, true/false, null, object ve array. undefined, fonksiyon, NaN, Infinity ya da Date gibi şeylerin JSON'da karşılığı yok.

Bu katılık bir eksiklik değil, bir tercih. Kurallar ne kadar az ve net olursa, farklı dillerin aynı metni aynı şekilde anlaması o kadar garanti olur.

JSON.stringify: Object'ten Metne

const product = { title: "Keyboard", price: 750, tags: ["usb", "mechanical"] };
const json = JSON.stringify(product);

console.log(json);        // {"title":"Keyboard","price":750,"tags":["usb","mechanical"]}
console.log(typeof json); // "string"

Sonuç artık bir object değil, bir string. Key'ler çift tırnağa alındı, boşluklar atıldı. Bu hâliyle bir sunucuya gönderilmeye ya da bir yerde saklanmaya hazır.

Okunur çıktı için girinti

Tek satırlık çıktı makineler için ideal ama insan gözü için yorucu. JSON.stringify'ın üçüncü argümanı girinti miktarı:

const product = { title: "Keyboard", price: 750 };
console.log(JSON.stringify(product, null, 2));
// {
//   "title": "Keyboard",
//   "price": 750
// }

Debug ederken ya da bir ayar dosyası yazarken çok işe yarar. Ağ üzerinden gönderirken ise girintiyi bırak, gereksiz boşluk taşımış olursun.

replacer ile seçmek ve gizlemek

İkinci argüman, ki az önce null verdik, replacer. Ona bir array verirsen yalnızca o key'ler metne girer. Bir fonksiyon verirsen her key-value çifti için çağrılır ve döndürdüğün değer kullanılır; undefined döndürürsen o property atlanır:

const account = { name: "Ada", email: "ada@example.com", password: "secret123" };

console.log(JSON.stringify(account, ["name", "email"]));
// {"name":"Ada","email":"ada@example.com"}

const safe = JSON.stringify(account, (key, value) => {
  if (key === "password") {
    return undefined;
  }
  return value;
});
console.log(safe); // {"name":"Ada","email":"ada@example.com"}

Bir object'i log'a yazarken ya da bir yere gönderirken hassas alanları dışarıda bırakmanın pratik bir yolu.

Neler Sessizce Kaybolur?

Baştaki sorunun cevabı şu:

// {"name":"Ada","joined":"2026-01-15T10:00:00.000Z","score":null}

Beş property'den üçü kaldı, onlar da tam olarak aynı değil. JSON'da karşılığı olmayan değerler hata vermez; ya sessizce kaybolur ya da başka bir şeye dönüşür. Değeri undefined ya da fonksiyon olan property'ler tamamen atlanır. NaN ve Infinity null olur. Date ise ISO formatında bir string'e çevrilir; geri okuduğunda artık bir tarih değil, düz bir metindir. Array içinde aynı durum biraz farklı:

console.log(JSON.stringify([1, undefined, function () {}, NaN]));
// [1,null,null,null]

console.log(JSON.stringify({ tags: new Set(["a", "b"]) })); // {"tags":{}}

try {
  JSON.stringify({ id: 10n });
} catch (error) {
  console.log(error.name); // "TypeError"
}

Array'de element atlanırsa sıralama bozulacağı için motor onların yerine null koyar. Map ve Set boş bir object'e dönüşür, içindekiler gider. Daha önce gördüğümüz bigint ise hiç affedilmez ve TypeError fırlatır. Kısacası JSON'a yalnızca düz veri koy; içinde fonksiyon, tarih ya da özel tipler varsa çevirmeden önce ne olacağını düşün.

JSON.parse: Metinden Object'e

Ters yön çok daha sade. Geçerli bir JSON metni verirsin, karşılığında object ya da array alırsın:

const text = '{"title":"Keyboard","price":750,"inStock":true}';
const product = JSON.parse(text);

console.log(product.title);   // "Keyboard"
console.log(product.price + 50); // 800
console.log(JSON.parse("[1, 2, 3]").length); // 3

reviver ile değerleri dönüştürmek

JSON.parse'ın ikinci argümanı reviver. Her key-value çifti için çağrılır ve değeri değiştirmene izin verir. En yaygın kullanımı, string olarak gelen tarihleri tekrar Date'e çevirmek:

const text = '{"title":"Meeting","date":"2026-01-15T10:00:00.000Z"}';

const meeting = JSON.parse(text, (key, value) => {
  if (key === "date") {
    return new Date(value);
  }
  return value;
});

console.log(meeting.date instanceof Date); // true
console.log(meeting.date.getUTCFullYear()); // 2026

ES2026: reviver'a gelen kaynak metin

ES2026 ile reviver'a üçüncü bir argüman geldi: değerin JSON metnindeki orijinal hâlini taşıyan bir context object'i. Bunun neden işe yaradığını number yazısında gördüğümüz güvenli tam sayı sınırı açıklıyor:

const text = '{"id": 12345678901234567890}';

console.log(JSON.parse(text).id); // 12345678901234567000

const order = JSON.parse(text, (key, value, context) => {
  if (key === "id") {
    return BigInt(context.source);
  }
  return value;
});
console.log(order.id); // 12345678901234567890n

Normal okumada sayı daha reviver'a ulaşmadan yuvarlanmış oluyor. context.source ise metindeki rakamları olduğu gibi verdiği için onları kayıpsız bir bigint'e çevirebiliyoruz. Bu özellik bütün güncel tarayıcılarda var.

Bozuk JSON'u try/catch ile Yakalamak

Dışarıdan gelen bir metnin geçerli JSON olacağının garantisi yok. Bozuk bir metni JSON.parse'a verirsen SyntaxError fırlatır ve önlem almadıysan kodun orada durur:

const broken = "{'name': 'Ada'}";

try {
  const user = JSON.parse(broken);
  console.log(user.name);
} catch (error) {
  console.log(error.name);  // "SyntaxError"
  console.log("Couldn't read the data, using default settings.");
}

console.log("The code keeps running.");

try bloğu "bunu dene" der. İçinde bir hata fırlatılırsa motor bloğun geri kalanını atlar ve doğrudan catch'e geçer; error değişkeni fırlatılan hatayı taşır. Hata yakalandığı için program çökmez ve son satır da çalışır. error.message sana ayrıntıyı verir ama metni tarayıcıdan tarayıcıya değişir, bu yüzden kullanıcıya doğrudan göstermek yerine kendi mesajını yaz. Hata yönetimini, finally'yi ve kendi hata türlerini yazmayı Orta seride detaylandıracağız.

Derin Kopya: JSON mu, structuredClone mu?

Eski kodlarda şu satıra sık rastlarsın:

const original = {
  name: "Ada",
  birthday: new Date("1998-05-20T00:00:00Z"),
  nickname: undefined,
  address: { city: "Izmir" }
};

const viaJson = JSON.parse(JSON.stringify(original));
const viaClone = structuredClone(original);

console.log(typeof viaJson.birthday);          // "string"
console.log(viaClone.birthday instanceof Date); // true
console.log("nickname" in viaJson);            // false
console.log("nickname" in viaClone);           // true

Object'i metne çevirip hemen geri okumak, iç içe her şeyin yeni bir kopyasını üretir. structuredClone'dan önce derin kopyanın en kısa yolu buydu. Ama az önce gördüğün bütün kayıplar burada da geçerli: tarih string'e döner, undefined değerler kaybolur, Map ve Set boşalır, kendine referans veren bir object'te ise TypeError alırsın. Bir önceki yazıda gördüğümüz structuredClone bu tiplerin hepsini doğru kopyalar ve hem daha net hem daha güvenilirdir. Derin kopya için onu kullan; JSON'u verinin taşınması için sakla.

Sık Düşülen Tuzaklar

Zaten object olan şeyi parse etmek

const user = { name: "Ada" };

try {
  JSON.parse(user);
} catch (error) {
  console.log(error.name); // "SyntaxError"
}

JSON.parse bir string bekler. Ona object verirsen önce object'i string'e çevirir, bu da "[object Object]" olur ve geçerli bir JSON değildir. Elindeki değerin metin mi, object mi olduğunu bil; zaten parse edilmiş bir veriyi tekrar parse etme.

İki kez stringify etmek

const once = JSON.stringify({ a: 1 });
const twice = JSON.stringify(once);

console.log(once);  // {"a":1}
console.log(twice); // "{\"a\":1}"

Zaten string olan bir JSON'u tekrar stringify edersen, tırnakları kaçış karakterleriyle dolu bir string elde edersin. Karşı taraf bunu açtığında object değil, yine bir string bulur.

Object literal'i JSON sanmak

Kodundaki { name: "Ada" } bir object literal'dir, JSON değildir. JSON her zaman bir metindir. Elle JSON yazarken tek tırnak, tırnaksız key ya da sondaki virgül alışkanlıkları JavaScript'ten sızar ve JSON.parse hata verir. Emin değilsen metni konsolda bir kez JSON.parse'tan geçirip kontrol et.

Küçük Bir Deneme

★☆☆ Kendi me object'ini JSON.stringify ile önce tek satır, sonra 2 boşluk girintiyle yazdır. Çıktıyı JSON.parse ile geri çevir ve bir property'sini okuyarak doğru geldiğini gör.

★★☆ Çalıştırmadan önce tahmin et: JSON.stringify({ a: [undefined, 2], b: undefined, c: Infinity, d: "null" }) konsola ne yazar? Sonra çalıştır ve her property için neden öyle olduğunu açıkla.

★★☆ Bir safeParse(text) fonksiyonu yaz: metin geçerli JSON ise object'i, değilse null döndürsün. '{"ok": true}', "{ok: true}" ve boş string ile dene.

★★★ Bir kullanıcı listesini JSON'a çeviren ama her kullanıcının password alanını silen ve email alanını "a***@example.com" biçiminde maskeleyen bir replacer yaz. Daha önce string'ler yazısında gördüğümüz slice ve indexOf işine yarayacak.

String'ler, sayılar, array'ler, fonksiyonlar, object'ler ve JSON artık elinde. Şimdiye kadar her şeyi konsolda yaptık; artık sayfanın kendisine dokunma zamanı. Bir sonraki yazıda DOM'a giriyoruz ve JavaScript ile bir HTML sayfasındaki element'leri bulmayı, değiştirmeyi ve yenilerini eklemeyi göreceğiz.