1 Ekim 2022

JSON metodları, toJSON

Diyelim ki karmaşık bir yapı var, bunu karakter dizisine çevirip ağ üzerinden loglanması için başka bir yere iletilmek isteniyor.

Doğal olarak, bu karakter dizisi tüm önemli özellikleri içermeli

Bu çevirim şu şekilde yapılabilir:

let kullanici = {
  adi: "Ahmet",
  yasi: 30,

  toString() {
    return `{adi: "${this.adi}", yasi: ${this.yasi}}`;
  }
};

alert(kullanici); // {adi: "Ahmet", yasi: 30}

… Fakat geliştirme esnasında yeni özellikler eklendi ve öncekiler ya silindi ya da isim değiştirdi. Böyle bir durumda toString metoduyla her zaman değişiklik yapmak oldukça zordur. Özellikleri döngüye sokup buradan değerler alınabilir. Bu durumda da iç içe objelere ne olacak? Bunlarında çevirimlerini yapmak gerekir. Ayrıca ağ üzerinden objeyi göndermeye çalıştığınızda ayrıca bu objenin alan yer tarafından nasıl okunacağına dair bilgi göndermek zorundasınız.

Neyse ki bunların hiçbiri için kod yazmaya gerek yok. Bu problem bizim için çözülmüş durumda.

JSON.stringify

JSON (JavaScript Object Notation) genelde objelerin değerlerini ifade eder.RFC 4627 standardında tanımı yapılmıştır. Öncelikle JavaScript düşünülerek yapılmış olsa da birçok dil de kendine has kütüphanelerle JSON desteği vermektedir. Böylece client JavaScript kullanırken server Ruby/PHP/Java/Her neyse… kullansa bile JSON kullanımında bir sorun oluşturmaz.

JavaScript aşağıdaki metodları destekler:

  • JSON.stringify objeyi JSON’a çevirir.
  • JSON.parse JSON’dan objeye çevirmeye yarar.

Örneğin, aşağıda JSON.stringify metodu ögrenci objesi için kullanılmıştır:

let ogrenci = {
  adi: 'Ahmet',
  yasi: 30,
  adminMi: false,
  dersler: ['html', 'css', 'js'],
  esi: null
};

let json = JSON.stringify(ogrenci);

alert(typeof json); // string dönecektir.!

alert(json);
/* JSON'a çevirilmiş obje:
{
  "adi": 'Ahmet',
  "yasi": 30,
  "adminMi": false,
  "dersler": ['html', 'css', 'js'],
  "esi": null
}
*/

JSON.stringify(ogrenci) metodu objeyi alır ve bunu karaktere çevirir, buna Json-kodlanmış , seri hale getirilmiş veya karakter haline getirilmiş denir. Bunu ağ üzerinden karşı tarafa göndermek veya basit bir şekilde kaydetmek mümkündür.

JSON kodlanmış objenin normal obje ile arasında birkaç tane önemli farklılık vardır:

  • Karakterler çift tırnak kullanır. JSON’da tek tırnak veya ters tırnak kullanılmaz. Bundan dolayı 'Ahmet'"Ahmet" olur.
  • Obje özelliklerinin isimleri de çift tırnak içinde alınır. Bu da zorunludur. Bundan dolayı yas:30 , "yas":30 olur.

JSON.stringify ilkel tiplere de uygulanabilir.

Desteklenen JSON tipleri:

  • Objeler { ... }
  • Diziler [ ... ]
  • İlkel Tipler:
    • karakterler,
    • sayılar,
    • boolean değerler true/false,
    • null.

Örneğin:

// normal bir sayı JSON için de normal bir sayıdır.
alert( JSON.stringify(1) ) // 1

// karakterler de JSON içinde karakterdir fakat çift tırnak içinde gösterilir.
alert( JSON.stringify('test') ) // "test"

alert( JSON.stringify(true) ); // true

alert( JSON.stringify([1, 2, 3]) ); // [1,2,3]

JSON sadece veriyi tanımlayan diller arası bir şartname bulunmaktadır. Bundan dolayı JavaScript’e özel obje özelliklerikleri JSON.stringify tarafından pas geçilir.

Yani:

  • Fonksiyon özellikleri ( metodlar ).
  • Sembolik özellikler.
  • undefined’ı saklayan özellikler.
let kullanici = {
  merhaba() { // ihmal edilir
    alert("Merhaba");
  },
  [Symbol("id")]: 123, // ihmal edilir
  baska: undefined // ihmal edilir
};

alert( JSON.stringify(kullanici) ); // {} (boş obje)

Bu özellik kabul edilebilir. Eğer istediğiniz bu değilse, bu işlemi nasıl özelleştirebilirsiniz bunu göreceksiniz.

Harika olan ise iç içe objeler otomatik olarak çevrilir.

Örneğin:

let tanisma = {
  baslik: "Konferans",
  oda: {
    sayi: 123,
    katilimcilar: ["ahmet", "mehmet"]
  }
};

alert( JSON.stringify(tanisma) );
/* tüm yapı karakter şekline çevrildi:
{
  "tanisma":"baslik",
  "oda":{"sayi":23,"katilimcilar":["ahmet","mehmet"]},
}
*/

Önemli bir sınırlama: Dairesel referans olmamalıdır.

Örneğin:

let oda = {
  sayi: 23
};

let tanisma = {
  baslik: "Konferans",
  katilimcilar: ["ahmet", "mehmet"]
};

tanisma.yeri = oda;       // tanisma odaya referans veriyor.
oda.dolduruldu = tanisma; // oda tanismaya referans veriyor

JSON.stringify(tanisma); // Hata: Dairesel yapı JSON'a çevrilememiştir.

Çeviri yapılırken hata olmasının nedeni: oda.dolduruldu tanisma’ya referans olurken, tanisma.yeri oda’ya referans verir.

Hariç tutmak ve dönüştürmek: yer değiştirici ( replacer )

JSON.stringfy’ın tam yazımı:

let json = JSON.stringify(deger[, degistirici, bosluk])
deger
Kodlanacak metin.
degistirici
Mapleme (haritalama) fonksiyonu ( function(key,value)) veya kodlanacak özelliklerin dizisi.
boşluk
Formatlanmak için kullanılacak boşluk.

Çoğu zaman JSON.stringify’ın sadece ilk argümanı kullanılır. Fakat daha derinlemesine bir değiştirici işlemi yapmak istiyorsanız. Örneğin dairesel referansı filtrelemek gibi, JSON.stringify’ın diğer argümanlarını da kullanabilirsiniz.

Eğer üçüncü parametreyi de gönderirseniz, sadece gönderdiğiniz özellikler kodlanacaktır.

Örneğin:

let oda = {
  sayi: 23
};

let tanisma = {
  baslik: "Konferans",
  katilimcilar: [{adi: "Ahmet"}, {adi: "Mehmet"}],
  yer: oda // tanışma odayı referans gösteriyor.
};

oda.dolduruldu = tanisma; // oda tanışmayı referans gösteriyor.

alert( JSON.stringify(tanisma, ['baslik', 'katilimcilar']) );
// {"baslik":"Konferans","katilimcilar":[{},{}]}

Burada çok sıkı kullandık. Özellik listesi tüm yapı için kullanıldı. Bundan dolayı katılımcılar boş döndür, adi alanı da istenseydi bu durumda değer gelecekti.

Dairesel referansa neden olabilecek oda.dolduruldu hariç hepsini içermek isterseniz:

let oda = {
  sayi: 23
};

let tanisma = {
  baslik: "Konferans",
  katilimcilar: [{adi: "Ahmet"}, {adi: "Mehmet"}],
  yer: oda // tanışma odayı referans gösteriyor.
};

oda.dolduruldu = tanisma; // oda tanışmayı referans gösteriyor.

alert( JSON.stringify(tanisma, ['baslik', 'katilimcilar', 'yer', 'adi', 'sayi']) );

/*
{
  "baslik":"Konferans",
  "katilimcilar":[{"adi":"Ahmet"},{"adi":"Mehmet"}],
  "yer":{"sayi":23}
}
*/

Şimdi ise dolduruldu hariç her yer seri haline getirildi. Fakat özelliklerin listesi oldukça büyük oldu.

Neyse ki degistirici yerine fonksiyon kullanılabilir.

Bu fonksiyon her (anahtar, deger) ikilisi için çağırılabilir ve “değiştirilmiş” değeri çevirir, bu da orijinalinin yerine geçer.

Daha önce yaptığımız örnekte dolduruldu özelliği hariç diğer özelliklerin deger’in olduğu gibi kullanılabilir. dolduruldu özelliğini pas geçmek için aşağıdaki kod undefined döndürür.

let oda = {
  sayi: 23
};

let tanisma = {
  baslik: "Konferans",
  katilimcilar: [{adi: "Ahmet"}, {adi: "Mehmet"}],
  yer: oda // tanışma odayı referans gösteriyor.
};

oda.dolduruldu = tanisma; // oda tanışmayı referans gösteriyor

alert( JSON.stringify(tanisma, function degistirici(anahtar, deger) {
  alert(`${anahtar}: ${deger}`); // degistiriciye gelen
  return (anahtar == 'dolduruldu') ? undefined : deger;
}));

/* degistiriciye gelen anahtar:deger çifti:
:             [object Object]
baslik:        Conference
katilimci:    [object Object],[object Object]
0:            [object Object]
adi:         Ahmet
1:            [object Object]
adi:         Mehmet
yer:        [object Object]
sayi:       23
*/

degistirici fonksiyonu iç içe objeler ve diziler dahil her şeyi alır. Tüm objelere yinelemeli olarak uygulanır. this’in değeri degistirici içerisinde o anki özellikleri tutar.

İlk çağrı özeldir. “Sarıcı obje” vasıtasıyla: {"": tanisma}. Diğer bir deyişle ilk (anahtar, deger) çifti boş anahtar ile gelir ve değeri hedef objenin tamamıdır. Bundan dolayı yukarıdaki örnekte ilk satır: ":[object Object]"'dir.

Fikir degistirici’yi olabildiğince güçlü yapmaktır: Böylece gelen tüm objeyi pas geçme veya analiz etme gibi imkanlar sağlanır.

Formatlama: bosluk

JSON.stringify(deger, degistirici, boşluk)'ın 3. argümanı formatlamayı güzel yapmak için kaç boşluk bırakılması gerektiği bilgisini alır.

Önceden, karakter dizisi haline getirilmiş objelerin hiç boşlukları bulunmamaktaydı. Eğer bunu obje üzerinden göndermek istiyorsanız pek önemli değildir. bosluk sadece güzel çıktı vermek amacıyla kullanılır.

Burada bosluk = 2 kullanılmıştır, iç içe objelerin birkaç satırda ve objeler arasında 2 boşluk olacak şekilde ayarlamasını söyler.

let kullanici = {
  adi: "Ahmet",
  yas: 25,
  roller: {
    admin: false,
    editor: true
  }
};

alert(JSON.stringify(kullanici, null, 2));
/* iki boşluk:
{
  "adi": "Ahmet",
  "yasi": 25,
  "roller": {
    "admin": false,
    "editor": true
  }
}
*/

/*  JSON.stringify(user, null, 4) için ise çıktı aşağıdaki gibi olur:
{
    "adi": "Ahmet",
    "yasi": 25,
    "roller": {
        "admin": false,
        "editor": true
    }
}
*/

bosluk genelde loglama veya güzel çıktı almak için kullanılır.

İsteğe göre uyarlanmış “toJSON”

Karakterlerin çeviriminde toString metodunun kullanılabileceğini daha önce söylemiştik. Objeler için toJSON metodu varsa JSON.stringify çağırıldığında bu otomatik olarak çağırılır.

Örneğin:

let oda = {
  sayi: 23
};

let toplanti = {
  baslik: "Konferans",
  tarih: new Date(Date.UTC(2017, 0, 1)),
  oda
};

alert( JSON.stringify(toplanti) );
/*
  {
    "baslik":"Konferans",
    "tarih":"2017-01-01T00:00:00.000Z",  // (1)
    "oda": {"sayi":23}               // (2)
  }
*/

Gördüğünüz gibi date (1) karaktere dönüştü. Bunun nedeni date objesinin toJSON metoduna sahip olmasıdır.

Eğer toJSON metodunu oda objesine uygularsanız:

let oda = {
  sayi: 23,
  toJSON() {
    return this.sayi;
  }
};

let toplanti = {
  baslik: "Konferans",
  oda
};

alert( JSON.stringify(oda) ); // 23

alert( JSON.stringify(toplanti) );
/*
  {
    "baslik":"Konferans",
    "oda": 23
  }
*/

Gördüğünüz gibi toJSON hem doğrudan çağrı için hem de iç içe objeler için kullanılabilir.

JSON.parse

JSON-karakterinin kodlamasını geri çevirmek için ( decode ), JSON.parse adında diğer bir metoda ihtiyaç vardır.

Yazımı:

let deger = JSON.parse(str[, alıcı]);
str
Çözülecek JSON metni.
alıcı
Opsiyonel function(anahtar,deger) ile her (anahtar,deger) çifti için çağrılacaktır. Bu değerler fonksiyon içerisinde değiştirilebilir.

Örneğin:

// metne çevrilmiş dizi
let sayilar = "[0, 1, 2, 3]";

sayilar = JSON.parse(sayilar);

alert( sayilar[1] ); // 1

İç içe objeler için:

let kullanici = '{ "adi": "Ahmet", "yasi": 35, "admin": false, "arkadaslar": [0,1,2,3] }';

kullanici = JSON.parse(kullanici);

alert( kullanici.arkadaslar[1] ); // 1

JSON gerektiği kadar karmaşık olabilir, içerisinde objeler diziler ve bu objelerin içerisinde objeler diziler olabilir. Tek yapması gereken formata uymaktır.

Aşağıda elle yazılan JSON’da en çok karşılaşılan hatalar sıralanmıştır. (Bazen test etme amaçlı elle JSON yazılabilir)

let json = `{
  adi: "Ahmet",                     // hata: İki tırnak olmadan anahtar yazmak
  "soyadi": 'Güngör',               // hata: Değerde tek tırnak kıllanılmıştır. Bu çift tırnak olmalı
  'admin': false                  // hata: Anahtar için tek tırnak kullanılmıştır.
  "dogumGunu": new Date(2000, 2, 3), // hata: "new" değeri kabul edilmez, sadece değer girilmelidir.
  "friends": [0,1,2,3]              // hata yok!
}`;

Bunun yanında JSON yorumlara izin vermez. Yorum eklenirse JSON çalışmaz hale gelir.

JSON5 adında farklı bir format bulunmaktadır. Bu format tırnaksız yazıma ve yorumlara izin vermektedir. Fakat bu ayrı bir kütüphanedir ve JSON’un şartnamesinde bulunmamaktadır.

JSON’un daha sıkı yazıma sahip olmasının nedeni geliştiricilerinin tembel olması vs değildir. Asıl amaç çok daha hızlı ayrıştırma algoritması uygulayabilmektir.

Alıcı kullanma

Diyelim ki sunucunuzda tanisma diye bir objeyi metin şeklinde tutuyorsunuz.

Aşağıdaki gibi görünecektir:

// baslik: (tanisma basligi), tarih: (tanisma tarihi)
let str = '{"baslik":"Konferans","tarih":"2017-11-30T12:00:00.000Z"}';

… Şimdi bunun tekrar obje haline getirilmesi gerekmektedir. ( deserialize )

JSON.parse kullanarak yapıldığından:

let str = '{"baslik":"Konferans","tarih":"2017-11-30T12:00:00.000Z"}';

let tanisma = JSON.parse(str);

alert( tanisma.date.getDate() ); // Hata!

HATA!

tanisma.tarih karakter dizisidir, tarih değil. JSON.parse bu karakter dizisini Date objesine çevireceğini nasıl bilebilir ?

Bunu Alıcı fonksiyon ile tüm değerler olduğu gibi alıp sadece tarih Date objesi olarak çevrilebilir.

let str = '{"baslik":"Konferans","tarih":"2017-11-30T12:00:00.000Z"}';

let tanisma = JSON.parse(str, function(anahtar, deger) {
  if (anahtar == 'tarih') return new Date(deger);
  return deger;
});

alert( tanisma.tarih.getDate() ); // Şimdi çalışıyor!

Bu iç içe objeler için de aynı şekilde çalışır:

let program = `{
  "tanismalar": [
    {"baslik":"Konferans","tarih":"2017-11-30T12:00:00.000Z"},
    {"baslik":"DogumGunu","tarih":"2017-04-18T12:00:00.000Z"}
  ]
}`;

program = JSON.parse(program, function(anahtar, deger) {
  if (anahtar == 'tarih') return new Date(deger);
  return deger;
});

alert( program.tanismalar[1].tarih.getDate() ); // çalışır!

Özet

  • JSON kendine ait standardı olan ve birçok programlama dilinde kütüphanesi olan bir veri formatıdır.
  • JSON basit objeleri, dizileri, karakterleri, sayıları, boolean değerleri ve null’u destekler.
  • JavaScript objeleri seri hale getirmek için JSON.stringify metodunu ve tekrar obje haline getirmek için JSON.parse metodunu sağlar.
  • Her iki metod da çevirilerde kendinize ait fonksiyonlar kullanmanıza olanak verir.
  • Eğer obje toJSON metoduna sahipse, JSON.stringify sırasında bu metod kullanılır.

Görevler

önem: 5

kullanici objesini JSON’a çevirin. Sonra başka bir değişkene JSON’u okuyup atama yapın.

let kullanici = {
  adi: "Ahmet Mutsuz",
  yas: 35
};
let kullanici = {
  adi: "Ahmet Mutsuz",
  yas: 35
};

let kullanici2 = JSON.parse(JSON.stringify(kullanici));
önem: 5

Basit dairesel referanslarda özelliğin ismi verilip seri hale getirilirken görmezden gelme işlemi yapılabilir.

Fakat bazen çok fazla dairesel referans olabilir. Ayrıca isimleri hem dairesel referanslarda hem de normal özelliklerde kullanılabilir.

degistirici fonksiyonu ile her şeyi karaktere çevirin, fakat tanisma’ya referans veren özellikleri silin:

let oda = {
  sayi: 23
};

let tanisma = {
  baslik: "Konferans",
  dolduruldu: [{adi: "Ahmet"}, {adi: "Mehmet"}],
  yer: oda
};


// dairesel referanslar
oda.dolduruldu = tanisma;
tanisma.self = tanisma;

alert( JSON.stringify(meetup, function degistirici(key, value) {
  /* Kodunuz */
}));

/* Sonuç şu şekilde olmalıdır:
{
  "baslik":"Konferans",
  "dolduruldu":[{"adi":"Ahmet"},{"adi":"Mehmet"}],
  "yer":{"sayi":23}
}
*/
let oda = {
  sayi: 23
};

let tanisma = {
  baslik: "Konferans",
  dolduruldu: [{adi: "Ahmet"}, {adi: "Mehmet"}],
  yer: oda
};

oda.dolduruldu = tanisma;
tanisma.self = tanisma;

alert( JSON.stringify(tanisma, function degistirici(anahtar, deger) {
  return (anahtar != "" && deger == tanisma) ? undefined : deger;
}));

/*
{
  "baslik":"Konferans",
  "dolduruldu":[{"adi":"Ahmet"},{"adi":"Mehmet"}],
  "yer":{"sayi":23}
}
*/

Burada anahtar =="" kontrolü de yapılmalı çünkü ilk çağrıda deger==tanisma’dır, ve anahtar boş gelir.

Eğitim haritası