Net-Base Rivista

13.08.2026

JSON in Delphi: Parser di stream veloce con System.JSON + insidie UTF-8 legate ai caratteri speciali

Quando i payload JSON in Delphi arrivano direttamente da un stream, il 'parsing al volo' si trasforma rapidamente in un problema di produzione: elevato fabbisogno di memoria, errori di parsing sporadici e segni diacritici corrotti. Questo contributo pratico mostra come, con System.JSON, costruire un parser di stream performante...

13.08.2026

Dal tema della rivista alla pratica di progetto

Pagine di servizi e tecniche correlate all'articolo

„JSON in Delphi“ sembra un problema risolto: System.JSON è a bordo, REST-call restituiscono testo, fatto. Nella pratica però gli errori reali emergono dove il JSON non è disponibile come una comoda stringa, ma come stream: HTTP-Response-Stream, Datei-Stream, Named Pipe, Message-Queue o un grande BLOB dal database. A quel punto si combinano tre aspetti spesso sottovalutati nella quotidianità: comportamento della memoria, codifica dei caratteri (in particolare UTF-8) e casi limite legati ai caratteri speciali.

Questo contributo mostra un approccio pulito e veloce per effettuare il parsing di JSON da un TStream senza generare copie non necessarie — e soprattutto evitando le tipiche trappole UTF-8 in cui le Umlaute vengono „danneggiate“ o i parser terminano sporadicamente con messaggi criptici. Il focus è sugli effetti per l’operatività e la stabilità delle interfacce: debugging riproducibile, confini chiari dell’approccio e criteri per stabilire quando l’impegno è realmente giustificato.

Perché gli stream si comportano diversamente nel parsing JSON in Delphi

Finché un documento JSON è piccolo, la strada „leggere lo stream in una stringa, poi fare il parse“ è comoda. Superata una certa dimensione del payload (tipico: liste grandi, report, dati di sync, esportazioni di log) però diventa costosa:

  • Duplicati in memoria: Leggi byte in un buffer, converti in una stringa Unicode (stringa Delphi = UTF-16), il parser crea internamente ulteriori strutture. Questo può comportare temporaneamente molteplici copie.
  • Pressione su GC/heap: Molte stringhe temporanee e valori JSON aumentano la frammentazione e l’overhead di allocazione, specialmente in processi long-running (Services, Worker, Import-Jobs).
  • Il quadro degli errori diventa poco chiaro: Se durante la lettura vengono fatte assunzioni errate sull’encoding, il parser JSON vede solo „caratteri strani“ o byte di controllo inattesi.

È importante una separazione chiara: JSON è formalmente Unicode, sulla linea è quasi sempre UTF-8. Delphi però lavora internamente con UTF-16. Il passaggio da byte (Stream) a caratteri (String) è il punto in cui sorgono i problemi con i caratteri speciali — non nel JSON stesso.

System.JSON: cosa sa fare bene — e dove devi fare attenzione

System.JSON è in Delphi lo standard per JSON basato su DOM: ottieni un modello a oggetti (TJSONObject, TJSONArray), puoi interrogare valori, iterare, serializzare. Questo è robusto per integrazioni business tipiche, ma comporta due conseguenze:

  • Non è un vero parser streaming: Il modello a oggetti viene costruito per intero. Puoi risparmiare eventualmente la lettura in una stringa separata, ma il DOM rimane esoso in termini di memoria.
  • L’input del parser è generalmente testo: A seconda della versione di Delphi e dell’API usata, ci si ritrova rapidamente di nuovo con la stringa, inclusa la conversione di encoding.

Se il tuo obiettivo è un „parser di stream veloce“, nella pratica intendi solitamente una di due cose: (1) nessuna copia inutile e (2) il fail-fast il più presto possibile su payload corrotti. Entrambe le cose sono ottenibili con System.JSON, a condizione che tu controlli lo stadio byte-verso-testo.

Insidie di UTF-8 con i caratteri speciali: le cause tipiche

Grafico astratto che mostra caratteri UTF-8 multibyte oltre i confini dei chunk e un buffering corretto nel decoder prima del parsing JSON
Il chunking è innocuo — finché il decoder UTF-8 mette in buffer le sequenze multibyte oltre i confini.

Se le Umlaut (ä/ö/ü/ß) o altri caratteri speciali nel risultato appaiono errati (ä, – ecc.), si tratta quasi sempre di un mismatch di encoding. Nell’ambito Delphi queste cause sono particolarmente frequenti:

1) Fallback ANSI causato da „comodi“ Helper

Alcuni percorsi di lettura assumono tacitamente l’encoding ANSI di sistema (codepage del sistema Windows) quando non viene fornita una codifica esplicita. Questo emerge solo quando un payload contiene più di ASCII. Nei dati di test spesso è casualmente „okay“, in produzione si manifesta con nomi, luoghi e testi liberi reali.

2) Confusione sul BOM (Byte Order Mark)

UTF-8 può iniziare con un BOM (byte EF BB BF). Nel contesto web il BOM è piuttosto raro, ma nei file capita. Alcuni reader riconoscono il BOM e adattano la codifica, altri no o solo in modalità specifiche. Se un BOM finisce nella stringa come carattere normale, spesso si vede un invisibile „Zero Width No-Break Space“ all’inizio o il parser JSON fallisce già sul primo token.

3) Doppia conversione (UTF-8 viene „di nuovo“ interpretato)

Il tipico errore „ä“ al posto di „ä“ si verifica quando i byte UTF-8 vengono prima decodificati correttamente in Unicode, ma poi reinterpretati come byte ANSI/UTF-8 (o viceversa). In Delphi questo accade spesso quando la conversione tra TBytes, RawByteString e string non è chiara.

4) Troncamento a metà di un carattere multibyte

UTF-8 codifica i caratteri speciali in 2–4 byte. Se leggi a chunk (p.es. 8 KB) e il confine del chunk cade a metà di un carattere, il decoder deve mettere correttamente in buffer quei byte. Un approccio ingenuo che converte ogni chunk separatamente in string e li concatena produce sequenze invalide. Questo può manifestarsi come un errore „sporadico“, dipendente dai confini dei pacchetti, dal comportamento del proxy o dall’HTTP-Chunking.

5) Assunzioni errate dagli header HTTP

Nell’ambito di REST la fonte spesso dichiara Content-Type: application/json; charset=utf-8. Alcuni server però non forniscono il charset, altri danno informazioni sbagliate. Se ti affidi ciecamente all’header, questo può variare a seconda della versione del backend. Per il funzionamento e il supporto è utile verificare lo stream di byte reale e loggarlo in caso di errore.

Un approccio pulito: Stream → Decoder UTF-8 → Parser JSON

La pipeline robusta consiste in tre fasi chiare:

  1. Leggere i byte dallo stream (controllato, eventualmente con limit/timeout nel client HTTP).
  2. Decodifica in Unicode con UTF-8 esplicito (tollerare il BOM opzionalmente).
  3. Parsing con System.JSON in un modello a oggetti o in estrazioni mirate.

La leva più importante è la fase 2: non vuoi che da qualche parte decida il „Default Encoding“. In Delphi questo significa: impostare esplicitamente TEncoding.UTF8 e non affidarsi a conversioni implicite.

Cosa significa concretamente „schnell“ qui

Con System.JSON non eliminerai il DOM. Ma puoi evitare:

  • una copia aggiuntiva dell’intera payload come stringa intermedia, quando internamente ti servono comunque solo pochi valori (in quel caso è più sensato usare un altro parser; ne parleremo più avanti),
  • ricodifiche ripetute,
  • e puoi leggere in modo controllato payload molto grandi (con limite di dimensione e messaggio d’errore chiaro), invece di terminare con Out-of-Memory o Access Violations.

Il caso limite concreto: caratteri speciali corrotti, ma solo a volte

Un caso limite pratico è particolarmente insidioso: la payload è di per sé un JSON UTF-8 valido, ma la leggi a blocchi e converti ogni blocco in String. Finché compare solo ASCII non noti nulla. Non appena un Umlaut si trova esattamente al confine di un blocco, si creano sequenze UTF-8 non valide. Risultato: o caratteri corrotti o un errore di parsing in una posizione che non corrisponde al contenuto reale.

Come riconoscerlo:

  • Gli errori di parsing si verificano „a caso“ su risposte grandi, non su quelle piccole.
  • La stessa richiesta funziona a volte e altre no (a seconda del chunking/trasporto).
  • Un hexdump dei byte mostra UTF-8 valido, ma la stringa che hai loggato contiene Replacement Characters (�) o il classico Mojibake.

La soluzione non è usare „più readln“ o „buffer più grandi“, ma un decoder che metta correttamente in buffer le sequenze multibyte attraverso i confini dei blocchi. Proprio qui TStreamReader in combinazione con una codifica UTF-8 può essere utile — se inizializzato correttamente.

Guida pratica: verificare in modo riproducibile UTF-8 in Delphi

Setup di debugging con stampe del dump dei byte e materiale di lavoro per controllare byte UTF-8 e BOM nelle payload JSON
Per un debugging pulito conta prima di tutto lo stream di byte: BOM, troncamento e sequenze non valide diventano visibili rapidamente.

Prima di mettere mano al parser, ti serve un setup di debugging che renda visibile l’effettiva sequenza di byte. Per supporto e operation è oro, perché più tardi potrai dire chiaramente se la controparte invia dati errati o se la tua pipeline li decodifica in modo sbagliato.

1) Controllare i primi byte (BOM, inizio JSON)

Se il JSON arriva con BOM, all’inizio dello stream vedrai EF BB BF. Subito dopo dovrebbe tipicamente comparire „{“ o „[„. Se nella stringa appare già „ï»¿“, il BOM non è stato trattato come BOM ma decodificato come testo.

2) Loggare i byte grezzi in esadecimale – ma con limiti

Non loggare payload complete in produzione (protezione dei dati, costi, volume dei log). Sono pratiche le seguenti informazioni:

  • Prefisso (es. i primi 256 o 1024 byte),
  • Suffisso (ultimi 256 byte),
  • e un hash (SHA-256) per correlazione, quando devi confrontare payload.

Con questo spesso riesci a inquadrare problemi di caratteri speciali in pochi minuti: la sequenza di byte per „ä“ è corretta (C3 A4)? C’è un troncamento? Compare un inaspettato 0x00 (null byte), ad esempio per un’errata assunzione di UTF-16?

3) Loggare Content-Type e Charset

Per HTTP/REST: registra il Content-Type e il charset dichiarato. Se i byte sono chiaramente UTF-8 ma il charset dichiara altro, il client non dovrebbe seguirlo ciecamente. Per JSON UTF-8 è lo standard de facto. In caso di dubbio: l’analisi dei byte prevale sull’header.

Parser di stream veloce con System.JSON: design senza copie inutili

Rappresentazione schematica di una pipeline composta da stream, decodifica UTF-8, limite di dimensione e parsing JSON-DOM in Delphi
Una pipeline chiara con limite ed UTF-8 esplicito separa nettamente trasporto, decodifica e parsing.

Un pattern pratico è: leggi dallo stream in un buffer di byte, costruisci da questi una stringa esattamente una volta usando UTF-8, e passi questa stringa al parser JSON. Non è „Streaming“ nel senso di SAX, ma è una pipeline controllata e performante senza sorprendenti cambi di encoding.

Importante per l’architettura: progetta la funzione in modo che decida centralmente in un unico punto:

  • Quale regola di encoding si applica (di norma UTF-8, BOM opzionale)?
  • Qual è la dimensione massima consentita della payload (protezione DoS, limite operativo)?
  • Come sono strutturati i messaggi di errore (con contesto, ma senza perdita di dati)?

Strategia di lettura: buffering limitato anziché „StreamToString“ senza limite

Se accetti JSON da fonti esterne (partner, client mobili, terze parti), un limite di dimensione è obbligatorio. Senza limite un singolo request sfortunato può mettere un servizio sotto Memory Pressure. Praticamente significa: durante la lettura sommare i byte e interrompere al superamento di una soglia – con una eccezione chiara e interpretabile nel monitoring.

Perché spesso mi aspetto UTF-8 „senza BOM“, ma tollererei il BOM

Nei payload di REST il BOM è raro. Nei file (export, modifiche manuali) invece è più frequente. Per canali di import robusti è sensato tollerare il BOM, ma renderlo visibile nel log, perché può indicare il passaggio dal ‚mondo dei file‘ a quello delle API.

I killer silenziosi: TStreamReader-Defaults und Text-Reader im Mischbetrieb

TStreamReader è comodo, ma devi tenere sotto controllo due aspetti:

  • Impostare esplicitamente l’encoding: non sperare che lo „riconosca“.
  • Comprendere il buffering: il reader effettua buffering internamente. Se poi leggi lo stesso stream altrove, la posizione è rilevante. Sembra banale, ma in pipeline di importazione più grandi diventa rapidamente una fonte di errori.

Particolarmente problematico è l’uso misto: prima leggere una porzione come byte (es. per logging o magic bytes), poi continuare con TStreamReader. Se non riavvolgi correttamente o non inizializzi il reader nella posizione giusta, leggerai a partire dal byte 257 invece che dallo 0. Il parser JSON segnalerà quindi „Invalid character at position …“, anche se la payload è corretta.

Quando i caratteri speciali restano „danneggiati“ nonostante UTF-8: Escaping vs. echtes Unicode

Il JSON può contenere caratteri speciali in due modi:

  • Come caratteri UTF-8 reali (es. „München“ come byte C3 BC …).
  • Come sequenza di escape (ad es. „Mu00fcnchen“).

Entrambe le soluzioni sono valide. Per la pratica è importante: le sequenze di escape aggirano molti problemi di trasporto, ma mascherano gli errori di encoding solo apparentemente. Se il tuo sistema interpreta bytes in modo errato da qualche parte, è un rischio operativo, non solo un bug cosmetico. Inoltre le sequenze di escape possono creare confusione in logging/monitoring quando i team si aspettano di vedere «testo leggibile».

System.JSON ti restituisce in entrambi i casi, alla fine, normali Delphi-stringhe (UTF-16), purché il percorso fino a quel punto sia stato corretto.

Valutare realisticamente le prestazioni: costi del DOM, grandi array e selettività

Il principale leva sulle prestazioni spesso non è «rendere il parser più veloce», ma parsing ridotto. Con System.JSON è difficile, perché ottieni il DOM. Tre situazioni tipiche:

  • Grandi Array (10.000+ Elemente): La costruzione del DOM richiede tempo e RAM. Se ti servono solo 2 campi per elemento, un parser con supporto streaming (SAX/Tokenizer) è spesso più sensato. System.JSON non è fatto per questo.
  • Oggetti singoli con molti campi: Se ti servono solo pochi campi, puoi comunque usare il DOM, ma evita di attraversarlo più volte. Recupera i valori una sola volta e mappali nelle tue strutture.
  • Più payload grandi consecutivi: In import job o sync worker conviene incapsulare il parsing in un passaggio distinto e, dopo ogni documento, rilasciare tutte le referenze in modo che il memory manager possa liberare risorse. È banale, ma nei servizi viene spesso fatto «di riflesso».

Un „schneller Stream-Parser“ con System.JSON è quindi spesso un buon compromesso: lettura efficiente e corretta, uso consapevole del DOM, definizione dei limiti. Se ti serve una semantica di streaming reale (p. es. processare gli elementi di un array uno per uno senza mantenerli tutti in memoria), System.JSON non è la base giusta.

Robustezza in esercizio: scenari d’errore e come renderli immediatamente identificabili

Gli errori di parsing JSON nei log spesso non sono utili, perché indicano solo una posizione. Per la gestione operativa e il supporto serve contesto:

  • Posizione in byte vs. posizione in caratteri: Con UTF-8 non coincidono. Se il parser riporta una posizione in caratteri, la posizione in byte può differire. Per i dump in byte la posizione in byte è quella rilevante.
  • Snippet attorno al punto di errore: Registra nel log, in caso di errore, una piccola finestra attorno alla posizione (p.es. 40 caratteri prima/dopo), ma solo se non contiene dati sensibili. In alternativa: registra solo in esadecimale.
  • Correlazione: Request-ID, Endpoint, Partner-ID, hash del payload. Altrimenti non ritroverai mai „quell’errore“.

L’obiettivo è che, dopo un incidente in produzione, tu sia in grado di rispondere entro pochi minuti: «encoding interpretato in modo errato», «payload troncato», «server restituisce JSON non valido» o «abbiamo un problema di mapping».

Evitare le insidie di UTF-8: checklist

  • Specificare sempre l’encoding: Nel leggere da uno stream e nel scrivere su log/file non affidarsi ai valori di default.
  • Nessuna conversione chunk->string: Se leggi a chunk, accumula i byte o usa un decoder che bufferizzi le sequenze multibyte.
  • Tolleranza al BOM, ma visibile: Accettare, ma poterlo riconoscere in fase di debug.
  • Imporre limiti: Dimensione massima del payload, profondità massima di oggetti/array (quando controllabile), timeout nel client HTTP.
  • Responsabilità separate: incapsulare separatamente la lettura del trasporto e l’analisi JSON. In questo modo il debug è più rapido e in seguito si può sostituire il parser.
  • Quando vale la pena lo sforzo per un parser a stream davvero?

    Non è necessario ottimizzare ogni singolo punto JSON. L’approccio conviene tipicamente se si verifica almeno una delle seguenti condizioni:

    • Payload di grandi dimensioni (diversi MB) si presentano regolarmente o possono presentarsi.
    • Processi longrunning (Service, Worker) elaborano molti payload e si osservano picchi di memoria o frammentazione.
    • Interoperabilità con sistemi eterogenei: più partner, piattaforme diverse, codifiche occasionalmente errate.
    • Storico incidenti: si sono già verificati caratteri speciali corrotti (ad es. Umlaute), errori di parsing sporadici o interruzioni di import difficili da riprodurre.

    Se i tuoi payload sono piccoli e provengono da una fonte controllata, spesso basta una soluzione semplice — ma anche in quel caso: impostare esplicitamente UTF-8 costa quasi nulla e previene sorprese successive.

    Distinzione: quando ti serve il vero streaming

    System.JSON è orientato al DOM. Se vuoi processare i dati realmente „in transito“, ad es. un grande array elemento per elemento senza tenerlo tutto in memoria, serve un altro approccio al parser (tokenizer/SAX). Non è un giudizio di valore, ma una decisione architetturale:

    • DOM (System.JSON): comodo, adatto per oggetti business tipici, ma dispendioso in memoria.
    • Streaming/SAX: uso di memoria ridotto, adatto per dati molto grandi, ma richiede più lavoro di implementazione e un handling degli errori più accurato.

    Un compromesso sensato è spesso: costruire in modo chiaro la gestione degli stream, l’encoding e i limiti, e solo dopo decidere se il DOM è ancora adeguato. In molti progetti questo da solo stabilizza significativamente il funzionamento.

    Conclusione: JSON in Delphi diventa affidabile se tratti encoding e stream come uno strato separato

    La maggior parte dei problemi legati a “JSON in Delphi” non dipende dal parser JSON in sé, ma dal tratto poco appariscente che lo precede: i byte letti da uno stream vengono trasformati in testo. Se lì gestisci esplicitamente UTF-8, rilevi i casi con BOM, non ignori i confini dei chunk e imposti limiti di dimensione chiari, spariscono i tipici errori sui caratteri speciali — e gli errori di parsing sporadici diventano riproducibili.

    System.JSON rimane in questo contesto uno standard pragmatico: non il parser in streaming più veloce, ma solido se controlli l’input e accetti consapevolmente i costi del DOM. Se vuoi, possiamo analizzare insieme il tuo percorso concreto di import/REST e identificare il punto in cui encoding o chunking cedono: contattaci.

    Anche i parser di stream JSON sono importanti per questo tema. L’articolo inquadra questi aspetti in modo comprensibile e mostra cosa conta nella pratica quotidiana.

    Discutere un progetto o un intervento di modernizzazione con Net-Base.

    Passo successivo

    Quando un tema diventa un progetto reale, architettura, sistemi esistenti e gestione operativa dovrebbero essere considerati insieme fin dalle fasi iniziali.

    Non forniamo solo supporto per questioni isolate, ma anche quando da frammenti di codice sorgente, tematiche legacy o idee di portale deve nascere un progetto aziendale solido.

    • Stato attuale, stato obiettivo e rischi tecnici vengono valutati insieme.
    • REST, l'accesso ai dati, i portali e il rollout non vengono rinviati a fasi successive.
    • Vede in anticipo quale percorso è economicamente e operativamente sostenibile.

    Condividi il post

    Condividi direttamente questo articolo

    LinkedIn, X, XING, Facebook, WhatsApp e e-mail sono immediatamente disponibili. Per Instagram prepariamo direttamente il link e un breve testo.

    E-mail

    Instagram si apre in una nuova scheda. Il link e il breve testo vengono copiati prima negli appunti.