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.
| Python | JSON | Geri dönüşte |
|---|---|---|
| dict | object { } | dict |
| list, tuple | array [ ] | list (demet listeye döner!) |
| str | string | str |
| int, float | number | int / float |
| True / False / None | true / false / null | True / False / None |
| datetime, Decimal, set, dataclass | desteklenmez | default= 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=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)
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())
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ş: 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)
| Parametre | Nerede | Ne işe yarar |
|---|---|---|
ensure_ascii=False | dumps | Türkçe karakterleri olduğu gibi yazar |
indent=2, sort_keys=True | dumps | Okunur ve kararlı çıktı (git, imza) |
separators=(",", ":") | dumps | En küçük çıktı (ağ, önbellek (cache) anahtarı) |
allow_nan=False | dumps | NaN/Infinity yazmaya çalışınca ValueError |
default=fonk | dumps | Desteklenmeyen tipi çevirir (date, Decimal) |
parse_float=Decimal | loads | Ondalıkları kayıpsız okur |
object_hook=fonk | loads | Her sözlüğü dönüştürür (tarih, dataclass) |
delimiter=";", quotechar | csv | Türk Excel'i ve özel biçimler |
quoting=csv.QUOTE_ALL | csv yazma | Her hücreyi tırnağa alır |
extrasaction="ignore", restval="" | DictWriter | Fazla anahtarı atla, eksik alanı doldur |
restkey, restval | DictReader | Fazla/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"))
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())
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
=, +, -, @ ile başlayan hücreler kaçırılır ve JSON çıktılar sort_keys=True ile imzalanır.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).
defusedxml kullan.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.