773 votes

Données binaires dans une chaîne JSON. Quelque chose de mieux que Base64

Le format JSON ne prend pas en charge nativement les données binaires. Les données binaires doivent être échappées afin de pouvoir être placées dans un élément de chaîne (c'est-à-dire zéro ou plusieurs caractères Unicode entre guillemets doubles avec des échappements en barre oblique) en JSON.

Une méthode évidente pour échapper des données binaires est d'utiliser le Base64. Cependant, le Base64 a une surcharge de traitement élevée. De plus, il étend 3 octets en 4 caractères, ce qui entraîne une augmentation de la taille des données d'environ 33%.

Un cas d'utilisation pour cela est le brouillon v0.8 de la spécification API de stockage cloud CDMI. Vous créez des objets de données via un service web REST en utilisant JSON, par exemple

PUT /MyContainer/BinaryObject HTTP/1.1
Host: cloud.example.com
Accept: application/vnd.org.snia.cdmi.dataobject+json
Content-Type: application/vnd.org.snia.cdmi.dataobject+json
X-CDMI-Specification-Version: 1.0
{
    "mimetype" : "application/octet-stream",
    "metadata" : [ ],
    "value" :   "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz
    IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg
    dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu
    dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo
    ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=",
}

Existe-t-il de meilleures façons et des méthodes standard pour encoder des données binaires dans des chaînes JSON?

50 votes

Pour le téléchargement : vous pourriez être surpris de la façon dont le base64 se compresse sous gzip, donc si vous avez gzip activé sur votre serveur, vous êtes probablement également en sécurité.

6 votes

Une autre solution digne msgpack.org pour les nerds hardcore : github.com/msgpack/msgpack/blob/master/spec.md

5 votes

@cloudfeet, Une fois par utilisateur par action. Un très gros deal.

563voto

hobbs Points 71946

Il existe 94 caractères Unicode qui peuvent être représentés comme un octet selon la spécification JSON (si votre JSON est transmis en UTF-8). Avec cela à l'esprit, je pense que le mieux que vous puissiez faire en termes d'espace est le base85 qui représente quatre octets comme cinq caractères. Cependant, cette méthode n'améliore que de 7% le base64, elle est plus coûteuse à calculer et les implémentations sont moins courantes que pour le base64, donc ce n'est probablement pas une victoire.

Vous pourriez également simplement mappé chaque octet d'entrée au caractère correspondant dans U+0000-U+00FF, puis effectuer l'encodage minimum requis par la norme JSON pour passer ces caractères; l'avantage ici est que le décodage requis est nul au-delà des fonctions intégrées, mais l'efficacité de l'espace est mauvaise - une expansion de 105% (si tous les octets d'entrée sont également probables) par rapport à 25% pour base85 ou 33% pour base64.

Verdict final : base64 l'emporte à mon avis, car il est commun, facile et pas assez mauvais pour justifier un remplacement.

Voir aussi : Base91 et Base122

0 votes

Ne serait-ce pas plus efficace ? Je ne comprends pas comment base-85 peut être le meilleur.

0 votes

Comment l'encodage dont vous parlez dans le deuxième paragraphe est-il lié à celui décrit ici?

7 votes

Attendez comment en utilisant simplement le byte réel lors du codage des caractères de citation une expansion de 105% et base64 seulement 33%? N'est-ce pas que base64 est de 133%?

46voto

chmike Points 2514

Le problème avec UTF-8 est que ce n'est pas le codage le plus efficace en termes d'espace. De plus, certaines séquences aléatoires d'octets binaires sont des encodages UTF-8 invalides. Donc vous ne pouvez pas simplement interpréter une séquence d'octets binaires aléatoire comme des données UTF-8 car ce serait un encodage UTF-8 invalide. L'avantage de cette contrainte sur l'encodage UTF-8 est que cela le rend robuste et permet de localiser le début et la fin des caractères multioctets peu importe l'octet à partir duquel nous commençons à regarder.

En conséquence, si l'encodage d'une valeur d'octet dans la plage [0..127] nécessiterait seulement un octet dans l'encodage UTF-8, l'encodage d'une valeur d'octet dans la plage [128..255] nécessiterait 2 octets ! Pire que cela. En JSON, les caractères de contrôle, " et \ ne sont pas autorisés à apparaître dans une chaîne. Donc les données binaires nécessiteraient une certaine transformation pour être correctement encodées.

Regardons. Si nous supposons des valeurs d'octets aléatoires uniformément distribuées dans nos données binaires, alors, en moyenne, la moitié des octets seraient encodés en un octet et l'autre moitié en deux octets. Les données binaires encodées en UTF-8 auraient une taille 150% de la taille initiale.

L'encodage Base64 ne dépasse que de 133% la taille initiale. Donc l'encodage Base64 est plus efficace.

Que diriez-vous d'utiliser un autre encodage de base ? En UTF-8, l'encodage des 128 valeurs ASCII est le plus efficace en termes d'espace. En 8 bits, vous pouvez stocker 7 bits. Donc si nous coupons les données binaires en morceaux de 7 bits pour les stocker dans chaque octet d'une chaîne encodée en UTF-8, les données encodées ne grossiraient que jusqu'à 114% de la taille initiale. Mieux que Base64. Malheureusement, nous ne pouvons pas utiliser cette astuce simple car JSON n'autorise pas certains caractères ASCII. Les 33 caractères de contrôle de l'ASCII ( [0..31] et 127) ainsi que " et \ doivent être exclus. Cela ne nous laisse que 128-35 = 93 caractères.

Donc en théorie, nous pourrions définir un encodage Base93 qui ferait grossir la taille encodée à 8/log2(93) = 8*log10(2)/log10(93) = 122%. Mais un encodage Base93 ne serait pas aussi pratique qu'un encodage Base64. Base64 nécessite de découper la séquence d'octets d'entrée en morceaux de 6 bits pour lesquels des opérations simples en bits fonctionnent bien. De plus, 133% n'est pas beaucoup plus que 122%.

C'est pourquoi je suis arrivé indépendamment à la conclusion commune que Base64 est effectivement le meilleur choix pour encoder des données binaires en JSON. Ma réponse présente une justification à ce sujet. Je conviens que ce n'est pas très attrayant d'un point de vue performances, mais pensez aussi au bénéfice d'utiliser JSON avec sa représentation de chaîne lisible par l'homme, facile à manipuler dans tous les langages de programmation.

Si les performances sont critiques, alors un encodage binaire pur devrait être envisagé comme remplacement de JSON. Mais avec JSON, ma conclusion est que Base64 est le meilleur choix.

0 votes

Que diriez-vous de Base128 mais en laissant le sérialiseur JSON échapper les caractères " et \ ? Je pense qu'il est raisonnable de s'attendre à ce que l'utilisateur utilise une implémentation de parseur JSON.

1 votes

@jcalfee314 Malheureusement, ceci n'est pas possible car les caractères avec un code ASCII inférieur à 32 ne sont pas autorisés dans les chaînes JSON. Les encodages avec une base comprise entre 64 et 128 ont déjà été définis, mais le calcul requis est supérieur à la base64. Le gain de taille de texte encodé n'en vaut pas la peine.

0 votes

Si le chargement d'un grand nombre d'images en base64 (disons 1000), ou le chargement via une connexion vraiment lente, le base85 ou le base93 seraient-ils jamais préférables pour réduire le trafic réseau (avec ou sans gzip) ? Je me demande s'il existe un point où des données plus compactes pourraient justifier l'utilisation de l'une des méthodes alternatives.

45voto

DarcyThomas Points 352

Le BSON (Binary JSON) peut vous convenir. http://fr.wikipedia.org/wiki/BSON

Éditez : Pour information, la bibliothèque .NET json.net prend en charge la lecture et l'écriture du BSON si vous recherchez un peu d'amour côté serveur C#.

1 votes

"Dans certains cas, BSON utilisera plus d'espace que JSON en raison des longueurs préfixes et des indices de tableau explicites." fr.wikipedia.org/wiki/BSON

1 votes

Bonne nouvelle : BSON prend en charge nativement des types tels que Binaire, DateHeure, et quelques autres (particulièrement utile si vous utilisez MongoDB). Mauvaise nouvelle : son encodage est en octets binaires... donc ce n'est pas une réponse à la question. Cependant, cela serait utile sur un canal qui prend en charge nativement le binaire tel que les messages RabbitMQ, messages ZeroMQ, ou un socket TCP ou UDP personnalisé.

23voto

andrej Points 702

Si vous rencontrez des problèmes de bande passante, essayez de compresser les données côté client en premier, puis encodez-les en base64.

Un bel exemple de cette magie se trouve sur http://jszip.stuartk.co.uk/ et plus de discussions sur ce sujet sont disponibles sur Implémentation JavaScript de Gzip

2 votes

Voici une implémentation zip JavaScript qui revendique de meilleures performances : zip.js

0 votes

Notez que vous pouvez (et devez) toujours compresser après également (généralement via Content-Encoding), car la compression en base64 fonctionne assez bien.

0 votes

@MahmoudAl-Qudsi tu veux dire que tu base64(zip(base64(zip(data))))? Je ne suis pas sûr que rajouter une autre compression zip puis le mettre en base64 (pour pouvoir l'envoyer en tant que données) soit une bonne idée.

14voto

richardtallent Points 17534

YEnc pourrait fonctionner pour vous :

http://fr.wikipedia.org/wiki/Yenc

"yEnc est un schéma de codage binaire en texte pour transférer des fichiers binaires en [texte]. Il réduit les frais généraux par rapport aux méthodes de codage précédentes basées sur l'US-ASCII en utilisant une méthode de codage de l'ASCII étendu sur 8 bits. Le surcoût de yEnc est souvent (si chaque valeur de byte apparaît approximativement avec la même fréquence en moyenne) aussi peu que 1 à 2 %, comparé à un surcoût de 33 % à 40 % pour les méthodes de codage sur 6 bits comme uuencode et Base64. ... En 2003, yEnc est devenu le système de codage standard de facto pour les fichiers binaires sur Usenet."

Cependant, yEnc est un encodage sur 8 bits, donc le stocker dans une chaîne JSON présente les mêmes problèmes que le stockage des données binaires originales - le faire de manière naïve signifie une expansion d'environ 100 %, ce qui est pire que le base64.

52 votes

Étant donné que de nombreuses personnes semblent encore consulter cette question, je tiens à mentionner que je ne pense pas que yEnc aide vraiment ici. yEnc est un codage sur 8 bits, donc le stocker dans une chaîne JSON pose les mêmes problèmes que le stockage des données binaires d'origine - le faire de manière naïve signifie une expansion d'environ 100%, ce qui est pire que base64.

0 votes

Dans les cas où l'utilisation de codages comme yEnc avec de grands alphabets avec des données JSON est considérée acceptable, escapeless peut fonctionner comme une bonne alternative en fournissant un surdébit fixe connu à l'avance.

0 votes

@hobbs Comment le stockage d'octets 8 bits dans un encodage 8 bits entraîne-t-il un surcoût de 100%?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X