web-dev-qa-db-fra.com

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

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

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

Le brouillon v0.8 de la spécification de l'API de stockage en nuage CDMI est un cas d'utilisation de ce logiciel. Vous créez des objets de données via un service Web REST 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 meilleurs moyens et méthodes standard pour coder des données binaires en chaînes JSON?

527
dmeister

Il y a 94 caractères Unicode qui peuvent être représentés sous la forme d'un octet conformément à la spécification JSON (si votre JSON est transmis au format UTF-8). Dans cet esprit, je pense que le mieux que vous puissiez faire en matière d'espace est base85 , qui représente quatre octets sous la forme de cinq caractères. Cependant, il ne s'agit que d'une amélioration de 7% par rapport à base64, son coût de calcul est plus élevé et les implémentations étant moins courantes que pour base64, ce n'est donc probablement pas une victoire.

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

Verdict final: base64 gagne, à mon avis, au motif qu’il est courant, facile et pas mal suffisant de justifier un remplacement.

Voir aussi: Base91

406
hobbs

Je sais que la question remonte à presque six ans, mais je rencontre le même problème et je pensais partager une solution: multipart/form-data.

En envoyant un formulaire en plusieurs parties, vous envoyez d'abord sous forme de chaîne vos métadonnées de données JSON, puis séparément sous forme de fichiers binaires bruts (images, wavs, etc.) indexés par le nom Content-Disposition .

Voici un joli tutoriel sur la procédure à suivre dans obj-c, et voici un article de blog qui explique comment partitionner les données de chaîne avec la limite de formulaire et les séparer des données binaires.

Le seul changement que vous devez réellement faire est du côté serveur; vous devrez capturer vos méta-données qui doivent référencer les données binaires POSTées de manière appropriée (en utilisant une limite Content-Disposition). 

Certes, cela demande du travail supplémentaire côté serveur, mais si vous envoyez beaucoup d'images ou de grandes images, cela en vaut la peine. Combinez cela avec la compression gzip si vous le souhaitez.

IMHO envoyer des données encodées en base64 est un hack; le RFC multipart/form-data a été créé pour des problèmes tels que: l'envoi de données binaires en combinaison avec du texte ou des méta-données.

207
Ælex

BSON (binaire JSON) peut fonctionner pour vous . http://en.wikipedia.org/wiki/BSON

Edit: FYI la bibliothèque .NET json.net prend en charge la lecture et l’écriture de bson si vous recherchez un amour côté serveur C #.

32
DarcyThomas

Le problème avec UTF-8 est que ce n'est pas l'encodage le plus économe en espace. De plus, certaines séquences d’octets binaires aléatoires constituent un encodage UTF-8 invalide. Vous ne pouvez donc pas interpréter une séquence d'octets binaires aléatoires comme des données UTF-8, car ce sera un codage UTF-8 non valide. L'avantage de cette contrainte sur le codage UTF-8 est qu'il rend robuste et possible la localisation du début et de la fin des caractères multi-octets, quel que soit l'octet considéré.

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

Laisse voir. Si nous supposons des valeurs d'octets aléatoires uniformément réparties dans nos données binaires, alors, en moyenne, la moitié des octets serait codée dans un octet et l'autre moitié dans deux octets. Les données binaires codées en UTF-8 auraient 150% de la taille initiale. 

L'encodage Base64 n'augmente que jusqu'à 133% de la taille initiale. Donc, l'encodage Base64 est plus efficace.

Qu'en est-il de l'utilisation d'un autre encodage de base? En UTF-8, le codage des 128 valeurs ASCII est le plus économe en 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 codée UTF-8, les données codées n'augmenteront que jusqu'à 114% de la taille initiale. Mieux que Base64. Malheureusement, nous ne pouvons pas utiliser cette astuce car JSON n'autorise pas certains caractères ASCII. Les 33 caractères de contrôle de ASCII ([0..31] et 127) et des "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 augmenterait 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 couper la séquence d'octets d'entrée en fragments de 6 bits pour lesquels une opération au niveau du bit fonctionne bien. À part 133% n’est pas beaucoup plus que 122%. 

C'est pourquoi je suis venu indépendamment à la conclusion commune que Base64 est en effet le meilleur choix pour coder des données binaires en JSON. Ma réponse présente une justification pour cela. Je conviens que cela n’est pas très attrayant du point de vue des performances, mais considérons également l’avantage d’utiliser JSON avec sa représentation sous forme de chaîne lisible par l’homme, facile à manipuler dans tous les langages de programmation. 

Si les performances sont critiques, un encodage binaire pur doit être considéré comme un remplacement du JSON. Mais avec JSON, ma conclusion est que Base64 est le meilleur.

26
chmike

Si vous rencontrez des problèmes de bande passante, essayez d’abord de compresser les données côté client, puis base64-it.

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

18
andrej

yEnc pourrait travailler pour vous:

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

18
richardtallent

Format du sourire

Il est très rapide d'encoder, décoder et compacter

Comparaison de vitesse (basé sur Java mais néanmoins significatif): https://github.com/eishay/jvm-serializers/wiki/

De plus, c'est une extension du JSON qui vous permet d'ignorer l'encodage en base64 des tableaux d'octets.

Les chaînes encodées Smile peuvent être compressées lorsque l'espace est critique

9
Stefano Fratini

Il est vrai que base64 a un taux d’expansion d’environ 33%, mais il n’est pas forcément vrai que le temps de traitement est beaucoup plus long: cela dépend vraiment de la bibliothèque JSON/toolkit que vous utilisez. Le codage et le décodage sont des opérations simples et directes, et ils peuvent même être optimisés pour le codage de caractères (JSON ne prenant en charge que UTF-8/16/32) - les caractères base64 sont toujours à octet unique pour les entrées de chaîne JSON . Pour. Par exemple, sur la plate-forme Java, certaines bibliothèques peuvent effectuer le travail de manière assez efficace, de sorte que les frais généraux sont principalement dus à une taille étendue.

Je suis d'accord avec deux réponses précédentes:

  • base64 est un standard simple et couramment utilisé. Il est donc peu probable que vous trouviez quelque chose de mieux à utiliser spécifiquement avec JSON (la base 85 est utilisée par PostScript, etc.; mais les avantages sont au mieux marginaux quand on y pense).
  • la compression avant le codage (et après le décodage) peut sembler très logique, selon les données que vous utilisez
7
StaxMan

Étant donné que vous recherchez la possibilité de regrouper les données binaires dans un format strictement limité au texte, je pense que les frais généraux de Base64 sont minimes par rapport à la commodité que vous attendez avec JSON. Si la puissance de traitement et le débit vous préoccupent, vous devrez probablement reconsidérer les formats de fichier.

2
jsoverson

(Edit 7 ans plus tard: Google Gears est parti. Ignorez cette réponse.)

L'équipe de Google Gears a rencontré le problème du manque de types de données binaires et a tenté de le résoudre:

API Blob

JavaScript a un type de données intégré pour les chaînes de texte, mais rien pour les données binaires. L'objet Blob tente de remédier à cette limitation.

Peut-être que vous pouvez intégrer cela d'une manière ou d'une autre.

2
a paid nerd

Juste pour ajouter le point de vue des ressources et de la complexité à la discussion. Puisque vous utilisez PUT/POST et PATCH pour stocker de nouvelles ressources et les modifier, rappelez-vous que le transfert de contenu est une représentation exacte du contenu stocké et reçu en émettant une opération GET.

Un message en plusieurs parties est souvent utilisé comme sauveur, mais pour des raisons de simplicité et pour des tâches plus complexes, je préfère l’idée de donner le contenu dans son ensemble. C'est explicite et c'est simple. 

Et oui, JSON est quelque chose de handicapant mais à la fin, JSON lui-même est prolixe. Et la surcharge de mappage vers BASE64 est un moyen de gagner du temps.

Si vous utilisez correctement les messages en plusieurs parties, vous devez démanteler l'objet à envoyer, utiliser un chemin de propriété comme nom de paramètre pour la combinaison automatique ou créer un autre protocole/format pour exprimer simplement la charge utile.

Également en faveur de l’approche BSON, ce n’est pas aussi largement et facilement supporté que l’on voudrait. 

En gros, il nous manque quelque chose ici, mais intégrer des données binaires car base64 est bien établi et reste beaucoup à faire à moins d’avoir vraiment identifié le besoin de faire le vrai transfert binaire (ce qui est rarement le cas).

1
Martin Kersten

Le type de données concerne vraiment. J'ai testé différents scénarios lors de l'envoi de la charge depuis une ressource RESTful. J'ai utilisé Base64 (Apache) pour l'encodage et GZIP (Java.utils.Zip. *) Pour la compression. J'ai compressé et encodé les fichiers image et audio, ce qui a considérablement dégradé les performances. L'encodage avant la compression s'est bien passé. Les images et le contenu audio ont été envoyés sous forme d'octets codés et compressés []. 

0
Koushik

Je creuse un peu plus (lors de la mise en oeuvre de base128 ), et expose cela lorsque nous envoyons des caractères dont les codes ascii sont supérieurs à 128, puis un navigateur (chrome) envoie en fait DEUX caractères (octets) à la place de l'un :(. La raison en est que JSON par défaut utilise des caractères utf8 pour lesquels les caractères avec des codes ascii supérieurs à 127 sont codés sur deux octets, ce qui a été mentionné par chmike answer. J'ai fait test de cette façon: tapez chrome url bar chrome: // net-export /, sélectionnez "Inclure les octets bruts", démarrez la capture, envoyez des demandes POST (en utilisant l'extrait de code au bas), arrêtez la capture et enregistrez le fichier JSON avec les données de demandes brutes. Ensuite, nous regardons à l'intérieur de ce fichier json:

  • Nous pouvons trouver notre requête en base64 en recherchant la chaîne 4142434445464748494a4b4c4d4e il s’agit du codage hexadécimal de ABCDEFGHIJKLMN et nous verrons cela "byte_count": 639 pour elle.
  • Nous pouvons trouver notre requête ci-dessus127 en trouvant la chaîne C2BCC2BDC380C381C382C383C384C385C386C387C388C389C38AC38B ce sont les codes de caractères utf8 request-hexa ¼½ÀÁÂÃÄÅÆÇÈÉÊË (cependant les codes hexadécimaux ascii de ces caractères sont c1c2c3c4c5c6c7c8c9cacbcccdce). Le "byte_count": 703 est donc 64 octets plus long que la requête base64 car les caractères avec des codes ascii supérieurs à 127 sont codés sur 2 octets dans la requête :(

Donc, en fait, nous n’avons aucun intérêt à envoyer des caractères dont le code est supérieur à 127 :(. Pour les chaînes en base64, nous n’observons pas un tel comportement négatif (probablement aussi pour base85 - je ne le vérifie pas). Cependant, une solution à ce problème sera peut-être envoi de données dans la partie binaire de POST multipart/form-data décrit dans Lex answer (toutefois, dans ce cas, nous n'avons pas besoin d'utiliser un codage de base du tout ...). 

L'approche alternative peut s'appuyer sur le mappage d'une partie de données de deux octets en un caractère utf8 valide en le codant en utilisant quelque chose comme base65280/base65k, mais il serait probablement moins efficace que base64 en raison de la spécification utf8 ...

function postBase64() {
  let formData = new FormData();
  let req = new XMLHttpRequest();

  formData.append("base64ch", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
  req.open("POST", '/testBase64ch');
  req.send(formData);
}


function postAbove127() {
  let formData = new FormData();
  let req = new XMLHttpRequest();

  formData.append("above127", "¼½ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüý");
  req.open("POST", '/testAbove127');
  req.send(formData);
}
<button onclick=postBase64()>POST base64 chars</button>
<button onclick=postAbove127()>POST chars with codes>127</button>

0

Voir: http://snia.org/sites/default/files/Multi-part%20MIME%20Extension%20v1.0g.pdf

Il décrit un moyen de transférer des données binaires entre un client et un serveur CDMI à l'aide d'opérations de type de contenu CDMI sans nécessiter une conversion en base64 des données binaires.

Si vous pouvez utiliser l'opération «Type de contenu non-CDMI», il est idéal de transférer des «données» vers/depuis un objet. Les métadonnées peuvent ensuite être ajoutées/extraites ultérieurement à/de l'objet en tant qu'opération 'Type de contenu CDMI' ultérieure.

0
Dheeraj Sangamkar

Si vous utilisez Node, je pense que le moyen le plus efficace et le plus simple est de convertir en UTF16 avec:

Buffer.from(data).toString('utf16le');

Vous pouvez récupérer vos données en:

Buffer.from(s, 'utf16le');
0
Sharcoux