Outils de développement · Encodeur et décodeur Base64
Base64 dans les API JSON : pourquoi les champs binaires sont codés et ce que cela vous coûte
· Pourquoi c'est important
base64 codage
JSON n'a pas de type d'octet, donc les données binaires sont généralement codées en Base64 dans une chaîne. Cet article explique pourquoi cette convention existe, ce qu'elle coûte en taille et en CPU, et quand un point de terminaison binaire distinct est le meilleur choix.
Le champ PDF qui a dominé la réponse : une charge utile d'API concrète où un blob Base64 l'emportait sur tout le reste.
Une réponse API contient un objet volumineux avec un seul champ qui domine la taille de la charge utile. La réponse est JSON, donc chaque valeur est une chaîne ou un nombre. La plupart des champs sont petits : identifiants d'utilisateur, horodatages, codes d'état. Un champ contient imageData ou fileContents et est une chaîne Base64 de 40-kilobyte. La réponse entière fait 50 kilo-octets. Ce seul champ représente 80 pour cent du transfert, ce qui semble inutile car le serveur l'a envoyé sous forme d'octets à l'origine et le client a finalement besoin d'octets à nouveau.
Base64 a résolu ce problème : JSON n'a pas de type d'octet natif, les données binaires doivent donc être enveloppées dans une chaîne. Base64 convertit les octets arbitraires en caractères ASCII sécurisés dans JSON. Le client et le serveur doivent encoder à l'envoi et décoder à la réception, ce qui ajoute une surcharge du processeur. La charge utile résultante est environ un tiers plus grande que les octets bruts. Cet article explique pourquoi cette convention existe, ce qu'elle coûte en pratique et quand briser la contrainte JSON avec un point de terminaison binaire distinct en vaut la peine.
Pourquoi JSON ne peut pas transporter d'octets bruts : les chaînes doivent être du texte Unicode valide, donc les octets arbitraires nécessitent un wrapper textuel
JSON est un format de texte où toutes les valeurs doivent être du texte Unicode valide. La spécification définit des chaînes, des nombres, des booléens et des valeurs nulles. Il n'a pas de tableau d'octets ni de type de tampon. Si une API doit renvoyer des données binaires comme une image, une signature cryptographique ou un téléchargement de fichier, elle ne peut pas placer les octets bruts directement dans l'objet JSON. Les octets peuvent contenir des caractères que les analyseurs JSON interprètent comme des marqueurs structurels. Un octet nul au milieu d'un blob binaire pourrait mettre fin à une chaîne plus tôt ou interrompre l'analyseur.
La solution courante consiste à coder les données binaires en Base64, produisant une chaîne de caractères ASCII que les analyseurs JSON traitent comme du texte brut. Le client récepteur décode ensuite le Base64 en octets et les utilise. Cette étape d'encodage se produit au niveau de l'API, cachée à la plupart des développeurs, mais c'est un coût réel qui s'accumule lorsque les API renvoient de nombreux champs binaires. Les coûts de Base64 dans JSON s'aggravent tout au long du cycle demande-réponse. La pénalité de taille est la première : la sortie Base64 est environ 33 pour cent plus grande que son entrée en raison de la surcharge d'encodage.
Les coûts : un tiers d'octets supplémentaires, du temps de décodage et des copies de mémoire - où chaque coût apparaît dans un client type
Un fichier vidéo de 30 mégaoctets devient 40 mégaoctets lorsqu'il est encodé en Base64. Le téléchargement de 40 au lieu de 30 mégaoctets coûte de la bande passante et de la batterie sur les appareils mobiles, ainsi que du temps pour les utilisateurs utilisant des connexions lentes. Le deuxième coût est le temps CPU. Le serveur doit coder les données binaires en Base64 avant de pouvoir les chaîner dans JSON. Le client doit analyser le JSON, puis décoder chaque champ Base64 en octets. Pour une réponse avec plusieurs champs binaires ou pour un client qui traite des milliers de réponses, ce temps CPU s'accumule.
Sur les appareils contraints comme les téléphones, les opérations sur les chaînes JavaScript et le TextDecoder utilisé pour le décodage Base64 consomment de la batterie et ralentissent l'application. Le troisième coût est la mémoire : l'analyseur JSON crée un objet chaîne pour le champ Base64, puis le décodage crée une autre copie sous forme de Uint8Array. Un grand champ est instancié deux fois en mémoire avant que l'application puisse l'utiliser. Un exemple concret clarifie le coût. Supposons qu'un point de terminaison d'API renvoie des données de profil utilisateur, notamment une image d'avatar de 100 kilobyte. Le serveur lit l'image du disque sous forme d'octets, la code en Base64 et l'inclut dans la réponse JSON.
Exemple pratique : inspection d'un champ Base64 à partir d'une réponse API – décodage dans le navigateur pour confirmer ce que le serveur a réellement envoyé
La réponse JSON fait désormais environ 135 kilo-octets (33 % de surcharge plus les autres champs). Le client télécharge 135 kilo-octets au lieu de 100. Dans le navigateur, l'analyseur JSON crée un objet chaîne JavaScript pour les données Base64.
Lorsque l'application a besoin de l'image, elle appelle le décodeur Base64, qui crée un Uint8Array des 100 kilo-octets d'origine. Pendant les quelques millisecondes de décodage, les deux objets existent en mémoire. Si la page affiche dix profils avec avatars, le coût est multiplié. L'alternative est que l'API renvoie la réponse JSON avec une URL distincte pour chaque ressource d'avatar, permettant au navigateur de gérer les téléchargements d'images avec sa mise en cache native, son rendu progressif et sa gestion de la mémoire.
Alternatives : URL de téléchargement séparées en plusieurs parties et points de terminaison binaires bruts – les compromis de chacun
Le compromis entre l'inclusion du binaire dans JSON et sa récupération séparément dépend de l'objectif de l'API et des modèles d'utilisation. Pour une page de résultats de recherche qui renvoie des centaines de petites images miniatures, la récupération de chacune d’elles sous forme de requête distincte va à l’encontre du regroupement et de la mise en cache des connexions HTTP. Les intégrer en Base64 dans la réponse JSON pourrait être plus rapide. Pour une page de profil détaillée qui demande une ou deux images haute résolution, des téléchargements séparés sont clairement préférables. La documentation de l'API doit indiquer la taille maximale des champs Base64 et le moment où les clients doivent s'attendre à des points de terminaison distincts.
Si un champ dépasse régulièrement un kilo-octet ou deux, la stratégie inline-Base64 est un signe que la conception de l'API doit être reconsidérée. Des alternatives à Base64 dans JSON existent mais chacune comporte des compromis. Les réponses MIME en plusieurs parties séparent le binaire et le texte, de sorte que la section binaire est envoyée sous forme d'octets bruts et que seule la section de texte est JSON. Cela nécessite que le client analyse un message en plusieurs parties au lieu de simplement appeler JSON.parse, ce qui ajoute de la complexité. Une URL de téléchargement distincte dans la réponse JSON indique au client de récupérer la ressource binaire séparément.
Conventions qui méritent d'être mentionnées dans vos documents API : alphabet standard par rapport à l'alphabet base64url, remplissage et tailles maximales
Cela fonctionne bien lorsque la ressource binaire est volumineuse ou utilisée moins fréquemment que les métadonnées. Un point de terminaison binaire brut qui renvoie uniquement des octets et abandonne entièrement JSON est l'approche la plus simple, mais supprime la structure fournie par JSON. Certaines API renvoient des données binaires compressées et les encodent en Base64, réduisant ainsi la pénalité de taille mais ajoutant une surcharge de décompression. Le choix dépend de l'utilisation attendue : les petits champs conviennent parfaitement, les grands champs appartiennent à des ressources distinctes et les données structurées valent la peine d'être conservées dans JSON même avec le coût Base64.
Les conventions sont importantes pour l'interopérabilité. Les API qui encodent les données binaires en Base64 doivent les documenter clairement et indiquer si l'alphabet est standard ou sécurisé pour les URL. Standard Base64 utilise + et /, qui sont en sécurité dans les chaînes JSON mais pas dans les URL. Base64 sécurisé pour les URL les remplace par - et _, ce qui est approprié pour les URI data: mais inutilement échappé dans JSON. La documentation doit spécifier si le remplissage est inclus ou omis, car les deux sont en Base64 valide, mais un client qui attend un remplissage et reçoit des données non complétées échouera silencieusement ou produira des déchets.
Ce que cela ne couvre pas : protobuf, CBOR et autres formats de sérialisation binaire
Pour les champs très volumineux ou fréquemment mis à jour, il est essentiel de documenter un point de terminaison binaire distinct afin que les clients n'essaient pas de récupérer des kilo-octets de données inutiles. Le débogage des API avec des champs Base64 est simple avec le bon outil. L'encodeur et décodeur Base64 vous permet de décoder n'importe quel champ localement dans le navigateur, sans le stocker ni l'envoyer nulle part. Copiez un champ Base64 à partir d'une réponse JSON, collez-le dans le décodeur et appuyez sur Décoder. Pour les données de type texte (JSON à l'intérieur du Base64, par exemple), la sortie décodée apparaît instantanément.
Pour les données binaires comme les images, la vue hexadécimale vous montre les octets. Cela permet de confirmer que le serveur a envoyé ce que vous attendiez et que votre décodeur client fonctionne correctement. Si un champ décode des données inattendues, le problème vient du codage du serveur ou de la manière dont vous copiez le champ. S'il décode en un blob partiel, le champ peut avoir été tronqué ou la longueur Base64 peut être erronée. Le décodage local accélère le débogage par rapport à l'écriture du champ dans un fichier et à l'ouverture d'outils externes.
À retenir : Base64 dans JSON est un compromis, alors documentez-le - comment l'encodeur et le décodeur Base64 vous aident à inspecter et à vérifier les champs codés localement
L'approche pratique de Base64 dans les API est la sensibilisation plutôt que l'évitement. Base64 est le moyen standard de transporter des données binaires dans JSON et cela fonctionne. Comprenez que chaque champ Base64 coûte un tiers de plus en taille et quelques millisecondes de temps CPU par cycle requête-réponse. Pour les petites métadonnées critiques telles que les jetons d'authentification (où le JWT lui-même est codé en Base64), le coût est négligeable. Pour les pièces jointes volumineuses, demandez-vous si le binaire doit voyager dans la même réponse ou en tant que ressource distincte.
Documentez le schéma de codage et les tailles maximales dans la spécification de votre API. Lors de l'inspection des réponses, utilisez l'encodeur et le décodeur Base64 pour vérifier que les champs sont correctement décodés et comprendre ce que le serveur a réellement envoyé. Cette discipline maintient le compromis visible et la décision délibérée plutôt qu’accidentelle.