Datos binarios en cadena JSON. Algo mejor que Base64
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 entre comillas dobles usando barras invertidas) en JSON.
Un método obvio para escapar de datos binarios es utilizar Base64. Sin embargo, Base64 tiene una alta sobrecarga de procesamiento. También expande 3 bytes en 4 caracteres, lo que aumenta el tamaño de los datos en aproximadamente un 33%.
Un caso de uso para esto es el borrador v0.8 de la especificación API de almacenamiento en la nube CDMI . Los objetos de datos se crean 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=",
}
¿Existen mejores formas y métodos estándar para codificar datos binarios en cadenas JSON?
Hay 94 caracteres Unicode que se pueden representar como un byte según la especificación JSON (si su JSON se transmite como UTF-8). Con eso en mente, creo que lo mejor que puedes hacer en cuanto al espacio es base85 , que representa cuatro bytes como cinco caracteres. Sin embargo, esto es solo una mejora del 7% con respecto a 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 realizar 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 el 33% para base64.
Veredicto final: base64 gana, en mi opinión, porque es común, fácil y no lo suficientemente malo como para justificar un reemplazo.
Ver también: Base91 y Base122
Me encontré con el mismo problema y pensé en compartir una solución: multipart/form-data.
Al enviar un formulario de varias partes, envía primero como cadena sus metadatos JSON y luego los envía por separado como binario sin formato (imagen(es), wavs, etc.) indexados por el nombre de disposición de contenido .
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 particionar los datos de cadena con el límite del formulario y separarlos de los datos binarios.
El único cambio que realmente necesitas hacer es en el lado del servidor; Tendrá que capturar sus metadatos, que deben hacer referencia apropiada a los datos binarios publicados (mediante el uso de un límite de disposición de contenido).
Es cierto que requiere trabajo adicional en el lado del servidor, pero si envía muchas imágenes o imágenes grandes, vale la pena. Combine esto con compresión gzip si lo desea.
En mi humilde opinión, enviar datos codificados en base64 es un truco; El RFC multipart/form-data se creó para problemas como este: enviar datos binarios en combinación con texto o metadatos.
El problema con UTF-8 es que no es la codificación que ahorra más espacio. Además, algunas secuencias de bytes binarios aleatorios no tienen codificación UTF-8 válida. Por lo tanto, no puede simplemente interpretar una secuencia de bytes binarios aleatorios como 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 la hace robusta y posible ubicar caracteres de varios bytes al inicio y al final de 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 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 un byte y la otra mitad en dos bytes. Los datos binarios codificados en UTF-8 tendrían el 150% del tamaño inicial.
La codificación Base64 crece solo hasta el 133% del tamaño inicial. Entonces la codificación Base64 es más eficiente.
¿Qué tal si utilizamos otra codificación Base? En UTF-8, codificar los 128 valores ASCII es la forma más eficiente en cuanto a espacio. En 8 bits puedes almacenar 7 bits. Entonces, si cortamos los datos binarios en fragmentos de 7 bits para almacenarlos en cada byte de una cadena codificada en UTF-8, los datos codificados crecerían solo hasta el 114% del tamaño inicial. Mejor que Base64. Lamentablemente no podemos utilizar este sencillo truco 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 bit a bit simple funciona bien. Además del 133% no es mucho más que el 122%.
Es por eso que llegué de forma independiente 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 y fácil de manipular en todos los lenguajes de programación.
Si el rendimiento es crítico, se debe considerar una codificación binaria pura como reemplazo de JSON. Pero con JSON mi conclusión es que Base64 es el mejor.