Frå magasinetema til prosjektpraksis
Passande teneste- og tekniske sider til innlegget
„JSON in Delphi“ høyrest ut som eit løyst problem: System.JSON er om bord, REST-Calls leverer tekst, ferdig. I praksis oppstår dei reelle feila der JSON ikkje ligg som ein komfortabel streng, men som ein stream: HTTP-respons-stream, filstream, named pipe, meldingskø eller ein stor BLOB frå databasen. Då kjem tre forhold saman som gjerne blir undervurderte i kvardagen: minnehandsaming, teiknkoding (særleg UTF-8) og randtilfelle knytt til spesialteikn.
Denne artikkelen viser ein rein og rask tilnærming for å parse JSON frå eit TStream utan unødvendige kopiar — og særleg utan dei vanlege UTF-8-fellene der vokalar «blir øydelagde» eller parserar sporadisk feilar med kryptiske meldingar. Fokus ligg på konsekvensar for drift og grensesnittstabilitet: reproducerbar debugging, klare avgrensingar i tilnærminga og kriterium for når innsatsen verkeleg lønar seg.
Kvifor streamar oppfører seg annleis ved JSON-parsing i Delphi
Så lenge eit JSON-dokument er lite, er vegen «les stream til streng, så parse» praktisk. Ved ei viss payload-storleik (typisk: store lister, rapportar, synk-data, log-eksportar) blir dette derimot kostbart:
- Minne-duplikat: Du les byte inn i ein buffer, konverterer til ein Unicode-streng (Delphi-String = UTF-16), og parseren opprettar internt ytterlegare datastrukturar. Dette kan kortvarig gi fleire kopiar.
- GC-/heap-trykk: Mange midlertidige strengar og JSON-verdiar aukar fragmentering og allokerings-overhead, spesielt i langkøyrande prosessar (tenester, worker-prosessar, importjobbar).
- Feilbildet blir uklart: Dersom innlesinga byggjer på feil kodingsforutsetnader, ser JSON-parseren berre «merkelege teikn» eller uventa kontrollbyte.
Viktig er ei klar skilje: JSON er formelt Unicode, på lina er det nesten alltid UTF-8. Delphi arbeider internt derimot med UTF-16. Overgangen frå byte (stream) til teikn (streng) er staden der problem med spesialteikn oppstår — ikkje i JSONet i seg sjølv.
System.JSON: Det det kan godt — og kva du må passe på
System.JSON er i Delphi standarden for DOM-basert JSON: du får eit objektmodell (TJSONObject, TJSONArray), kan hente verdiar, iterere og serialisere. Det er robust for typiske business-integrasjonar, men har to konsekvensar:
- Det er ikkje ein ekte streaming-parser: Objektmodellen blir bygd opp i sin heilskap. Du sparar kanskje innlesinga til ein ekstra streng, men DOM-en er framleis minnekrevjande.
- Parser-inndata er som oftast tekst: Avhengig av Delphi-versjon og vald API endar ein raskt opp med streng att, inklusive koding-konvertering.
Om målet ditt er ein «rask stream-parser», meiner du i praksis oftast to ting: (1) inga unødvendige kopiar og (2) så tidleg som mogleg fail-fast ved øydelagde payloads. Begge deler er mogleg med System.JSON, så lenge du kontrollerer steget frå byte til tekst.
UTF-8-fellene ved spesialteikn: dei typiske årsakene
Om umlautar (ä/ö/ü/ß) eller andre spesialteikn i resultatet ser feil ut (ä, – osv.), er det nesten alltid eit Encoding-Mismatch. I Delphi-sammanheng er desse årsakene særleg vanlege:
1) ANSI-fallback frå «praktiske» hjelpefunksjonar
Nokre lesevegar antar stilletiande system-ANSI-encoding (codepage på Windows-systemet) dersom ingen eksplisitt koding blir levert. Det blir først synleg når eit payload inneheld meir enn bare ASCII. I testdata er det ofte tilfeldig «ok», i produksjon blir det problem ved verkelege namn, stadnamn og fritekst.
2) BOM-forvirring (Byte Order Mark)
UTF-8 kan starte med BOM (Bytes EF BB BF). I web-samanheng er BOM mindre vanleg, men i filer førekjem det. Nokre reader gjenkjenner BOM og justerer kodinga, andre gjer det ikkje eller berre i visse modus. Hvis eit BOM hamnar inn i strengen som eit vanleg teikn, ser du ofte eit usynleg «Zero Width No-Break Space» i starten, eller JSON-parsaren feilar direkte på det første tokenet.
3) Dobbel-konvertering (UTF-8 blir «på nytt» tolka)
Det klassiske feilbildet «ä» i staden for «ä» oppstår når UTF-8-Bytes først er korrekt dekodet til Unicode, men seinare feilinterpretert att som ANSI/UTF-8-Bytes (eller omvendt). I Delphi skjer dette ofte når det er uklår konvertering mellom TBytes, RawByteString og string.
4) Avkorting midt i eit fleirbyte-teikn
UTF-8 koder spesialteikn i 2–4 Bytes. Dersom du les i Chunks (t.d. 8 KB) og chunk-grensa ligg midt i teiknet, må dekodaren buffre dette korrekt. Ein naiv tilnærming som konverterer kvar Chunk for seg til String og limer dei saman, skapar ugyldige sekvensar. Dette kan framstå som eit sporadisk feil, avhengig av pakkegrenser, proxy-åtferd eller HTTP-Chunking.
5) Feil anta frå HTTP-Headern
For REST er kjelda ofte Content-Type: application/json; charset=utf-8. Nokre serverar sender derimot inga charset, andre oppgir feil informasjon. Dersom du blindt stoler på headeren, kan det skifte med backend-versjonar. For drift og support er det nyttig å undersøkje den faktiske Byte-Streamen og logge ved feil.
Ein robust tilnærming: Stream → UTF-8-Decoder → JSON-Parser
Den robuste Pipeline består av tre klare Stufen:
- Bytes aus dem Stream lesen (kontrolliert, ggf. mit Limit/Timeout im HTTP-Client).
- Decoding nach Unicode mit explizitem UTF-8 (BOM optional tolerieren).
- Parsing mit System.JSON in ein Objektmodell oder in gezielte Extraktion.
Der wichtigste Hebel ist Stufe 2: Du willst nicht, dass irgendwo „Default Encoding“ entscheidet. In Delphi bedeutet das: TEncoding.UTF8 explizit setzen und nicht auf implizite Konvertierungen vertrauen.
Kva «raskt» konkret tyder her
Med System.JSON vil du ikkje «fjerne» DOM-en. Men du kan unngå:
- ei ekstra kopi av heile payloaden som ein mellomstreng, når du internt uansett berre treng få verdiar (då lønner det seg heller med ein annan parser; meir om det seinare),
- gjentatt omkoding,
- og du kan lese svært store payloadar kontrollert inn (med storleiksgrense og klar feilmelding), i staden for å hamne i Out-of-Memory eller Access Violations.
Det konkrete randtilfellet: spesialteikn øydelagde, men berre av og til
Eit praksistilfelle er særleg snedig: payloaden er i utgangspunktet gyldig UTF-8-JSON, men du les ho i chunks og konverterer kvar chunk til ein String. Så lenge det berre er ASCII, legg du ikkje merke til det. Så snart ein Umlaut ligg nøyaktig på ei chunk-grense, oppstår ugyldige UTF-8-sekvensar. Resultat: anten øydelagde teikn eller ein parse-feil på eit punkt som ikkje svarar til det faktiske innhaldet.
Slik kjenner du det igjen:
- Parse-feil oppstår «tilfeldig» ved store svar, ikkje ved små.
- Den same requesten lykkast av og til, og av og til ikkje (avhengig av chunking/transport).
- Ein hexdump av bytea viser gyldig UTF-8, men den logga strengen din inneheld Replacement Characters (�) eller klassisk Mojibake.
Løysinga er ikkje å bruke «mer zu readln» eller «større Buffer», men å bruke ein dekoder som korrekt bufre multibyte-sekvensar over chunk-grenser. Dette er nøyaktig der TStreamReader i kombinasjon med ein UTF-8-Encoding kan vere nyttig – dersom du initialiserer han korrekt.
Praktisk rettleiing: UTF-8 i Delphi reproduserbart kontrollere
Før du endrar parseren, treng du eit debugging-oppsett som gjer den faktiske bytefølga synleg. For support og drift er dette svært verdifullt, fordi du seinare klart kan seie om motparten leverer feil data eller om pipelines dekoding er feil.
1) Dei første bytea kontrollere (BOM, JSON-start)
Dersom JSON-en kjem med BOM, ser du i starten av straumen EF BB BF. Rett etter bør typisk «{» eller «[» følgje. Dersom «ï»¿» allereie står i strengen, vart BOM ikkje handsama som BOM, men dekoda som tekst.
2) Råbyte i hex logge – men avgrensa
Logg ikkje komplette payloadar i produksjon (personvern, kostnader, logg-volum). Nyttig har vist seg å vere:
- Prefiks (t.d. dei første 256 eller 1024 byte),
- Suffiks (dei siste 256 byte),
- og ein Hash (SHA-256) for korrelasjon, når du treng å samanlikne payloadar.
Med dette kan du ofte plassere problem med spesialteikn på nokre minutt: Er bytefølga for «ä» korrekt (C3 A4)? Er det trunkering? Dukkar eit uventa 0x00 (nullbyte) opp, t.d. på grunn av feilaktig antaing om UTF-16?
3) Content-Type und Charset mitloggen
Ved HTTP/REST: Logg den Content-Type og det deklarerte charset. Om bytea tydeleg er UTF-8, men charset påstår noko anna, bør du ikkje følgje dette blint i klienten. For JSON er UTF-8 de facto-standard. Ved tvil: byte-analyse slår header.
Rask Stream-Parser mit System.JSON: Design ohne unnötige Kopien
Eit praksistilpassa mønster er: Du les frå streamen inn i ein byte-puffer, byggjer ein String nøyaktig éin gong med UTF-8, og sender denne String-en til JSON-parsaren. Det er ikkje «streaming» i tydinga av SAX, men det er ein kontrollert, høgeffektiv pipeline utan overraskande enkodingsskifte.
Viktig for arkitekturen: Bygg funksjonen slik at den avgjer sentralt på eitt punkt:
- Kva encoding-regel gjeld (vanlegvis UTF-8, BOM valfritt)?
- Kor stor kan payload vere maksimalt (DoS-vern, driftsgrense)?
- Korleis ser feilmeldingane ut (med kontekst, men utan datalekkasje)?
Innlesings-Strategi: Avgrensa puffring i staden for «StreamToString» utan limit
Dersom du tek imot JSON frå eksterne kjelder (partnarar, mobile clients, tredjepartar), er ein storleiksgrense obligatorisk. Uten grense kan ein einskild uheldig request setje ein teneste under minnepress. Praktisk tyder dette: under lesing tel du summen av byte og avbryt ved ei grense – med ein tydeleg Exception som er forståeleg i overvakinga.
Kvifor eg ofte forventar UTF-8 «utan BOM», men vil tolerere BOM
I REST-payloads opptrer BOM sjeldan. I filer (eksportar, manuell redigering) førekjem det oftare. For robuste importstraumar er det fornuftig å tolerere BOM, men synleggjer det i loggen, fordi det kan vere eit teikn på «fil-verda» i staden for «API-verda».
Dei stille killer: TStreamReader-Defaults und Text-Reader im Mischbetrieb
TStreamReader er praktisk, men du må ha to ting under kontroll:
- Sett encoding eksplisitt: Ikkje håp at han «gjenkjenner» det.
- Forstå buffering: Readeren buffrar internt. Dersom du seinare les same stream ein gong til andre stader, er posisjonen relevant. Det høyrast trivielt ut, men i større importpipar blir det raskt ei feilkjelde.
Særleg ubehageleg er kombinert bruk: Først lesa ein del som bytes (t.d. for logging eller magic-bytes), deretter fortsetje med TStreamReader. Dersom du ikkje spolar tilbake ordentleg eller initialiserer readeren på riktig posisjon, les du frå byte 257 i staden for frå 0. JSON-parsaren vil då melde «Invalid character at position …», sjølv om payloaden i seg sjølv er korrekt.
Når særteikn trass i UTF-8 framleis er „kaputt“: Escaping vs. ekte Unicode
JSON kan innehalde særteikn på to måtar:
- Som ekte UTF-8-teikn (t. d. „München“ som bytes C3 BC …).
- Som escape-sekvens (t.d. „Mu00fcnchen“).
Begge er gyldige. For praksis er det viktig: Escape-sekvensar kringgår mange transportproblem, men dei tildekkjer berre skjønnleg encoding-feil. Hvis systemet ditt ein stad tolkar byte feil, er det eit driftsrisiko, ikkje berre ein kosmetisk bug. I tillegg kan escape-sekvensar forvirre ved logging/monitorering når team forventar «lesbar tekst».
System.JSON leverer deg i begge tilfelle til slutt normale Delphi-strengar (UTF-16), så lenge vegen fram til dit var korrekt.
Ytelse — realistisk vurdering: DOM-kostnader, store arrays og selektivitet
Den største ytelsesspaken er ofte ikkje å «gjere parseren raskare», men å parse mindre. Med System.JSON er det vanskeleg, fordi du får eit DOM. Tre typiske situasjonar:
- Store arrays (10.000+ element): Oppbygging av DOM kostar tid og RAM. Hvis du berre treng 2 felt per element, er ein streaming-vennleg parser (SAX/Tokenizer) ofte meir hensiktsmessig. System.JSON er ikkje laga for dette.
- Enkelobjekt med mange felt: Dersom du berre treng eit par felt, kan du framleis bruke DOM, men unngå å gå gjennom treet fleire gonger. Hent verdiane éin gong og map dei inn i strukturane dine.
- Fleire store payloads på rad: I importjobbar eller sync-workerar løner det seg å kapsle parsing i eit klart steg og frigje alle referansar etter kvart dokument slik at minnehandsamaren kan rydde opp. Det er banalt, men i tenester blir det gjerne gjort «ved sidan av».
Ein «rask stream-parser» saman med System.JSON er derfor ofte eit godt kompromiss: Lesing effektivt og korrekt, bruk DOM medvite, definer grenser. Treng du ekte streaming-semantikk (t.d. handtere array-element eitt om gongen utan å bufre alt), er ikkje System.JSON eit heldig utgangspunkt.
Robustheit i drift: Feilmønster og korleis du gjer dei umiddelbart handfaste
JSON-parse-feil i loggar hjelper sjeldan fordi dei ofte berre oppgir ei posisjon. For drift og support treng du kontekst:
- Byte-posisjon vs. teikn-posisjon: Ved UTF-8 er desse ikkje det same. Når parseren rapporterer ei teiknposisjon, kan byte-posisjonen avvike. For byte-dumps er byte-posisjon avgjerande.
- Utdrag rundt feilstaden: Logg ved feilsituasjon eit lite vindu rundt posisjonen (t.d. 40 teikn før/etter), men berre når det ikkje finst sensitive data. Alternativt: logg kun heksadesimalt.
- Korrelasjon: Request-ID, Endpoint, Partner-ID, Payload-Hash. Elles finn du «den eine feilen» aldri att.
Målet er at du etter ein produksjonshending innan få minutt skal kunne svare: «Encoding tolka feil», «Payload avkappa», «Server leverer invalid JSON» eller «vi har eit mapping-problem».
Unngå UTF-8-fallgruver målretta: Sjekkliste
- Angi encoding alltid eksplisitt: Ved lesing frå stream og ved skriving til loggar/filer, ikkje stol på standardinnstillingar.
- Ikkje konverter chunk til streng: Når du les chunked, samle byte eller bruk ein dekoder som buffrar multibyte-sekvensar.
- BOM-tolerant, men synleg: Godta BOM, men gjer ho synleg i feilsøking.
- Sett grenser: Maks. payload-storleik, maks. objekt-/array-djupn (dersom du kan kontrollere det), timeouts i HTTP-klienten.
Når lønner innsatsen for ein Stream-Parser seg verkeleg?
Du treng ikkje optimalisere kvar einaste JSON-stad. Tilnærminga løner seg typisk når minst éin av følgjande punkt gjeld:
- Store payloads (fleire MB) førekjem jamleg eller kan oppstå.
- Langkøyrande prosessar (Service, Worker) behandlar mange payloads og du observerer minne-spikar eller fragmentering.
- Interop med heterogene system: fleire partnarar, ulike plattformer, av og til feilaktige kodingar.
- Incident-historikk: Det har allereie vore „øydelagde umlaut-teikn“, sporadiske parse-feil eller vanskeleg reproducerbare importavbrot.
Om payloadane dine er små og kjem frå ein kontrollert kjelde, held ofte ein enkel løysing — men sjølv då: å eksplisitt setje UTF-8 kostar nesten ingenting og forhindrar seinare overraskingar.
Avgrensing: Når du treng ekte streaming
System.JSON er DOM-orientert. Dersom du verkeleg vil prosessere data «i gjennomløp», t.d. eit stort array element for element utan å halde det heilt i minnet, treng du ein annan parser-tilnærming (Tokenizer/SAX). Dette er ikkje eit verdijudgment, men ei arkitekturoversikt:
- DOM (System.JSON): praktisk, godt for typiske forretningsobjekt, men minnekrevjande.
- Streaming/SAX: lågt minneforbruk, godt for svært store datasett, men meir implementasjonsarbeid og meir grundig feilhandsaming.
Eit fornuftig kompromiss er ofte å byggje god stream-handtering, koding og grenser som eigne, ryddige komponentar, og så avgjere om DOM framleis passar. I mange prosjekt stabiliserer dette drifta mykje aleine.
Konklusjon: JSON in Delphi blir påliteleg, om du behandlar koding og streams som eige lag
Dei fleste problema kring „JSON in Delphi“ handlar ikkje om JSON-parsaren i seg sjølv, men om den lite synlege strekninga før: byte frå ein stream blir omsette til tekst. Dersom du der handterer UTF-8 eksplisitt, gjenkjenner BOM-tilfelle, ikkje ignorerer chunk-grenser og set tydelege storleiksgrenser, forsvinn dei typiske spesialteikn-feila — og sporadiske parse-problem blir reproducerbare.
System.JSON held fram som ein pragmatisk standard: ikkje den raskaste streaming-parsaren, men solid når du kontrollerer inputen og medvite aksepterer DOM-kostnadene. Om du ønskjer, kan vi gå gjennom din konkrete import-/REST-veg saman og identifisere staden der koding eller chunking sviktar: Ta kontakt.
For dette temaet er også JSON Stream Parser viktige. Innlegget set desse aspekta i systematisk samanheng og viser kva som betyr noko i praksis.
neste steg
Når temaet blir eit reelt prosjekt, bør arkitektur, eksisterande system og drift tidleg saman vurderast.
Vi støttar ikkje berre ved enkeltspørsmål, men òg når korte kildekodesnuttar, legacy-tema eller portalidéar skal utviklast til eit robust bedriftsprosjekt.
- Eksisterande tilstand, målbiletet og tekniske risikoar blir vurderast samla.
- REST, datatilgang, portalar og utrulling blir ikkje utsett til seinare fasar.
- De ser tidleg kva veg som er økonomisk og driftsmessig berekraftig.