Net-Base Ajakiri

13.08.2026

JSON Delphi-s: Kiire vooluparser System.JSON-iga + UTF-8-lõksud erimärkide puhul

Kui JSON-payloadid tulevad Delphi otse voost, muutub „kiirelt parsimine“ kiiresti tootmisprobleemiks: suur mälunõudlus, sporadilised parsimisvead ja rikutud umlautid. See praktiline artikkel näitab, kuidas ehitada System.JSON abil kiire vooparser...

13.08.2026

Ajakirjateemast projektipraktikasse

Sobivad teenuse- ja tehnilised lehed postituse jaoks

„JSON in Delphi“ kõlab nagu lahendatud probleem: System.JSON on kohal, REST-kutsed tagastavad teksti, ja see ongi kõik. Praktikas tekivad tõelised vead aga seal, kus JSON ei esine mugava stringina, vaid voona: HTTP-response-Stream, failivoog, Named Pipe, Message-Queue või suur BLOB andmebaasist. Sel hetkel liituvad kolm asja, mida igapäevatöös sageli alahinnatakse: mälu käitumine, tähestiku kodeering (eriti UTF-8) ja erandjuhtumid seoses erimärkidega.

See artikkel näitab puhast, kiiret lähenemist JSON-i parsimiseks otse TStream-ist ilma liigsete koopiateta — ja eelkõige ilma tüüpiliste UTF-8 lõksudeta, kus täpitähed „purunevad“ või parserid aeg-ajalt krüptiliste vigadega kukuvad. Fookus on käituse ja liideste stabiilsusel: reprodutseeritav silumine, lähenemise selged piirid ja kriteeriumid, millal vaev end reaalselt õigustab.

Miks Streams JSON-parsimisel Delphi-s teisiti käituvad

Kuni JSON-dokument on väike, on tee „loeme voo stringi, siis parsime“ mugav. Teatud payload-suurusest alates (tüüpiliselt: suured nimekirjad, raportid, sünkroonimisandmed, logi-ekspordid) muutub see aga kalliks:

  • Mälu-duplikaadid: Sa loed baitid pufferisse, teisendad need Unicode-stringiks (Delphi-string = UTF-16) ja parser tekitab seesmiste struktuurideks veel koopiaid. See võib lühiajaliselt tähendada mitu koopiat.
  • GC/Heap-rõhk: Paljud ajutised stringid ja JSON-väärtused suurendavad fragmentatsiooni ja allokatsioonikulu, eriti pikaealistes protsessides (Services, Worker, Import-Jobs).
  • Veapildi segasus: Kui lugemisel tehakse juba valesid kodeeringu-eeldusi, näeb JSON-parser vaid „imedaid märke“ või ootamatuid kontrollbaite.

Tähtis on selge eristamine: JSON on formaalselt Unicode, võrgul on see pea alati UTF-8. Delphi töötab sisemiselt aga UTF-16-ga. Üleminek baitidelt (Stream) märkidele (String) on koht, kus erimärkide probleemid tekivad — mitte JSON-is endas.

System.JSON: Mida see hästi teeb — ja millele pead tähelepanu pöörama

System.JSON on Delphi-s DOM-põhise JSON-i standard: saad objektimudeli (TJSONObject, TJSONArray), võid pärida väärtusi, iteratsioonida ja serialiseerida. See on robustne tüüpiliste äriliste integratsioonide jaoks, kuid sel on kaks tagajärge:

  • See ei ole tõeline streaming-parser: Objektimudel koostatakse täielikult. Võib-olla säästad eraldi stringisse lugimise, kuid DOM jääb mäluintensiivseks.
  • Parseri sisend on tavaliselt tekst: Sõltuvalt Delphi versioonist ja kasutatavast API-st jõutakse kiiresti taas stringi juurde, kaasaarvatud kodeeringu-konversioon.

Kui su eesmärk on „kiire stream-parser“, mõtled sa praktikas tavaliselt ühte kahest: (1) vältida liigseid koopiaid ja (2) saavutada vigaste payloadide puhul võimalikult varajane fail-fast. Mõlemad on System.JSON-iga võimalikud, kui kontrollid baiti-tekst etappi.

UTF-8 lõksud erimärkide korral: tüüpilised põhjused

Abstrakte Grafik zeigt UTF-8-Mehrbytezeichen über Chunk-Grenzen und korrekte Pufferung im Decoder vor dem JSON-Parsing
Chunking on ohutu – seni, kuni UTF‑8-dekooder puhverdab mitmebaidilisi järjestusi üle piiride.

Kui täpitähed (ä/ö/ü/ß) või teised erimärgid tulemuses valesti kuvatakse (ä, – jne), on see peaaegu alati kodeeringu mittevastavus. Delphi-keskkonnas esinevad need põhjused eriti sageli:

1) ANSI-Fallback durch „bequeme“ Helper

Mõned lugimisteed eeldavad vaikimisi süsteemi ANSI-kodeeringut (Codepage Windows-süsteemist), kui konkreetset kodeeringut ei anta. See hakkab paistma alles siis, kui payload ei koosne ainult ASCII-st. Testandmetes on see sageli juhuslikult „okei“, tootmises põhjustab see probleeme pärisnimede, asukohtade ja vabatekstide puhul.

2) BOM-Verwirrung (Byte Order Mark)

UTF‑8 võib algatauda BOM-iga (baitid EF BB BF). Veebikontekstis on BOM pigem ebatavaline, failide puhul esineb seda mõnikord. Mõned readerid tunnevad BOM-i ära ja kohandavad kodeeringut, teised mitte või ainult teatud režiimides. Kui BOM satub stringi nagu tavaline märk, näed sageli nähtamatut „Zero Width No-Break Space“ rea alguses või JSON-parser ebaõnnestub kohe esimese tokeni juures.

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

Tüüpiline vea kuvand „ä“ statt „ä“ tekib siis, kui UTF‑8-baitid dekodeeritakse esmalt korrektselt Unicode-iks, kuid hiljem tõlgitakse need uuesti valesti kui ANSI/UTF‑8-baitid (või vastupidi). Delphi-keskkonnas juhtub see sageli, kui konversioonid TBytes, RawByteString ja string vahel on ebaselged.

4) Trunkierung mitten in einem Multibyte-Zeichen

UTF‑8 kodeerib erimärgid 2–4 baitiga. Kui loed chunkidena (nt 8 KB) ja chunk‑piir asub tähe keskel, peab dekooder selle korrektselt puhverdama. Naiivne lähenemine, kus iga chunk teisendatakse eraldi stringiks ja allesjärel liimitakse kokku, tekitab vigaseid järjestusi. See võib ilmuda „sporadilise“ veana, sõltudes pakettide piiridest, proksi käitumisest või HTTP‑chunkingust.

5) Falsche Annahmen aus HTTP-Headern

REST puhul on allikas tihti Content-Type: application/json; charset=utf-8. Mõned serverid aga ei saada charset-i, mõned annavad vale väärtuse. Kui kasutad päist pimesi, võib see sõltuvalt backend‑versioonist muutuda. Operatsiooni ja toe jaoks on kasulik kontrollida tegelikku baitide voogu ja vea puhul selle logida.

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

Robustne töövoog koosneb kolmest selgest etapist:

  1. Baidid voost lugeda (kontrollitult, vajadusel limiidi/aegumisega HTTP‑kliendis).
  2. Unicodesse dekodeerimine ekspilitselt UTF‑8-ga (BOM-i valikuline talumine).
  3. Parsimine System.JSON-iga objektimudeliks või sihipäraseks ekstraktsiooniks.

Olulisem nihe on etapp 2: sa ei taha, et kuskil otsustab „Default Encoding“. Delphi puhul tähendab see: TEncoding.UTF8 ekspilitselt seada ja mitte loota implitsiitsetele konverteerimistele.

Was „schnell“ hier konkret bedeutet

Mit System.JSON wirst du das DOM nicht „wegoptimieren“. Aber du kannst vermeiden:

  • eine zusätzliche Kopie der gesamten Payload als Zwischenstring, wenn du intern ohnehin nur wenige Werte brauchst (dann lohnt eher ein anderer Parser; dazu später),
  • mehrfaches Umkodieren,
  • und du kannst sehr große Payloads kontrolliert einlesen (mit Größenlimit und sauberer Fehlermeldung), statt bei Out-of-Memory oder Access Violations zu enden.

Der konkrete Randfall: Sonderzeichen kaputt, aber nur manchmal

Ein Randfall aus der Praxis ist besonders tückisch: Die Payload ist grundsätzlich gültiges UTF-8-JSON, aber du liest sie in Chunks und konvertierst pro Chunk zu String. Solange nur ASCII vorkommt, merkst du nichts. Sobald ein Umlaut genau auf einer Chunk-Grenze liegt, entstehen ungültige UTF-8-Sequenzen. Ergebnis: entweder kaputte Zeichen oder ein Parse-Fehler an einer Stelle, die nicht zum eigentlichen Inhalt passt.

Woran du das erkennst:

  • Parse-Fehler treten „zufällig“ bei großen Antworten auf, nicht bei kleinen.
  • Der gleiche Request klappt mal, mal nicht (je nach Chunking/Transport).
  • Ein Hexdump der Bytes zeigt gültiges UTF-8, aber dein geloggter String enthält Replacement Characters (�) oder klassische Mojibake.

Die Lösung ist nicht, „mehr zu readln“ oder „größere Buffer“ zu verwenden, sondern einen Decoder zu nutzen, der Multibyte-Sequenzen über Chunk-Grenzen hinweg korrekt puffert. Das ist genau der Punkt, an dem TStreamReader in Kombination mit einem UTF-8-Encoding hilfreich sein kann – wenn du ihn korrekt initialisierst.

Praxisleitfaden: UTF-8 in Delphi reproduzierbar prüfen

Debugging-Setup mit Byte-Dump-Ausdrucken und Arbeitsmaterial, um UTF-8-Bytes und BOM in JSON-Payloads zu prüfen
Puhas silumine algab baitide voost: BOM, kärpimine ja vigased järjestused on nii kiiresti nähtavad.

Bevor du am Parser schraubst, brauchst du ein Debugging-Setup, das dir die tatsächliche Bytefolge sichtbar macht. Für Support und Betrieb ist das Gold wert, weil du später klar sagen kannst, ob die Gegenstelle falsche Daten liefert oder deine Pipeline falsch decodiert.

1) Die ersten Bytes prüfen (BOM, JSON-Start)

Wenn das JSON mit BOM kommt, siehst du am Streamanfang EF BB BF. Direkt danach sollte typischerweise „{“ oder „[“ kommen. Wenn da bereits „“ im String steht, wurde BOM nicht als BOM behandelt, sondern als Text decodiert.

2) Rohbytes in Hex loggen – aber begrenzt

Logge nicht komplette Payloads in Produktion (Datenschutz, Kosten, Log-Volumen). Bewährt haben sich:

  • Prefix (z. B. erste 256 oder 1024 Bytes),
  • Suffix (letzte 256 Bytes),
  • und ein Hash (SHA-256) für Korrelation, wenn du Payloads vergleichen musst.

Damit kannst du Sonderzeichen-Probleme oft in Minuten einordnen: Ist die Bytefolge für „ä“ korrekt (C3 A4)? Liegt eine Trunkierung vor? Taucht ein unerwartetes 0x00 (Nullbyte) auf, z. B. durch UTF-16-Fehlannahme?

3) Content-Type und Charset mitloggen

HTTP/REST puhul: logi Content-Type ja deklareeritud charset. Kui baidid on selgelt UTF-8, kuid charset väidab midagi muud, ei tohiks klient sellele pimesi järgida. JSON-i puhul on UTF-8 de-facto standard. Kahtluse korral: baitide analüüs võidab päise üle.

Kiire voo-analüsaator System.JSON-iga: disain ilma tarbetute koopiateta

Põhimõtteline joonis voopõhisest torustikust: Stream, UTF-8 dekodeerimine, suurusepiirang ja JSON-DOM-parsimine Delphi juures
Selge torustik piiriga ja otsese UTF-8-ga eraldab transporti, dekodeerimist ja parsingu puhtalt.

Töökorras muster on: loed voost bait-puhvrisse, koostad sellest Stringi täpselt üks kord UTF-8-ga ja annad selle stringi JSON-parsijale. See ei ole „streaming“ SAX-i mõttes, kuid on kontrollitud ja jõudluslikult efektiivne torustik ilma üllatavate kodeeringumuutusteta.

Arhitektuuri jaoks oluline: ehita funktsioon nii, et otsus tehakse ühes keskuses:

  • Milline kodeerimisreegel kehtib (tavaliselt UTF-8, BOM valikuline)?
  • Kui suur tohib sisu maksimaalselt olla (DoS-kaitse, tööpiir)?
  • Millised on veateated (kontekstiga, kuid ilma andmeleketeta)?

Lugemisstrateegia: piiratud puhver vs. „StreamToString“ ilma piirita

Kui võtad JSON-i vastu välistest allikatest (partnerid, mobiilsed kliendid, kolmandad osapooled), on suurusepiirang kohustuslik. Ilma piirita võib üks ebaõnnestunud päring viia teenuse mälurõhu alla. Praktikas tähendab see: lugemisel loe baitide summa ja katkesta piirini jõudes – selge erandiga, mis on monitorimisel arusaadav.

Miks ma UTF-8 puhul sageli ootan „ilma BOM-ita“, kuid BOM-i taluks

REST-payloadides esineb BOM harva. Failide puhul (eksport, manuaalne redigeerimine) seevastu sagedamini. Tugevates impordiradaades on mõistlik BOM-i taluda, kuid logis seda nähtavaks teha, sest see võib viidata pigem „failimaailmale“ kui „API-maailmale“.

Vaiksed tapjad: TStreamReader-i vaikeseaded ja tekstilugejad segakasutusel

TStreamReader on mugav, kuid sul tuleb kaks asja korrektselt hallata:

  • Määra kodeering ekspliciitselt: Ära looda, et ta seda „tuvastab“.
  • Mõista puhverdamist: Reader pufrib sisemiselt. Kui loed sama voogu hiljem kusagil mujal uuesti, on asukoht oluline. See kõlab triviaalselt, kuid suuremates imporditorustikes muutub see kiiresti veakohaks.

Eriti problemaatiline on segakasutus: loed esmalt osa baitidena (nt logimiseks või magic-bytes jaoks) ja jätkad seejärel TStreamReader-iga. Kui sa ei pöördu korrektselt tagasi ega alusta Reader-i initsialiseerimist õiges positsioonis, loed alates baitist 257 mitte 0-st. JSON-parsija teatab siis „Invalid character at position …“, kuigi payload iseenesest on korrektne.

Kui erimärgid jäävad vaatamata UTF-8-le „katki“: escapeimine vs tõeline Unicode

JSON võib sisaldada erimärke kahel viisil:

  • Tõeliste UTF-8-märkidena (nt „München“ baitidena C3 BC …).
  • Escape-järjestusena (nt „Mu00fcnchen“).

Mõlemad on kehtivad. Praktikas oluline: escape-järjestused väldivad paljusid transpordiprobleeme, kuid nad peidavad kodeerimisvigu ainult näiliselt. Kui su süsteem kuskil baite valesti interpreteerib, on see käitusrisk, mitte ainult kosmeetiline viga. Lisaks võivad escape-järjestused logimisel/monitooringul segadust tekitada, kui meeskonnad eeldavad „loetavat teksti“.

System.JSON annab mõlemal juhul lõpuks tavalised Delphi-stringid (UTF-16), eeldusel et tee sinna oli korrektne.

Jõudluse realistlik hindamine: DOM-kulud, suured massiivid ja selektiivsus

Suurim jõudluse mõjutaja ei ole sageli „parseri kiirendamine“, vaid vähem parsimist. System.JSONiga on see keerulisem, sest sa saad DOM-i. Kolm tüüpilist olukorda:

  • Suured massiivid (10.000+ Elemente): DOM-i ülesehitus nõuab aega ja mälu. Kui vajad iga elemendi kohta vaid kahte välja, on sageli mõistlikum streaminguvõimeline parser (SAX/Tokenizer). System.JSON ei ole selleks mõeldud.
  • Üksikobjektid paljude väljadega: Kui vajad vaid mõnda välja, võid DOM-i ikkagi kasutada, kuid väldi mitmekordset läbikäimist. Võta väärtused ühe korra ja kaardista need oma struktuuridesse.
  • Mitu suurt Payloadi järjest: Import-töödes või Sync-Workerites tasub parsimine kapseldada selgeks etapiks ja pärast iga dokumenti vabastada kõik viited, et mäluhaldur saaks koristada. See on triviaalne, ent teenustes kiputakse seda sageli „kõrvalttööna“ tegema.

Seetõttu on „kiire stream-parser“ koos System.JSON-iga sageli hea kompromiss: sisendi lugemine tõhusalt ja korrektselt, DOM-i teadlik kasutamine ning piiride määratlemine. Kui vajad tõelist streaming-semantikat (nt massiivi elementide järjestikune töötlemine ilma kõike mäletamata), ei ole System.JSON õige alus.

Robustsus tootmiskeskkonnas: veastseenid ja kuidas need kohe tuvastatavaks teha

JSON-i parsimisvead logides ei ole sageli abiks, sest need toovad välja ainult positsiooni. Operatsiooni ja toe jaoks vajad konteksti:

  • Baiti-positsioon vs. märgi-positsioon: UTF-8 puhul ei lange need kokku. Kui parser teatab märgi-positsioonist, võib baitide positsioon erineda. Baiti-dumpide puhul on baitide positsioon määrav.
  • Viga ümbritsev lõik: Logi veateate korral väike aken positsiooni ümber (nt 40 märki enne/peale), kuid ainult juhul, kui seal ei ole tundlikke andmeid. Alternatiivina logi ainult heksadezimaalsena.
  • Korrelatsioon: Request-ID, Endpoint, Partner-ID, Payload-Hash. Muidu ei leia sa „seda ühte viga“ kunagi uuesti.

Eesmärk on, et pärast tootmisintsidenti suudaksid mõne minuti jooksul vastata: „kodeering tõlgendati valesti“, „payload katkes“, „server tagastas kehtetu JSON“ või „meil on kaardistamisviga“.

UTF-8 lõkse sihipäraselt vältida: kontrollnimekiri

  • Kodeering alati selgelt määrata: voost lugemisel ja logides/failidesse kirjutamisel ära usalda vaikeväärtusi.
  • Ära konverteeri chunk’e stringiks: Kui loed chunkidena, kogu baitid või kasuta dekooderit, mis puhverdab mitmebaidilisi järjestusi.
  • BOM tolerantne, kuid nähtav: aktsepteeri, kuid suuda silumise ajal seda tuvastada.
  • Sea piirid: maksimaalne payloadi suurus, maksimaalne objekti-/massiivi sügavus (kui suudad seda kontrollida), timeout’id HTTP-Clientis.
  • Eraldiseisvad vastutused: „transpordi lugemine“ ja „JSON-i parsimine“ kapselda eraldi. Nii debugid kiiremini ja saad hiljem parseri välja vahetada.
  • Millal tasub tegelikult vaeva näha voogparseri jaoks?

    Sa ei pea optimeerima iga JSON-kohta. Lähenemine tasub end tavaliselt, kui kehtib vähemalt üks järgmistest:

    • Suured payloadid (mitmed MB) esinevad regulaarselt või võivad esineda.
    • Pikaajalised protsessid (Service, Worker) töötlevad palju payload’e ja täheldad mälupiike või fragmentatsiooni.
    • Interoperatsioon heterogeensete süsteemidega: mitu partnerit, erinevad platvormid, aeg-ajalt vigased kodeeringud.
    • Intsidendi ajalugu: On juba esinenud „rikkinud täpitähti“, juhuslikke parsimisvigu või raskesti reprodutseeritavaid impordikatkestusi.

    Kui su payloadid on väikesed ja pärinevad kontrollitud allikast, piisab sageli lihtsast lahendusest – kuid isegi siis: UTF-8 selgesõnaline määramine ei maksa peaaegu midagi ja väldib hilisemaid üllatusi.

    Piiritlemine: Kui vajad tõelist voopõhist töötlemist

    System.JSON on DOM-suunaline. Kui soovid andmeid tõesti „läbivoolus“ töödelda, nt suurt massiivi element haaval ilma seda täielikult mälus hoidmata, vajad teistsugust parseri lähenemist (Tokenizer/SAX). See ei ole hinnang, vaid arhitektuuriline otsus:

    • DOM (System.JSON): mugav, sobib tüüpiliste äriobjektide jaoks, kuid mäluintensiivne.
    • Streaming/SAX: madalam mälunõudlus, sobib väga suurte andmete jaoks, kuid nõuab rohkem implementeerimistööd ja hoolikamat veakäsitlust.

    Tark kompromiss on sageli: ehita voogude käitlemine, kodeering ja piirid korrektselt üles ning otsusta alles seejärel, kas DOM ikka sobib. Paljudes projektides stabiliseerib juba see operatsiooni oluliselt.

    Kokkuvõte: JSON in Delphi on usaldusväärne, kui käsitled kodeerimist ja vooge eraldi kihina

    Enamik probleeme seoses „JSON in Delphi“ ei tulene JSON-parserist endast, vaid nähtamatust lõigust enne seda: voost tulevad baidid teisendatakse tekstiks. Kui käsitled seal UTF-8 selgesõnaliselt, tuvastad BOM-juhtumid, ei ignoreeri tükkide piirjooni ja sead selged suurusepiirid, kaovad tüüpilised erimärgivead – ja juhuslikud parsimisvead muutuvad reprodutseeritavateks.

    System.JSON jääb sel juhul pragmaatiliseks standardiks: mitte kiireim vooparser, aga stabiilne, kui kontrollid sisendit ja aktsepteerid DOM-kulusid teadlikult. Kui soovid, saame koos läbi vaadata sinu konkreetse import-/REST-tee ja lokaliseerida koha, kus kodeerimine või tükkimine läheb viltu: võta ühendust.

    Selle teema puhul on olulised ka JSON-vooparserid. Artikkel asetab need aspektid arusaadavasse konteksti ja näitab, millele igapäevatöös tähelepanu pöörata.

    Aruta projekti või moderniseerimisettevõtmist koos Net-Base.

    järgmine samm

    Kui teemast saab reaalne projekt, tuleks arhitektuuri, olemasolevat keskkonda ja ekspluatatsiooni varakult koos vaadelda.

    Me ei toeta ainult üksikute küsimuste lahendamist, vaid ka siis, kui lähtekoodilõikudest, pärandsüsteemidest või portaalikontseptsioonidest peab saama usaldusväärne ettevõtteprojekt.

    • Olemasolev olukord, sihtpilt ja tehnilised riskid hinnatakse üheskoos.
    • REST, andmejuurdepääs, portaalid ja juurutamine ei lükata hilisemateks tagajärgedeks edasi.
    • Te näete varakult, milline tee on majanduslikult ja operatiivselt jätkusuutlik.

    Jaga postitust

    Jaga seda postitust otse

    LinkedIn, X, XING, Facebook, WhatsApp ja e-post on kohe saadaval. Instagrami jaoks valmistame lingi ja lühiteksti otse ette.

    e-post

    Instagram avatakse uues vahekaardis. Link ja lühitekst kopeeritakse eelnevalt lõikepuhvrisse.