Dergi konusundan proje pratiğine
İçeriğe Uygun Hizmet ve Teknik Sayfalar
„JSON in Delphi“ çözülmüş bir sorun gibi görünüyor: System.JSON mevcut, REST-Calls metin döndürüyor, iş tamam. Ancak pratikte gerçek hatalar JSON rahat bir string olarak değil, bir Stream olarak geldiğinde ortaya çıkar: HTTP-Response-Stream, Datei-Stream, Named Pipe, Message-Queue veya veritabanından büyük bir BLOB. O zaman günlük kullanımda sıkça hafife alınan üç konu bir araya gelir: bellek davranışı, karakter kodlaması (özellikle UTF-8) ve özel karakterlerle ilgili kenar durumlar.
Bu yazı, JSONun bir TStream kaynağından gereksiz kopyalar oluşturmadan ayrıştırmak için temiz ve hızlı bir yaklaşımı gösterir — ve özellikle Umlaut’ların „kaputt“ olduğu ya da parser’ların ara sıra anlaşılmaz hatalarla çıktığı tipik UTF-8 tuzaklarından kaçınır. Odak, işletim ve arayüz kararlılığı üzerindeki etkilerdedir: tekrarlanabilir hata ayıklama, yaklaşımın net sınırları ve çabanın gerçekten değip değmediğine dair kriterler.
Neden Delphi’de JSON ayrıştırırken Stream’ler farklı davranır
Bir JSON belgesi küçük olduğu sürece „Stream’i String’e okuyup sonra parse etmek“ pratiktir. Ancak belirli bir payload boyutunun ötesinde (tipik: büyük listeler, raporlar, sync-verileri, log dışa aktarımları) bu yöntem maliyetli hale gelir:
- Bellek kopyaları: Baytları bir tamponda okursun, bunları bir Unicode-String’e dönüştürürsün (Delphi-String = UTF-16), parser dahili olarak ek yapılar oluşturur. Bu kısa vadede birden fazla kopya anlamına gelebilir.
- GC/Heap yükü: Çok sayıda geçici String ve JSON değeri parçalanmayı ve tahsis overhead’ini artırır, özellikle uzun süre çalışan süreçlerde (Services, Worker, Import-Jobs).
- Hata tablosu belirsizleşir: Eğer okuma sırasında zaten yanlış encoding varsayımları yapılmışsa, JSON-Parser sadece „tuhaf karakterler“ ya da beklenmeyen kontrol baytları görür.
Önemli olan net bir ayrım: JSON resmi olarak Unicode’dur, iletimde neredeyse her zaman UTF-8‚dir. Delphi içsel olarak ise UTF-16 ile çalışır. Baytlardan (Stream) karakterlere (String) geçiş, özel karakter sorunlarının ortaya çıktığı yerdir — sorun JSON’un kendisinde değildir.
System.JSON: İyi yaptığı şeyler — ve nelere dikkat etmelisin
System.JSON, Delphi’de DOM tabanlı JSON için standarttır: bir nesne modeli (TJSONObject, TJSONArray) elde edersin, değerleri sorgulayabilir, yineleyebilir, serileştirebilirsin. Bu tipik iş entegrasyonları için sağlamdır, ancak iki sonucu vardır:
- Gerçek bir streaming-parser değildir: Nesne modeli tamamen kurulur. Belki ayrı bir String’e okuma adımını atlayarak tasarruf edersin, ama DOM yine de bellek açısından maliyetlidir.
- Parser girdisi genellikle metindir: Delphi versiyonuna ve kullanılan API’ye bağlı olarak hızla tekrar String seviyesine dönülür; bu dönüşüm sırasında encoding konversiyonları meydana gelebilir.
Eğer hedefin „hızlı Stream-Parser“ ise, pratikte genellikle iki şeyi kastedersin: (1) gereksiz kopyalardan kaçınmak ve (2) bozuk payload’larda mümkün olduğunca erken fail-fast uygulamak. Her ikisi de, baytlardan metne geçiş aşamasını kontrol ettiğin sürece System.JSON ile sağlanabilir.
Özel karakterlerde UTF-8 tuzakları: tipik nedenler
Eğer ünlü harfler (ä/ö/ü/ß) veya diğer özel karakterler çıktıda yanlış görünüyorsa (ä, – vb.), bu neredeyse her zaman bir kodlama uyuşmazlığıdır. Delphi ortamında bu nedenler özellikle sık görülür:
1) ANSI-Fallback durch „bequeme“ Helper
Bazı okuma yolları, açık bir kodlama verilmediğinde sessizce sistem-ANSI kodlamasını (Codepage des Windows-Systems) varsayar. Bu, bir payload sadece ASCII içermiyorsa fark edilir. Test verilerinde bu genellikle tesadüfen „okay“ görünür; üretimde gerçek isimler, yerler veya serbest metinlerle karşılaşıldığında sorun çıkar.
2) BOM-Verwirrung (Byte Order Mark)
UTF-8 BOM ile başlayabilir (Bytes EF BB BF). Web bağlamında BOM nadiren kullanılır; dosyalarda ise görülebilir. Bazı okuyucular BOM’u algılar ve kodlamayı buna göre ayarlar, bazıları ise algılamaz veya sadece belirli modlarda algılar. Eğer BOM normal bir karakter olarak stringe karışırsa, genellikle başta görünmez bir „Zero Width No-Break Space“ görürsün ya da JSON-Parser doğrudan ilk token’da başarısız olur.
3) Doppel-Konvertierung (UTF-8 wird „nochmal“ interpretiert)
Klasik hata görüntüsü „ä“ yerine „ä“ oluşur; bu, UTF-8 baytlarının önce doğru şekilde Unicode’a dekode edilip sonra tekrar ANSI/UTF-8 baytları olarak yanlış yorumlanması (veya tersinin) sonucu meydana gelir. Delphi içinde bu durum, TBytes, RawByteString ve string arasında belirsiz dönüşümler yapıldığında sıklıkla görülür.
4) Trunkierung mitten in einem Multibyte-Zeichen
UTF-8 özel karakterleri 2–4 byte ile kodlar. Eğer parça parça (ör. 8 KB) okuyor ve parça sınırı bir karakterin ortasına denk geliyorsa, çözücünün bunu düzgün tamponlaması gerekir. Her parçayı ayrı ayrı string’e çevirip sonra yapıştıran naif bir yaklaşım geçersiz diziler üretir. Bu, paket sınırlarına, proxy davranışına veya HTTP-Chunking’e bağlı olarak „ara sıra“ oluşan bir hata gibi görünebilir.
5) Falsche Annahmen aus HTTP-Headern
REST durumunda kaynak genellikle Content-Type: application/json; charset=utf-8 olur. Ancak bazı sunucular charset göndermiyor, bazıları yanlış bilgi veriyor. Başlığı körü körüne kullanırsan bu backend sürümüne göre değişebilir. İşletme ve destek için gerçek bayt akışını incelemek ve hata durumunda loglamak faydalıdır.
Ein sauberer Ansatz: Stream → UTF-8-Decoder → JSON-Parser
Sağlam pipeline üç net aşamadan oluşur:
- Akıştan baytları okumak (kontrollü, gerekirse HTTP-istemcisinde limit/zaman aşımı ile).
- Unicode’a dekode etmek açıkça UTF-8 ile (BOM isteğe bağlı olarak tolere edilebilir).
- Parsing System.JSON kullanarak bir nesne modeline parse etmek veya hedeflenmiş çıkarım yapmak.
En önemli kaldıraç 2. aşamadır: Hiçbir yerde „Default Encoding“in karar vermesini istemezsin. Delphi bağlamında bu demektir: TEncoding.UTF8’i açıkça set etmek ve örtük konversiyonlara güvenmemek.
Was „schnell“ hier konkret bedeutet
System.JSON ile DOM’u „wegoptimieren“ yapmayacaksın. Ancak şunlardan kaçınabilirsin:
- tüm payload’un ek bir kopyasını ara bir string olarak oluşturmayı, eğer dahili olarak zaten sadece birkaç değere ihtiyaç duyuyorsan (bu durumda daha çok başka bir Parser tercih edilir; buna sonra değineceğiz),
- birden fazla yeniden kodlama,
- ve çok büyük Payload’ları kontrollü şekilde okuyabilirsin (boyut sınırı ve net hata mesajı ile), Out-of-Memory veya Access Violations ile sonlanmak yerine.
Somut Randfall: Özel karakterler bozuk, ama sadece bazen
Pratikten bir kenar durum özellikle sinsi: Payload temel olarak geçerli bir UTF-8-JSON, ancak onu Chunk’lar halinde okuyor ve her Chunk’ı String’e dönüştürüyorsun. Sadece ASCII varsa hiçbir şey fark etmezsin. Bir Umlaut (örn. ö/ä/ü) tam olarak bir Chunk sınırında yer aldığında, geçersiz UTF-8 dizileri oluşur. Sonuç: ya bozuk karakterler ya da gerçek içeriğe uymayan bir yerde bir ayrıştırma hatası.
Bunu nasıl anlarsın:
- Ayrıştırma hataları „rastgele“ büyük yanıtlarda ortaya çıkar, küçüklerde değil.
- Aynı Request bazen çalışır bazen çalışmaz (Chunking/Transport’a bağlı olarak).
- Byte’ların bir Hex-Dökümü geçerli UTF-8 gösterir, ama logladığın String yerine geçen karakterler (�) veya klasik Mojibake içerir.
Çözüm „daha fazla readln“ veya „daha büyük Buffer“ kullanmak değil, multibyte dizilerini Chunk sınırlarının ötesinde doğru şekilde tamponlayan bir Decoder kullanmaktır. Tam da bu noktada TStreamReader ile bir UTF-8-Encoding kombinasyonu yardımcı olabilir – tabii onu doğru şekilde initialisierst.
Praxisleitfaden: UTF-8 in Delphi reproduzierbar prüfen
Parser ile oynamadan önce gerçek byte akışını görünür kılan bir Debugging-Setup gerekir. Destek ve Betrieb için bu çok değerli, çünkü sonrasında karşı tarafın yanlış veri gönderip göndermediğini ya da pipeline’ının yanlış decode ettiğini net olarak söyleyebilirsin.
1) Die ersten Bytes prüfen (BOM, JSON-Start)
JSON BOM ile geliyorsa stream başlangıcında EF BB BF görürsün. Hemen ardından tipik olarak „{“ veya „[“ gelmelidir. Eğer string’de zaten „“ görünüyorsa, BOM BOM olarak ele alınmamış, bunun yerine metin olarak decodiert edilmiş demektir.
2) Rohbytes in Hex loggen – aber begrenzt
Üretimde tam Payload’ları loglama (Datenschutz, Kosten, Log-Volumen). Aşağılar işe yarar:
- Önek (örneğin ilk 256 veya 1024 Bytes),
- Sonek (son 256 Bytes),
- ve karşılaştırma için bir Hash (SHA-256), eğer Payload’ları kıyaslaman gerekiyorsa.
Böylece özel karakter sorunlarını çoğunlukla dakikalar içinde sınıflandırabilirsin: „ä“ için byte dizisi doğru mu (C3 A4)? Bir kesilme (Trunkierung) var mı? Beklenmeyen bir 0x00 (Nullbyte) ortaya çıkıyor mu, örneğin UTF-16 yanlış varsayımı nedeniyle?
3) Content-Type und Charset mitloggen
Bei HTTP/REST: Logge den Content-Type und das deklarierte charset. Wenn die Bytes klar UTF-8 sind, aber charset etwas anderes behauptet, solltest du im Client nicht blind folgen. Für JSON ist UTF-8 der De-facto-Standard. Im Zweifel: Byte-Analyse gewinnt gegen Header.
Schneller Stream-Parser mit System.JSON: Design ohne unnötige Kopien
Pratikte kullanılan bir desen şudur: Stream’den byte’ları bir byte-belleğe oku, bu veriden tam olarak bir kez UTF-8 ile bir String oluştur ve bu String’i JSON-Parser’a ver. Bu SAX anlamındaki „Streaming“ değil, ama beklenmedik encoding-değişimlerine karşı kontrollü ve performanslı bir pipeline sağlar.
Mimari açısından önemli: Fonksiyonu şu üç konuda merkezi olarak karar verecek şekilde tasarla:
- Hangi Encoding-kuralı uygulanacak (genellikle UTF-8, BOM opsiyonel)?
- Payload maksimum ne kadar olabilir (DoS-Schutz, işletme sınırı)?
- Hata mesajları nasıl görünecek (bağlamla ama veri sızıntısı olmadan)?
Einlese-Strategie: Begrenztes Puffern statt „StreamToString“ ohne Limit
JSON’i dış kaynaklardan (partnerler, mobil istemciler, üçüncü taraflar) kabul ediyorsan, bir boyut limiti zorunludur. Limit yoksa tek bir kötü niyetli veya hatalı istek servisi Memory Pressure altına alabilir. Pratikte bu şu demektir: okuma sırasında toplam byte sayısını tut ve bir sınırı geçince işlemi durdur — monitoring’in anlayacağı açık bir Exception ile.
Warum ich bei UTF-8 oft „ohne BOM“ erwarte, aber BOM tolerieren würde
Bei REST-Payloads kommt BOM selten vor. Bei Dateien (Exports, manuelle Bearbeitung) dagegen öfter. Für robuste Importstrecken ist es sinnvoll, BOM zu tolerieren, aber im Log sichtbar zu machen, weil es ein Hinweis auf „Datei-Welt“ statt „API-Welt“ sein kann.
Die stillen Killer: TStreamReader-Defaults und Text-Reader im Mischbetrieb
TStreamReader ist bequem, aber du musst zwei Dinge sauber im Griff haben:
- Encoding explizit setzen: Nicht hoffen, dass er es „erkennt“.
- Buffering verstehen: Der Reader puffert intern. Wenn du denselben Stream später nochmal woanders liest, ist die Position relevant. Das klingt trivial, wird aber in größeren Importpipelines schnell eine Fehlerquelle.
Besonders unangenehm ist Mischbetrieb: Erst ein Teil als Bytes lesen (z. B. für Logging oder Magic-Bytes), dann mit TStreamReader weiter. Wenn du dabei nicht sauber zurückspulst oder die Reader-Initialisierung an der richtigen Position machst, liest du ab Byte 257 statt ab 0. Der JSON-Parser meldet dann „Invalid character at position …“, obwohl die Payload an sich korrekt ist.
Wenn Sonderzeichen trotz UTF-8 „kaputt“ bleiben: Escaping vs. echtes Unicode
JSON kann Sonderzeichen auf zwei Arten enthalten:
- Als echte UTF-8-Zeichen (z. B. „München“ als Bytes C3 BC …).
- Kaçış dizisi olarak (örn. „Mu00fcnchen“).
Her ikisi de geçerlidir. Pratikte önemli olan şudur: kaçış dizileri birçok taşıma sorununu aşar, ancak kodlama hatalarını yalnızca görünürde gizler. Sistem herhangi bir yerde byte’ları yanlış yorumluyorsa, bu sadece kozmetik bir hata değil işletme riski demektir. Ayrıca kaçış dizileri, ekipler “okunabilir metin” bekliyorsa logging/monitoring sırasında kafa karışıklığına yol açabilir.
System.JSON her iki durumda da sonunda normal Delphi-String’ler (UTF-16) sağlar, tabii ki buraya kadar olan yol doğruysa.
Performansı gerçekçi değerlendirme: DOM maliyeti, büyük diziler ve seçicilik
En büyük performans kazancı çoğu zaman „Parser’i daha hızlı yapmak“ değil, daha az ayrıştırmaktır. System.JSON ile bu zor, çünkü DOM elde ediyorsun. Üç tipik durum:
- Büyük diziler (10.000+ öğe): DOM oluşturmak zaman ve RAM gerektirir. Eğer her öğe için sadece 2 alan gerekiyorsa, streaming destekli bir ayrıştırıcı (SAX/Tokenizer) genellikle daha uygundur. System.JSON bunun için tasarlanmamıştır.
- Tek nesneler içinde çok sayıda alan: Sadece birkaç alana ihtiyacın varsa yine DOM kullanabilirsin, ancak birden fazla kez dolaşmaktan kaçın. Değerleri bir kerede al ve kendi yapılarına map et.
- Arka arkaya gelen birden çok büyük payload: Import job’larda veya sync worker’larda ayrıştırmayı net bir adıma kapsüllemek ve her belge sonrası tüm referansları serbest bırakmak, böylece memory manager’ın temizlik yapabilmesini sağlamak faydalıdır. Bu basit bir şeydir, ancak servislerde genelde „yan iş“ olarak yapılır.
Bu nedenle System.JSON ile bir „hızlı stream-ayrıştırıcı“ genellikle iyi bir uzlaşıdır: Okuma verimli ve doğru olsun, DOM bilinçli kullanılsın, sınırlar tanımlansın. Eğer gerçek streaming semantiğe ihtiyacın varsa (örn. tümünü tutmadan dizi öğelerini ardışık işlemek), System.JSON doğru temel değildir.
İşletmede dayanıklılık: hata türleri ve bunları hemen erişilebilir kılma
JSON ayrıştırma hataları loglarda sıkça yetersiz bilgi verir çünkü genellikle sadece bir pozisyon belirtir. İşletme ve destek için bağlama ihtiyacın var:
- Byte-konumu vs. karakter-konumu: UTF-8’de bunlar aynı değildir. Ayrıştırıcı bir karakter konumu bildiriyorsa bayt konumu farklı olabilir. Bayt dökümleri için bayt konumu belirleyicidir.
- Hata noktasının çevresinden kesit: Hata durumunda pozisyon etrafında küçük bir pencereyi (örn. önce/sonra 40 karakter) logla, ancak içinde hassas veri yoksa. Alternatif olarak: yalnızca onaltılık (hex) olarak logla.
- Korelasyon: Request-ID, Endpoint, Partner-ID, Payload-Hash. Aksi takdirde „o tek hatayı“ bir daha asla bulamazsın.
Hedef, üretim ortamında bir olaydan sonra birkaç dakika içinde şu cevabı verebilmen olmalı: “Encoding yanlış yorumlandı”, “Payload kesildi”, “Sunucu geçersiz JSON döndürüyor” veya “bir mapping sorunumuz var”.
UTF-8 tuzaklarını hedefli olarak önleme: Kontrol listesi
- Kodlamayı her zaman açıkça belirt: Stream’den okurken ve loglara/dosyalara yazarken varsayılanlara güvenme.
- Chunk’tan string’e dönüşüm yapma: Eğer chunk’lı okuyorsan, byte’ları topla veya çok baytlı dizileri tamponlayan bir decoder kullan.
- BOM’a toleranslı ama görünür: Kabul et, fakat hata ayıklamada tespit edilebilsin.
- Limitler belirle: Maks. payload boyutu, maks. nesne/dizi derinliği (eğer kontrol edebiliyorsan), HTTP-Client’ta timeouts.
Bir akış-ayrıştırıcı için çaba gerçekten ne zaman değer?
Her JSON noktasını optimize etmen gerekmez. Bu yaklaşım genellikle aşağıdakilerden en az biri geçerliyse faydalıdır:
- Büyük yükler (birkaç MB) düzenli olarak ortaya çıkıyor veya ortaya çıkabilir.
- Uzun çalışan süreçler (Service, Worker) birçok yük işliyor ve bellek sıçramaları veya parçalanma gözlüyorsun.
- Heterojen sistemlerle birlikte çalışmak: birden fazla partner, farklı platformlar, zaman zaman hatalı kodlamalar.
- Olay geçmişi: Daha önce “bozuk Ümlaute”, ara sıra oluşan ayrıştırma hataları veya zor tekrar üretilebilen içe aktarma kesintileri oldu.
Yüklerin küçükse ve kontrollü bir kaynaktan geliyorsa genellikle basit bir yol yeterlidir – ama yine de: UTF-8’i açıkça ayarlamak neredeyse hiç maliyet getirmez ve ileride sürprizleri önler.
Sınırlandırma: Gerçekten akış gerekip gerekmediği
System.JSON DOM-odaklıdır. Verileri gerçekten “okurken” işlemek istiyorsan, örneğin büyük bir diziyi tamamen tutmadan eleman eleman işlemek istiyorsan, başka bir ayrıştırıcı yaklaşımına (Tokenizer/SAX) ihtiyacın olur. Bu bir değer yargısı değil, bir mimari karardır:
- DOM (System.JSON): kullanışlı, tipik iş nesneleri için iyi, ama bellek açısından maliyetli.
- Streaming/SAX: düşük bellek, çok büyük veriler için iyi, ama daha fazla uygulama çabası ve daha dikkatli hata yönetimi gerektirir.
Mantıklı bir uzlaşma genellikle şudur: akış yönetimi, kodlama ve limitleri düzgün inşa et, sonra DOM’un hala uygun olup olmadığına karar ver. Birçok projede buna odaklanmak bile işletmeyi belirgin şekilde istikrara kavuşturur.
Sonuç: JSON in Delphi güvenilir olur, eğer kodlama ve akışları ayrı bir katman olarak ele alırsan
“JSON in Delphi” ile ilgili çoğu sorun JSON ayrıştırıcısının kendisinden değil, öncesindeki görünmez aşamadan kaynaklanır: akıştan gelen baytlar metne dönüştürülür. Orada UTF-8’i açıkça ele alır, BOM vakalarını tespit eder, parça sınırlarını ihmal etmez ve net boyut limitleri koyarsan tipik özel karakter hataları kaybolur – ve rastgele ortaya çıkan ayrıştırma problemleri tekrarlanabilir hale gelir.
System.JSON yine de pragmatik bir standart olarak kalır: En hızlı akış ayrıştırıcı değil, ama girdiyi kontrol ettiğinde ve DOM maliyetlerini bilinçli olarak kabul ettiğinde sağlamdır. İstersen, somut içe aktarma-/REST-yolunu birlikte gözden geçirebiliriz ve kodlama veya Chunking’in sorun çıkardığı noktayı tespit edebiliriz: İletişime geç.
Bu konu için JSON akış ayrıştırıcıları da önemlidir. Yazı bu yönleri anlaşılır şekilde konumlandırıyor ve gündelik kullanımda nelere dikkat edilmesi gerektiğini gösteriyor.
Sonraki adım
Konu gerçek bir projeye dönüştüğünde, mimari, mevcut sistemler ve işletme erken dönemde birlikte değerlendirilmelidir.
Bireysel sorularda destek vermekle kalmıyoruz; kaynak kodu parçacıklarından, legacy konularından veya portal fikirlerinden sağlam bir kurumsal projeye dönüşene kadar da destek veriyoruz.
- Mevcut durum, hedef durum ve teknik riskler birlikte değerlendirilir.
- REST, veri erişimi, portallar ve Rollout daha sonra ortaya çıkan sonuçlar olarak ertelenmez.
- Hangi yolun ekonomik ve işletme açısından sürdürülebilir olduğunu erken görürsünüz.