Net-Base Žurnāls

13.08.2026

JSON in Delphi: Ātrs straumes parseris ar System.JSON + UTF‑8 nianses attiecībā uz īpašajām rakstzīmēm

Ja JSON payloadi Delphi tiek tieši lasīti no straumes, no “ātri parstimt” drīz kļūst ražošanas problēma: liels atmiņas patēriņš, sporādiskas parsēšanas kļūdas un bojāti umlauti. Šajā praksē balstītajā rakstā parādīts, kā ar System.JSON uzbūvēt ātru straumes parseri...

13.08.2026

No žurnāla tēmas līdz projektu praksei

Atbilstošas pakalpojumu un tehniskās lapas rakstam

„JSON in Delphi“ izklausās pēc atrisinātas problēmas: System.JSON ir iekļauts, REST-Calls atgriež tekstu, un viss. Taču praksē reālās kļūdas rodas tur, kur JSON nav ērtā String formā, bet gan kā straume: HTTP-Response-Stream, faila straume, Named Pipe, Message-Queue vai liels BLOB no datu bāzes. Tad saplūst trīs lietas, kuras ikdienā mēdz nenovērtēt: atmiņas uzvedība, rakstzīmju kodēšana (īpaši UTF-8) un malējie gadījumi saistībā ar īpašajiem simboliem.

Šis raksts parāda tīru, ātru pieeju JSON parsēšanai no TStream, neveidojot liekas kopijas — un galvenokārt bez tipiskajām UTF-8 slazdiem, kuros diakritiskās zīmes sabojājas vai parsētāji periodiski apstājas ar neskaidriem paziņojumiem. Uzsvars ir uz ekspluatācijas un saskarnes stabilitātes ietekmi: reproducējama debugēšana, pieejas skaidras robežas un kritēriji, kad šāda piepūle tiešām atmaksājas.

Kāpēc straumes JSON parsēšanā Delphi uzvedas citādi

Kamēr JSON dokuments ir mazs, pieeja „nolasa straumi kā String, tad parsē“ ir ērta. Taču, sasniedzot noteiktu payload izmēru (tipiski: lielas sarakstes, atskaites, sinhronizācijas dati, žurnālu eksporta faili), tas kļūst dārgs:

  • Atmiņas dublikāti: Baiti tiek nolasīti buferī un pārvērsti par Unicode-String (Delphi-String = UTF-16), parsētājs iekšēji ģenerē papildu struktūras. Tas īslaicīgi var radīt vairākas kopijas.
  • GC/Heap-spiediens: Daudzi pagaidu stringi un JSON vērtības palielina fragmentāciju un alokācijas režiju, īpaši ilgi darbojošos procesos (Services, Worker, Import-Jobs).
  • Kļūdas aina kļūst neskaidra: Ja nolasīšanas laikā tiek pieņemti nepareizi kodējuma priekšnoteikumi, JSON parsētājs redz tikai „dīvainas zīmes“ vai negaidītus kontroles baitus.

Svarīga ir skaidra atdalīšana: JSON formāli ir Unicode, pār pārvades slēdzi tas gandrīz vienmēr ir UTF-8. Delphi iekšēji strādā ar UTF-16. Pāreja no baitu (Stream) uz rakstzīmēm (String) ir vieta, kur rodas problēmas ar īpašajiem simboliem — nevis pašā JSON.

System.JSON: ko tas labi prot — un kur jāuzmanās

System.JSON ir Delphi standarts DOM-bāzētam JSON: tiek nodrošināta objektu modeļa pieeja (TJSONObject, TJSONArray), var vaicāt vērtības, iterēt, serializēt. Tas ir robusts tipiskām biznesa integrācijām, tomēr tam ir divas sekas:

  • Tas nav īsts straumju parsētājs: Objektu modelis tiek uzbūvēts pilnībā. Iespējams, var ietaupīt uz atsevišķa String nolasīšanu, taču DOM joprojām ir atmiņietilpīgs.
  • Parsera ievade parasti ir teksts: Atkarībā no Delphi versijas un izmantotās API ātri nonāk atpakaļ pie String, ieskaitot kodējuma konvertāciju.

Ja mērķis ir „ātrs straumju parsētājs“, praksē tas parasti nozīmē vienu no divām lietām: (1) neveidot liekas kopijas un (2) pēc iespējas agrāku fail-fast uzvedību bojātu payload gadījumos. Abas lietas ir sasniedzamas ar System.JSON, ja kontrolē Byte-uz-Text līmeni.

UTF-8 slazdi ar īpašajiem simboliem: tipiskie cēloņi

Abstrakte Grafik zeigt UTF-8-Mehrbytezeichen über Chunk-Grenzen und korrekte Pufferung im Decoder vor dem JSON-Parsing
Chunking nav bīstams — ja UTF-8 dekoders buferē vairākbaitu secības, kas šķērso robežas.

Ja umlauti (ä/ö/ü/ß) vai citi speciālie simboli rezultātā izskatās nepareizi (ä, – utt.), tas gandrīz vienmēr ir kodējuma neatbilstības rezultāts. Delphi vidē šādi cēloņi ir īpaši izplatīti:

1) ANSI-Fallback durch „bequeme“ Helper

Dažas lasīšanas ceļas klusībā pieņem sistēmas ANSI kodējumu (Codepage des Windows-Systems), ja netiek nodots eksplicīts kodējums. Tas kļūst pamanāms tikai tad, kad payload satur ne tikai ASCII. Testdatu gadījumā tas bieži nejauši darbojas, bet produkcijā sabrūk pie īstiem vārdiem, vietām vai brīvteksta laukiem.

2) BOM-Verwirrung (Byte Order Mark)

UTF-8 var sākties ar BOM (baiti EF BB BF). Web kontekstā BOM parasti nav lietots, bet failos tas var parādīties. Daži lasītāji atpazīst BOM un pielāgo kodējumu, citi to nedara vai tikai noteiktos režīmos. Ja BOM nonāk kā parasts simbols virknē, bieži redzams neredzams „Zero Width No-Break Space“ sākumā vai JSON parsētājs izgāžas pie pirmā tokena.

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

Klasisks kļūdas gadījums, kur redzi „ä“ vietā „ä“, rodas, ja UTF‑8 baiti vispirms korekti dekodē uz Unicode, bet vēlāk tie tiek vēlreiz interpretēti kā ANSI/UTF‑8 baiti (vai otrādi). Delphi tas bieži notiek, ja konversijas starp TBytes, RawByteString un string nav skaidras.

4) Trunkierung mitten in einem Multibyte-Zeichen

UTF-8 kodē speciālās rakstzīmes 2–4 baitos. Ja lasi pa fragmentiem (piem., 8 KB) un fragmenta robeža iekrīt rakstzīmes vidū, dekoderim ir jāspēj to korekti buferēt. Naivs piegājiens, kas katru fragmentu atsevišķi pārveido uz virkni un pēc tam salīmē, radīs nederīgas secības. Tas var izskatīties kā sporādiska kļūda, atkarībā no pakešu robežām, starpniekserveru uzvedības vai HTTP chunking.

5) Falsche Annahmen aus HTTP-Headern

Pie REST avots bieži ir Content-Type: application/json; charset=utf-8. Tomēr daži serveri nesūta charset, citi nosūta nepareizu informāciju. Ja akli paļaujies uz header, tas var mainīties atkarībā no backend versijas. Operācijai un atbalstam ir lietderīgi pārbaudīt faktisko baitplūsmu un pierakstīt to žurnālā kļūmes gadījumā.

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

Robustā caurplūde sastāv no trim skaidriem posmiem:

  1. Nolasīt baitus no straumes (kontrolēti, nepieciešamības gadījumā ar limitu/timeout HTTP klientā).
  2. Dekodēšana uz Unicode ar eksplicītu UTF‑8 (BOM pēc izvēles tolerēt).
  3. Parsēšana ar System.JSON uz objektu modeli vai mērķtiecīgu ekstrakciju.

Galvenais sviras punkts ir 2. posms: tu nevēlies, lai kaut kur „Default Encoding“ izlemtu. Delphi tas nozīmē: eksplicīti iestatīt TEncoding.UTF8 un nepaļauties uz implicitām konversijām.

Ko šeit konkrēti nozīmē „ātri“

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.

Konkrētais robežgadījums: speciālās rakstzīmes bojātas, bet tikai dažkārt

Praktisks robežgadījums ir īpaši viltīgs: 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.

Kā to atpazīt:

  • 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.

Risinājums nav lietot „mehr zu readln“ oder „größere Buffer“, 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.

Praktiska rokasgrāmata: UTF-8 reproducējami pārbaudīt Delphi

Debugging-Setup mit Byte-Dump-Ausdrucken und Arbeitsmaterial, um UTF-8-Bytes und BOM in JSON-Payloads zu prüfen
Par tīru debugošanu svarīgākais ir baitu straume: BOM, Trunkierung und ungültige Sequenzen sind so schnell sichtbar.

Pirms pielāgo parsēšanu, tev nepieciešams 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.

Tas ļauj bieži dažu minūšu laikā identificēt problēmas ar speciālajiem simboliem: 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

Pie HTTP/REST: ieraksti Content-Type un deklarēto charset. Ja baiti acīmredzami ir UTF-8, bet charset norāda citādi, klientam nevajadzētu tam akli ticēt. JSON gadījumā UTF-8 ir de-facto standarts. Šauboties: baitu analīze uzvar pār headeru.

Ātrs straumes parsētājs ar System.JSON: dizains bez nevajadzīgām kopijām

Shēmisks attēlojums cauruļvada no straumes, UTF-8 dekodēšanas, izmēru ierobežojuma un JSON-DOM parsēšanas vietā Delphi
Skarta cauruļvada ar ierobežojumu un eksplizītu UTF-8 atdala transportu, dekodēšanu un parsēšanu skaidri.

Praxē lietojams modelis ir: nolasīt no straumes uz baitbuferi, no tā vienu reizi izveidot String ar UTF-8 un šo String nodot JSON-parserim. Tas nav „streaming“ SAX izpratnē, taču tas ir kontrolēts, efektīvs cauruļvads bez pārsteidzošām enkodēšanas maiņām.

Svarīgi arhitektūrai: izveido funkciju tā, lai tā vienuviet centrāli izlemtu:

  • Kura enkodēšanas kārtība tiek piemērota (parasti UTF-8, BOM pēc izvēles)?
  • Cik liela drīkst būt payload maksimāli (DoS aizsardzība, operacionāla robeža)?
  • Kā izskatās kļūdu ziņojumi (ar kontekstu, bet bez datu noplūdes)?

Iekraušanas stratēģija: ierobežota buferizācija nevis „StreamToString“ bez ierobežojuma

Ja pieņem JSON no ārējiem avotiem (partneri, mobilie klienti, trešās puses), izmēra ierobežojums ir obligāts. Bez ierobežojuma pietiek ar vienu neveiksmīgu pieprasījumu, lai pakalpojums nonāktu Memory Pressure stāvoklī. Praktiski tas nozīmē: lasīšanas laikā skaitīt kopējo baitus un sasniedzot robežu pārtraukt — ar skaidru Exception, kas saprotama monitoringā.

Kāpēc es bieži sagaidu UTF-8 bez BOM, bet BOM tomēr tolerētu

Pie REST-payloadiem BOM parādās reti. Failos (eksportos, manuālās rediģēšanas gadījumā) tas ir sastopamāks. Robusām importa gaitām ir lietderīgi BOM tolerēt, bet reģistrēt logā, jo tas var norādīt uz „failu pasauli“ nevis „API pasauli“.

Klusi slepkavas: TStreamReader noklusējuma vērtības un teksta lasītāji jauktā darbībā

TStreamReader ir ērts, bet tev jāuztur skaidrībā divas lietas:

  • Norādi enkodējumu eksplicīti: Neuzticies, ka tas to „atpazīs“.
  • Izproti buferizāciju: Lasītājs buferē iekšēji. Ja tu to pašu straumi vēlāk lasīsi citur, pozīcija ir nozīmīga. Tas izklausās triviali, taču lielākās importu caurlaidēs tas ātri kļūst par kļūdu avotu.

Īpaši nepatīkams ir jauktais režīms: vispirms nolasīt daļu kā baitus (piem., loģēšanai vai magic-byte nolūkiem), tad turpināt ar TStreamReader. Ja netiek skaidri atgriezta pozīcija vai Reader inicializēts nepareizā vietā, tu lasīsi no baits 257 nevis no 0. JSON-parseris tad ziņos „Invalid character at position …“, lai gan payload pats par sevi ir korekts.

Ja īpašās rakstzīmes pat ar UTF-8 paliek bojātas: Escaping vs. īsts Unicode

JSON var saturēt īpašās rakstzīmes divos veidos:

  • Kā īsti UTF-8 simboli (piem., „München“ kā baiti C3 BC …).
  • Kā Escape-sekvence (piemēram „Mu00fcnchen“).

Abi ir derīgi. Praktiskā ziņā svarīgi: Escape-sekvences apiet daudzas transportēšanas problēmas, bet tās tikai it kā slēpj kodējuma kļūdas. Ja sistēma kaut kur nepareizi interpretē baitus, tas ir darbības risks, ne tikai kosmētiska kļūda. Turklāt escape-sekvences var maldināt žurnālošanu/monitoringu, ja komandas sagaida „lasāmu tekstu“.

System.JSON nodrošina abos gadījumos beigās parastus Delphi-stringus (UTF-16), ja vien ceļš līdz tam bija korekts.

Veiktspējas reālistiska novērtēšana: DOM-izmaksas, lieli masīvi un selektivitāte

Lielākais veiktspējas sviras punkts bieži vien nav „parsētāju padarīt ātrāku“, bet gan mazāk parsēt. Ar System.JSON to ir grūtāk izdarīt, jo tu iegūsti DOM. Trīs tipiskas situācijas:

  • Lieli masīvi (10 000+ elementi): DOM veidošana prasa laiku un RAM. Ja katram elementam vajag tikai 2 laukus, bieži lietderīgāk izmantot straumēšanas spējīgu parseri (SAX/Tokenizer). System.JSON tam nav paredzēts.
  • Atsevišķi objekti ar daudziem laukiem: Ja tev vajag tikai dažus laukus, tu joprojām vari izmantot DOM, bet izvairies no atkārtotas iterācijas. Iegūsti vērtības vienreiz un kartē tās savās struktūrās.
  • Vairāki lieli payloads pēc kārtas: Importa darbos vai sinhronizācijas darba procesos ir vērts parsēšanu ieskaut skaidrā solī un pēc katra dokumenta atbrīvot visas atsauces, lai atmiņas pārvaldnieks var attīrīt. Tas ir vienkārši, bet servisos to bieži dara „blakus”.

Tāpēc „ātrs straumju parsētājs“ ar System.JSON bieži ir labs kompromiss: nolasīt efektīvi un pareizi, DOM izmantot apzināti, noteikt robežas. Ja tev vajadzīga īsta straumēšanas semantika (piem., apstrādāt masīva elementus pa vienam, neuzglabājot visu), System.JSON nav pareizā bāze.

Robustums darbībā: kļūdu scenāriji un kā tos nekavējoties padarīt saprotamus

JSON parsēšanas kļūdas žurnālos bieži nav noderīgas, jo norāda tikai pozīciju. Operācijām un atbalstam vajag kontekstu:

  • Baitu pozīcija pret rakstzīmju pozīciju: UTF-8 gadījumā tās nav identiskas. Ja parsētājs ziņo rakstzīmju pozīciju, baitu pozīcija var atšķirties. Baitu izdrukām izšķiroša ir baitu pozīcija.
  • Fragments ap kļūdas vietu: Žurnālojot kļūdu, ieraksti nelielu loga fragmentu ap pozīciju (piem., 40 rakstzīmju pirms/pēc), bet tikai, ja tajā nav sensitīvu datu. Alternatīvi: žurnālo tikai heksadecimāli.
  • Korelācija: Request-ID, Endpoint, Partner-ID, Payload-Hash. Pretējā gadījumā tu nekad vairs neatradīsi „to vienu kļūdu“.

Mērķis ir tāds, lai pēc produkcijas incidenta dažu minūšu laikā varētu atbildēt: „kodējums interpretēts nepareizi“, „payload saīsināts“, „serveris piegādā invalid JSON“ vai „mums ir mapping-problēma“.

UTF-8 bīstamību mērķtiecīga novēršana: kontrolsaraksts

  • Kodējumu vienmēr skaidri norādi: Nolasot no straumes un rakstot žurnālos/failos nepaļaujies uz noklusējumiem.
  • Neveido chunk→string konvertāciju: Ja lasi pa fragmentiem, tad krāj baitus vai izmanto dekoderi, kas buferē multibaitu secības.
  • BOM tolerants, bet atpazīstams: Pieņem BOM, bet spēj to atpazīt debug režīmā.
  • Uzstādi limitus: Maks. payload izmērs, maks. objekta/masīva dziļums (ja vari to kontrolēt), timeouts HTTP klientā.
  • Sadalītas atbildības: „Transporta lasīšana“ un „JSON parsēšana“ atsevišķi kapsulēt. Tā kļūdu novēršana notiek ātrāk un vēlāk var nomainīt parseri.
  • Kad patiešām atmaksājas pūles izstrādāt Stream-Parseri?

    Nav jāoptimizē katra JSON vieta. Šis piegājiens parasti atmaksājas, ja vismaz viens no šiem apstākļiem ir spēkā:

    • Lieli payloadi (vairāki MB) regulāri parādās vai var parādīties.
    • Ilgstoši procesi (Service, Worker) apstrādā daudz payloadu un tu novēro atmiņas pikus vai fragmentāciju.
    • Interop ar heterogēnām sistēmām: vairāki partneri, dažādas platformas, reizēm bojāti kodējumi.
    • Incidentu vēsture: jau bijuši „bojāti umlauti“, sporādiskas parse kļūdas vai grūti reproducējami importa pārtraukumi.

    Ja tavi payloadi ir mazi un nāk no kontrolēta avota, bieži pietiek ar vienkāršu risinājumu – bet arī tad: UTF-8 skaidri norādīt maksā maz un novērš nākotnes pārsteigumus.

    Atšķirība: kad tev vajadzīgs īsts streaming

    System.JSON ir DOM-orientēts. Ja tu datus patiešām vēlies apstrādāt „caurplūdes“ režīmā, piemēram, lielu masīvu elementu pa elementam bez pilnīgas glabāšanas atmiņā, tev vajadzēs citu parsera pieeju (Tokenizer/SAX). Tas nav vērtējums, bet arhitektūras lēmums:

    • DOM (System.JSON): ērti, labi tipiskiem biznesa objektiem, bet atmiņprasīgs.
    • Streaming/SAX: zemākas atmiņas prasības, piemērots ļoti lieliem datiem, bet prasa vairāk implementācijas darba un rūpīgāku kļūdu apstrādi.

    Saprātīgs kompromiss bieži ir: izveidot rūpīgu straumes apstrādi, kodējumu un limitus, un tikai pēc tam izlemt, vai DOM vēl der. Daudzos projektos tas vien stabilizē darbību būtiski.

    Secinājums: JSON in Delphi kļūst uzticams, ja tu kodējumu un straumes apstrādi traktē kā atsevišķu slāni

    Lielākā daļa problēmu ap „JSON in Delphi“ nav saistītas ar pašu JSON-parseri, bet gan ar nemanāmo posmu pirms tā: no straumes lasītie biti tiek pārvērsti tekstā. Ja tu tur skaidri apstrādā UTF-8, atpazīsti BOM gadījumus, neignorē chunk robežas un nosaki skaidrus izmēru limitus, tipiskās rakstzīmju kļūdas pazūd – un sporādiskās parse problēmas kļūst reproducējamas.

    System.JSON paliek pragmatisks standarts: ne ātrākais streaming-parseris, bet stabils, ja tu kontrolē ievadi un apzināti pieņem DOM izmaksas. Ja vēlies, varam kopīgi iziet cauri tavam konkrētajam import-/REST-ceļam un identificēt vietu, kur kodējums vai chunking izjūk: sazināties.

    Šajā tēmā svarīgi ir arī JSON Stream parseri. Raksts sakārto šos aspektus saprotami un rāda, kam ikdienā jāpievērš uzmanība.

    Projektu vai modernizācijas iniciatīvu ar Net-Base apspriest.

    Nākamais solis

    Ja no tēmas rodas reāls projekts, arhitektūru, esošo sistēmu un ekspluatāciju jāvērtē kopā jau agrīnā posmā.

    Mēs atbalstām ne tikai atsevišķu jautājumu risināšanā, bet arī tad, kad no avota koda fragmentiem, mantojuma sistēmu jautājumiem vai portāla idejām jāizveido stabils uzņēmuma līmeņa projekts.

    • Esošais stāvoklis, mērķa stāvoklis un tehniskie riski tiek kopīgi vērtēti.
    • REST, datu piekļuve, portāli un Rollout netiek pārcelti uz vēlākām fāzēm.
    • Jūs laikus redzat, kurš risinājums ir ekonomiski un darbības ziņā dzīvotspējīgs.

    Kopīgot ierakstu

    Kopīgot šo ierakstu tieši

    LinkedIn, X, XING, Facebook, WhatsApp un e-pasts ir nekavējoties pieejami. Instagramam mēs tūlīt sagatavojam saiti un īsu tekstu.

    E-pasts

    Instagram atveras jaunā cilnē. Saite un īss teksts tiek iepriekš nokopēti starpliktuvē.