Net-Base Magazin

13.08.2026

JSON a Delphi-ben: Gyors stream-elemző System.JSON-nel + UTF-8-buktatók speciális karakterek esetén

Ha JSON-payloadok a Delphi-ben közvetlenül egy Streamből érkeznek, a „csak gyorsan parszolni” hamar éles környezetbeli problémává válik: nagy memóriaigény, szórványos parszolási hibák és sérült ékezetek. Ez a gyakorlati cikk megmutatja, hogyan építhetsz System.JSON segítségével egy gyors Stream-parsert...

13.08.2026

A magazintémától a projektgyakorlatig

A bejegyzéshez tartozó szolgáltatási és technikai oldalak

„JSON in Delphi“ úgy hangzik, mintha megoldott probléma lenne: System.JSON beépítve, REST-hívások szöveget adnak vissza, kész. A gyakorlatban a valódi hibák azonban ott keletkeznek, ahol a JSON nem kényelmes Stringként áll elő, hanem Streamként: HTTP-Response-Stream, fájlstream, Named Pipe, Message-Queue vagy egy nagy BLOB az adatbázisból. Ilyenkor három, a mindennapokban gyakran alábecsült dolog találkozik: memóriahasználat, karakterkódolás (különösen UTF-8) és az ékezetekhez kapcsolódó szélső esetek.

Ez a bejegyzés egy tiszta, gyors megközelítést mutat be arra, hogyan parsoljunk JSON-t egy TStream-ből anélkül, hogy fölösleges másolatokat hoznánk létre – és ami fontosabb, hogyan kerüljük el a tipikus UTF-8-csapdákat, melyek során az umlautok „elromlanak” vagy a parszerek szakaszosan titokzatos hibákat dobálnak. A fókusz az üzemeltetésre és a felületstabilitásra gyakorolt hatásokon van: reprodukálható hibakeresés, a megközelítés határainak egyértelműsítése és azonosítási kritériumok, mikor éri meg valóban a plusz munka.

Miért másképp viselkednek a streamek a JSON-parsolásnál Delphi-ben

Amíg egy JSON-dokumentum kicsi, a „streamet beolvasom stringbe, aztán parse” út kényelmes. Egy bizonyos payload-méret fölött (jellemzően: nagy listák, reportok, szinkronizációs adatok, naplóexportok) ez azonban drágává válik:

  • Memóriamásolatok: Beolvasod a bájtokat egy pufferbe, átalakítod Unicode-stringgé (Delphi-String = UTF-16), a parser belsőleg további struktúrákat hoz létre. Ez rövid távon több másolatot eredményezhet.
  • GC/heap-nyomás: Sok ideiglenes String és JSON-érték növeli a fragmentációt és az allokációs overhead-et, különösen hosszú élettartamú folyamatoknál (Services, Worker, Import-jobok).
  • A hibajelenség tisztázatlanná válik: Ha az olvasás során már rossz kódolási feltételezéseket teszünk, a JSON-parser csak „furcsa karaktereket” vagy váratlan vezérőbájtokat lát.

Lényeges a világos szétválasztás: a JSON formálisan Unicode, az átvitel jellemzően UTF-8. Delphi viszont belsőleg UTF-16-tal dolgozik. A bájtok (Stream) és a karakterek (String) közötti átmenet az a pont, ahol az ékezetekkel kapcsolatos problémák keletkeznek – nem magában a JSON-ban.

System.JSON: miben jó – és mire kell figyelni

System.JSON a Delphi-ben a DOM-alapú JSON szabványos eszköze: kapsz egy objektummodellt (TJSONObject, TJSONArray), lekérdezhetsz értékeket, iterálhatsz, serializálhatsz. Ez robusztus tipikus üzleti integrációkhoz, ugyanakkor két következménnyel jár:

  • Nem igazi streaming-parser: Az objektummodell teljesen felépül. Lehet, hogy megspórolod a külön stringbe olvasást, de a DOM továbbra is memóriaigényes.
  • A parser bemenete jellemzően szöveg: A Delphi-verziótól és az használt API-tól függően hamar visszakerülhetsz a Stringhez, beleértve a kódoláskonverziót is.

Ha a célod „gyors stream-parser”, a gyakorlatban általában két dolgot értesz alatta: (1) nincs fölösleges másolatképzés és (2) a hibás payloadoknál a lehető legkorábbi Fail-fast. Mindkettő elérhető System.JSON-szal, amennyiben kontroll alatt tartod a bájtból-szövegbe lépést.

UTF-8-csapdák ékezetes karaktereknél: a tipikus okok

Absztrakt grafika: UTF-8 többbájtos karakterek chunk-határokon át és a dekóder helyes pufferelése a JSON-elemzés előtt
A chunkolás ártalmatlan – feltéve, hogy a UTF-8-dekóder az áthúzódó többbájtos sorozatokat puffereli.

Ha az umlautok (ä/ö/ü/ß) vagy más speciális karakterek az eredményben rosszul jelennek meg (ä, – stb.), az szinte mindig kódolási eltérés. A Delphi környezetben ezek az okok különösen gyakoriak:

1) ANSI-visszaesés a „kényelmes” helper-ek miatt

Néhány olvasási útvonal hallgatólagosan a rendszer ANSI-kódolását (a Windows rendszer codepage-je) veszi alapul, ha nincs explicit kódolás megadva. Ez csak akkor tűnik fel, amikor a payload nem csak ASCII-t tartalmaz. Tesztadatokban ez gyakran véletlenszerűen „oké”, éles környezetben viszont valódi nevek, helyek és szabad szövegek esetén problémát okoz.

2) BOM-zavar (Byte Order Mark)

Az UTF-8 BOM-mal kezdődhet (bájtok EF BB BF). Web-környezetben a BOM ritkább, fájloknál azonban előfordul. Néhány reader felismeri a BOM-ot és ennek megfelelően beállítja a kódolást, mások nem, vagy csak bizonyos módokban. Ha a BOM normál karakterként kerül a stringbe, gyakran egy láthatatlan „Zero Width No-Break Space” jelenik meg a legelején, vagy a JSON-Parser rögtön az első tokennél elbukik.

3) Dupla konvertálás (az UTF-8 „újra” értelmezése)

A klasszikus hibakép, ahol „ä” helyett „ä” jelenik meg, akkor alakul ki, ha az UTF-8 bájtokat először helyesen Unicode-ra dekódolták, majd később újra ANSI/UTF-8 bájtokként értelmezik (vagy fordítva). A Delphi környezetben ez gyakori, ha a TBytes, RawByteString és string közötti konverzió nem egyértelmű.

4) Levágás többbájtos karakter közepén

Az UTF-8 speciális karaktereket 2–4 bájtban kódolja. Ha chunkokban olvasol (pl. 8 KB), és a chunk-határ egy karakter közepére esik, a dekódernek tisztán kell pufferelnie. Egy naiv megközelítés, amely minden chunkot külön stringgé alakít és összefűz, érvénytelen sorozatokat eredményez. Ez „sporadikus” hibaként jelentkezhet, a csomaghatároktól, proxy-viselkedéstől vagy HTTP-chunkolástól függően.

5) Hibás feltételezések HTTP-fejlécek alapján

A REST esetén a forrás gyakran a Content-Type: application/json; charset=utf-8. Ugyanakkor egyes szerverek nem adnak meg charset-et, mások hibás értéket szolgáltatnak. Ha vakon a fejlécre hagyatkozol, az backend verziójától függően változhat. Üzemeltetés és support számára hasznos ellenőrizni a tényleges bájtfolyamot és hibák esetén logolni.

Egy tiszta megközelítés: Stream → UTF-8-dekóder → JSON-Parser

A robusztus pipeline három egyértelmű szakaszból áll:

  1. Bájtok olvasása a stream-ből (kontrolláltan, szükség esetén limit/timeout beállítással az HTTP-Clientben).
  2. Unicode-ra dekódolás explicit UTF-8 használatával (BOM opcionálisan tolerálható).
  3. Parsing a System.JSON használatával objektummodellbe vagy célzott kinyerésre.

A legfontosabb beavatkozási pont a 2. lépés: nem akarod, hogy valahol a „Default Encoding” döntsön. A Delphi esetén ez azt jelenti: TEncoding.UTF8 expliciten beállítani és nem implicit konverziókra támaszkodni.

Mit jelent itt konkrétan a „schnell”

A System.JSON használatával nem fogod „eloptimalizálni“ a DOM-ot. Viszont elkerülheted:

  • az egész payload egy további másolatát köztes stringként, ha belsőleg amúgy csak néhány értékre van szükséged (ilyenkor inkább másik parser éri meg; erről később),
  • többszörös átkódolást,
  • és képes leszel nagyon nagy payloadokat kontrolláltan beolvasni (méretkorláttal és tiszta hibaüzenettel), ahelyett, hogy Out-of-Memory vagy Access Violations miatt végeznél.

Konkrét szélső eset: speciális karakterek hibásak, de csak néha

Egy gyakorlatból vett szélső eset különösen alattomos: a payload alapvetően érvényes UTF-8-JSON, de te chunko(k)ban olvasod, és chunkonként konvertálsz Stringgé. Amíg csak ASCII szerepel, semmi nem tűnik fel. Amint egy umlaut pontosan egy chunk-határon lesz, érvénytelen UTF-8 szekvenciák keletkeznek. Eredmény: vagy törött karakterek, vagy egy parse-hiba egy olyan helyen, ami nem illeszkedik a tényleges tartalomhoz.

Hogyan ismerheted fel:

  • Parse-hibák „véletlenszerűen“ jelentkeznek nagy válaszoknál, nem a kicsiknél.
  • Ugyanaz a request néha sikerül, néha nem (a chunkolástól/átviteltől függően).
  • Egy hex-dump a byte-okról érvényes UTF-8-at mutathat, de a logolt stringed Replacement Characteröket (�) vagy klasszikus mojibake-ot tartalmaz.

A megoldás nem az, hogy „többet readln“-elsz vagy „nagyobb buffereket“ adsz, hanem egy olyan dekódert használsz, amely a multibyte-szekvenciákat a chunk-határokon átívelve helyesen puffereli. Pont ebben lehet hasznos TStreamReader UTF-8-kódolással kombinálva – feltéve, hogy helyesen inicializálod.

Gyakorlati útmutató: UTF-8 reprodukálható ellenőrzése az Delphi-ben

Debugging-Setup mit Byte-Dump-Ausdrucken und Arbeitsmaterial, um UTF-8-Bytes und BOM in JSON-Payloads zu prüfen
Tiszta hibakeresésnél először a bájtfolyam számít: a BOM, a troncírozás és az érvénytelen szekvenciák így gyorsan felismerhetők.

Mielőtt a parseren kezded állítgatni, szükséged van egy hibakereső beállításra, amely láthatóvá teszi a tényleges bájtsorrendet. Support és üzemeltetés szempontjából ez aranyat ér, mert később egyértelműen meg tudod mondani, hogy a partner hibás adatot küld-e, vagy a te csővezetéke decode-olja rosszul.

1) Az első bájtok ellenőrzése (BOM, JSON-kezdete)

Ha a JSON BOM-mal érkezik, a stream elején EF BB BF látszik. Rögtön ezután jellemzően „{“ vagy „[“ kell, hogy legyen. Ha a stringben már „“ látszik, a BOM-ot nem BOM-ként kezelték, hanem szövegként dekódolták.

2) Nyers bájtok hexben logolása – de korlátozva

Ne logolj teljes payloadokat éles környezetben (adatvédelem, költségek, log-méret). Bevált megoldások:

  • Prefix (pl. az első 256 vagy 1024 bájt),
  • Suffix (az utolsó 256 bájt),
  • és egy hash (SHA-256) korrelációhoz, ha payloadokat kell összehasonlítanod.

Ezzel gyakran percek alatt behatárolhatod a speciális karakterek problémáját: a „ä“ bájtsorozata helyes-e (C3 A4)? Van-e troncírozás? Megjelenik-e egy váratlan 0x00 (nullbyte), például egy UTF-16 téves feltételezés miatt?

3) Content-Type und Charset mitloggen

HTTP/REST esetén: Naplózd a Content-Type-ot és a deklarált charset-et. Ha a bájtok egyértelműen UTF-8-asok, de a charset mást állít, a kliens ne kövesse vakon. JSON esetén az UTF-8 a de-facto szabvány. Kétség esetén: a bájtelemzés felülírja a header-t.

Gyors stream-elemző System.JSON-sel: tervezés felesleges másolatok nélkül

Sémás ábra: pipeline (stream, UTF-8-dekódolás, méretkorlát és JSON-DOM-parszolás) Delphi-ben
Egy világos pipeline limiteléssel és explicit UTF-8-cal tisztán elválasztja a transzportot, a dekódolást és a parszolást.

Egy gyakorlatias minta: a streamből bájtpufferbe olvasol, abból pontosan egyszer hozol létre egy Stringet UTF-8-cal, és ezt a Stringet adod át a JSON-parszernak. Ez nem „streaming” SAX-értelemben, de kontrollált, teljesítményorientált pipeline, amely nem okoz meglepetésszerű kódolásváltásokat.

Architektúra szempontjából fontos: építsd a függvényt úgy, hogy egyetlen központi helyen döntsön:

  • Melyik kódolási szabály érvényes (alapértelmezésben UTF-8, BOM opcionális)?
  • Mekkora lehet maximálisan a payload (DoS-védelem, üzemhatár)?
  • Hogyan nézzenek ki a hibajelentések (kontextussal, de adatkiszivárgás nélkül)?

Beolvasási stratégia: korlátozott pufferelés a korlátlan „StreamToString” helyett

Ha külső forrásokból (partnerek, mobil kliens, harmadik fél) fogadsz JSON-t, a méretkorlát kötelező. Korlát nélkül egyetlen szerencsétlen kérés is Memory Pressure-t okozhat a szolgáltatásban. Gyakorlatban ez azt jelenti: az olvasásnál számold a bájtok összegét, és egy határ elérésekor szakítsd meg – egyértelmű Exception-nel, amely érthető a monitoring számára.

Miért várom gyakran az UTF-8-at „BOM nélkül”, de miért tolerálnám a BOM-ot

REST-payloadoknál a BOM ritka. Fájloknál (exportok, kézi szerkesztés) viszont gyakoribb. Robusztus importutaknál érdemes a BOM-ot tolerálni, de naplóban láthatóvá tenni, mert jelzés lehet arra, hogy „fájl-világ” helyett „API-világ” érkezett az adat.

A csendes gyilkosok: TStreamReader-alapértelmezések és a szövegolvasók vegyes használata

TStreamReader kényelmes, de két dolgot mindenképp szigorúan tarts kézben:

  • Állítsd be explicit módon az encodinget: Ne bízz abban, hogy „felismeri“.
  • Értsd a pufferelést: A reader belső pufferelést végez. Ha ugyanazt a streamet később máshol ismét olvasod, a pozíció számít. Ez triviálisnak hangzik, de nagyobb importpipeline-oknál gyorsan hibaforrás lesz.

Különösen kellemety a kevert használat: először egy részt bájtokként olvasol (pl. logolás vagy magic-byte ellenőrzés miatt), majd TStreamReader-rel folytatod. Ha nem állítod vissza rendesen a pozíciót, vagy nem a megfelelő helyen inicializálod a readert, előfordul, hogy byte 257-től olvasol a 0 helyett. A JSON-parszer ekkor „Invalid character at position …” hibát jelez, még ha a payload maga helyes is.

Ha a speciális karakterek UTF-8 mellett is „tönkremennek”: escapelés vs. valódi Unicode

A JSON kétféleképpen tartalmazhat speciális karaktereket:

  • Mint valódi UTF-8 karakterek (pl. „München” bájtjai C3 BC …).
  • Escape-szekvenciaként (pl. „Mu00fcnchen“).

Mindkettő érvényes. A gyakorlatban fontos: az escape-szekvenciák sok szállítási problémát megkerülnek, de csak látszólag fedik el az encoding-hibákat. Ha a rendszered valahol bájtokat értelmez rosszul, az üzemeltetési kockázat, nem csupán kozmetikai hiba. Emellett az escape-szekvenciák zavart okozhatnak a naplózás/monitorozás során, ha a csapatok „olvasható szöveget” várnak.

System.JSON mindkét esetben végül normális Delphi-stringeket (UTF-16) ad vissza, feltéve, hogy az odáig vezető út helyes volt.

Teljesítmény reálisan értékelve: DOM-költségek, nagy tömbök és szelektivitás

A legnagyobb teljesítmény-hatás gyakran nem az, hogy „a parser gyorsabb legyen”, hanem hogy kevesebbet parseolj. System.JSON esetén ez nehéz, mert a DOM-ot kapod. Három tipikus helyzet:

  • Nagy tömbök (10.000+ elem): A DOM felépítése időt és RAM-ot igényel. Ha elemenként csak 2 mezőre van szükséged, gyakran érdemes streaming-képes parseert (SAX/Tokenizer) használni. System.JSON erre nincs tervezve.
  • Egyedi objektumok sok mezővel: Ha csak néhány mező kell, használhatod a DOM-ot, de kerüld a többszöri bejárást. Olvasd ki az értékeket egyszer, és térképezd át őket a saját struktúráidba.
  • Több nagy payload egymás után: Import-feladatokban vagy sync-worker esetén érdemes a parseolást egy jól körülhatárolt lépésbe kapszulázni, és minden dokumentum után felszabadítani az összes referenciát, hogy a memory manager el tudja végezni a takarítást. Ez banális, mégis szolgáltatásoknál gyakran „mellékesen” történik.

Egy „gyors stream-parser” System.JSON mellett ezért gyakran jó kompromisszum: hatékony és helyes beolvasás, a DOM tudatos használata, határok definiálása. Ha valódi streaming-szemantikára van szükséged (pl. tömbelemek soronkénti feldolgozása anélkül, hogy mindent megtartanál), a System.JSON nem a megfelelő alap.

Robusztusság az üzemeltetésben: hibaképek és hogyan tedd azonnal kézzelfoghatóvá

JSON-parse-hibák a logokban gyakran nem informatívak, mert csak egy pozíciót adnak meg. Az üzemeltetés és a support számára kontextusra van szükség:

  • Bájtpozíció vs. karakterpozíció: UTF-8 esetén ezek nem azonosak. Ha a parser karakterpozíciót jelöl, a bájtpozíció eltérhet. Bájtdumpokhoz a bájtpozíció döntő.
  • Snippet a hibapozíció körül: Hibánál naplózz egy kis ablakot a pozíció körül (pl. 40 karakter előtte/utána), de csak akkor, ha nincs érzékeny adat. Alternatívaként: naplózz csak hexadecimálisan.
  • Korreláció: Request-ID, Endpoint, Partner-ID, Payload-Hash. Ellenkező esetben soha nem találod meg „az egy hibát” újra.

A cél, hogy egy produkciós incidens után perceken belül meg tudd válaszolni: „kódolás helytelenül értelmezve”, „payload le lett vágva”, „szerver invalid JSON-t szolgáltat” vagy „térképezési probléma van”.

UTF-8-buktatók célzott elkerülése: Ellenőrzőlista

  • Kódolás mindig explicit: Streamből olvasáskor és logok/fájlok írásakor ne bízz az alapértelmezésekben.
  • Ne konvertálj chunk-ot stringgé: Ha chunkelt olvasol, gyűjts bájtokat vagy használj egy dekódert, amely puffereli a multibájtos szekvenciákat.
  • BOM-toleráns, de látható: Fogadd el, de legyen detektálható hibakereséskor.
  • Korlátok beállítása: max. payload-méret, max. objektum-/tömbmélység (ha tudod kontrollálni), időtúllépések a HTTP-kliensekben.
  • Szétválasztott felelősségek: „Átvitel olvasása” és „JSON feldolgozása” külön kapszulázása. Így gyorsabban hibakereshetsz, és később kicserélheted a parsert.
  • Mikor éri meg valóban a befektetés egy Stream-Parserbe?

    Nem kell minden JSON-helyet optimalizálni. A megközelítés jellemzően akkor éri meg, ha legalább az alábbiak közül egy fennáll:

    • Nagy payloadok (több MB) rendszeresen előfordulnak vagy előfordulhatnak.
    • Hosszan futó folyamatok (Service, Worker) sok payloadot dolgoznak fel, és memóriaspikeket vagy fragmentációt tapasztalsz.
    • Interoperabilitás heterogén rendszerekkel: több partner, különböző platformok, időnként hibás kódolások.
    • Incidens-história: már voltak „tönkrement ékezetek”, időszakos parse-hibák vagy nehezen reprodukálható importmegszakadások.

    Ha a payloadjaid kicsik és ellenőrzött forrásból származnak, gyakran elegendő egy egyszerű megoldás – de még akkor is: az explicit UTF-8 beállítás alig kerül valamibe, és későbbi meglepetéseket előz meg.

    Elhatárolás: amikor valódi streamingre van szükség

    A System.JSON DOM-orientált. Ha az adatokat tényleg „átfolyás közben” akarod feldolgozni, például egy nagy tömböt elemenként anélkül, hogy teljesen a memóriában tartanád, más parser-megközelítésre van szükség (Tokenizer/SAX). Ez nem értékítélet, hanem architekturális döntés:

    • DOM (System.JSON): kényelmes, tipikus üzleti objektumokhoz alkalmas, de memóriaigényes.
    • Streaming/SAX: alacsony memóriaigény, nagyon nagy adatokhoz jó, de több implementációs munka és gondosabb hibakezelés szükséges.

    Gyakran értelmes kompromisszum: a stream-kezelést, a kódolást és a korlátokat tisztán megépíteni, és csak ezután dönteni, hogy a DOM még megfelel-e. Sok projektben már ez önmagában jelentősen stabilizálja az üzemet.

    Következtetés: JSON in Delphi megbízható lesz, ha a kódolást és a streameket külön rétegként kezeled

    A „JSON in Delphi” körüli problémák többsége nem maga a JSON-parser miatt van, hanem az előtte lévő észrevétlen szakasz miatt: a streamből érkező byte-ok szöveggé alakulnak. Ha ott explicit kezelsz UTF-8-at, felismered a BOM-eseteket, nem hagyod figyelmen kívül a chunk-határokat és tiszta méretkorlátokat állítasz, a tipikus speciális karakterekkel kapcsolatos hibák eltűnnek – és az időszakos parse-problémák reprodukálhatóvá válnak.

    A System.JSON továbbra is pragmatikus szabvány: nem a leggyorsabb streaming-parser, de szilárd, ha kontrollálod a bemenetet és tudatosan elfogadod a DOM-költségeket. Ha szeretnéd, közösen végig tudjuk venni a konkrét Import-/REST-útvonaladat és azonosítjuk azt a pontot, ahol a kódolás vagy a chunking felborul: vedd fel velünk a kapcsolatot.

    Ehhez a témához a JSON Stream Parser-ek is fontosak. A bejegyzés ezeket az aspektusokat érthetően rendezi, és megmutatja, mire kell figyelni a gyakorlatban.

    Projekt vagy modernizációs feladat megbeszélése Net-Base-vel.

    Következő lépés

    Ha a téma valós projektté válik, az architektúrát, a meglévő rendszert és az üzemeltetést már korán együtt kell értékelni.

    Nemcsak egyedi kérdésekben támogatunk, hanem akkor is, amikor forráskódrészletekből, örökölt rendszerekkel kapcsolatos témákból vagy portálötletekből robusztus vállalati projektet kell kialakítani.

    • A jelenlegi állapotot, a célállapotot és a műszaki kockázatokat együttesen értékeljük.
    • REST, az adathozzáférés, a portálok és a Rollout nem kerülnek utólagos teendőkként elhalasztásra.
    • Már korán láthatja, melyik út gazdaságilag és üzemeltetési szempontból életképes.

    Bejegyzés megosztása

    Ezt a bejegyzést közvetlenül megosztani

    LinkedIn, X, XING, Facebook, WhatsApp és e-mail azonnal elérhetők. Instagramhoz linket és rövid szöveget közvetlenül előkészítünk.

    E-mail

    Az Instagram egy új lapon nyílik meg. A link és a rövid szöveg előzetesen a vágólapra másolódik.