Ana içeriğe geç
Kesit Python
Orta · Standart kütüphane

JSON ve CSV: veri alışverişinin iki ortak dili

json ile dumps/loads, Türkçe karakter ve tarih gibi özel tipleri yönetir; csv ile reader/DictReader/DictWriter kullanır; iç içe veriyi düz tabloya çevirirsin.

Uygulamada aç, 25 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

Neden önemli?

Anadolu Market'in sipariş API'si JSON döner; muhasebe ekibi ise Excel'de açacağı CSV ister. Ilgaz Bank'ın eski sistemi CSV üretir, yeni mikroservisler JSON konuşur. Bir veri analisti ya da geliştirici olarak işinin önemli bir kısmı bu iki biçim arasında güvenle dönüşüm yapmaktır. İkisi de "düz metin" olduğu için basit görünür; ama Türkçe karakter, tarih, ondalık sayı ve iç içe yapılar en çok hatanın çıktığı yerlerdir.

JSON (JavaScript Object Notation)

JSON'da altı tip vardır: nesne (object, Python'da dict), dizi (array, list), metin (str), sayı (int/float), boolean (true/false) ve null (None). Demet, küme, tarih ve Decimal gibi tipler JSON'da yoktur; onları sen dönüştürürsün.

PythonJSONGeri dönüşte
dictobject { }dict
list, tuplearray [ ]list (demet listeye döner!)
strstringstr
int, floatnumberint / float
True / False / Nonetrue / false / nullTrue / False / None
datetime, Decimal, set, dataclassdesteklenmezdefault= ile çevir

dumps ve loads

dumps (dump string) nesneyi JSON metnine, loads metni nesneye çevirir. Dosyayla çalışırken dump(obj, f) ve load(f) kullanılır.

import json

siparis = {
    "no": "S-1001", "musteri": "Ayşe Yılmaz", "sehir": "İzmir",
    "kalemler": [{"urun": "laptop", "adet": 1, "fiyat": 25999.9}, {"urun": "mouse", "adet": 2, "fiyat": 349.0}],
    "odendi": True, "kupon": None,
}
print(json.dumps(siparis))                      # Türkçe harfler \u ile kaçırıldı
print(json.dumps(siparis, ensure_ascii=False))  # okunur Türkçe
print(json.dumps({"b": 1, "a": 2}, indent=2, sort_keys=True))

geri = json.loads('{"no": "S-1", "tutar": 120.5, "etiketler": ["hızlı", "kargo"], "iptal": null}')
print(geri["tutar"] + 1, geri["iptal"], type(geri["etiketler"]).__name__)
ensure_ascii=FalseVarsayılan ensure_ascii=True, "İzmir"i "\u0130zmir" yapar. Geçerli JSON'dur ama insan okuyamaz ve dosya büyür. Türkçe veri yazarken hep ensure_ascii=False ve dosyayı encoding="utf-8" ile aç.

default: desteklenmeyen tipleri çevirmek

json.dumps tanımadığı bir nesneyle karşılaşınca TypeError verir. default=fonksiyon parametresi bu durumda çağrılır; sen tipi JSON'un bildiği bir şeye çevirirsin.

import json
from datetime import date, datetime
from decimal import Decimal
from dataclasses import dataclass, asdict

@dataclass
class Islem:
    hesap: str
    tutar: Decimal
    tarih: date

def json_uyumlu(o):
    if isinstance(o, (date, datetime)):
        return o.isoformat()
    if isinstance(o, Decimal):
        return str(o)            # para için float'a çevirme, hassasiyet kaybolur
    if isinstance(o, set):
        return sorted(o)
    if hasattr(o, "__dataclass_fields__"):
        return asdict(o)
    raise TypeError(f"JSON'a çevrilemiyor: {type(o).__name__}")

kayit = {"islem": Islem("TR01", Decimal("1250.75"), date(2026, 3, 15)), "etiketler": {"eft", "acil"}}
print(json.dumps(kayit, default=json_uyumlu, ensure_ascii=False, indent=2))

try:
    json.dumps({"t": date.today()})
except TypeError as e:
    print("default olmadan:", e)

Okurken hata yakalama

import json

ham = ['{"a": 1}', "{'a': 1}", '{"a": 1,}', '']
for h in ham:
    try:
        print(repr(h), "→", json.loads(h))
    except json.JSONDecodeError as e:
        print(repr(h), "→ HATA:", e.msg, "konum", e.pos)
JSON Python değildirTek tırnak, sondaki virgül, yorum satırı ve True (büyük T) JSON'da geçersizdir. Elle JSON yazma; json.dumps ürettirsin.

CSV (Comma-Separated Values)

CSV, her satırın bir kayıt ve alanların virgülle (Türkiye'de Excel çoğu zaman noktalı virgülle) ayrıldığı düz metindir. "İstanbul, Avrupa" gibi virgül içeren değer tırnak içine alınır; bu yüzden split(",") ile ayrıştırmak yanlıştır, csv modülü tırnak kurallarını bilir.

Derste dosya yerine io.StringIO kullanıyoruz: bellekte bir "dosya gibi" nesne. Gerçek dosyada tek fark open("x.csv", newline="", encoding="utf-8") yazmaktır.

import csv, io

ham = """no,musteri,sehir,tutar
S-1,Ayşe Yılmaz,İzmir,1250.50
S-2,"Mehmet, Ali",Ankara,80
S-3,Zeynep Kaya,"İstanbul, Avrupa",349.90
"""
# reader: liste listesi
for satir in csv.reader(io.StringIO(ham)):
    print(satir)

# DictReader: başlıkları anahtar yapar
toplam = 0.0
for kayit in csv.DictReader(io.StringIO(ham)):
    toplam += float(kayit["tutar"])    # CSV'de her şey metindir!
    print(kayit["musteri"], "-", kayit["sehir"])
print("Toplam:", round(toplam, 2))

print("Yanlış yol:", 'S-2,"Mehmet, Ali",Ankara,80'.split(","))

Yazma: writer ve DictWriter

import csv, io

kayitlar = [
    {"hesap": "TR01", "tutar": 1250.5, "aciklama": "Kira, Mart"},
    {"hesap": "TR02", "tutar": -80, "aciklama": "Kart ödemesi"},
]
cikti = io.StringIO()
yazici = csv.DictWriter(cikti, fieldnames=["hesap", "tutar", "aciklama"], delimiter=";")
yazici.writeheader()
yazici.writerows(kayitlar)
print(cikti.getvalue())

# Noktalı virgüllü Türkçe Excel CSV'sini geri okuma
for k in csv.DictReader(io.StringIO(cikti.getvalue()), delimiter=";"):
    print(k)

# Satır listesi için writer
c2 = io.StringIO()
w = csv.writer(c2)
w.writerow(["ad", "not"])
w.writerow(["Ali", 'Dedi ki: "tamam"'])      # tırnak otomatik kaçırılır
print(c2.getvalue())
newline="" neden?Gerçek dosya açarken open(..., newline="") ver; aksi halde Windows'ta her kayıt arasına boş satır girer. Excel'in Türkçe ayarları için delimiter=";" ve ondalık virgül gerekebilir; dosya başına BOM için encoding="utf-8-sig" kullanılır.

İç içe veriyi düzleştirme

JSON doğal olarak iç içedir (sipariş → kalemler → ürün). CSV ise düzdür. Dönüşüm için iki yaygın strateji var: (1) iç sözlükleri "adres.sehir" gibi noktalı anahtarlara aç; (2) listeleri her eleman bir satır olacak şekilde "patlat" (explode). İkisini birlikte gösterelim.

import csv, io, json

ham = json.loads("""[
  {"no": "S-1", "musteri": {"ad": "Ayşe", "sehir": "İzmir"},
   "kalemler": [{"urun": "laptop", "adet": 1}, {"urun": "mouse", "adet": 2}]},
  {"no": "S-2", "musteri": {"ad": "Ali", "sehir": "Ankara"},
   "kalemler": [{"urun": "çanta", "adet": 1}]}
]""")

def duzlestir(d,):
    """İç içe sözlüğü noktalı anahtarlarla tek seviyeye indir (listeleri olduğu gibi bırak)."""
    duz = {}
    for k, v in d.items():
        ad = f"{onek}{k}"
        if isinstance(v, dict):
            duz.update(duzlestir(v, ad + "."))
        else:
            duz[ad] = v
    return duz

satirlar = []
for s in ham:
    temel = duzlestir({k: v for k, v in s.items() if k != "kalemler"})
    for kalem in s["kalemler"]:            # her kalem ayrı satır
        satirlar.append({**temel, **kalem})

print(satirlar[0])
cikti = io.StringIO()
w = csv.DictWriter(cikti, fieldnames=list(satirlar[0].keys()))
w.writeheader(); w.writerows(satirlar)
print(cikti.getvalue())
Derinleş

Derinleş: JSON ve CSV'nin ince ayarları

Temel okuma-yazmayı öğrendin. Gerçek işte ise farklar ayrıntıda çıkar: muhasebe ondalık virgüllü CSV gönderir, API para tutarını float olarak yollar, log sistemi her satıra bir JSON yazar, Excel bir hücreyi formül sanıp çalıştırır. Bu bölümde json ve csv modüllerinin az bilinen ama işte çok kullanılan parametrelerini görüyoruz.

Okurken tip kontrolü: parse_float ve object_hook

json.loads varsayılan olarak her ondalıklı sayıyı float yapar. Para için bu kabul edilemez; parse_float=Decimal verirsen sayı metni doğrudan Decimal'e çevrilir, hiç float'a uğramaz. object_hook ise her JSON nesnesi (sözlük) oluştuğunda çağrılır; tarih alanlarını date'e çevirmek ya da sözlüğü bir dataclass'a dönüştürmek için idealdir.

import json
from decimal import Decimal
from datetime import date

ham = '{"hesap": "TR01", "tutar": 0.1, "islem_tarih": "2026-03-15", "detay": {"ucret": 0.2}}'

print(json.loads(ham)["tutar"] + 0.2)          # float hatası
d = json.loads(ham, parse_float=Decimal)
print(d["tutar"] + d["detay"]["ucret"])         # tam 0.3

def tarih_coz(nesne):
    for k, v in nesne.items():
        if k.endswith("_tarih") and isinstance(v, str):
            nesne[k] = date.fromisoformat(v)
    return nesne

k = json.loads(ham, object_hook=tarih_coz)
print(type(k["islem_tarih"]).__name__, k["islem_tarih"].strftime("%d.%m.%Y"))

Yazarken boyut ve kararlılık: separators, sort_keys, allow_nan

API'ye gönderilen JSON'da her bayt önemliyse separators=(",", ":") boşlukları siler. sort_keys=True aynı veriden her zaman aynı metni üretir; imza (hash) hesaplarken ve git farklarında şarttır. float("nan") standart JSON değildir ama Python onu varsayılan olarak NaN diye yazar; karşı taraftaki JavaScript ya da Java ayrıştırıcısı patlar. allow_nan=False bu durumu kaynağında yakalar.

import json, hashlib

veri = {"tutar": 120.5, "no": "S-1", "etiket": ["hızlı"]}
kompakt = json.dumps(veri, separators=(",", ":"), sort_keys=True, ensure_ascii=False)
print(kompakt)
print("imza:", hashlib.sha256(kompakt.encode("utf-8")).hexdigest()[:16])

print(json.dumps({1: "bir", 2: "iki"}))         # int anahtarlar metne döner!
try:
    json.dumps({"oran": float("nan")}, allow_nan=False)
except ValueError as e:
    print("Yakalandı:", e)
ParametreNeredeNe işe yarar
ensure_ascii=FalsedumpsTürkçe karakterleri olduğu gibi yazar
indent=2, sort_keys=TruedumpsOkunur ve kararlı çıktı (git, imza)
separators=(",", ":")dumpsEn küçük çıktı (ağ, önbellek (cache) anahtarı)
allow_nan=FalsedumpsNaN/Infinity yazmaya çalışınca ValueError
default=fonkdumpsDesteklenmeyen tipi çevirir (date, Decimal)
parse_float=DecimalloadsOndalıkları kayıpsız okur
object_hook=fonkloadsHer sözlüğü dönüştürür (tarih, dataclass)
delimiter=";", quotecharcsvTürk Excel'i ve özel biçimler
quoting=csv.QUOTE_ALLcsv yazmaHer hücreyi tırnağa alır
extrasaction="ignore", restval=""DictWriterFazla anahtarı atla, eksik alanı doldur
restkey, restvalDictReaderFazla/eksik hücreli satırları yönet

JSON Lines: satır başına bir kayıt

Log ve veri hattı dosyaları genelde .jsonl biçimindedir. Avantajı: dosyanın tamamını belleğe almadan satır satır işlersin ve bozuk tek satır tüm dosyayı çöpe atmaz. Bozuk satırı atlayıp numarasını raporlamak iyi pratiktir.

import json

jsonl = """{"no": "S-1", "tutar": 120}
{"no": "S-2", "tutar": 80}
{"no": "S-3", "tutar":
{"no": "S-4", "tutar": 40}
"""
toplam, bozuk = 0, []
for no, satir in enumerate(jsonl.splitlines(), start=1):
    if not satir.strip():
        continue
    try:
        toplam += json.loads(satir)["tutar"]
    except json.JSONDecodeError:
        bozuk.append(no)
print("toplam:", toplam, "bozuk satırlar:", bozuk)

# Yazmak da kolay: her kayda bir dumps + "\n"
print("".join(json.dumps(k, ensure_ascii=False) + "\n" for k in [{"şehir": "İzmir"}, {"şehir": "Van"}]), end="")

CSV'nin kenar durumları

Gerçek CSV'ler temiz gelmez: bazı satırlarda hücre eksik, bazılarında fazla; ayırıcı bilinmiyor; sayılar 1.250,50 biçiminde. csv.Sniffer bir örnekten ayırıcıyı tahmin eder; DictReader eksik hücreye restval (varsayılan None), fazla hücrelere restkey anahtarını verir.

import csv, io
from decimal import Decimal

ham = "no;sube;tutar\nS-1;Kadıköy;1.250,50\nS-2;Çankaya\nS-3;Konak;80,00;FAZLA\n"

lehce = csv.Sniffer().sniff(ham.splitlines()[0] + "\n" + ham.splitlines()[1])
print("ayırıcı:", repr(lehce.delimiter))

def tr_sayi(metin):
    return Decimal(metin.replace(".", "").replace(",", "."))

for k in csv.DictReader(io.StringIO(ham), delimiter=lehce.delimiter, restval="0", restkey="ekstra"):
    print(k["no"], k["sube"], tr_sayi(k["tutar"]), k.get("ekstra"))
Sniffer bir tahmindirAyırıcıyı bildiğin her durumda açıkça delimiter ver. Sniffer yalnızca kullanıcıların farklı biçimde dosya yüklediği arayüzlerde (ör. "CSV yükle" düğmesi) işe yarar; tahmin başarısız olursa csv.Error fırlatır, onu yakala.

DictWriter: fazla ve eksik anahtarlar

Kayıt sözlüğünde fieldnames'te olmayan bir anahtar varsa DictWriter varsayılan olarak ValueError fırlatır. Bu bilinçli bir güvenliktir: yanlışlıkla gizli alanı (ör. parola özeti) dışa aktarmazsın. İstediğin sütunları seçiyorsan extrasaction="ignore", eksik alanları boş yazmak için restval="" ver.

import csv, io

musteriler = [
    {"ad": "Ayşe", "sehir": "İzmir", "parola_ozeti": "a1b2"},
    {"ad": "Can", "telefon": "0555"},
]
c = io.StringIO()
w = csv.DictWriter(c, fieldnames=["ad", "sehir", "telefon"], extrasaction="ignore", restval="-", quoting=csv.QUOTE_ALL)
w.writeheader()
w.writerows(musteriler)
print(c.getvalue())
Büyük tablo işi pandas'taSütun tipi çıkarımı, eksik değer ve milyonlarca satırlık CSV için pandas.read_csv daha uygundur; bunu pandas-giris dersinde göreceksin. Dosya kodlaması ve atomik yazma için dosya-ileri, ayar dosyaları için yapilandirma dersine bak. csv modülü ise bağımlılıksız, akış hâlinde ve tam kontrollü olduğu için betiklerde hâlâ ilk tercihtir.

Komut satırında JSON'u güzelleştirmek için Python yeterli: python -m json.tool siparis.json girintili yazdırır, geçersiz JSON'da satır ve sütun numarasıyla hata verir. Büyük dosyada --compact ve --sort-keys seçenekleri de vardır.

python -m json.tool siparis.json            # girintili göster, geçersizse hata konumu
python -m json.tool --sort-keys a.json > b.json
python -m json.tool --json-lines log.jsonl  # her satırı ayrı doğrula
Gerçek işteIlgaz Bank'ın entegrasyon ekibi dış kurumlardan gelen CSV'leri önce bir "giriş kontrolü" fonksiyonundan geçirir: ayırıcı ve başlık doğru mu, zorunlu sütunlar var mı, tutarlar Decimal'e çevrilebiliyor mu? Geçemeyen satırlar ayrı bir hata dosyasına satır numarasıyla yazılır, geçenler işlenir. Dışa verilen CSV'lerde ise formül enjeksiyonuna karşı =, +, -, @ ile başlayan hücreler kaçırılır ve JSON çıktılar sort_keys=True ile imzalanır.
Derinleş

XML ve ElementTree

Kurumsal entegrasyonlarda JSON kadar XML de karşına çıkar: e-Fatura, banka dosyaları, eski servisler. Python'ın standart kütüphanesi okur ve yazar, paket gerekmez.

import xml.etree.ElementTree as ET

kok = ET.fromstring('<Siparis no="7"><Kalem adet="2">Çay</Kalem><Kalem adet="1">Kahve</Kalem></Siparis>')
print(kok.tag, kok.get("no"))
for k in kok.findall("Kalem"):
    print(k.text, k.get("adet"))

.text etiketin içindeki metni, .get() özniteliği verir. Her şey metindir; sayıya kendin çevirirsin, para için Decimal kullan.

Ad alanları (namespace)

e-Fatura gibi belgelerde her etiketin bir ad alanı vardır. findall("InvoiceLine") burada boş döner ve hata vermez; en sık yapılan hata budur. Ön ek sözlüğü vererek ararsın: kok.findall("cac:InvoiceLine", NS).

Kurgu örnekGörevdeki fatura UBL biçimine benzetilmiş kurgu bir örnektir, geçerli bir e-Fatura değildir. Gerçek UBL-TR belgesi çok daha geniştir; şeması için Gelir İdaresi Başkanlığı kılavuzlarına bak. Güvenilmeyen kaynaktan gelen XML için defusedxml kullan.
Uzmanlaş

Büyük dosyalar, şema ve güvenlik

json.load tüm dosyayı belleğe alır; yüz megabaytlık bir dışa aktarımda bu sorun olabilir. Çözüm genellikle JSON Lines (her satırda bir JSON nesnesi, .jsonl) biçimidir: satır satır okur, her satıra json.loads uygularsın; log sistemleri ve veri hatları bu biçimi sever. CSV'de ise csv.reader zaten tembeldir; dosyayı satır satır tüketir.

Şema (schema): CSV'de her alan metindir; "tutar" sütununun sayı olduğunu sen bilirsin. Üretim kodunda okuduğun her satırı bir dataclass'a ya da pydantic modeline geçirip tip dönüşümünü tek yerde yapmak, hataları (boş hücre, "1.250,50" gibi Türk biçimi) erkenden yakalar. Para için float yerine Decimal kullan; JSON'a yazarken str olarak çık.

Güvenlik: json.loads güvenlidir (kod çalıştırmaz) ama pickle ve eval değildir; dış kaynaktan veri alırken asla kullanma. CSV'de "CSV enjeksiyonu" diye bir şey var: =HYPERLINK(...) ile başlayan hücre Excel'de formül olarak çalışır. Kullanıcı girdisini CSV'ye yazarken =, +, -, @ ile başlayan hücrelerin önüne tek tırnak koymak yaygın önlemdir. Son olarak, iki sistem arasında veri taşırken sort_keys=True ve sabit indent ile üretilen JSON, git farklarını okunur tutar.

Alıştırmalara geç (25)