Datos binarios en cadena JSON. Algo mejor que Base64

615

El formato JSON de forma nativa no admite datos binarios. Los datos binarios deben escaparse para que puedan colocarse en un elemento de cadena (es decir, cero o más caracteres Unicode en comillas dobles usando escapes de barra invertida) en JSON.

Un método obvio para escapar de datos binarios es usar Base64. Sin embargo, Base64 tiene una alta sobrecarga de procesamiento. También expande 3 bytes en 4 caracteres, lo que conduce a un mayor tamaño de datos en aproximadamente un 33%.

Un caso de uso para esto es el borrador v0.8 de la especificación de API de almacenamiento en la nube CDMI . Puede crear objetos de datos a través de un servicio web REST utilizando JSON, p. Ej.

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=",
}

¿Hay mejores formas y métodos estándar para codificar datos binarios en cadenas JSON?

dmeister
fuente
30
Para cargar: solo lo haces una vez, por lo que no es tan importante. Para la descarga, es posible que se sorprenda de lo bien que comprime base64 en gzip , por lo que si tiene habilitado gzip en su servidor, probablemente también esté bien.
cloudfeet
2
Otra solución digna msgpack.org para los nerds incondicionales: github.com/msgpack/msgpack/blob/master/spec.md
nicolallias
2
@cloudfeet, una vez por usuario por acción . Un gran negocio.
Pacerier
2
Tenga en cuenta que los caracteres suelen tener 2 bytes de memoria cada uno. Por lo tanto, base64 podría proporcionar una sobrecarga de + 33% (4/3) en el cable, pero colocar esos datos en el cable, recuperarlo y utilizarlo requeriría una sobrecarga de + 166% (8/3) . Caso en cuestión: si una cadena Javascript tiene una longitud máxima de 100k caracteres, solo puede representar 37.5k bytes de datos usando base64, no 75k bytes de datos. Estos números pueden ser un cuello de botella en muchas partes de la aplicación, por ejemplo, JSON.parseetc. ......
Pacerier
55
@Pacerier "normalmente 2 bytes de memoria [por carácter]" no es exacto. v8, por ejemplo, tiene cadenas OneByte y TwoByte. Las cadenas de dos bytes solo se usan cuando es necesario para evitar el consumo grotesco de memoria. Base64 es codificable con cadenas de un byte.
ZachB

Respuestas:

460

Hay 94 caracteres Unicode que se pueden representar como un byte de acuerdo con la especificación JSON (si su JSON se transmite como UTF-8). Con eso en mente, creo que lo mejor que puede hacer en el espacio es base85, que representa cuatro bytes como cinco caracteres. Sin embargo, esto es solo una mejora del 7% sobre base64, es más costoso de calcular y las implementaciones son menos comunes que para base64, por lo que probablemente no sea una victoria.

También puede simplemente asignar cada byte de entrada al carácter correspondiente en U + 0000-U + 00FF, luego hacer la codificación mínima requerida por el estándar JSON para pasar esos caracteres; La ventaja aquí es que la decodificación requerida es nula más allá de las funciones integradas, pero la eficiencia del espacio es mala: una expansión del 105% (si todos los bytes de entrada son igualmente probables) frente al 25% para base85 o 33% para base64.

Veredicto final: base64 gana, en mi opinión, con el argumento de que es común, fácil y no lo suficientemente malo como para justificar el reemplazo.

Vea también: Base91 y Base122

hobbs
fuente
55
Espere, ¿cómo está usando el byte real mientras codifica los caracteres de comillas con una expansión del 105% y base64 solo el 33%? ¿No es base64 133%?
jjxtra
17
Base91 es una mala idea para JSON, porque contiene comillas en alfabeto. En el peor de los casos (salida de todas las cotizaciones) después de la codificación JSON, es el 245% de la carga útil original.
jarnoh
25
Python 3.4 incluye base64.b85encode()y b85decode()ahora. Una simple medición de tiempo de codificación + decodificación muestra que b85 es más de 13 veces más lento que b64. Por lo tanto, tenemos una ganancia de tamaño del 7%, pero pérdida de rendimiento del 1300%.
Pieter Ennes
3
@hobbs JSON establece que los caracteres de control deben escapar. La sección 5.2 del RFC20 define DELser un personaje de control.
Tino
2
@Tino ECMA-404 enumera específicamente los caracteres que deben escaparse: la comilla doble U + 0022, la barra diagonal inversa U + 005C y "los caracteres de control U + 0000 a U + 001F".
hobbs
249

Me encontré con el mismo problema y pensé en compartir una solución: multipart / form-data.

Al enviar un formulario multiparte, primero envía como cadena sus metadatos JSON y luego lo envía por separado como binario sin procesar (imagen (es), wavs, etc.) indexado por el nombre de Content-Disposition .

Aquí hay un buen tutorial sobre cómo hacer esto en obj-c, y aquí hay un artículo de blog que explica cómo dividir los datos de la cadena con el límite del formulario y separarlos de los datos binarios.

El único cambio que realmente necesita hacer es en el lado del servidor; tendrá que capturar sus metadatos que deberían hacer referencia a los datos binarios POST'ed apropiadamente (mediante el uso de un límite de Disposición de contenido).

De acuerdo, requiere trabajo adicional en el lado del servidor, pero si está enviando muchas imágenes o imágenes grandes, vale la pena. Combina esto con la compresión gzip si quieres.

En mi humilde opinión, enviar datos codificados en base64 es un hack; El RFC multipart / form-data se creó para cuestiones como esta: enviar datos binarios en combinación con texto o metadatos.

Ælex
fuente
44
Por cierto, la API de Google Drive lo está haciendo de esta manera: developers.google.com/drive/v2/reference/files/update#examples
Mathias Conradt
2
¿Por qué esta respuesta es tan baja cuando usa características nativas en lugar de tratar de apretar una clavija redonda (binaria) en un agujero cuadrado (ASCII)? ...
Mark K Cowan
55
el envío de datos codificados en base64 es un hack, así que es multipart / form-data Incluso el artículo del blog que ha enlazado dice que Al usar el multiparte / tipo de datos de contenido que usted indica, lo que envía es en realidad un formulario. Pero no lo es. así que creo que el pirateo de base64 no solo es mucho más fácil de implementar, sino que también es más confiable. He visto algunas bibliotecas (por ejemplo, para Python), que tenían un tipo de contenido de datos multiparte / formulario codificado.
t3chb0t
44
@ t3chb0t El tipo de medio multiparte / datos de formulario nació para transportar datos de formulario, pero hoy se usa ampliamente fuera del mundo HTTP / HTML, especialmente para codificar contenido de correo electrónico. Hoy se propone como una sintaxis de codificación genérica. tools.ietf.org/html/rfc7578
lorenzo
3
@MarkKCowan Probable porque si bien esto es útil para el propósito de la pregunta, no responde la pregunta como se le preguntó, que es efectivamente "Codificación binaria a texto de bajo costo para su uso en JSON", esta respuesta abandona por completo a JSON.
Chinoto Vokro
34

El problema con UTF-8 es que no es la codificación más eficiente en cuanto al espacio. Además, algunas secuencias de bytes binarios aleatorios son codificaciones UTF-8 no válidas. Por lo tanto, no puede simplemente interpretar una secuencia aleatoria de bytes binarios como algunos datos UTF-8 porque será una codificación UTF-8 no válida. El beneficio de esta restricción en la codificación UTF-8 es que hace que sea robusto y posible localizar caracteres de varios bytes que comienzan y terminan cualquier byte que comencemos a mirar.

Como consecuencia, si codificar un valor de byte en el rango [0..127] necesitaría solo un byte en la codificación UTF-8, ¡codificar un valor de byte en el rango [128..255] requeriría 2 bytes! Peor que eso. En JSON, los caracteres de control "y \ no pueden aparecer en una cadena. Por lo tanto, los datos binarios requerirían alguna transformación para codificarse correctamente.

Vamos a ver. Si asumimos valores de bytes aleatorios distribuidos uniformemente en nuestros datos binarios, entonces, en promedio, la mitad de los bytes se codificarían en uno y la otra mitad en dos bytes. Los datos binarios codificados UTF-8 tendrían el 150% del tamaño inicial.

La codificación Base64 crece solo al 133% del tamaño inicial. Entonces, la codificación Base64 es más eficiente.

¿Qué pasa con el uso de otra codificación Base? En UTF-8, la codificación de los 128 valores ASCII es la más eficiente en cuanto al espacio. En 8 bits puede almacenar 7 bits. Entonces, si cortamos los datos binarios en fragmentos de 7 bits para almacenarlos en cada byte de una cadena codificada UTF-8, los datos codificados crecerían solo al 114% del tamaño inicial. Mejor que Base64. Desafortunadamente, no podemos usar este truco fácil porque JSON no permite algunos caracteres ASCII. Los 33 caracteres de control de ASCII ([0..31] y 127) y "y \ deben excluirse. Esto nos deja solo 128-35 = 93 caracteres.

Entonces, en teoría, podríamos definir una codificación Base93 que aumentaría el tamaño codificado a 8 / log2 (93) = 8 * log10 (2) / log10 (93) = 122%. Pero una codificación Base93 no sería tan conveniente como una codificación Base64. Base64 requiere cortar la secuencia de bytes de entrada en fragmentos de 6 bits para los cuales la operación simple a nivel de bits funciona bien. Además del 133% no es mucho más del 122%.

Es por eso que llegué independientemente a la conclusión común de que Base64 es de hecho la mejor opción para codificar datos binarios en JSON. Mi respuesta presenta una justificación para ello. Estoy de acuerdo en que no es muy atractivo desde el punto de vista del rendimiento, pero considere también el beneficio de usar JSON con su representación de cadena legible por humanos fácil de manipular en todos los lenguajes de programación.

Si el rendimiento es crítico, una codificación binaria pura debe considerarse como un reemplazo de JSON. Pero con JSON, mi conclusión es que Base64 es el mejor.

chmike
fuente
¿Qué hay de Base128 pero luego dejar que el serializador JSON escapar de la "y \ Creo que es razonable esperar que el usuario utilice una implementación JSON analizador?.
jcalfee314
1
@ jcalfee314 desafortunadamente esto no es posible porque los caracteres con código ASCII por debajo de 32 no están permitidos en las cadenas JSON. Las codificaciones con una base entre 64 y 128 ya se han definido, pero el cálculo requerido es mayor que base64. La ganancia en tamaño de texto codificado no vale la pena.
Chmike
Si cargar una gran cantidad de imágenes en base64 (digamos 1000), o cargar a través de una conexión realmente lenta, ¿base85 o base93 pagarían alguna vez el tráfico de red reducido (w / o w / o gzip)? Tengo curiosidad por saber si llega un punto en el que los datos más compactos justifiquen uno de los métodos alternativos.
vol7ron
Sospecho que la velocidad de cálculo es más importante que el tiempo de transmisión. Obviamente, las imágenes deben calcularse previamente en el lado del servidor. De todos modos, la conclusión es que JSON es malo para los datos binarios.
Chmike
Re " La codificación Base64 crece solo al 133% del tamaño inicial, por lo que la codificación Base64 es más eficiente ", esto es completamente incorrecto porque los caracteres son típicamente de 2 bytes cada uno. Vea la elaboración en stackoverflow.com/questions/1443158/…
Pacerier
34

BSON (Binary JSON) puede funcionar para usted. http://en.wikipedia.org/wiki/BSON

Editar: FYI, la biblioteca .NET json.net admite la lectura y escritura de bson si está buscando un poco de amor en el servidor C #.

DarcyThomas
fuente
1
"En algunos casos, BSON usará más espacio que JSON debido a los prefijos de longitud y los índices de matriz explícitos". en.wikipedia.org/wiki/BSON
Pawel Cioch
Buenas noticias: BSON admite de forma nativa tipos como Binary, Datetime y algunos otros (particularmente útil si está utilizando MongoDB). Malas noticias: su codificación es de bytes binarios ... por lo que no responde al OP. Sin embargo, sería útil en un canal que admita binarios de forma nativa, como un mensaje RabbitMQ, un mensaje ZeroMQ o un socket TCP o UDP personalizado.
Dan H
19

Si tiene problemas con el ancho de banda, primero intente comprimir los datos en el lado del cliente, luego base64-it.

Un buen ejemplo de tal magia está en http://jszip.stuartk.co.uk/ y más discusión sobre este tema está en la implementación de JavaScript de Gzip

andrej
fuente
2
aquí hay una implementación de JavaScript zip que afirma un mejor rendimiento: zip.js
Janus Troelsen
Tenga en cuenta que también puede (y debería) comprimir después (generalmente a través de Content-Encoding), ya que base64 se comprime bastante bien.
Mahmoud Al-Qudsi
@ MahmoudAl-Qudsi, ¿querías decir que base64 (zip (base64 (zip (data))))? No estoy seguro de que sea buena idea agregar otro archivo zip y luego base64 (para poder enviarlo como datos).
andrej
18

yEnc podría funcionar para usted:

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

"yEnc es un esquema de codificación de binario a texto para transferir archivos binarios en [texto]. Reduce la sobrecarga sobre los métodos de codificación basados ​​en ASCII de EE. UU. anteriores mediante el uso de un método de codificación ASCII extendido de 8 bits. La sobrecarga de yEnc es a menudo (si cada valor de byte aparece aproximadamente con la misma frecuencia en promedio) tan poco como 1–2%, en comparación con 33% –40% de sobrecarga para métodos de codificación de 6 bits como uuencode y Base64 ... En 2003, yEnc se convirtió en el estándar de facto sistema de codificación para archivos binarios en Usenet ".

Sin embargo, yEnc es una codificación de 8 bits, por lo que almacenarla en una cadena JSON tiene los mismos problemas que almacenar los datos binarios originales: hacerlo de manera ingenua significa una expansión del 100%, que es peor que base64.

richardtallent
fuente
42
Dado que muchas personas todavía parecen estar viendo esta pregunta, me gustaría mencionar que no creo que yEnc realmente ayude aquí. yEnc es una codificación de 8 bits, por lo que almacenarla en una cadena JSON tiene los mismos problemas que almacenar los datos binarios originales: hacerlo de manera ingenua significa una expansión del 100%, que es peor que base64.
hobbs
En los casos en que se considere aceptable usar codificaciones como yEnc con alfabetos grandes con datos JSON, escapeless puede funcionar como una buena alternativa que proporciona una sobrecarga fija conocida de antemano.
Ivan Kosarev
10

Si bien es cierto que base64 tiene una tasa de expansión de ~ 33%, no es necesariamente cierto que la sobrecarga de procesamiento sea significativamente mayor que esto: realmente depende de la biblioteca / kit de herramientas JSON que esté utilizando. La codificación y decodificación son operaciones simples y directas, e incluso se pueden optimizar la codificación de caracteres wrt (ya que JSON solo admite UTF-8/16/32): los caracteres base64 siempre son de un solo byte para las entradas de cadena JSON. Por ejemplo, en la plataforma Java hay bibliotecas que pueden hacer el trabajo de manera bastante eficiente, por lo que la sobrecarga se debe principalmente al tamaño expandido.

Estoy de acuerdo con dos respuestas anteriores:

  • base64 es un estándar simple y de uso común, por lo que es poco probable que encuentre algo mejor específicamente para usar con JSON (base-85 se usa en postscript, etc., pero los beneficios son, en el mejor de los casos, marginales cuando lo piensa)
  • la compresión antes de la codificación (y después de la decodificación) puede tener mucho sentido, dependiendo de los datos que use
StaxMan
fuente
10

Formato de sonrisa

Es muy rápido codificar, decodificar y compactar

Comparación de velocidad (basada en Java pero significativa): https://github.com/eishay/jvm-serializers/wiki/

También es una extensión de JSON que le permite omitir la codificación base64 para matrices de bytes

Las cadenas codificadas por sonrisa se pueden comprimir cuando el espacio es crítico

Stefano Fratini
fuente
3
... y el enlace está muerto. Este parece estar actualizado: github.com/FasterXML/smile-format-specification
Zero3
4

( Editar 7 años después: Google Gears se ha ido. Ignora esta respuesta).


El equipo de Google Gears se topó con el problema de la falta de tipos de datos binarios y ha intentado solucionarlo:

API de blob

JavaScript tiene un tipo de datos incorporado para cadenas de texto, pero nada para datos binarios. El objeto Blob intenta abordar esta limitación.

Tal vez puedas tejer eso de alguna manera.

un nerd pagado
fuente
Entonces, ¿cuál es el estado de los blobs en Javascript y JSON? ¿Se ha caído?
Chmike
w3.org/TR/FileAPI/#blob-section No es tan eficiente como la base64 para el espacio, si se desplaza hacia abajo encontrará que codifica usando el mapa utf8 (como una de las opciones mostradas por la respuesta de hobbs). Y no hay soporte de json, que yo sepa
Daniele Cruciani
3

Como está buscando la capacidad de calzar datos binarios en un formato estrictamente basado en texto y muy limitado, creo que la sobrecarga de Base64 es mínima en comparación con la comodidad que espera mantener con JSON. Si la potencia de procesamiento y el rendimiento son una preocupación, entonces probablemente deba reconsiderar sus formatos de archivo.

jsoverson
fuente
2

Solo para agregar el punto de vista de recursos y complejidad a la discusión. Como hacer PUT / POST y PATCH para almacenar nuevos recursos y modificarlos, uno debe recordar que la transferencia de contenido es una representación exacta del contenido que se almacena y se recibe emitiendo una operación GET.

Un mensaje de varias partes a menudo se usa como salvador, pero por razones de simplicidad y para tareas más complejas, prefiero la idea de dar el contenido como un todo. Se explica por sí mismo y es simple.

Y sí, JSON es algo paralizante, pero al final JSON en sí es detallado. Y la sobrecarga de mapeo a BASE64 es una forma de pequeña.

Al usar los mensajes de varias partes correctamente, uno tiene que desmantelar el objeto a enviar, usar una ruta de propiedad como nombre del parámetro para la combinación automática o necesitará crear otro protocolo / formato para expresar la carga útil.

También me gusta el enfoque BSON, esto no es tan amplio y fácil de soportar como a uno le gustaría que fuera.

Básicamente, simplemente echamos de menos algo aquí, pero la inserción de datos binarios como base64 está bien establecida y es un camino a seguir a menos que realmente haya identificado la necesidad de hacer la transferencia binaria real (que a menudo no es el caso).

Martin Kersten
fuente
1

Excavo un poco más (durante la implementación de base128 ), y expongo que cuando enviamos caracteres cuyos códigos ASCII son mayores que 128, el navegador (Chrome) de hecho envía DOS caracteres (bytes) en lugar de uno :( . La razón es que JSON por defecto use caracteres utf8 para los que los caracteres con códigos ASCII superiores a 127 están codificados por dos bytes, lo que fue mencionado por la respuesta de Chmike . Hice la prueba de esta manera: escriba chrome url bar chrome: // net-export / , seleccione "Incluir raw bytes ", comience a capturar, envíe solicitudes POST (usando un fragmento en la parte inferior), deje de capturar y guarde el archivo json con datos de solicitudes sin procesar. Luego miramos dentro de ese archivo json:

  • Podemos encontrar nuestra solicitud de base64 encontrando una cadena que 4142434445464748494a4b4c4d4ees una codificación hexadecimal ABCDEFGHIJKLMNy lo veremos "byte_count": 639por eso.
  • Podemos encontrar nuestra solicitud anterior127 encontrando cadenas que C2BCC2BDC380C381C382C383C384C385C386C387C388C389C38AC38Bson códigos de caracteres request-hex utf8 ¼½ÀÁÂÃÄÅÆÇÈÉÊË(sin embargo, los códigos hexadecimales ascii de estos caracteres son c1c2c3c4c5c6c7c8c9cacbcccdce). Por "byte_count": 703lo tanto, es 64bytes más largo que la solicitud base64 porque los caracteres con códigos ascii superiores a 127 tienen un código de 2 bytes en la solicitud :(

Entonces, de hecho, no tenemos ganancias al enviar caracteres con códigos> 127 :(. Para las cadenas de base64 no observamos un comportamiento tan negativo (probablemente también para base85, no lo verifico); sin embargo, puede haber alguna solución para este problema. envío de datos en la parte binaria de datos POST multiparte / formulario descritos en Ælex respuesta (sin embargo, en este caso generalmente no necesitamos usar ninguna codificación base ...).

El enfoque alternativo puede basarse en mapear la porción de datos de dos bytes en un carácter utf8 válido codificándolo usando algo como base65280 / base65k, pero probablemente sería menos efectivo que base64 debido a la especificación utf8 ...

Kamil Kiełczewski
fuente
0

El tipo de datos realmente preocupa. He probado diferentes escenarios sobre el envío de la carga desde un recurso RESTful. Para codificar he usado Base64 (Apache) y para compresión GZIP (java.utils.zip. *). La carga contiene información sobre una película, una imagen y un archivo de audio. Comprimí y codifiqué los archivos de imagen y audio que degradaron drásticamente el rendimiento. La codificación antes de la compresión resultó bien. El contenido de imagen y audio se envió como bytes codificados y comprimidos [].

Koushik
fuente
0

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

Describe una forma de transferir datos binarios entre un cliente CDMI y un servidor utilizando operaciones de 'tipo de contenido CDMI' sin requerir la conversión de base64 de los datos binarios.

Si puede utilizar la operación 'Tipo de contenido no CDMI', es ideal transferir 'datos' a / desde un objeto. Los metadatos pueden luego agregarse / recuperarse a / desde el objeto como una operación posterior de 'tipo de contenido CDMI'.

Dheeraj Sangamkar
fuente
-1

Mi solución ahora, XHR2 está usando ArrayBuffer. El ArrayBuffer como secuencia binaria contiene contenido multiparte, video, audio, gráfico, texto, etc. con múltiples tipos de contenido. Todo en una respuesta.

En el navegador moderno, que tiene DataView, StringView y Blob para diferentes componentes. Ver también: http://rolfrost.de/video.html para más detalles.

Rolf Rost
fuente
Hará que sus datos crezcan + 100% al serializar una matriz de bytes
Sharcoux
@Sharcoux wot ??
Mihail Malostanidis
La serialización de una matriz de bytes en JSON es algo así como: [16, 2, 38, 89]que es muy ineficiente.
Sharcoux