Du thème du magazine à la pratique des projets
Pages de services et techniques pertinentes pour l'article
«JSON in Delphi» semble être un problème résolu : System.JSON est intégré, les appels REST retournent du texte, point final. En pratique, les véritables erreurs surviennent toutefois là où le JSON n’existe pas sous forme de chaîne confortable, mais sous forme de flux : flux de réponse HTTP, flux de fichier, pipe nommé, file d’attente de messages ou un gros BLOB dans la base de données. Trois éléments se conjuguent alors et sont souvent sous-estimés au quotidien : comportement mémoire, encodage des caractères (en particulier UTF-8) et cas limites liés aux caractères spéciaux.
Ce billet présente une approche propre et rapide pour parser du JSON à partir d’un TStream sans générer de copies inutiles — et surtout sans les pièges typiques liés à UTF-8, où les Umlauts sont « cassés » ou où les parsers plantent sporadiquement avec des messages cryptiques. L’accent est mis sur les conséquences pour l’exploitation et la stabilité des interfaces : débogage reproductible, limites claires de l’approche et critères pour déterminer quand l’effort en vaut réellement la peine.
Pourquoi les flux se comportent différemment lors du parsing JSON dans Delphi
Tant qu’un document JSON est petit, la voie « lire le flux en chaîne, puis parser » est commode. Au-delà d’une certaine taille de charge utile (typique : grandes listes, rapports, données de synchronisation, exports de logs), cela devient cependant coûteux :
- Copies en mémoire : vous lisez des octets dans un tampon, les convertissez en une chaîne Unicode (chaîne Delphi = UTF-16) et le parser crée en interne d’autres structures. Cela peut entraîner temporairement plusieurs copies.
- Pression sur le GC/heap : de nombreuses chaînes temporaires et valeurs JSON augmentent la fragmentation et le surcoût d’allocation, en particulier dans les processus long-running (Services, Worker, Import-Jobs).
- Le profil d’erreur devient flou : si des hypothèses d’encodage incorrectes sont déjà faites lors de la lecture, le parser JSON ne voit que des « caractères bizarres » ou des octets de contrôle inattendus.
Il est important d’opérer une séparation claire : le JSON est formellement Unicode, sur le canal il est presque toujours UTF-8. Delphi travaille en interne cependant en UTF-16. La transition des octets (flux) aux caractères (chaîne) est l’endroit où apparaissent les problèmes de caractères spéciaux — pas dans le JSON lui‑même.
System.JSON : ce qu’il sait bien faire — et où il faut être prudent
System.JSON est dans Delphi la norme pour le JSON basé sur le DOM : vous obtenez un modèle d’objet (TJSONObject, TJSONArray), pouvez interroger des valeurs, itérer, sérialiser. C’est robuste pour les intégrations métier typiques, mais cela a deux conséquences :
- Ce n’est pas un véritable parseur en streaming : le modèle d’objet est entièrement construit. Vous économisez éventuellement la lecture dans une chaîne séparée, mais le DOM reste gourmand en mémoire.
- L’entrée du parser est généralement du texte : selon la version de Delphi et l’API utilisée, on se retrouve rapidement à nouveau avec une chaîne, incluant une conversion d’encodage.
Si votre objectif est un « parseur de flux rapide », vous entendez en pratique généralement l’une des deux choses suivantes : (1) pas de copies inutiles et (2) un fail-fast aussi précoce que possible en cas de payloads défectueux. Les deux sont atteignables avec System.JSON, à condition de contrôler l’étape de conversion octets→texte.
Pièges UTF-8 avec les caractères spéciaux : causes typiques
Si les Umlauts (ä/ö/ü/ß) ou d’autres caractères spéciaux s’affichent incorrectement (ä, – etc.), il s’agit presque toujours d’un mismatch d’encodage. Dans l’environnement Delphi ces causes sont particulièrement fréquentes :
1) Repli sur ANSI dû à des helpers « pratiques »
Certaines voies de lecture supposent tacitement l’encodage ANSI du système (page de codes du Windows-système) lorsqu’aucun encodage explicite n’est fourni. Cela ne se remarque que lorsque le payload contient autre chose que de l’ASCII. Avec des jeux de test, cela passe souvent par hasard ; en production, ça casse sur des noms réels, des lieux ou des champs de texte libre.
2) Confusion liée au BOM (Byte Order Mark)
UTF-8 peut commencer par un BOM (octets EF BB BF). Dans le contexte web le BOM est plutôt rare, mais il apparaît dans des fichiers. Certains lecteurs détectent le BOM et ajustent l’encodage, d’autres non ou seulement dans certains modes. Si un BOM se retrouve comme caractère normal dans la chaîne, on observe souvent un caractère invisible « Zero Width No-Break Space » en début de texte ou le parser JSON échoue dès le premier token.
3) Double conversion (UTF-8 interprété « encore une fois »)
Le schéma d’erreur classique « ä au lieu de ä » survient quand les octets UTF-8 ont d’abord été correctement décodés en Unicode, puis sont de nouveau interprétés à tort comme des octets ANSI/UTF-8 (ou l’inverse). Dans Delphi cela arrive facilement quand la conversion entre TBytes, RawByteString et string n’est pas clairement maîtrisée.
4) Troncature au milieu d’un caractère multioctet
UTF-8 encode les caractères spéciaux sur 2 à 4 octets. Si vous lisez en chunks (par ex. 8 KB) et que la frontière de chunk tombe au milieu d’un caractère, le décodeur doit tamponner correctement. Une approche naïve qui convertit chaque chunk séparément en chaîne puis les concatène produit des séquences invalides. Cela peut apparaître comme une erreur « sporadique », dépendant des limites de paquets, du comportement du proxy ou du chunking HTTP.
5) Hypothèses incorrectes basées sur les en-têtes HTTP
Pour REST la source est souvent Content-Type: application/json; charset=utf-8. Certains serveurs n’envoient pas de charset, d’autres fournissent une valeur erronée. Si vous vous fiez aveuglément à l’en-tête, cela peut varier selon la version du backend. Pour l’exploitation et le support, il est utile d’inspecter le flux d’octets réel et de logger en cas d’erreur.
Ein sauberer Ansatz: Stream → UTF-8-Decoder → JSON-Parser
La pipeline robuste se compose de trois étapes distinctes :
- Lire les octets depuis le flux (contrôlé, éventuellement avec limite/timeout dans le client HTTP).
- Décodage en Unicode en spécifiant explicitement UTF-8 (tolérer éventuellement le BOM).
- Parsing avec System.JSON en modèle d’objet ou en extraction ciblée.
Le levier principal est l’étape 2 : vous ne voulez pas que quelque part l’« Default Encoding » décide. Dans Delphi cela signifie : TEncoding.UTF8 explizit setzen et ne pas se fier à des conversions implicites.
Was „schnell“ hier konkret bedeutet
Avec System.JSON, tu ne vas pas « supprimer » le DOM. En revanche, tu peux éviter :
- une copie supplémentaire de l’ensemble de la Payload sous forme de chaîne intermédiaire, alors que tu as de toute façon besoin en interne de seulement quelques valeurs (dans ce cas, un autre parser est généralement plus adapté ; j’y reviens plus loin),
- des re-encodages multiples,
- et tu peux lire des payloads très volumineuses de façon contrôlée (avec limite de taille et message d’erreur clair), au lieu de finir sur un Out-of-Memory ou des Access Violations.
Cas particulier concret : caractères spéciaux corrompus, mais seulement parfois
Un cas limite issu de la pratique est particulièrement sournois : la payload est globalement un JSON UTF-8 valide, mais tu la lis en chunks et tu convertis chaque chunk en chaîne. Tant que seul de l’ASCII apparaît, tu ne remarques rien. Dès qu’un caractère accentué tombe exactement sur la frontière d’un chunk, des séquences UTF-8 invalides apparaissent. Résultat : soit des caractères corrompus, soit une erreur de parsing à un endroit qui ne correspond pas au contenu réel.
Comment le reconnaître :
- Les erreurs de parsing surviennent « aléatoirement » sur des réponses volumineuses, pas sur des petites.
- La même requête fonctionne parfois, parfois non (selon le chunking/le transport).
- Un hexdump des octets montre un UTF-8 valide, mais ta chaîne journalisée contient des caractères de remplacement (�) ou un mojibake classique.
La solution n’est pas d’utiliser « plus de readln » ou des « buffers plus grands », mais d’employer un décodeur qui met correctement en tampon les séquences multioctets qui traversent les frontières de chunks. C’est précisément là qu’TStreamReader combiné à un encodage UTF-8 peut être utile — à condition de l’initialiser correctement.
Guide pratique : vérifier de manière reproductible l’UTF-8 dans Delphi
Avant de modifier le parser, tu as besoin d’une configuration de débogage qui rende visible la séquence d’octets réelle. Pour le support et l’exploitation, c’est précieux, car tu pourras ensuite dire clairement si la contrepartie fournit des données incorrectes ou si ta pipeline décode mal.
1) Vérifier les premiers octets (BOM, début du JSON)
Si le JSON arrive avec un BOM, tu verras en début de flux EF BB BF. Juste après devrait typiquement venir « { » ou « [ ». Si tu vois déjà «  » dans la chaîne, le BOM n’a pas été traité comme tel, mais décodé comme du texte.
2) Consigner les octets bruts en hexadécimal — mais de façon limitée
Ne consigne pas les payloads complets en production (protection des données, coûts, volume des logs). Les pratiques éprouvées sont :
- préfixe (p. ex. les 256 ou 1024 premiers octets),
- suffixe (les 256 derniers octets),
- et un hash (SHA-256) pour la corrélation si tu dois comparer des payloads.
Avec cela, tu peux souvent diagnostiquer les problèmes de caractères spéciaux en quelques minutes : la séquence d’octets pour « ä » est-elle correcte (C3 A4) ? Y a-t-il une troncature ? Un 0x00 inattendu (octet nul) apparaît-il, par exemple à cause d’une mauvaise hypothèse d’UTF-16 ?
3) Journaliser le Content-Type et le Charset
Pour HTTP/REST : enregistrez le Content-Type et le charset déclaré. Si les octets sont clairement en UTF-8, mais que le charset affirme autre chose, le client ne doit pas suivre aveuglément. Pour JSON, UTF-8 est la norme de facto. En cas de doute : l’analyse des octets l’emporte sur l’en-tête.
Schneller Stream-Parser mit System.JSON: Design ohne unnötige Kopien
Un pattern éprouvé est : vous lisez depuis le flux dans un tampon d’octets, vous en construisez une chaîne exactement une fois en UTF-8, et vous passez cette chaîne au parseur JSON. Ce n’est pas du « streaming » au sens de SAX, mais c’est une pipeline contrôlée et performante sans changements d’encodage surprenants.
Important pour l’architecture : concevez la fonction de manière à ce qu’elle décide de façon centralisée :
- Quelle règle d’encodage s’applique (généralement UTF-8, BOM optionnel) ?
- Quelle est la taille maximale autorisée pour la Payload (protection DoS, limite opérationnelle) ?
- À quoi ressemblent les messages d’erreur (avec contexte, mais sans fuite de données) ?
Einlese-Strategie: Begrenztes Puffern statt „StreamToString“ ohne Limit
Si vous acceptez du JSON provenant de sources externes (partenaires, clients mobiles, tiers), une limite de taille est obligatoire. Sans limite, une seule requête malheureuse suffit à mettre un service sous pression mémoire. Concrètement, cela signifie : compter la somme des octets lors de la lecture et interrompre au-delà d’une limite — avec une exception claire, compréhensible dans le monitoring.
Warum ich bei UTF-8 oft „ohne BOM“ erwarte, aber BOM tolerieren würde
Les payloads pour REST comportent rarement un BOM. En revanche, les fichiers (exports, édition manuelle) en contiennent plus souvent. Pour des pipelines d’import robustes, il est logique de tolérer le BOM, mais de le rendre visible dans les logs, car il peut indiquer un contexte de « monde fichier » plutôt que de « monde API ».
Die stillen Killer: TStreamReader-Defaults und Text-Reader im Mischbetrieb
TStreamReader est pratique, mais vous devez maîtriser deux aspects :
- Définir explicitement l’encodage : Ne pas espérer qu’il le « détecte ».
- Comprendre le buffering : Le reader bufferise en interne. Si vous relisez le même flux ailleurs par la suite, la position est importante. Cela semble trivial, mais cela devient rapidement une source d’erreurs dans des pipelines d’import plus vastes.
Le mix est particulièrement gênant : lire d’abord une partie en octets (p. ex. pour le logging ou les magic bytes), puis continuer avec TStreamReader. Si vous ne rembobinez pas proprement ou n’initialisez pas le lecteur à la bonne position, vous lirez à partir de l’octet 257 au lieu de 0. Le parseur JSON signale alors „Invalid character at position …“, alors que la Payload est correcte.
Wenn Sonderzeichen trotz UTF-8 „kaputt“ bleiben: Escaping vs. echtes Unicode
Le JSON peut contenir des caractères spéciaux de deux façons :
- Comme de vrais caractères UTF-8 (p. ex. « München » sous forme d’octets C3 BC …).
- Comme séquence d’échappement (p. ex. „Mu00fcnchen“).
Les deux sont valides. En pratique, il est important de noter : les séquences d’échappement contournent de nombreux problèmes de transport, mais elles ne font qu’occulter apparemment les erreurs d’encodage. Si votre système interprète des octets de manière erronée quelque part, c’est un risque opérationnel, pas seulement un bug cosmétique. De plus, les séquences d’échappement peuvent perturber la journalisation/le monitoring si les équipes s’attendent à voir « du texte lisible ».
System.JSON fournit dans les deux cas, au final, des chaînes Delphi normales (UTF-16), pour autant que le chemin jusqu’à là ait été correct.
Évaluer la performance de manière réaliste : coûts du DOM, grands tableaux et sélectivité
Le levier de performance principal n’est souvent pas de « rendre le parser plus rapide », mais de parser moins. Avec System.JSON c’est difficile, car vous obtenez le DOM. Trois situations typiques :
- Grandes tableaux (10.000+ éléments) : La construction du DOM coûte du temps et de la RAM. Si vous n’avez besoin que de 2 champs par élément, un parser compatible streaming (SAX/Tokenizer) est souvent plus pertinent. System.JSON n’est pas conçu pour cela.
- Objets individuels avec de nombreux champs : Si vous n’avez besoin que de quelques champs, vous pouvez malgré tout utiliser le DOM, mais évitez de le traverser plusieurs fois. Récupérez les valeurs une fois et mappez-les dans vos structures.
- Plusieurs grosses Payloads successives : Dans des jobs d’import ou des workers de synchronisation, il vaut la peine d’encapsuler le parsing dans une étape claire et de libérer toutes les références après chaque document pour permettre au gestionnaire de mémoire de nettoyer. C’est banal, mais dans les services c’est souvent fait « en passant ».
Un « parser stream rapide » avec System.JSON est donc souvent un bon compromis : lire efficacement et correctement, utiliser le DOM de façon consciente, définir des limites. Si vous avez besoin d’une véritable sémantique de streaming (p. ex. traiter les éléments d’un tableau un par un sans tout conserver en mémoire), System.JSON n’est pas la base appropriée.
Robustesse en exploitation : scénarios d’erreur et comment les rendre immédiatement exploitables
Les erreurs de parsing JSON ne sont souvent pas utiles dans les logs, car elles indiquent seulement une position. Pour l’exploitation et le support, vous avez besoin de contexte :
- Position en octets vs position en caractères : Avec UTF-8 ce n’est pas identique. Si le parser rapporte une position en caractères, la position en octets peut différer. Pour des dumps byte, la position en octets est déterminante.
- Extrait autour du point d’erreur : En cas d’erreur, loggez une petite fenêtre autour de la position (p. ex. 40 caractères avant/après), mais uniquement si elle ne contient pas de données sensibles. En alternative : enregistrer uniquement en hexadécimal.
- Corrélation : Request-ID, Endpoint, Partner-ID, Payload-Hash. Sinon vous ne retrouverez jamais « cette » erreur.
L’objectif est qu’après un incident en production vous puissiez, en quelques minutes, répondre : « encodage interprété incorrectement », « payload tronquée », « le serveur renvoie du JSON invalide » ou « nous avons un problème de mapping ».
Éviter les pièges UTF-8 de façon ciblée : Checkliste
- Encodage toujours explicite : Lors de la lecture depuis un flux et de l’écriture dans des logs/fichiers, ne vous fiez pas aux valeurs par défaut.
- Pas de conversion chunk-vers-string : Si vous lisez par chunks, accumulez les octets ou utilisez un décodeur qui bufferise les séquences multioctets.
- BOM toléré, mais visible : Accepter, mais être capable de le repérer en phase de débogage.
- Définir des limites : Taille maximale de payload, profondeur maximale d’objet/array (si vous pouvez la contrôler), timeouts du client HTTP.
Quand l’effort pour un parseur en flux vaut-il vraiment la peine ?
Vous n’avez pas besoin d’optimiser chaque endroit où apparaît du JSON. L’approche est généralement pertinente si au moins un des cas suivants s’applique :
- Grosses payloads (plusieurs Mo) surviennent régulièrement ou peuvent survenir.
- Processus long-running (service, worker) traitent de nombreuses payloads et vous observez des pics mémoire ou de la fragmentation.
- Interopérabilité avec des systèmes hétérogènes : plusieurs partenaires, plateformes différentes, encodages parfois erronés.
- Historique d’incidents : il y a déjà eu des « Umlaut kaputt », des erreurs de parsing sporadiques ou des interruptions d’import difficiles à reproduire.
Si vos payloads sont petits et proviennent d’une source contrôlée, une solution simple suffit souvent — mais même alors : définir explicitement UTF-8 coûte presque rien et évite des surprises ultérieures.
Délimitation : quand avez-vous réellement besoin de streaming ?
System.JSON est orienté DOM. Si vous devez traiter les données « en cours de lecture », par exemple un grand tableau élément par élément sans le retenir intégralement, vous avez besoin d’une autre approche d’analyse (tokenizer/SAX). Ce n’est pas un jugement de valeur, mais une décision d’architecture :
- DOM (System.JSON) : pratique, adapté aux objets métier typiques, mais gourmand en mémoire.
- Streaming/SAX : faible empreinte mémoire, adapté aux jeux de données très volumineux, mais exige plus d’effort d’implémentation et un traitement des erreurs plus rigoureux.
Un compromis pertinent consiste souvent à : construire proprement la gestion des streams, l’encodage et les limites, puis décider si le modèle DOM reste acceptable. Dans de nombreux projets, cela suffit déjà à stabiliser sensiblement l’exploitation.
Conclusion : le JSON dans Delphi devient fiable si vous traitez l’encodage et les flux comme une couche distincte
La plupart des problèmes autour de « JSON in Delphi » ne viennent pas du parseur JSON lui‑même, mais du tronçon discret qui le précède : des octets issus d’un flux transformés en texte. Si vous traitez explicitement UTF-8, détectez les cas BOM, ne négligez pas les limites de chunk et définissez des limites de taille claires, les erreurs typiques liées aux caractères spéciaux disparaissent — et les problèmes de parsing sporadiques deviennent reproductibles.
System.JSON reste un standard pragmatique : pas le parseur streaming le plus rapide, mais robuste si vous contrôlez l’entrée et acceptez consciemment le coût DOM. Si vous le souhaitez, nous pouvons passer en revue votre chemin d’import/concrete/REST-Pfad ensemble et identifier l’endroit où l’encodage ou le chunking bascule : Contactez‑nous.
Pour ce sujet, les parseurs JSON en streaming sont également importants. L’article situe ces aspects de manière compréhensible et montre ce qui compte au quotidien.
Discuter d’un projet ou d’un volet de modernisation avec Net-Base.
Étape suivante
Lorsque le sujet devient un projet réel, l'architecture, l'existant et l'exploitation doivent être examinés ensemble dès le départ.
Nous n'intervenons pas seulement sur des questions ponctuelles, mais aussi lorsque des fragments de code source, des problématiques liées aux systèmes legacy ou des concepts de portail doivent se transformer en un projet d'entreprise robuste.
- L'état des lieux, l'état cible et les risques techniques sont évalués conjointement.
- REST, l’accès aux données, les portails et le déploiement ne sont pas reportés à des phases ultérieures.
- Vous identifiez tôt quelle voie est viable économiquement et opérationnellement.