Od teme magazina do projektne prakse
Povezane stranice usluga i tehnologije za članak
„JSON in Delphi“ zvuči kao riješen problem: System.JSON je uključen, REST-pozivi vraćaju tekst, gotovo. U praksi se pravi problemi pojavljuju tamo gdje JSON nije dostupan kao zgodan String, nego kao Stream: HTTP-Response-Stream, Datei-Stream, Named Pipe, Message-Queue ili veliki BLOB iz baze podataka. Tada se spoje tri stvari koje se u svakodnevnom radu često podcjenjuju: ponašanje memorije, kodiranje znakova (osobito UTF-8) i rubni slučajevi vezani uz posebne znakove.
Ovaj članak pokazuje čist, brz pristup za parsiranje JSON-a iz jednog TStream-a bez stvaranja nepotrebnih kopija – i prije svega bez tipičnih UTF-8 zamki zbog kojih se dijakritički znakovi „pokvare“ ili parseri povremeno izlaze s kriptičnim porukama. Fokus je na utjecaju na rad i stabilnost sučelja: reproducibilno debugiranje, jasne granice pristupa i kriteriji kada se troškovi doista isplate.
Zašto streamovi kod parsiranja JSON‑a u Delphi ponašaju drugačije
Dok god je JSON-dokument mali, put „čitati Stream u String, pa parsirati“ je praktičan. Ipak, iznad određene veličine payloada (tipično: velike liste, izvještaji, sinkronizacijski podaci, izvoz logova) to postaje skupo:
- Duplikati memorije: Čitaš bajtove u buffer, pretvaraš u Unicode-String (Delphi-String = UTF-16), parser interno stvara dodatne strukture. To može kratkoročno značiti više kopija.
- GC/Heap-pritisak: Mnogi privremeni Strings i JSON-vrijednosti povećavaju fragmentaciju i overhead alokacija, posebno u Longrunning-Prozessen (Services, Worker, Import-Jobs).
- Slika greške postaje nejasna: Ako se pri čitanju pogrešno pretpostavi enkodiranje, JSON-parser vidi samo „čudne znakove“ ili neočekivane kontrolne bajtove.
Važno je jasna separacija: JSON je formalno Unicode, na prijenosu je gotovo uvijek UTF-8. Delphi međutim interno radi s UTF-16. Prijelaz od bajtova (Stream) do znakova (String) je mjesto gdje nastaju problemi sa posebnim znakovima – ne u samom JSON-u.
System.JSON: Što radi dobro – i gdje trebaš paziti
System.JSON je u Delphi standard za DOM-bazirani JSON: dobiješ objektni model (TJSONObject, TJSONArray), možeš čitati vrijednosti, iterirati, serializirati. To je robustno za tipične poslovne integracije, ali ima dvije posljedice:
- Nije pravi streaming-parser: Objektni model se u potpunosti izgradi. Iako možda uštediš učitavanje u dodatni String, DOM i dalje ostaje memorijski intenzivan.
- Ulaz parsera je u pravilu tekst: Ovisno o Delphi-verziji i korištenom API-ju brzo se opet vratiš na String, uključujući konverziju enkodiranja.
Ako je tvoj cilj „brzi Stream-Parser“, u praksi obično misliš na jedno od dva: (1) nema nepotrebnih kopija i (2) moguće rano fail-fast kod oštećenih payloada. Oba su moguća sa System.JSON, pod uvjetom da kontroliraš razinu pretvorbe bajtova u tekst.
UTF-8-zamke kod posebnih znakova: tipični uzroci
Ako dijakritički znakovi (ä/ö/ü/ß) ili drugi posebni znakovi u rezultatu izgledaju krivo (ä, – itd.), to je gotovo uvijek problem neusklađenosti enkodiranja. U okruženju Delphi ti su uzroci posebno česti:
1) ANSI-Fallback durch „bequeme“ Helper
Neki načini čitanja implicitno prihvaćaju sistemsko ANSI-enkodiranje (codepage sustava Windows) ako nije proslijeđena eksplicitna kodiranje. To se primijeti tek kad payload ne sadrži samo ASCII. U testnim podacima često slučajno „prođe“, u produkciji se to pokaže na stvarnim imenima, lokacijama i slobodnom tekstu.
2) BOM-Verwirrung (Byte Order Mark)
UTF-8 može početi s BOM-om (bytes EF BB BF). U web-kontekstu BOM je prilično neuobičajen, ali se pojavljuje u datotekama. Neki čitači detektiraju BOM i prilagode enkodiranje, drugi ne ili samo u određenim načinima rada. Ako se BOM progura kao normalan znak u string, često vidiš nevidljivi „Zero Width No-Break Space“ na početku ili JSON-parser odmah zakaže na prvom tokenu.
3) Doppel-Konvertierung (UTF-8 wird „nochmal“ interpretiert)
Klasičan obrazac pogreške „ä“ umjesto „ä“ nastaje kad su UTF-8 bajtovi prvo ispravno dekodirani u Unicode, ali kasnije ponovno pogrešno interpretirani kao ANSI/UTF-8 bajtovi (ili obrnuto). U Delphi se to često događa kad je pretvorba između TBytes, RawByteString i string nejasna.
4) Trunkierung mitten in einem Multibyte-Zeichen
UTF-8 kodira posebne znakove u 2–4 bajta. Ako čitaš u chunkovima (npr. 8 KB) i granica chunka padne usred znaka, dekoder mora uredno međuspremiti preostale bajtove. Naivan pristup koji svaki chunk zasebno pretvara u string i potom ih spaja proizvodi nevažeće sekvence. To se može manifestirati kao „sporadična“ greška, ovisno o granicama paketa, ponašanju proxyja ili HTTP-chunkingu.
5) Falsche Annahmen aus HTTP-Headern
Kod REST izvor često navodi Content-Type: application/json; charset=utf-8. Međutim, neki serveri ne šalju charset, a neki šalju netočne podatke. Ako bez provjere koristiš zaglavlje, ponašanje može varirati ovisno o verziji backenda. Za operacije i podršku korisno je provjeriti stvarni tok bajtova i u slučaju greške zabilježiti ga u logu.
Ein sauberer Ansatz: Stream → UTF-8-Decoder → JSON-Parser
Robusna pipeline sastoji se od tri jasne faze:
- Čitanje bajtova iz streama (kontrolirano, po potrebi s limitom/timeoutom u HTTP-klijentu).
- Dekodiranje u Unicode s eksplicitnim UTF-8 (BOM opcionalno tolerirati).
- Parsiranje sa System.JSON u objektni model ili ciljanu ekstrakciju.
Najvažnija poluga je faza 2: ne želite da negdje „Default Encoding“ odlučuje. U Delphi to znači: TEncoding.UTF8 explizit setzen und nicht auf implizite Konvertierungen vertrauen.
Was „schnell“ hier konkret bedeutet
S System.JSON-om nećeš „optimizirati“ DOM. Ali možeš izbjeći:
- dodatnu kopiju cijele payload‑poruke kao privremeni string, ako interno ionako trebaš samo nekoliko vrijednosti (tad se više isplati drugi parser; o tome kasnije),
- višestruko prekodiranje,
- i možeš vrlo velike payloadove kontrolirano učitavati (s ograničenjem veličine i jasnom porukom o pogrešci), umjesto da završe s Out‑of‑Memory ili Access Violations.
Der konkrete Randfall: Sonderzeichen kaputt, aber nur manchmal
Jedan praktičan rubni slučaj je posebno neugodan: payload je u osnovi valjani UTF-8‑JSON, ali ga čitaš u chunkovima i svaki chunk konvertiraš u string. Dok se pojavljuje samo ASCII ne primijetiš ništa. Čim neki umlaut padne točno na granicu chunka, nastaju nevažeće UTF‑8 sekvence. Ishod: ili pokvareni znakovi ili parse‑pogreška na mjestu koje nema veze s pravim sadržajem.
Kako to prepoznati:
- Parse‑pogreške se pojavljuju „nasumično“ kod velikih odgovora, ne kod malih.
- Isti request ponekad prođe, ponekad ne (ovisno o chunkiranju/transportu).
- Hexdump bajtova pokazuje valjani UTF‑8, ali zabilježeni string sadrži Replacement Characters (�) ili klasični Mojibake.
Rješenje nije „više readln“ ili „veći buffer“ nego koristiti decoder koji ispravno buferira multibajtne sekvence preko granica chunkova. To je upravo trenutak kad TStreamReader u kombinaciji s UTF‑8‑Encoding može pomoći – pod uvjetom da ga ispravno inicijaliziraš.
Praxisleitfaden: UTF-8 in Delphi reproduzierbar prüfen
Prije nego što kreneš dirati parser, trebaš debug‑postavku koja ti prikazuje stvarni slijed bajtova. Za podršku i operacije to je neprocjenjivo, jer ćeš kasnije moći jasno reći isporučuje li suprotna strana krive podatke ili ih tvoja pipeline krivo dekodira.
1) Die ersten Bytes prüfen (BOM, JSON-Start)
Ako JSON dolazi s BOM‑om, na početku streama vidiš EF BB BF. Odmah nakon toga tipično treba doći „{“ ili „[“. Ako u stringu već stoji „“, onda BOM nije tretiran kao BOM nego je dekodiran kao tekst.
2) Rohbytes in Hex loggen – aber begrenzt
Ne logiraj kompletne payloadove u produkciji (zaštita podataka, troškovi, volumen logova). Dokazano korisno je:
- prefiks (npr. prvih 256 ili 1024 bajta),
- sufiks (posljednjih 256 bajta),
- i hash (SHA‑256) za korelaciju, ako trebaš uspoređivati payloadove.
Uz to često u par minuta razlučiš probleme sa posebnim znakovima: je li bajt‑niz za „ä“ ispravan (C3 A4)? Radi li se o trunciranju? Pojavljuje li se neočekivani 0x00 (null‑bajt), npr. zbog pogrešne pretpostavke UTF‑16?
3) Content-Type und Charset mitloggen
Pri HTTP/REST: zabilježi Content-Type i deklarirani charset. Ako su bajtovi očito UTF-8, ali charset tvrdi nešto drugo, klijent ne bi trebao slijepo vjerovati. Za JSON je UTF-8 de-facto standard. U sumnji: analiza bajtova nadjačava zaglavlje.
Brzi Stream-Parser s System.JSON: Dizajn bez nepotrebnih kopija
Praktičan obrazac je: čitaš iz streama u byte-bufer, iz njega izgradiš string točno jednom s UTF-8 i proslijediš taj string JSON-parseru. To nije „Streaming“ u smislu SAX, ali je to kontrolirana, visokoperformansna pipeline bez iznenadnih promjena enkodiranja.
Važno za arhitekturu: izgradi funkciju tako da centralno na jednom mjestu odlučuje:
- Koje pravilo enkodiranja vrijedi (obično UTF-8, BOM opcionalno)?
- Kolika maksimalna veličina payloada je dopuštena (DoS-zaštita, operativna granica)?
- Kako izgledaju poruke o pogrešci (s kontekstom, ali bez curenja podataka)?
Strategija čitanja: ograničeno bufferiranje umjesto „StreamToString“ bez limita
Ako prihvaćaš JSON iz vanjskih izvora (partneri, mobilni klijenti, treće strane), ograničenje veličine je obavezno. Bez limita jedan nesretan zahtjev može dovesti servis u stanje visokog opterećenja memorije. U praksi to znači: pri čitanju brojati zbroj bajtova i prekinuti nakon granice — s jasnim izuzetkom koji je čitljiv u monitoringu.
Zašto kod UTF-8 često očekujem „bez BOM-a“, ali bih BOM tolerirao
BOM je rijetko prisutan u REST-payloadima. U datotekama (eksporti, ručna obrada) pojavljuje se češće. Za robusne importne tokove smisleno je tolerirati BOM, ali ga učiniti vidljivim u logu, jer može ukazivati na „svijet datoteka“ umjesto „svijeta API-ja“.
Tihi ubojice: TStreamReader-zadane postavke i Text-Reader u miješanom radu
TStreamReader je praktičan, ali moraš čvrsto kontrolirati dvije stvari:
- Izričito postaviti enkodiranje: Ne očekuj da će ga „prepoznati”.
- Razumjeti bufferiranje: Reader interno buferira. Ako isti stream kasnije negdje drugdje ponovno čitaš, položaj je presudan. To zvuči trivijalno, ali u većim importnim pipelinama brzo postane izvor pogrešaka.
Posebno problematičan je miješani rad: prvo pročitati dio kao bajtove (npr. za logging ili magic-bajtove), a zatim nastaviti s TStreamReader. Ako pritom ne vratiš točno poziciju ili ne inicijaliziraš reader na ispravnoj poziciji, čitat ćeš od bajta 257 umjesto od 0. JSON-parser će tada prijaviti „Invalid character at position …“, iako je sama Payload ispravna.
Kad posebni znakovi unatoč UTF-8 ostaju „pokvareni“: Escaping vs. pravi Unicode
JSON može sadržavati posebne znakove na dva načina:
- Kao pravi UTF-8 znakovi (npr. „München“ kao bajtovi C3 BC …).
- Kao escape-sekvenca (npr. „Mu00fcnchen“).
Oboje je valjano. U praksi je važno: escape-sekvencije zaobilaze mnoge probleme prijenosa, ali samo prividno prikrivaju greške enkodiranja. Ako tvoj sustav negdje pogrešno interpretira bajtove, to je operativni rizik, a ne samo kozmetička greška. Osim toga, escape-sekvencije mogu zbuniti pri logiranju/monitoringu ako timovi očekuju „čitljiv tekst“.
System.JSON ti u oba slučaja na kraju isporučuje normalne Delphi-stringove (UTF-16), pod uvjetom da je put do tamo bio ispravan.
Realistična procjena performansi: troškovi DOM-a, veliki nizovi i selektivnost
Najveći utjecaj na performanse često nije „ubrzati parser“, nego manje parsirati. Sa System.JSON to je teško, jer dobivaš DOM. Tri tipične situacije:
- Veliki nizovi (10.000+ elemenata): Izgradnja DOM-a košta vrijeme i RAM. Ako ti trebaju samo 2 polja po elementu, parser sposoban za streaming (SAX/Tokenizer) često je smisleniji. System.JSON nije za to namijenjen.
- Pojedinačni objekti s mnogo polja: Ako ti trebaju samo nekoliko polja, možeš i dalje koristiti DOM, ali izbjegavaj višestruko traversiranje. Dohvati vrijednosti jednom i mapiraj ih u svoje strukture.
- Više velikih Payloada zaredom: U Import-Jobs ili Sync-Worker scenarijima isplati se kapsulirati parsiranje u jasan korak i nakon svakog dokumenta osloboditi sve reference, kako bi Memory-Manager mogao očistiti. To je banalno, ali u servisima se rado radi „usput“.
Ein „schneller Stream-Parser“ mit System.JSON ist daher oft ein guter Kompromiss: Einlesen effizient und korrekt, DOM bewusst nutzen, Grenzen definieren. Wenn du echte Streaming-Semantik brauchst (z. B. Array-Elemente nacheinander verarbeiten, ohne alles zu halten), ist System.JSON nicht die richtige Basis.
Robusnost u pogonu: obrasci pogrešaka i kako ih odmah učiniti dohvatljivima
Pogreške pri parsiranju JSON-a u logovima često nisu korisne jer navode samo poziciju. Za operacije i podršku treba ti kontekst:
- Pozicija u bajtovima vs. pozicija u znakovima: Kod UTF-8 to nije isto. Ako parser prijavi poziciju u znakovima, pozicija u bajtovima može biti drugačija. Za byte-dumpove je presudna pozicija u bajtovima.
- Isječak oko mjesta pogreške: U slučaju greške logiraj mali prozor oko pozicije (npr. 40 znakova prije/nakon), ali samo ako ne sadrži osjetljive podatke. Alternativno: logiraj samo heksadecimalno.
- Korelacija: Request-ID, Endpoint, Partner-ID, Payload-Hash. Inače nikad nećeš ponovno pronaći „onu jednu grešku“.
Cilj je da nakon produkcijskog incidenta unutar nekoliko minuta možeš odgovoriti: „enkodiranje pogrešno interpretirano“, „Payload odsječen“, „server vraća nevaljan JSON“ ili „imamo problem s mapiranjem“.
Namjerno izbjegavanje zamki UTF-8: kontrolna lista
- Uvijek eksplicitno odrediti encoding: Prilikom čitanja iz streama i prilikom zapisivanja u logove/datoteke ne oslanjaj se na zadane postavke.
- Bez konverzije chunk→string: Ako čitaš u chunkovima, skupljaj bajtove ili koristi decoder koji bufferira multibajtne sekvence.
- BOM tolerantan, ali vidljiv: Prihvati ga, ali moraš ga moći detektirati pri debugiranju.
- Postavi limite: Max. veličina Payloada, max. dubina objekta/niza (ako to možeš kontrolirati), Timeouts u HTTP-klijentu.
Wann lohnt sich der Aufwand für einen Stream-Parser wirklich?
Ne moraš optimizirati svako mjesto gdje se pojavi JSON. Pristup se obično isplati kada vrijedi barem jedno od sljedećeg:
- Veliki payloadi (nekoliko MB) pojavljuju se redovito ili mogu nastati.
- Dugotrajni procesi (Service, Worker) obrađuju mnogo payloada i uočavaš memorijske skokove (Memory‑Spikes) ili fragmentaciju.
- Interoperabilnost s heterogenim sustavima: više partnera, različite platforme, povremeno neispravna enkodiranja.
- Povijest incidenata: već je bilo „pokvarenih dijakritičkih znakova“, sporadičnih grešaka parsiranja ili teško reproducirajućih prekida uvoza.
Ako su tvoji payloadi mali i dolaze iz kontroliranog izvora, često je dovoljan jednostavan pristup – ali i tada: izričito postaviti UTF‑8 gotovo ništa ne košta i sprječava kasnije iznenađenja.
Abgrenzung: Wenn du echtes Streaming brauchst
System.JSON je orijentiran na DOM. Ako želiš podatke zaista „u prolazu“ obraditi, npr. veliko polje element po element bez držanja cijelog u memoriji, treba ti drugi pristup parsera (Tokenizer/SAX). To nije vrijednosni sud, nego arhitektonska odluka:
- DOM (System.JSON): praktičan, dobar za tipične poslovne objekte, ali memorijski zahtjevan.
- Streaming/SAX: niska potrošnja memorije, dobar za vrlo velike podatke, ali veći implementacijski napor i zahtjevnije upravljanje greškama.
Često je razuman kompromis: uredno izgraditi rukovanje streamom, enkodiranje i limitiranja, pa tek onda odlučiti odgovara li DOM. U mnogim projektima upravo to značajno stabilizira rad u produkciji.
Fazit: JSON in Delphi wird zuverlässig, wenn du Encoding und Streams als eigene Schicht behandelst
Većina problema vezanih uz „JSON in Delphi“ nema veze s JSON‑parserom kao takvim, nego s neupadljivim dijelom prije njega: bajtovi iz streama pretvaraju se u tekst. Ako tamo izričito rukuješ UTF‑8, prepoznaješ slučajeve s BOM‑om, ne ignoriraš granice chunkova i postaviš jasne veličinske limite, tipične pogreške sa znakovima nestaju – a sporadične greške parsiranja postaju reproducibilne.
System.JSON ostaje pragmatičan standard: nije najbrži streaming‑parser, ali je solidan ako kontroliraš ulaz i svjesno prihvaćaš troškove DOM‑a. Ako želiš, možemo zajedno proći tvoj konkretni Import-/REST‑put i identificirati mjesto na kojem enkodiranje ili chunking zakaže: Kontaktirajte nas.
Za ovu temu važni su i JSON stream parseri. Članak kontekstualizira ove aspekte razumljivo i pokazuje na što treba paziti u praksi.
Razgovarajte o projektu ili modernizacijskom pothvatu s Net-Base.
sljedeći korak
Ako se tema pretvori u stvarni projekt, arhitekturu, postojeće sustave i operacije trebalo bi rano zajednički razmotriti.
Podržavamo vas ne samo u pojedinačnim pitanjima, već i kada iz isječaka izvornog koda, naslijeđenih sustava ili ideja za portale treba nastati pouzdan poslovni projekt.
- Postojeće stanje, ciljna slika i tehnički rizici procjenjuju se zajedno.
- REST, pristup podacima, portali i rollout neće biti odgođeni kao naknadne posljedice.
- Rano prepoznajete koji je put ekonomski i operativno održiv.