Net-Base Magazín

13.08.2026

JSON in Delphi: Rýchly streamový parser s System.JSON + úskalia UTF-8 pri špeciálnych znakoch

Keď JSON-Payloads v Delphi prichádzajú priamo zo Streamu, zo „rýchleho parsovania“ sa rýchlo stane produkčný problém: vysoké nároky na pamäť, sporadické chyby pri parsovaní a poškodené diakritické znaky. Tento praktický príspevok ukazuje, ako pomocou System.JSON vybudovať rýchly streamový parser...

13.08.2026

Od témy magazínu k projektovej praxi

Súvisiace stránky služieb a technológií k príspevku

„JSON v Delphi“ znie ako vyriešený problém: System.JSON je na palube, REST-volania vracajú text, hotovo. V praxi sa však skutočné chyby objavujú tam, kde JSON nie je k dispozícii ako pohodlný reťazec, ale ako stream: HTTP-Response-Stream, Datei-Stream, Named Pipe, Message-Queue alebo veľký BLOB v databáze. Potom sa skombinujú tri aspekty, ktoré sa v bežnej prevádzke často podceňujú: správanie pamäte, kódovanie znakov (najmä UTF-8) a hraničné prípady súvisiace so špeciálnymi znakmi.

Tento článok ukazuje čistý, rýchly prístup na parsovanie JSON z TStream bez zbytočných kópií — a predovšetkým bez typických pascí UTF-8, pri ktorých sa prehlásenia diakritiky „poškodia“ alebo parsery náhodne zlyhávajú s kryptickými chybami. Zameranie je na dopady pre prevádzku a stabilitu rozhraní: reprodukovateľné ladenie, jasné hranice prístupu a kritériá, kedy sa námaha skutočne oplatí.

Prečo sa streamy pri JSON-parsovaní v Delphi správajú inak

Pokým je JSON-dokument malý, cesta „načítať stream do reťazca a potom parse“ je pohodlná. Pri určitej veľkosti payloadu (typicky: veľké zoznamy, reporty, sync-dáta, exporty logov) sa to však stáva drahým:

  • Duplikáty v pamäti: Načítaš byty do bufferu, prevedieš ich na Unicode-reťazec (Delphi-String = UTF-16), parser si interné štruktúry vytvorí ďalej. To krátkodobo znamená viacnásobné kópie.
  • Zaťaženie GC/haldy: Množstvo dočasných reťazcov a JSON-hodnôt zvyšuje fragmentáciu a overhead alokácií, obzvlášť v dlhodobo bežiacich procesoch (servisy, workery, import-joby).
  • Chybový obraz je nejasný: Ak sa pri načítaní spravia nesprávne predpoklady o kódovaní, JSON-parser vidí len „divné znaky“ alebo neočakávané riadiace byty.

Podstatné je jasné oddelenie: JSON je formálne Unicode, na linke je takmer vždy UTF-8. Delphi však pracuje interne s UTF-16. Prechod od bytov (stream) k znakom (String) je miesto, kde vznikajú problémy so špeciálnymi znakmi — nie v samotnom JSON-e.

System.JSON: Čo vie dobre — a kde musíš dávať pozor

System.JSON je v Delphi štandard pre DOM‑založené JSON: dostaneš objektový model (TJSONObject, TJSONArray), môžeš čítať hodnoty, iterovať, serializovať. To je robustné pre typické business‑integrácie, ale má to dve dôsledky:

  • Nie je to skutočný streaming‑parser: Objektový model sa postaví celý. Aj keď ušetríš prípadné načítanie do samostatného reťazca, DOM zostáva náročný na pamäť.
  • Vstup parsera je zvyčajne text: Podľa verzie Delphi a použitej API sa rýchlo vraciaš k reťazcu vrátane konverzie kódovania.

Ak je tvoj cieľ „rýchly stream‑parser“, v praxi zvyčajne myslíš jedno z dvoch: (1) žiadne zbytočné kópie a (2) čo najskorší fail‑fast pri poškodených payloadoch. Obe veci sú s System.JSON dosiahnuteľné, pokiaľ kontroluješ krok od bytov k textu.

Pasce UTF-8 pri špeciálnych znakoch: typické príčiny

Abstrakte Grafik zeigt UTF-8-Mehrbytezeichen über Chunk-Grenzen und korrekte Pufferung im Decoder vor dem JSON-Parsing
Chunking je neškodný – pokiaľ UTF-8 dekodér vyrovnáva viacbajtové sekvencie cez hranice.

Ak diakritické znaky (ä/ö/ü/ß) alebo iné špeciálne znaky vo výsledku vyzerajú nesprávne (ä, – atď.), ide takmer vždy o nesúlad kódovania. V Delphi-prostredí sú tieto príčiny obzvlášť časté:

1) ANSI-Fallback durch „bequeme“ Helper

Niektoré spôsoby čítania implicitne používajú systémové ANSI-kódovanie (codepage systému Windows), ak nie je zadané explicitné kódovanie. To sa prejaví až vtedy, keď payload obsahuje niečo iné než ASCII. V testovacích dátach je to často náhodne „v poriadku“, v produkcii to zlyhá pri skutočných menách, miestach a voľnom texte.

2) BOM-Verwirrung (Byte Order Mark)

UTF-8 môže začínať BOM (bajty EF BB BF). V webovom kontexte je BOM skôr neobvyklý, pri súboroch sa však vyskytuje. Niektoré readery rozpoznajú BOM a prispôsobia kódovanie, iné nie, alebo len v určitých režimoch. Ak sa BOM dostane do reťazca ako bežný znak, často vidíš na začiatku neviditeľný „Zero Width No-Break Space“ alebo JSON-Parser zlyhá hneď na prvom tokene.

3) Doppel-Konvertierung (UTF-8 wird „nochmal“ interpretiert)

Klasický problém „ä“ namiesto „ä“ vzniká, keď UTF-8-bajty sú najprv správne dekódované do Unicode, ale neskôr sú znovu nesprávne interpretované ako ANSI/UTF-8-bajty (alebo naopak). V Delphi sa to často stáva, keď sa nejasne konvertuje medzi TBytes, RawByteString a string.

4) Trunkierung mitten in einem Multibyte-Zeichen

UTF-8 kóduje špeciálne znaky do 2–4 bajtov. Ak čítaš po chunkoch (napr. 8 KB) a hranica chunka padne do stredu znaku, dekodér musí toto správne bufferovať. Naivný prístup, ktorý každý chunk samostatne prekonvertuje na string a ich následne spojí, vytvorí neplatné sekvencie. Môže sa to prejavovať ako „sporadická“ chyba, závislá od hraníc paketov, správania proxy alebo HTTP-chunkingu.

5) Falsche Annahmen aus HTTP-Headern

Pri REST je zdroj často Content-Type: application/json; charset=utf-8. Niektoré servery však neposielajú charset, iné uvádzajú nesprávne informácie. Ak slepo použiješ hlavičku, môže sa to meniť v závislosti od verzie backendu. Pre prevádzku a support je užitočné skontrolovať skutočný bytestream a v prípade chyby ho zalogovať.

Ein sauberer Ansatz: Stream → UTF-8-Decoder → JSON-Parser

Robustná pipeline sa skladá z troch jasných fáz:

  1. Byty zo streamu čítať (kontrolovane, prípadne s limitom/timeoutom v HTTP-klientovi).
  2. Dekódovanie na Unicode s explicitným UTF-8 (BOM voliteľne tolerovať).
  3. Parsovanie pomocou System.JSON do objektového modelu alebo do cielenej extrakcie.

Najdôležitejší páč je fáza 2: nechceš, aby niekde rozhodoval „Default Encoding“. V Delphi to znamená: explicitne nastaviť TEncoding.UTF8 a nespoliehať sa na implicitné konverzie.

Čo tu konkrétne znamená „rýchlo“

S System.JSON nebudeš DOM „odoptimalizovávať“. Ale môžeš sa vyhnúť:

  • dodatočnej kópii celej payload ako medzireťazca, ak interne potrebuješ aj tak len niekoľko hodnôt (v tom prípade sa viac oplatí iný parser; viac neskôr),
  • opakovanému prekódovávaniu,
  • a môžeš veľmi veľké payloady načítať kontrolovane (s limitom veľkosti a jasnou chybovou správou) namiesto toho, aby si skončil pri Out-of-Memory alebo Access Violations.

Konkrétny okrajový prípad: diakritika poškodená, ale len občas

Jeden z praktických okrajových prípadov je obzvlášť zákerý: payload je v zásade platné UTF-8 JSON, ale čítaš ho v chunk-och a pre každý chunk konvertuješ na String. Pokiaľ sa vyskytuje len ASCII, nič si nevšimneš. Hneď ako stojí prehláska (Umlaut) presne na hranici chunku, vzniknú neplatné UTF-8 sekvencie. Výsledok: buď poškodené znaky, alebo chyba pri parse na mieste, ktoré s obsahom nesúvisí.

Ako to spoznáš:

  • chyby pri parse sa vyskytujú „náhodne“ pri veľkých odpovediach, nie pri malých,
  • ten istý request niekedy prejde, niekedy nie (podľa chunkingu/transportu),
  • hexdump bytov ukáže platné UTF-8, ale tvoj zalogovaný String obsahuje replacement characters (�) alebo klasické mojibake.

Riešenie nie je „viac readln“ alebo „väčšie buffre“, ale použiť decoder, ktorý dokáže viacbajtové sekvencie správne bufferovať cez hranice chunkov. Presne tu môže byť užitočný TStreamReader v kombinácii s kódovaním UTF-8 – pokiaľ ho inicializuješ správne.

Praxisleitfaden: UTF-8 v Delphi reprodukovateľne overiť

Debugging-Setup mit Byte-Dump-Ausdrucken und Arbeitsmaterial, um UTF-8-Bytes und BOM in JSON-Payloads zu prüfen
Pre čisté debugovanie je rozhodujúci najskôr byte-stream: BOM, trunkovanie a neplatné sekvencie sú tak rýchlo viditeľné.

Skôr než začneš upravovať parser, potrebuješ debugging-setup, ktorý ti sprístupní skutočnú postupnosť bytov. Pre support a prevádzku má to obrovskú hodnotu, pretože neskôr jasne povieš, či protistrana dodáva chybné dáta alebo či tvoja pipeline zle dekóduje.

1) Skontrolovať prvé byty (BOM, začiatok JSON)

Ak JSON prichádza s BOM, na začiatku streamu uvidíš EF BB BF. Ihneď za tým by typicky malo nasledovať „{“ alebo „[“. Ak sa v reťazci už nachádza „“, BOM nebol spracovaný ako BOM, ale bol dekódovaný ako text.

2) Logovať surové byty v hex – ale obmedzene

Neloguj kompletné payloady v produkcii (ochrana údajov, náklady, objem logov). Osvedčené postupy sú:

  • prefix (napr. prvých 256 alebo 1024 bytov),
  • suffix (posledných 256 bytov),
  • a hash (SHA-256) na koreláciu, ak potrebuješ payloady porovnávať.

Týmto často dokážeš problémy so špeciálnymi znakmi za pár minút roztriediť: je postupnosť bytov pre „ä“ správna (C3 A4)? Ide o trunkovanie (skrátenie)? Objaví sa neočakávané 0x00 (nulový bajt), napr. kvôli mylnej interpretácii ako UTF-16?

3) Content-Type und Charset mitloggen

Pri HTTP/REST: zaznamenaj Content-Type a deklarované charset. Ak sú bajty jednoznačne UTF-8, ale charset tvrdí niečo iné, klient by tomu nemal slepo veriť. Pre JSON je UTF-8 de-facto štandard. V pochybnostiach: analýza bajtov má prednosť pred headerom.

Rýchly Stream-Parser s System.JSON: návrh bez zbytočných kópií

Schemahafte Darstellung einer Pipeline aus Stream, UTF-8-Decoding, Größenlimit und JSON-DOM-Parsing in Delphi
Jasná pipeline s limitom a explicitným UTF-8 striktne oddelí transport, dekódovanie a Parsing.

Praktický pattern je: čítaš zo streamu do byte-puffra, z toho vytvoríš reťazec presne raz s UTF-8 a tento reťazec odovzdáš JSON-parseru. Nie je to „Streaming“ v zmysle SAX, ale ide o kontrolovanú, výkonnú pipeline bez prekvapivých zmien kódovania.

Dôležité pre architektúru: Navrhni funkciu tak, aby na jednom mieste centrálne rozhodovala:

  • Aké pravidlo kódovania platí (vo väčšine prípadov UTF-8, BOM voliteľný)?
  • Aká môže byť maximálna veľkosť payloadu (ochrana proti DoS, prevádzkový limit)?
  • Ako majú vyzerať chybové hlásenia (s kontextom, ale bez úniku dát)?

Stratégia čítania: obmedzené bufferovanie namiesto „StreamToString“ bez limitu

Ak prijímaš JSON z externých zdrojov (partneri, mobilní klienti, tretie strany), je limit veľkosti povinný. Bez limitu môže jediný nešťastný request priviesť službu do Memory Pressure. Prakticky to znamená: pri čítaní sčítavaj počet bajtov a pri dosiahnutí hranice preruši čítanie – s jasnou výnimkou, ktorá je v monitoringu zrozumiteľná.

Prečo často očakávam UTF-8 „bez BOM“, ale BOM by som toleroval

Pri REST-payloadoch sa BOM vyskytuje zriedka. Pri súboroch (exporty, manuálna úprava) sa vyskytuje častejšie. Pre robustné importné toky je rozumné BOM tolerovať, ale zaznamenať ho v logu, pretože to môže indikovať „súborový svet“ namiesto „API-sveta“.

Tiché zabijaci: TStreamReader-Defaults und Text-Reader im Mischbetrieb

TStreamReader je pohodlný, ale musíš mať dve veci pevne pod kontrolou:

  • Explicitne nastaviť kódovanie: Nedomnievaj sa, že ho automaticky „rozpozná“.
  • Pochopiť buffering: Reader buffruje interné dáta. Ak ten istý stream neskôr čítaš niekde inde, je dôležitá aktuálna pozícia. Znie to triviálne, ale v rozsiahlych importných pipeline sa to rýchlo stane zdrojom chýb.

Zvlášť nepríjemný je zmiešaný režim: najprv prečítaš časť ako byty (napr. pre logovanie alebo magic-bytes), potom pokračuješ s TStreamReader. Ak pri tom nespravne nevrátiš pozíciu alebo neinicializuješ reader na správnom mieste, budeš čítať od bytu 257 namiesto od 0. JSON-parser potom nahlási „Invalid character at position …“, hoci payload ako taký je korektný.

Keď špeciálne znaky napriek UTF-8 zostanú „poškodené“: Escaping vs. skutočný Unicode

JSON môže obsahovať špeciálne znaky dvoma spôsobmi:

  • Ako skutočné UTF-8 znaky (napr. „München“ ako bajty C3 BC …).
  • Ako escape sekvencia (napr. „Mu00fcnchen“).

Obidve sú platné. Pre prax je dôležité: escape sekvencie obchádzajú mnoho problémov pri prenose, ale len navonok zakrývajú chyby kódovania. Ak tvoj systém niekde nesprávne interpretuje bajty, je to prevádzkové riziko, nie len kosmetický bug. Navyše môžu escape sekvencie pri logovaní/monitorovaní miasť, ak tímy očakávajú „čitateľný text“.

System.JSON ti v oboch prípadoch nakoniec vráti normálne Delphi reťazce (UTF-16), ak bola cesta až tam korektná.

Výkon realisticky zhodnoťte: náklady DOM, veľké polia a selektivita

Najväčší pákový efekt pre výkon často nie je „urobiť parser rýchlejším“, ale menej parsovať. So System.JSON je to ťažké, pretože dostaneš DOM. Tri typické situácie:

  • Veľké polia (10 000+ prvkov): Vytvorenie DOM stojí čas a RAM. Ak potrebuješ len 2 polia na prvok, často je vhodnejší streamovací parser (SAX/Tokenizer). System.JSON nie je na to určený.
  • Jednotlivé objekty s mnohými poľami: Ak potrebuješ len niekoľko polí, môžeš DOM stále použiť, ale vyhýbaj sa opakovanému prechádzaniu. Získaj hodnoty raz a namapuj ich do svojich štruktúr.
  • Viacero veľkých payloadov za sebou: V importných joboch alebo sync-workeroch sa oplatí zabaliť parsovanie do jasného kroku a po každom dokumente uvoľniť všetky referencie, aby mohol memory manager upratať. Je to triviálne, ale v službách sa to často robí „bokom“.

Preto je „rýchly stream-parser“ so System.JSON často dobrým kompromisom: načítanie efektívne a korektne, vedomé používanie DOM, definovanie hraníc. Ak potrebuješ skutočnú streamingovú sémantiku (napr. spracovanie prvkov poľa postupne bez udržania všetkého v pamäti), System.JSON nie je správnou bázou.

Robustnosť v prevádzke: chybové scenáre a ako ich okamžite spracovateľne zachytiť

Chyby pri parsovaní JSON sú v logoch často nepomocné, pretože uvádzajú len pozíciu. Pre prevádzku a support potrebuješ kontext:

  • Bajtová pozícia vs. znaková pozícia: Pri UTF-8 to nie je totožné. Ak parser nahlási znakovu pozíciu, môže sa bajtová pozícia líšiť. Pre bajtové dumpy je rozhodujúca bajtová pozícia.
  • Ukážka okolo miesta chyby: Pri chybe loguj malé okno okolo pozície (napr. 40 znakov pred/za), ale len ak neobsahuje citlivé údaje. Alternatívne: loguj len hexadecimálne.
  • Korelácia: Request-ID, Endpoint, Partner-ID, Payload-Hash. Inak toho „jediného“ chybu nikdy znova nenájdeš.

Cieľom je, aby si po produkčnom incidente dokázal do niekoľkých minút odpovedať: „Encoding falsch interpretiert“, „Payload abgeschnitten“, „Server liefert invalid JSON“ alebo „wir haben ein Mapping-Problem“.

Vyvarovať sa pascí UTF-8 cielene: kontrolný zoznam

  • Kódovanie vždy explicitne: Pri čítaní zo streamu a pri zápise do logov/súborov sa nespoliehaj na predvolené nastavenia.
  • Žiadna konverzia chunk→string: Ak čítaš po častiach, zbieraj bajty alebo používaj decoder, ktorý pufruje multibajtové sekvencie.
  • BOM tolerantný, ale viditeľný: Akceptovať, ale v debugovaní ho vedieť rozpoznať.
  • Nastavte limity: max. veľkosť payloadu, max. hĺbka objektu/pola (ak to môžeš kontrolovať), time-outy v HTTP-klientovi.
  • Oddelené zodpovednosti: oddelene zapuzdriť „čítanie transportu“ a „parsovanie JSON“. Tak sa chyby ladia rýchlejšie a parser je možné neskôr vymeniť.
  • Kedy sa naozaj oplatí investovať do streamového parsera?

    Nie je potrebné optimalizovať každé miesto s JSON. Tento prístup sa typicky oplatí, ak platí aspoň jedno z nasledujúceho:

    • Veľké payloady (niekoľko MB) sa vyskytujú pravidelne alebo môžu nastať.
    • Dlhodobo bežiace procesy (Service, Worker) spracovávajú veľa payloadov a pozoruješ výrazné výkyvy využitia pamäte alebo fragmentáciu.
    • Interop s heterogénnymi systémami: viacerí partneri, rôzne platformy, občas chybné kódovanie.
    • História incidentov: už sa vyskytli „poškodené prehlasované znaky“, sporadické chyby parsovania alebo ťažko reprodukovateľné prerušenia importu.

    Ak sú tvoje payloady malé a pochádzajú z kontrolovaného zdroja, často stačí jednoduché riešenie – ale aj vtedy: explicitné nastavenie UTF-8 takmer nič nestojí a zabráni neskorším prekvapeniam.

    Rozlíšenie: Kedy potrebuješ skutočné streamovanie

    System.JSON je orientované na DOM. Ak chceš dáta skutočne spracovávať „priebežne“, napr. veľké pole element po elemente bez držania celého obsahu v pamäti, potrebuješ iný prístup k parseru (Tokenizer/SAX). To nie je hodnotenie, ale architektonické rozhodnutie:

    • DOM (System.JSON): pohodlné, vhodné pre typické business-objekty, ale náročné na pamäť.
    • Streaming/SAX: nízka náročnosť na pamäť, vhodné pre veľmi veľké dáta, ale väčšia implementačná námaha a opatrnejšie spracovanie chýb.

    Často rozumný kompromis je: dôsledne navrhnúť spracovanie streamu, kódovanie a limity, a až potom rozhodnúť, či DOM stále vyhovuje. V mnohých projektoch to už samo o sebe výrazne stabilizuje prevádzku.

    Záver: JSON in Delphi bude spoľahlivý, ak budeš kódovanie a streamy spracovávať ako samostatnú vrstvu

    Väčšina problémov okolo „JSON in Delphi“ nesúvisí priamo s JSON parserom, ale s nenápadnou časťou pred ním: bajty zo streamu sa menia na text. Ak tam explicitne riešiš UTF-8, rozpoznáš prípady s BOM, neignoruješ hranice chunkov a nastavíš jasné veľkostné limity, typické chyby so špeciálnymi znakmi zmiznú – a sporadické chyby parsovania budú reprodukovateľné.

    System.JSON zostáva pragmatickým štandardom: nie najrýchlejší streaming-parser, ale spoľahlivý, ak kontroluješ vstup a vedome akceptuješ náklady DOMu. Ak chceš, môžeme spolu prejsť tvoj konkrétny import-/REST-priebeh a identifikovať miesto, kde kódovanie alebo chunking zlyhá: nadviazať kontakt.

    Pre túto tému sú dôležité aj JSON stream parsery. Príspevok tieto aspekty jasne zaraďuje a ukazuje, na čo záleží v praxi.

    Prediskutovať projekt alebo modernizačný zámer s Net-Base.

    ďalší krok

    Keď sa z témy stane reálny projekt, architektúru, existujúci stav a prevádzku treba včas posudzovať spoločne.

    Podporujeme nielen pri jednotlivých otázkach, ale aj vtedy, keď sa z fragmentov zdrojového kódu, tém súvisiacich s legacy systémami alebo nápadov na portál má stať robustný podnikový projekt.

    • Stav, cieľový obraz a technické riziká sa hodnotia spoločne.
    • REST, prístup k údajom, portály a nasadenie nebudú odložené na neskôr ako následné úlohy.
    • Včas identifikujete, ktorá cesta je ekonomicky a prevádzkovo životaschopná.

    Zdieľať príspevok

    Tento príspevok priamo zdieľať

    LinkedIn, X, XING, Facebook, WhatsApp a e‑mail sú okamžite k dispozícii. Pre Instagram pripravíme priamo odkaz a stručný text.

    E-mail

    Instagram sa otvorí v novej karte. Odkaz a krátky text sa predtým skopírujú do schránky.