Net-Base Magasin

13.08.2026

JSON i Delphi: Rask Stream-Parser med System.JSON + UTF-8-fallgruver ved spesialteikn

Når JSON-payloads i Delphi kjem direkte frå ein stream, blir «å berre parse» raskt eit produksjonsproblem: stort minnebehov, sporadiske parse-feil og øydelagde umlaut-teikn. Denne praksisartikkelen viser korleis du med System.JSON byggjer opp ein rask stream-parser...

13.08.2026

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

Abstrakte Grafik zeigt UTF-8-Mehrbytezeichen über Chunk-Grenzen und korrekte Pufferung im Decoder vor dem JSON-Parsing
Chunking er ufarlig – så lenge UTF-8-dekodaren buffrar fleirbyte-sekvensar over grenser.

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:

  1. Bytes aus dem Stream lesen (kontrolliert, ggf. mit Limit/Timeout im HTTP-Client).
  2. Decoding nach Unicode mit explizitem UTF-8 (BOM optional tolerieren).
  3. 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

Debugging-oppsett med byte-dump-trykk og arbeidsmaterial for å kontrollere UTF-8-byta og BOM i JSON-payloadar
For ryddig debugging tel byte-strømmen først: BOM, trunkering og ugyldige sekvensar blir då raskt synlege.

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

Schemahafte Darstellung einer Pipeline aus Stream, UTF-8-Decoding, Größenlimit und JSON-DOM-Parsing in Delphi
Ei tydeleg pipeline med grense og eksplisitt UTF-8 skil transport, dekoding og parsing klart.

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.
  • Separate ansvarsområde: „transportlesing“ og „JSON-parsing“ kapslast kvar for seg. Slik feilsøker ein raskare og kan seinare byte ut ein parser.
  • 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.

    Drøft prosjekt eller moderniseringsoppdrag med Net-Base.

    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.

    Del innlegg

    Del dette innlegget direkte

    LinkedIn, X, XING, Facebook, WhatsApp og e-post er straks tilgjengelege. For Instagram klargjer vi lenke og kort tekst med det same.

    E-post

    Instagram opnar i ein ny fane. Lenkje og kort tekst blir kopiert til utklippstavla på førehand.