Ruby'de JSON ile çalışmak için önce require "json" yazarsınız. Metni Ruby nesnesine çevirmek için JSON.parse, Ruby nesnesini JSON metnine çevirmek için JSON.generate ya da nesnenin to_json metodu kullanılır. Okunabilir, girintili çıktı için JSON.pretty_generate vardır. Anahtarların metin yerine sembol olmasını istiyorsanız JSON.parse(metin, symbolize_names: true) yazarsınız; geçersiz girdide JSON::ParserError fırlatılır.

json kütüphanesi Ruby ile birlikte gelen bir varsayılan gem'dir; Ruby 4.0 NEWS dosyasına göre bu sürümle json 2.18.0 geliyor ve bizim denememizde JSON::VERSION değeri "2.18.0" döndü. Aşağıdaki örnekleri Ruby 4.0.9 ile çalıştırdık. Ruby 4.0'da hash'lerin inspect çıktısının {ad: "Ayşe"} ve {"ad" => "Ayşe"} biçiminde göründüğünü unutmayın. Dilin geneli için Ruby öğrenme rehberi sayfasına bakabilirsiniz.

JSON ve Ruby türlerinin karşılıkları

JSON ve Ruby türlerinin karşılıkları: nesne {"a": 1}, dizi [1, 2], metin "abc", sayı 1 ve 1.5
JSON değeriJSON.parse sonucuJSON.generate girdisi
nesne {"a": 1}HashHash
dizi [1, 2]ArrayArray
metin "abc"StringString, Symbol metne çevrilir
sayı 1 ve 1.5Integer ve FloatInteger ve Float
true, falsetrue, falsetrue, false
nullnilnil

Bu dört temel türün dışında kalan nesneler, örneğin Time, varsayılan olarak metin temsilleriyle yazılır. Geri okurken yine metin olarak gelirler; JSON tarih türü tanımaz.

JSON.parse ile metni Ruby nesnesine çevirmek

ruby
require "json"

kaynak = '{"ad": "Ayşe", "yas": 30, "aktif": true, "etiketler": ["ruby", "json"], "not": null}'
veri = JSON.parse(kaynak)
veri
# => {"ad" => "Ayşe", "yas" => 30, "aktif" => true, "etiketler" => ["ruby", "json"], "not" => nil}
veri["ad"]  # => "Ayşe"
veri.class  # => Hash

JSON.parse("42")      # => 42
JSON.parse('"metin"') # => "metin"
JSON.parse("2.0e2")   # => 200.0

Kaynak bir JSON nesnesiyse sonuç Hash, diziyse Array olur; tek başına bir sayı ya da metin de geçerli JSON sayılır. Belge, JSON.parse! adında bazı kontrolleri atlayan bir sürüm de olduğunu, bunun yalnızca güvenilir veri için kullanılması gerektiğini belirtiyor.

symbolize_names: anahtarları sembol yapmak

Varsayılan olarak anahtarlar metin olarak gelir. symbolize_names: true verdiğinizde tüm iç içe hash'lerde anahtarlar sembole çevrilir. Böylece veri[:ad] biçiminde erişebilir ya da sonucu doğrudan anahtar kelime argümanlarına aktarabilirsiniz:

ruby
s = JSON.parse(kaynak, symbolize_names: true)
s
# => {ad: "Ayşe", yas: 30, aktif: true, etiketler: ["ruby", "json"], not: nil}
s[:etiketler] # => ["ruby", "json"]

JSON.parse('{"a": {"b": [1, 2]}}').dig("a", "b", 1) # => 2

Sembol ve metin farkı Ruby Symbol ve String farkı yazısında, dig ve diğer hash metotları Ruby Hash metotları yazısında.

JSON.generate ve to_json ile JSON üretmek

JSON.generate ve to_json ile JSON üretmek

JSON.generate en kısa çıktıyı üretir: tek satır, boşluksuz. require "json" sonrasında temel sınıflara eklenen to_json metodu da aynı sonucu verir. Sembol anahtarlar ve sembol değerler metne çevrilir, nil değeri null olur:

ruby
siparis = {no: 1024, urun: "Çay", adet: 3, fiyat: 12.5, teslim: nil, etiket: :acil}
JSON.generate(siparis)
# => "{\"no\":1024,\"urun\":\"Çay\",\"adet\":3,\"fiyat\":12.5,\"teslim\":null,\"etiket\":\"acil\"}"

siparis.to_json == JSON.generate(siparis) # => true
[1, "iki", nil, true].to_json             # => "[1,\"iki\",null,true]"
"Merhaba".to_json                         # => "\"Merhaba\""

Türkçe karakterler varsayılan olarak olduğu gibi yazılır. ASCII dışındaki her karakteri kaçışlı yazmak isterseniz ascii_only: true seçeneğini kullanın:

ruby
JSON.generate("ğüş", ascii_only: true) # => "\"\\u011f\\u00fc\\u015f\""

pretty_generate ile okunabilir çıktı

JSON.pretty_generate, iki boşluk girinti, iki noktadan sonra bir boşluk ve her öğeden sonra yeni satır kullanır. Yapılandırma dosyası yazarken ya da hata ayıklarken işinize yarar:

ruby
puts JSON.pretty_generate(siparis)
# çıktı:
# {
#   "no": 1024,
#   "urun": "Çay",
#   "adet": 3,
#   "fiyat": 12.5,
#   "teslim": null,
#   "etiket": "acil"
# }

Daha ince ayar için JSON.generate metoduna indent, space, space_before, object_nl ve array_nl seçeneklerini verebilirsiniz.

Kendi sınıfınız için to_json

Kendi sınıfınızdan bir nesneyi doğrudan JSON.generate metoduna verirseniz, temel türlerden biri olmadığı için metin temsili yazılır. Anlamlı bir çıktı için sınıfa to_json(*args) tanımlayıp işi bir hash'e devretmek yaygın bir yoldur. JSON belgesindeki özel ekleme örneği de aynı imzayı kullanır; argümanları aktarmak pretty_generate gibi biçimlendirme seçeneklerinin iç nesnelere ulaşmasını sağlar:

ruby
class Urun
  def initialize(ad, fiyat)
    @ad = ad
    @fiyat = fiyat
  end

  def to_h = {ad: @ad, fiyat: @fiyat}

  def to_json(*args) = to_h.to_json(*args)
end

urunler = [Urun.new("Kalem", 15), Urun.new("Defter", 40)]
JSON.generate(urunler)
# => "[{\"ad\":\"Kalem\",\"fiyat\":15},{\"ad\":\"Defter\",\"fiyat\":40}]"

puts JSON.pretty_generate(urunler)
# çıktı:
# [
#   {
#     "ad": "Kalem",
#     "fiyat": 15
#   },
#   {
#     "ad": "Defter",
#     "fiyat": 40
#   }
# ]

Sabit alanlı veri nesneleri için Ruby Struct ve Data yazısındaki to_h yaklaşımı da işinizi görür.

JSON dosyası okumak ve yazmak

JSON.load_file(yol, seçenekler), belgeye göre JSON.parse(File.read(yol), seçenekler) çağrısının kısa yoludur. JSON.dump ise ikinci argüman olarak bir IO nesnesi alırsa JSON metnini doğrudan oraya yazar:

ruby
ayar = {sunucu: "127.0.0.1", port: 8080, moduller: ["log", "cache"]}
File.write("ayar.json", JSON.pretty_generate(ayar))

geri = JSON.load_file("ayar.json", symbolize_names: true)
geri         # => {sunucu: "127.0.0.1", port: 8080, moduller: ["log", "cache"]}
geri == ayar # => true

File.open("ayar2.json", "w") { |f| JSON.dump(ayar, f) }
File.read("ayar2.json")
# => "{\"sunucu\":\"127.0.0.1\",\"port\":8080,\"moduller\":[\"log\",\"cache\"]}"

Dosya modları ve kodlama ayrıntıları için Ruby dosya ve IO işlemleri yazısına bakın.

Ayrıştırma hataları ve JSON::ParserError

Geçersiz bir girdi JSON::ParserError fırlatır. Bu sınıf JSON::JSONError üzerinden StandardError sınıfından türediği için sade bir rescue da yakalar, ama hatayı adıyla yakalamak daha açıktır. Mesajda satır ve sütun bilgisi bulunur:

ruby
["{\"a\": 1,}", "", "[NaN]", "{'a': 1}"].each do |bozuk|
  JSON.parse(bozuk)
rescue JSON::ParserError => e
  puts "#{e.class}: #{e.message}"
end
# çıktı: JSON::ParserError: expected object key, got: '}' at line 1 column 9
# çıktı: JSON::ParserError: unexpected end of input at line 1 column 1
# çıktı: JSON::ParserError: unexpected token 'NaN]' at line 1 column 2
# çıktı: JSON::ParserError: expected object key, got ''a':' at line 1 column 2

JSON::ParserError.ancestors.take(4)
# => [JSON::ParserError, JSON::JSONError, StandardError, Exception]

Sondaki virgül, boş metin, NaN ve tek tırnaklı anahtar JSON'da geçersizdir. Dış kaynaktan (örneğin bir API yanıtından) gelen veriyi her zaman rescue JSON::ParserError ile korumanızı öneririz. Rescue sözdizimi için Ruby begin, rescue, ensure yazısına bakın.

Üretim tarafında da hatalar vardır: NaN gibi JSON'da karşılığı olmayan değerler JSON::GeneratorError, izin verilen derinliği aşan iç içe yapılar JSON::NestingError verir:

ruby
begin
  JSON.generate(Float::NAN)
rescue JSON::GeneratorError => e
  puts "#{e.class}: #{e.message}"
end
# çıktı: JSON::GeneratorError: NaN not allowed in JSON

begin
  JSON.parse("[[[1]]]", max_nesting: 2)
rescue JSON::NestingError => e
  puts "#{e.class}: #{e.message}"
end
# çıktı: JSON::NestingError: nesting of 3 is too deep

Pratik örnek: dışarıdan gelen bir gövdeyi güvenle okumak

Gerçek projelerde JSON çoğunlukla bir HTTP isteğinin gövdesinden ya da bir kuyruk mesajından gelir. Hem bozuk JSON'u hem de eksik alanları ayrı ayrı ele almak, hataların nedenini anlamayı kolaylaştırır. Aşağıdaki metot zorunlu alan için Hash#fetch, isteğe bağlı alan için varsayılan değer kullanıyor:

ruby
def kullanici_oku(govde)
  veri = JSON.parse(govde, symbolize_names: true)
  {ad: veri.fetch(:ad), eposta: veri[:eposta] || "yok"}
rescue JSON::ParserError
  {hata: "geçersiz JSON"}
rescue KeyError => e
  {hata: e.message}
end

kullanici_oku('{"ad": "Mehmet", "eposta": "m@ornek.com"}') # => {ad: "Mehmet", eposta: "m@ornek.com"}
kullanici_oku('{"ad": "Zeynep"}')            # => {ad: "Zeynep", eposta: "yok"}
kullanici_oku('{"eposta": "x@ornek.com"}')   # => {hata: "key not found: :ad"}
kullanici_oku("<html>")                      # => {hata: "geçersiz JSON"}

Metot gövdesinde doğrudan rescue yazmak, ayrı bir begin bloğuna gerek bırakmaz. Hata mesajını kullanıcıya göstermeden önce içinde hassas veri olmadığından emin olun.

Sık kullanılan ayrıştırma seçenekleri

Sık kullanılan ayrıştırma seçenekleri: symbolize_names, freeze, max_nesting, allow_nan
SeçenekVarsayılanEtkisi
symbolize_namesfalseHash anahtarlarını sembol yapar
freezefalseDönen nesneleri dondurur
max_nesting100İzin verilen en fazla iç içe derinlik, false ile kapanır
allow_nanfalseNaN, Infinity ve -Infinity kabul edilir
allow_trailing_commafalseSondaki virgüle izin verir
allow_control_charactersfalseMetin içinde kaçışsız kontrol karakterlerine izin verir
object_class, array_classHash, ArrayNesne ve diziler için kullanılacak sınıf
allow_duplicate_keybelirtilmemişYinelenen anahtarda uyarı, true ile sessiz, false ile hata
ruby
JSON.parse("[1,]", allow_trailing_comma: true) # => [1]
JSON.parse('{"a": 1}', freeze: true).frozen?    # => true

Yinelenen anahtarlar

Belgeye göre seçenek verilmezse yinelenen anahtarda son değer kullanılır ve kullanımdan kaldırma uyarısı basılır; json 3.0 ile varsayılan davranış hata vermek olacak. Ruby kullanımdan kaldırma uyarılarını varsayılan olarak göstermediği için denememizde uyarıyı Warning[:deprecated] = true ile açtık:

ruby
Warning[:deprecated] = true
JSON.parse('{"a": 1, "a": 2}') # => {"a" => 2}
# stderr: warning: detected duplicate key "a" in JSON object. This will raise an error
#         in json 3.0 unless enabled via `allow_duplicate_key: true` at line 1 column 1

JSON.parse('{"a": 1, "a": 2}', allow_duplicate_key: false)
# JSON::ParserError: duplicate key "a" at line 1 column 1

JSON.load ve güvenlik

Belge, JSON.load metodunun yalnızca kendi veritabanınız gibi güvendiğiniz kaynaklar için düşünüldüğünü, güvenilmeyen kullanıcı verisini buna vermenin tehlikeli olabileceğini açıkça belirtiyor. JSON eklentileri (additions) özelliği de yalnızca güvenilir veriyle kullanılmalı ve kullanımdan kaldırılmış durumda. Dışarıdan gelen veri için JSON.parse tercih edin.

Ruby 4.0 ve json 3.0 yolunda dikkat edilecekler

Ruby 4.0 NEWS dosyası, json varsayılan gem'inin 3.4 ile gelen 2.9.1 sürümünden 2.18.0 sürümüne güncellendiğini listeliyor. Kütüphane belgesi ise ileride gelecek json 3.0 için iki önemli değişikliği şimdiden duyuruyor: yinelenen anahtarlar varsayılan olarak hata verecek ve JSON.load metodunda create_additions varsayılan olarak kapalı olacak. Belgeye göre json 2.8.0 sürümünden beri load, create_additions açıkça etkinleştirilmeden yerel olmayan bir tür üretildiğinde kullanımdan kaldırma uyarısı veriyor. Kodunuzu bugünden JSON.parse ve açık seçeneklerle yazarsanız bu geçiş sizi etkilemez.

Sık Sorulan Sorular

require "json" yazmak zorunlu mu?

Evet. json, Ruby ile gelen bir kütüphane ama çekirdeğin parçası değil; JSON modülünü ve to_json metotlarını kullanmadan önce yüklemeniz gerekir.

Anahtarları metin mi sembol mü almalıyım?

İkisi de çalışır. Veriyi koda yakın, sabit adlı alanlarla kullanıyorsanız symbolize_names: true okunabilirliği artırır. Anahtarlar dışarıdan geliyor ve sayısı belirsizse metin anahtarlar daha güvenli bir varsayılandır.

JSON.generate ile to_json arasında fark var mı?

Temel türlerde sonuç aynıdır. Kendi sınıflarınız için to_json tanımladığınızda JSON.generate da bu metodu kullanır.

CSV verisini JSON'a nasıl çeviririm?

Önce CSV satırlarını hash dizisine dönüştürüp ardından JSON.generate uygularsınız. Format kuralları ve dönüşüm mantığı için CSV formatı ve JSON dönüşümü yazısına bakabilirsiniz.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Ruby öğrenme rehberi · Ruby Hash metotları · Ruby dosya ve IO işlemleri · Ruby hata yönetimi · CSV ve JSON dönüşümü · Symbol ve String farkı