Del tema de la revista a la pràctica del projecte
Pàgines de serveis i tècniques pertinents per a l'article
„JSON in Delphi“ sona com un problema resolt: System.JSON és a bord, REST-calls retornen text, llest. En la pràctica, però, els errors reals s’originen allà on el JSON no està disponible com una cadena còmoda, sinó com un Stream: HTTP-Response-Stream, Datei-Stream, Named Pipe, Message-Queue o un gran BLOB de la base de dades. Aleshores conflueixen tres factors que sovint es subestimen en el dia a dia: comportament de memòria, codificació de caràcters (en particular UTF-8) i casos límit relacionats amb caràcters especials.
Aquest article mostra un enfocament net i ràpid per parsejar JSON des d’un TStream sense generar còpies innecessàries — i sobretot evitant les típiques trampes d’UTF-8 en què els Umlaute queden „destrossats“ o els parsers fallen esporàdicament amb missatges críptics. L’enfocament se centra en les implicacions per a l’operació i l’estabilitat de les interfícies: depuració reproduïble, límits clars de l’enfocament i criteris per valorar quan l’esforç compensa realment.
Per què els Streams funcionen diferent en el parsing de JSON a Delphi
Mentre un document JSON sigui petit, el camí „llegir Stream a String, i després parsejar“ és còmode. A partir d’una certa mida de payload (típic: llistes grans, informes, dades de sincronització, exportacions de logs) això, però, esdevé car:
- Duplicats de memòria: Llegeixes bytes a un búfer, converteixes a una cadena Unicode (Delphi-String = UTF-16), i el parser genera internament més estructures. Això pot significar diverses còpies en curt termini.
- Pressió sobre GC/Heap: Moltes cadenes temporals i valors JSON augmenten la fragmentació i l’overhead d’allocació, especialment en processos de llarga durada (services, worker, import-jobs).
- El patró d’errors es torna poc clar: Si durant la lectura ja s’han fet suposicions errònies sobre l’encoding, el parser JSON només veu «caràcters estranys» o bytes de control inesperats.
És important una separació clara: JSON és formalment Unicode, a la capa de transmissió és gairebé sempre UTF-8. Delphi treballa internament, però, amb UTF-16. La transició de bytes (Stream) a caràcters (String) és el punt on apareixen els problemes amb caràcters especials — no pas en el JSON en si.
System.JSON: què fa bé — i on has d’anar amb compte
System.JSON és a Delphi l’estàndard per a JSON basat en DOM: obtens un model d’objectes (TJSONObject, TJSONArray), pots consultar valors, iterar, serialitzar. Això és robust per a integracions de negoci típiques, però té dues conseqüències:
- No és un veritable parser en streaming: El model d’objectes es construeix completament. Potser t’estalvies llegir en una cadena addicional, però el DOM continua sent intensiu en memòria.
- L’entrada del parser és, en regla general, text: Depenent de la versió de Delphi i de l’API utilitzada, ràpidament es torna a treballar amb cadenes, incloent la conversió d’encoding.
Si el teu objectiu és un „parser de streams ràpid“, en la pràctica normalment busques una de dues coses: (1) evitar còpies innecessàries i (2) fail-fast tan aviat com sigui possible enfront de payloads defectuosos. Ambdues coses són assolibles amb System.JSON, sempre que controlis l’etapa de byte-a-text.
Trampes UTF-8 amb caràcters especials: les causes típiques
Si les vocals amb umlaut (ä/ö/ü/ß) o altres caràcters especials al resultat apareixen malament (ä, – etc.), això gairebé sempre és un desajust d’encoding. En l’entorn Delphi aquestes causes són especialment comunes:
1) Retorn a ANSI per helpers «còmodes»
Algunes rutes de lectura assumeixen silenciosament l’encoding ANSI del sistema (codepage del sistema Windows) si no se’ls passa una codificació explícita. Això només es nota quan un Payload no conté només ASCII. En dades de prova sovint és per casualitat «ok», en producció falla amb noms reals, llocs i textos lliures.
2) Confusió amb BOM (Byte Order Mark)
UTF-8 pot començar amb BOM (bytes EF BB BF). En el context web el BOM és poc habitual; en fitxers apareix. Alguns lectors detecten el BOM i ajusten l’encoding, d’altres no o només en certs modes. Si un BOM entra a la cadena com a caràcter normal, sovint veuràs un «Zero Width No-Break Space» invisible al principi o el parser JSON fallarà directament en el primer token.
3) Doble conversió (UTF-8 s’interpreta «de nou»)
El patró d’error clàssic «ä» en lloc de «ä» apareix quan els bytes UTF-8 primer s’han decodificat correctament a Unicode, però més tard s’interpreten de nou erròniament com bytes ANSI/UTF-8 (o a l’inrevés). En Delphi això passa sovint quan hi ha conversions ambigües entre TBytes, RawByteString i string.
4) Truncament al mig d’un caràcter multibyte
UTF-8 codifica caràcters especials en 2–4 bytes. Si llegeixes en chunks (p. ex. 8 KB) i el límit del chunk cau enmig del caràcter, el decodificador ha d’emmagatzemar en buffer correctament. Un enfocament ingenu que converteix cada chunk per separat a cadena i els concatena genera seqüències invàlides. Això pot aparèixer com un error «esporàdic», depenent dels límits de paquets, del comportament del proxy o de l’HTTP-chunking.
5) Supòsits erronis derivats dels headers HTTP
En REST la font sovint indica Content-Type: application/json; charset=utf-8. Però alguns servidors no envien charset, altres envien dades incorrectes. Si fas cas del header de manera cega, això pot variar segons la versió del backend. Per a operacions i suport és útil verificar el flux real de bytes i registrar-lo en cas d’error.
Una aproximació neta: Stream → decodificador UTF-8 → parser JSON
La pipeline robusta consta de tres etapes clares:
- Llegir bytes des del Stream (controlat, si cal amb límit/timeout al client HTTP).
- Decodificació a Unicode amb UTF-8 explícit (tolerar BOM opcionalment).
- Parsing amb System.JSON cap a un model d’objectes o per a extracció dirigida.
La palanca més important és l’etapa 2: no vols que algun «Default Encoding» decideixi. En Delphi això vol dir: establir explícitament TEncoding.UTF8 i no confiar en conversions implícites.
Què significa aquí concretament «ràpid»
Amb System.JSON no eliminaràs el DOM. Però pots evitar:
- una còpia addicional de tota la payload com a string intermedi, si internament només necessites pocs valors (aleshores val més un altre Parser; en parlarem més endavant),
- reencodificacions múltiples,
- i pots llegir de manera controlada payloads molt grans (amb límit de mida i missatge d’error clar), en lloc d’acabar amb Out-of-Memory o Access Violations.
Cas límit concret: caràcters especials trencats, però només de vegades
Un cas límit de la pràctica és especialment insidiós: la payload és, en principi, un UTF-8-JSON vàlid, però la llegeixes en chunks i converteixes cada chunk a String. Mentre hi hagi només ASCII, no ho notes. Tan bon punt una lletra amb diacrític cau exactament en el límit d’un chunk, es generen seqüències UTF-8 no vàlides. Resultat: o bé caràcters malmesos o bé un error de parseig en una posició que no correspon al contingut real.
Com ho detectes:
- Els errors de parseig apareixen «aleatòriament» en respostes grans, no en les petites.
- La mateixa Request funciona de vegades i d’altres no (segons el chunking/transport).
- Un hexdump dels bytes mostra un UTF-8 vàlid, però la cadena que has registrat conté caràcters de substitució (�) o el clàssic mojibake.
La solució no és utilitzar «més readln» o «buffers més grans», sinó fer servir un decodificador que emmagatzemi correctament les seqüències multibyte que travessen els límits de chunk. Aquest és precisament el punt en què TStreamReader, en combinació amb una codificació UTF-8, pot ser útil — si el inicialitzes correctament.
Guia pràctica: comprovar de manera reproduïble l’UTF-8 en Delphi
Abans de tocar el Parser, necessites una configuració de depuració que et faci visible la seqüència de bytes real. Per a suport i operacions això val or, perquè més endavant podràs dir amb claredat si la contrapart envia dades errònies o si la teva pipeline les decodifica malament.
1) Comprovar els primers bytes (BOM, inici del JSON)
Si el JSON arriba amb BOM, veuràs al principi del stream EF BB BF. Tot seguit hi hauria típicament «{» o «[». Si al teu String ja hi ha «ï»¿», vol dir que el BOM no es va tractar com a BOM, sinó que es va decodificar com a text.
2) Registrar els bytes en cru en hexadecimal — però de manera limitada
No registris payloads complets en producció (protecció de dades, costos, volum de logs). Han demostrat ser útils:
- Prefix (p. ex., els primers 256 o 1024 bytes),
- Suffix (els darrers 256 bytes),
- i un hash (SHA-256) per correlació, si has de comparar payloads.
Amb això sovint pots diagnosticar problemes amb caràcters especials en minuts: la seqüència de bytes per a «ä» és correcta (C3 A4)? Hi ha un truncament? Apareix un 0x00 inesperat (nullbyte), p. ex. per una assimilació errònia com a UTF-16?
3) Registrar el Content-Type i el Charset
En HTTP/REST: Registra el Content-Type i el charset declarat. Si els octets són clarament UTF-8 però el charset afirma el contrari, no segueixis cegament el client. Per a JSON, UTF-8 és l’estàndard de facto. En cas de dubte: l’anàlisi de bytes preval sobre la capçalera.
Parser de stream ràpid amb System.JSON: disseny sense còpies innecessàries
Un patró pràctic és: llegeixes del Stream a un búfer de bytes, construeixes a partir d’això una cadena exactament una vegada amb UTF-8, i passes aquesta cadena al parser JSON. Això no és «streaming» en el sentit de SAX, però és una pipeline controlada i eficient sense canvis d’encoding inesperats.
Important per a l’arquitectura: construeix la funció de manera que decideixi de forma centralitzada a un sol lloc:
- Quina regla d’encoding s’aplica (habitualment UTF-8, BOM opcional)?
- Quina mida màxima pot tenir la payload (protecció DoS, límit operatiu)?
- Com han de ser els missatges d’error (amb context, però sense fuga de dades)?
Estratègia de lectura: búfer limitat en lloc de „StreamToString“ sense límit
Si acceptes JSON de fonts externes (socis, clients mòbils, tercers), un límit de mida és obligatori. Sense límit, una sola petició desafortunada pot portar un servei a pressió de memòria. Pràcticament això vol dir: comptar la suma de bytes mentre llegeixes i abortar quan s’arriba a un límit — amb una excepció clara que sigui comprensible a la monitorització.
Per què sovint espero UTF-8 „sense BOM“, però toleraria el BOM
En les payloads de REST el BOM apareix rarament. En fitxers (exports, edició manual) en canvi més sovint. Per a rutes d’importació robustes és raonable tolerar el BOM, però fer-lo visible als logs, ja que pot ser un indici del «món de fitxers» en lloc del «món d’API».
Els assassins silenciosos: TStreamReader-Defaults i lectors de text en ús mixt
TStreamReader és còmode, però has de tenir dues coses ben controlades:
- Establir explícitament la codificació: No esperis que la «reconegui».
- Entendre el buffering: El reader fa búfer internament. Si després llegeixes el mateix Stream en una altra part, la posició és rellevant. Això sembla trivial, però en pipelines d’importació més grans es converteix ràpidament en una font d’errors.
Particularment molest és l’ús mixt: primer llegir una part com bytes (p. ex. per logging o magic-bytes), després continuar amb TStreamReader. Si no retrocedeixes netament o no inicialitzes el reader a la posició correcta, llegiràs a partir del byte 257 en lloc de 0. El parser JSON reportarà „Invalid character at position …“, encara que la payload sigui correcta.
Quan els caràcters especials continuen „trencats“ malgrat UTF-8: Escaping vs. Unicode real
El JSON pot contenir caràcters especials de dues maneres:
- Com a caràcters UTF-8 reals (p. ex. „München“ com a bytes C3 BC …).
- Com a seqüència d’escape (p. ex. „Mu00fcnchen“).
Tots dos són vàlids. Per a la pràctica és important: les seqüències d’escape eviten molts problemes de transport, però només amaguen errors d’encoding de manera aparent. Si el teu sistema interpreta bytes incorrectament en algun punt, això és un risc d’operació, no només un bug cosmètic. A més, les seqüències d’escape poden confondre el logging/monitoring quan els equips esperen veure «text llegible».
System.JSON et lliura en ambdós casos, al final, cadenes Delphi normals (UTF-16), sempre que el camí fins allà hagi estat correcte.
Valorar el rendiment de manera realista: costos del DOM, arrays grans i selectivitat
El palanca de rendiment més gran sovint no és «fer el parser més ràpid», sinó parsejar menys. Amb System.JSON això és difícil perquè obtens el DOM. Tres situacions típiques:
- Arrays grans (10.000+ elements): la construcció del DOM costa temps i RAM. Si només necessites 2 camps per element, sovint és més adient un parser amb suport de streaming (SAX/Tokenizer). System.JSON no està pensat per això.
- Objectes individuals amb molts camps: Si només necessites pocs camps, encara pots utilitzar el DOM, però evita recórrer-lo diverses vegades. Llegeix els valors una vegada i mapeja’ls a les teves estructures.
- Diverses càrregues grans consecutives: En tasques d’importació o workers de sincronització val la pena encapsular l’anàlisi en un pas clar i alliberar totes les referències després de cada document perquè el gestor de memòria pugui netejar. És banal, però als serveis sovint es fa «de passada».
Un «parser de stream ràpid» amb System.JSON és per tant sovint un bon compromís: llegir eficientment i correctament, fer ús conscient del DOM i definir límits. Si necessites veritable semàntica de streaming (p. ex. processar elements d’un array un per un sense mantenir-ho tot en memòria), System.JSON no és la base adequada.
Robustesa en explotació: patrons d’errors i com fer-los identificables de manera immediata
Els errors de parseig JSON als logs sovint no són útils perquè només indiquen una posició. Per a operacions i suport necessites context:
- Posició de bytes vs. posició de caràcters: Amb UTF-8 això no coincideix. Si el parser reporta una posició en caràcters, la posició en bytes pot diferir. Per a volcats en bytes la posició en bytes és la rellevant.
- Fragment al voltant de la posició d’error: Registra al log, en cas d’error, una petita finestra al voltant de la posició (p. ex. 40 caràcters abans/després), però només si no hi ha dades sensibles. Alternativament: registra-ho només en hexadecimal.
- Correlació: Request-ID, endpoint, Partner-ID, hash de la payload. Si no, no tornaràs a trobar «aquest» error mai més.
L’objectiu és que després d’un incident en producció puguis respondre en pocs minuts: «encoding interpretat incorrectament», «payload tallada», «el servidor retorna invalid JSON» o «tenim un problema de mapeig».
Evitar paranys d’UTF-8 de manera específica: llista de comprovació
- Codificació sempre explícita: En llegir des d’un stream i en escriure a logs/arxius no confiïs en els valors per defecte.
- Cap conversió de chunk a string: Si llegeixes per chunks, acumula els bytes o usa un decodificador que emmagatzemi les seqüències multibyte.
- Tolerant amb BOM, però visible: Accepta’l, però sigues capaç d’identificar-lo en depuració.
- Fixar límits: Mida màxima de la payload, profunditat màxima d’objecte/array (si la pots controlar), timeouts al client HTTP.
Quan val la pena l’esforç per un parser de streaming?
No cal optimitzar cada ocurrència de JSON. L’enfocament sol valer la pena quan es compleix almenys una d’aquestes condicions:
- Payloads grans (diversos MB) es produeixen regularment o poden produir-se.
- Processos de llarga durada (servei, worker) processen molts payloads i observes pics de memòria o fragmentació.
- Interoperabilitat amb sistemes heterogenis: diversos socis, diferents plataformes, codificacions ocasionalment errònies.
- Historial d’incidents: ja s’han produït «dièresis trencades», errors de parseig esporàdics o aturades d’importació difícils de reproduir.
Si els teus payloads són petits i provenen d’una font controlada, sovint n’hi ha prou amb un mètode simple – però encara així: establir explícitament UTF-8 costa gairebé res i evita sorpreses posteriors.
Delimitació: quan necessites streaming real
System.JSON és orientat a DOM. Si realment vols processar dades a mesura que arriben, p. ex. un gran array element per element sense mantenir-lo complet, necessites un altre enfocament de parser (Tokenizer/SAX). Això no és un judici de valor, sinó una decisió d’arquitectura:
- DOM (System.JSON): còmode, bo per a objectes de negoci típics, però intens en memòria.
- Streaming/SAX: baix consum de memòria, adequat per a dades molt grans, però amb més esforç d’implementació i maneig d’errors més acurat.
Un compromís raonable sovint és: dissenyar netament la gestió del flux, l’encoding i els límits, i només després decidir si DOM encara escau. En molts projectes, això ja estabilitza notablement l’operació.
Conclusió: JSON a Delphi es torna fiable si tractes l’encoding i els streams com una capa separada
La majoria de problemes entorn de „JSON in Delphi“ no tenen a veure amb el parser JSON en si, sinó amb el tram discret anterior: els bytes d’un stream es transformen en text. Si allà tractes explícitament UTF-8, detectes casos de BOM, no ignores els límits dels chunks i estableixes límits de mida clars, desapareixen els errors típics amb caràcters especials – i els problemes de parseig esporàdics passen a ser reproduïbles.
System.JSON continua sent un estàndard pragmàtic: no és el parser de streaming més ràpid, però és sòlid si controles l’input i acceptes de manera conscient els costos del DOM. Si vols, podem revisar conjuntament el teu camí d’importació/REST concret i identificar el punt on l’encoding o el chunking fallen: Contacta’ns.
Per a aquest tema també són importants els analitzadors de flux JSON. L’article situa aquests aspectes de manera comprensible i mostra en què cal fixar-se a la pràctica.
Discutir un projecte o una iniciativa de modernització amb Net-Base.
Pas següent
Quan d'un tema se'ndevé un projecte real, s'han de considerar aviat i de manera conjunta l'arquitectura, els actius existents i l'operació.
No només donem suport en qüestions puntuals, sinó també quan, a partir de fragments de codi font, temes de sistemes heredats o idees de portal, ha de sorgir un projecte empresarial sòlid.
- L'estat actual, la visió objectiu i els riscos tècnics s'avaluen conjuntament.
- REST, accés a dades, portals i desplegament no es posposaran com a efectes retardats.
- Veu aviat quin camí és viable des del punt de vista econòmic i operatiu.