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


le Format JSON nativement ne supporte pas 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 d'une barre oblique inverse) dans JSON.

Une méthode évidente pour échapper à des données binaires consiste à utiliser Base64. Cependant, Base64 a un surcoût de traitement élevé. Il étend également 3 octets en 4 caractères, ce qui conduit à une augmentation de la taille des données d'environ 33%.

Un cas d'utilisation pour ceci est le brouillon v0.8 du Spécification de l'API de stockage en nuage CDMI. Vous créez des objets de données via un service Web REST en utilisant JSON, par ex.

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 encoder des données binaires en chaînes JSON?


485
2017-09-18 08:08


origine


Réponses:


Il y a 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). Dans cet esprit, je pense que le mieux que vous pouvez faire dans l'espace est base85 ce qui représente quatre octets comme cinq caractères. Cependant, il s'agit seulement d'une amélioration de 7% par rapport à base64, il est plus coûteux à calculer, et les implémentations sont moins courantes que pour base64, donc ce n'est probablement pas une victoire.

Vous pouvez également simplement mapper chaque octet d'entrée au caractère correspondant dans U + 0000-U + 00FF, puis effectuer le codage 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 l'efficacité de l'espace est mauvaise - 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, sur le fait que c'est commun, facile, et pas mal assez pour justifier le remplacement.


376
2017-09-18 08:33



Je sais que c'est une question vieille de près de 6 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 en tant que chaîne votre Méta-données JSON, puis envoyer séparément en tant que binaire brut (image (s), wavs, etc) indexé par le Contenu-Disposition prénom.

Voici une belle Didacticiel sur la façon de le faire dans obj-c, et voici un article de blog Cela explique comment partitionner les données de chaîne avec les limites du formulaire et les séparer des données binaires.

Le seul changement que vous devez vraiment faire est côté serveur; vous devrez capturer vos méta-données qui doivent référencer correctement les données binaires POST'ed (en utilisant une limite Content-Disposition).

Certes, cela demande un 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 voulez.

IMHO envoyer des données encodées en base64 est un hack; RFC multipart / form-data a été créé pour des problèmes comme celui-ci: envoyer des données binaires en combinaison avec du texte ou des méta-données.


187
2018-01-22 02:31



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

Modifier: FYI la bibliothèque .NET json.net prend en charge la lecture et l'écriture bson si vous cherchez un peu d'amour côté serveur C #.


30
2017-09-20 21:45



Le problème avec UTF-8 est qu'il ne s'agit pas du codage le plus efficace en termes d'espace. En outre, certaines séquences d'octets binaires aléatoires sont des codages UTF-8 non valides. Donc, vous ne pouvez pas simplement interpréter une séquence d'octets binaires aléatoires comme des données UTF-8, car il s'agira d'un codage UTF-8 non valide. L'avantage de cette contrainte sur le codage UTF-8 est qu'elle rend robuste et possible le repérage et la fin des caractères multi-octets, quel que soit l'octet que nous commençons à regarder.

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

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

L'encodage Base64 ne croît qu'à 133% de la taille initiale. L'encodage Base64 est donc plus efficace.

Qu'en est-il de l'utilisation d'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 blocs de 7 bits pour les stocker dans chaque octet d'une chaîne codée en UTF-8, les données codées augmenteront seulement 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 le "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 passer la taille codé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 morceaux de 6 bits pour lesquels une simple opération au niveau du bit fonctionne bien. À part 133%, ce 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 encoder des données binaires dans JSON. Ma réponse présente une justification pour cela. Je suis d'accord que ce n'est pas très intéressant du point de vue des performances, mais considérez aussi l'avantage 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, un encodage binaire pur doit être considéré comme un remplacement de JSON. Mais avec JSON ma conclusion est que Base64 est le meilleur.


24
2017-09-22 19:21



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

Bel exemple d'une telle magie est à http://jszip.stuartk.co.uk/ et plus de discussion à ce sujet est à Implémentation JavaScript de Gzip


17
2018-03-15 09:54



yEnc pourrait travailler pour vous:

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


16
2017-09-18 08:12



Smile format

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

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

C'est aussi une extension à JSON qui vous permet d'ignorer l'encodage en base64 pour les tableaux d'octets

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


9
2018-01-06 23:42



Bien qu'il soit vrai que base64 a un taux d'expansion de ~ 33%, il n'est pas forcément vrai que le temps de traitement est significativement plus élevé: cela dépend vraiment de la bibliothèque JSON / toolkit que vous utilisez. L'encodage et le décodage sont des opérations simples et peuvent même être optimisés avec le codage des caractères (JSON ne supportant que les UTF-8/16/32) - les caractères base64 sont toujours à un octet pour les entrées de chaînes JSON. Par exemple sur la plate-forme Java, il existe des bibliothèques qui peuvent faire le travail de manière plutôt efficace, de sorte que les frais généraux sont principalement dus à la taille étendue.

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

  • base64 est une norme simple et couramment utilisée, il est donc peu probable de trouver quelque chose de mieux à utiliser 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 l'encodage (et après le décodage) peut avoir beaucoup de sens, en fonction des données que vous utilisez

7
2018-03-15 06:32



Étant donné que vous recherchez la possibilité de transférer des données binaires dans un format strictement textuel et très limité, je pense que la surcharge de Base64 est minime par rapport à la commodité que vous attendez avec JSON. Si la puissance de traitement et le débit sont une préoccupation, alors vous devrez probablement reconsidérer vos formats de fichiers.


2
2017-09-18 08:29



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

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

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 d'adresser cette limitation.

Peut-être que vous pouvez tisser ça d'une façon ou d'une autre.


2
2017-09-18 08:30



Juste pour ajouter le point de vue ressource et complexité à la discussion. Depuis que PUT / POST et PATCH sont stockés pour stocker de nouvelles ressources et les modifier, il ne faut pas oublier que le transfert de contenu est une représentation exacte du contenu stocké et reçu lors d'une opération GET.

Un message en plusieurs parties est souvent utilisé comme un 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 auto-explicatif et c'est simple.

Et oui JSON est quelque chose de paralysant mais à la fin JSON lui-même est verbeux. Et l'overhead de la cartographie à BASE64 est un moyen de petite taille.

En utilisant correctement les messages multi-parties, il faut soit démonter l'objet à envoyer, utiliser un chemin de propriété comme nom de paramètre pour la combinaison automatique, soit créer un autre protocole / format pour simplement exprimer la charge utile.

Appréciant également l'approche BSON, ce n'est pas si largement et facilement supporté comme on le voudrait.

Fondamentalement, nous manquons quelque chose ici, mais l'intégration des données binaires comme base64 est bien établie et le chemin à parcourir, à moins que vous ayez vraiment identifié le besoin de faire le vrai transfert binaire (ce qui est rarement le cas).


1
2018-04-12 11:13