Dati binari nella stringa JSON. Qualcosa di meglio di Base64


615

Il formato JSON non supporta nativamente i dati binari. I dati binari devono essere sottoposti a escape in modo che possano essere inseriti in un elemento stringa (ovvero zero o più caratteri Unicode tra virgolette doppie utilizzando gli escape di barra rovesciata) in JSON.

Un metodo ovvio per sfuggire ai dati binari è usare Base64. Tuttavia, Base64 ha un elevato sovraccarico di elaborazione. Inoltre, espande 3 byte in 4 caratteri, il che comporta un aumento delle dimensioni dei dati di circa il 33%.

Un caso d'uso per questo è la bozza v0.8 della specifica API di archiviazione cloud CDMI . È possibile creare oggetti dati tramite un servizio Web REST utilizzando JSON, ad es

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

Esistono modi migliori e metodi standard per codificare i dati binari nelle stringhe JSON?


30
Per il caricamento: lo stai facendo solo una volta, quindi non è un grosso problema. Per il download, potresti essere sorpreso di quanto comprenda base64 sotto gzip , quindi se hai gzip abilitato sul tuo server probabilmente stai anche bene.
cloudfeet,

2
Un'altra degna soluzione msgpack.org per i nerd hardcore: github.com/msgpack/msgpack/blob/master/spec.md
nicolallias

2
@cloudfeet, una volta per utente per azione . Un grande affare.
Pacerier,

2
Si noti che i caratteri sono in genere 2 byte di memoria ciascuno. Pertanto, base64 potrebbe generare un sovraccarico del + 33% (4/3) sul filo, ma mettere quei dati sul filo, recuperarli e utilizzarli richiederebbe un sovraccarico del + 166% (8/3) . Caso in questione: se una stringa Javascript ha una lunghezza massima di 100k caratteri, è possibile rappresentare solo 37,5k byte di dati utilizzando base64, non 75k byte di dati. Questi numeri possono rappresentare un collo di bottiglia in molte parti dell'applicazione, ad es JSON.parse.
Ecc

5
@Pacerier "in genere 2 byte di memoria [per carattere]" non è accurato. v8 ad esempio ha stringhe OneByte e TwoByte. Le stringhe a due byte vengono utilizzate solo dove necessario per evitare un consumo di memoria grottesco. Base64 è codificabile con stringhe di un byte.
ZachB,

Risposte:


460

Esistono 94 caratteri Unicode che possono essere rappresentati come un byte in base alle specifiche JSON (se JSON viene trasmesso come UTF-8). Con questo in mente, penso che il meglio che tu possa fare nello spazio sia base85 che rappresenta quattro byte come cinque caratteri. Tuttavia, questo è solo un miglioramento del 7% rispetto a base64, è più costoso da calcolare e le implementazioni sono meno comuni rispetto a base64, quindi probabilmente non è una vittoria.

Puoi anche semplicemente mappare ogni byte di input sul carattere corrispondente in U + 0000-U + 00FF, quindi eseguire la codifica minima richiesta dallo standard JSON per passare quei caratteri; il vantaggio qui è che la decodifica richiesta è nulla oltre le funzioni integrate, ma l'efficienza dello spazio è scarsa - un'espansione del 105% (se tutti i byte di input sono ugualmente probabili) rispetto al 25% per base85 o 33% per base64.

Giudizio finale: base64 vince, secondo me, per il fatto che è comune, facile e non abbastanza grave da giustificare la sostituzione.

Vedi anche: Base91 e Base122


5
Aspetta come sta semplicemente usando il byte effettivo mentre codifica i caratteri di citazione un'espansione del 105% e base64 solo il 33%? Base64 133%?
jjxtra,

17
Base91 è una cattiva idea per JSON, perché contiene la citazione in alfabeto. Nel peggiore dei casi (tutti i preventivi vengono emessi) dopo la codifica JSON, è il 245% del payload originale.
jarnoh,

25
Python 3.4 include base64.b85encode()e b85decode()ora. Una semplice misurazione del tempo di codifica + decodifica mostra che b85 è più di 13 volte più lento di b64. Quindi abbiamo una vincita del 7%, ma una perdita di prestazioni del 1300%.
Pieter Ennes,

3
@hobbs JSON afferma che i caratteri di controllo devono essere sottoposti a escape. La sezione 5.2 di RFC20 definisce DELun carattere di controllo.
Tino,

2
@Tino ECMA-404 elenca in modo specifico i caratteri che devono essere esclusi: la doppia virgola U + 0022, la barra rovesciata U + 005C e "i caratteri di controllo da U + 0000 a U + 001F".
Hobbs,

249

Ho riscontrato lo stesso problema e ho pensato di condividere una soluzione: multipart / form-data.

Inviando un modulo multipart, si invia prima come stringa i metadati JSON , quindi si invia separatamente come binario non elaborato (immagine (i), wav, ecc.) Indicizzato dal nome di disposizione del contenuto .

Ecco un bel tutorial su come eseguire questa operazione in obj-c, ed ecco un articolo di blog che spiega come partizionare i dati delle stringhe con il limite del modulo e separarli dai dati binari.

L'unica modifica che devi veramente fare è sul lato server; dovrai acquisire i tuoi metadati che dovrebbero fare riferimento ai dati binari POST in modo appropriato (usando un limite di disposizione dei contenuti).

Concesso, richiede un lavoro aggiuntivo sul lato server, ma se stai inviando molte immagini o immagini di grandi dimensioni, ne vale la pena. Combina questo con la compressione gzip se vuoi.

IMHO che invia dati con codifica base64 è un hack; i dati multipart / form RFC sono stati creati per problemi come questo: invio di dati binari in combinazione con testo o metadati.


4
A proposito, l'API di Google Drive lo sta facendo in questo modo: developers.google.com/drive/v2/reference/files/update#examples
Mathias Conradt

2
Perché questa risposta è così bassa quando usa le funzioni native invece di cercare di infilare un piolo tondo (binario) in una buca quadrata (ASCII)? ...
Mark K Cowan,

5
l'invio di dati con codifica base64 è un hack così come multipart / form-data. Anche l'articolo del blog che hai linkato recita che Usando il tipo di contenuto multipart / form-data che dichiari, quello che invii è in realtà un modulo. Ma non è. quindi penso che l'hack di base64 non sia solo molto più facile da implementare ma anche più affidabile ho visto alcune librerie (per esempio Python), che avevano un tipo di contenuto multipart / form-data hardcoded.
t3chb0t,

4
@ t3chb0t Il tipo di supporto multipart / form-data è nato per trasportare i dati dei moduli ma oggi è ampiamente utilizzato al di fuori del mondo HTTP / HTML, in particolare per codificare il contenuto della posta elettronica. Oggi viene proposta come sintassi di codifica generica. tools.ietf.org/html/rfc7578
lorenzo

3
@MarkKCowan Probabilmente perché, sebbene ciò sia utile allo scopo della domanda, non risponde alla domanda come è stata posta, che è effettivamente "Binario overhead basso per la codifica del testo da utilizzare in JSON", questa risposta elimina completamente JSON.
Chinoto Vokro,

34

Il problema con UTF-8 è che non è la codifica più efficiente in termini di spazio. Inoltre, alcune sequenze casuali di byte binari sono codifiche UTF-8 non valide. Quindi non puoi semplicemente interpretare una sequenza di byte binari casuali come alcuni dati UTF-8 perché sarà una codifica UTF-8 non valida. Il vantaggio di questo vincolo sulla codifica UTF-8 è che rende robusto e possibile individuare l'inizio e la fine dei caratteri multibyte qualunque byte iniziamo a guardare.

Di conseguenza, se la codifica di un valore di byte nell'intervallo [0..127] richiederebbe solo un byte nella codifica UTF-8, la codifica di un valore di byte nell'intervallo [128..255] richiederebbe 2 byte! Peggio ancora. In JSON, i caratteri di controllo "e \ non possono essere visualizzati in una stringa. Pertanto, i dati binari richiederebbero che alcune trasformazioni siano codificate correttamente.

Vediamo. Se assumiamo valori di byte casuali distribuiti uniformemente nei nostri dati binari, in media, la metà dei byte verrebbe codificata in un byte e l'altra metà in due byte. I dati binari codificati UTF-8 avrebbero il 150% della dimensione iniziale.

La codifica Base64 aumenta solo al 133% della dimensione iniziale. Quindi la codifica Base64 è più efficiente.

Che ne dici di usare un'altra codifica Base? In UTF-8, la codifica dei 128 valori ASCII è la più efficiente in termini di spazio. In 8 bit è possibile memorizzare 7 bit. Quindi se tagliamo i dati binari in blocchi di 7 bit per memorizzarli in ogni byte di una stringa codificata UTF-8, i dati codificati aumenterebbero solo del 114% della dimensione iniziale. Meglio di Base64. Sfortunatamente non possiamo usare questo semplice trucco perché JSON non consente alcuni caratteri ASCII. I 33 caratteri di controllo di ASCII ([0..31] e 127) e "e \ devono essere esclusi. Questo ci lascia solo 128-35 = 93 caratteri.

Quindi in teoria potremmo definire una codifica Base93 che aumenterebbe la dimensione codificata a 8 / log2 (93) = 8 * log10 (2) / log10 (93) = 122%. Ma una codifica Base93 non sarebbe conveniente come una codifica Base64. Base64 richiede di tagliare la sequenza di byte di input in blocchi di 6 bit per i quali la semplice operazione bit a bit funziona bene. Oltre il 133% non è molto più del 122%.

Questo è il motivo per cui sono giunto indipendentemente alla conclusione comune che Base64 è davvero la scelta migliore per codificare i dati binari in JSON. La mia risposta presenta una giustificazione per questo. Sono d'accordo che non è molto attraente dal punto di vista delle prestazioni, ma considero anche il vantaggio di usare JSON con la sua rappresentazione di stringhe leggibili dall'uomo facile da manipolare in tutti i linguaggi di programmazione.

Se le prestazioni sono fondamentali rispetto a una codifica binaria pura, è necessario considerare la sostituzione di JSON. Ma con JSON la mia conclusione è che Base64 è il migliore.


Che ne pensi di Base128 ma poi lasciando che il serializzatore JSON sfugga a "e \? Penso che sia ragionevole aspettarsi che l'utente usi un'implementazione del parser json.
jcalfee314

1
@ jcalfee314 sfortunatamente questo non è possibile perché i caratteri con codice ASCII inferiore a 32 non sono consentiti nelle stringhe JSON. Sono già state definite codifiche con una base compresa tra 64 e 128, ma il calcolo richiesto è superiore a base64. Il guadagno nella dimensione del testo codificato non ne vale la pena.
chmike,

Se il caricamento di una grande quantità di immagini in base64 (diciamo 1000) o il caricamento tramite una connessione molto lenta, base85 o base93 pagherebbero mai per il traffico di rete ridotto (senza o senza gzip)? Sono curioso di sapere se arriva un punto in cui i dati più compatti farebbero caso a uno dei metodi alternativi.
vol7ron,

Sospetto che la velocità di calcolo sia più importante del tempo di trasmissione. Le immagini dovrebbero ovviamente essere pre-calcolate sul lato server. Ad ogni modo, la conclusione è che JSON è dannoso per i dati binari.
chmike,

Ri "La codifica Base64 cresce solo al 133% della dimensione iniziale. Quindi la codifica Base64 è più efficiente ", questo è completamente sbagliato perché i caratteri sono in genere 2 byte ciascuno. Vedere elaborazione a stackoverflow.com/questions/1443158/...
Pacerier

34

BSON (Binary JSON) potrebbe funzionare per te. http://en.wikipedia.org/wiki/BSON

Modifica: Cordiali saluti, la libreria .NET json.net supporta la lettura e la scrittura di bson se stai cercando un po 'd'amore lato server C #.


1
"In alcuni casi, BSON utilizzerà più spazio di JSON a causa dei prefissi di lunghezza e degli indici di array espliciti." en.wikipedia.org/wiki/BSON
Pawel Cioch

Buone notizie: BSON supporta nativamente tipi come Binary, Datetime e pochi altri (particolarmente utile se si utilizza MongoDB). Cattive notizie: la sua codifica è byte binari ... quindi è una non risposta all'OP. Tuttavia sarebbe utile su un canale che supporti nativamente binari come il messaggio RabbitMQ, il messaggio ZeroMQ o un socket TCP o UDP personalizzato.
Dan H

19

Se hai a che fare con problemi di larghezza di banda, prova prima a comprimere i dati sul lato client, quindi base64-it.

Un bell'esempio di tale magia è su http://jszip.stuartk.co.uk/ e ulteriori discussioni su questo argomento sono sull'implementazione JavaScript di Gzip


2
ecco un'implementazione zip JavaScript che pretende prestazioni migliori: zip.js
Janus Troelsen,

Nota che puoi (e dovresti) comprimere anche dopo (in genere tramite Content-Encoding), poiché base64 comprime abbastanza bene.
Mahmoud Al-Qudsi,

@ MahmoudAl-Qudsi intendevi che base64 (zip (base64 (zip (data))))? Non sono sicuro che aggiungere un'altra zip e quindi base64 (per poterlo inviare come dati) sia una buona idea.
Andrej,

18

yEnc potrebbe funzionare per te:

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

"yEnc è uno schema di codifica da binario a testo per il trasferimento di file binari in [testo]. Riduce il sovraccarico rispetto ai precedenti metodi di codifica basati su US-ASCII utilizzando un metodo di codifica ASCII estesa a 8 bit. Il sovraccarico di yEnc è spesso (se ogni valore di byte appare approssimativamente con la stessa frequenza in media) appena dell'1–2%, rispetto al 33% –40% di overhead per i metodi di codifica a 6 bit come uuencode e Base64 ... Entro il 2003 yEnc è diventato di fatto lo standard sistema di codifica per file binari su Usenet. "

Tuttavia, yEnc è una codifica a 8 bit, quindi la sua memorizzazione in una stringa JSON ha gli stessi problemi della memorizzazione dei dati binari originali: farlo in modo ingenuo significa circa un'espansione del 100%, che è peggio di base64.


42
Dal momento che molte persone sembrano ancora vedere questa domanda, vorrei ricordare che non penso che yEnc sia davvero d'aiuto qui. yEnc è una codifica a 8 bit, quindi la sua memorizzazione in una stringa JSON ha gli stessi problemi della memorizzazione dei dati binari originali: farlo in modo ingenuo significa circa un'espansione del 100%, che è peggio di base64.
Hobbs,

Nei casi in cui l'uso di codifiche come yEnc con alfabeti di grandi dimensioni con dati JSON è considerato accettabile, l' escapeless può funzionare come una buona alternativa fornendo un overhead noto in anticipo fisso.
Ivan Kosarev,

10

Sebbene sia vero che base64 abbia un tasso di espansione del ~ 33%, non è necessariamente vero che il sovraccarico di elaborazione sia significativamente superiore a questo: dipende in realtà dalla libreria / toolkit JSON che si sta utilizzando. La codifica e la decodifica sono semplici operazioni dirette e possono anche essere ottimizzate per la codifica dei caratteri wrt (poiché JSON supporta solo UTF-8/16/32) - i caratteri base64 sono sempre a byte singolo per le voci String JSON. Ad esempio sulla piattaforma Java ci sono librerie che possono fare il lavoro in modo piuttosto efficiente, quindi l'overhead è dovuto principalmente alle dimensioni espanse.

Sono d'accordo con due risposte precedenti:

  • base64 è uno standard semplice e comunemente usato, quindi è improbabile trovare qualcosa di meglio da usare specificamente con JSON (base-85 è usato da Postscript ecc; ma i vantaggi sono nella migliore delle ipotesi marginali se ci pensate)
  • la compressione prima della codifica (e dopo la decodifica) può avere molto senso, a seconda dei dati utilizzati

10

Formato sorriso

È molto veloce da codificare, decodificare e compattare

Confronto di velocità (basato su java ma comunque significativo): https://github.com/eishay/jvm-serializers/wiki/

Inoltre, è un'estensione di JSON che consente di saltare la codifica Base64 per array di byte

Le stringhe con codifica sorriso possono essere compresse con gzip quando lo spazio è critico


3
... e il link è morto. Questo sembra aggiornato: github.com/FasterXML/smile-format-specification
Zero3

4

( Modifica 7 anni dopo: Google Gears non c'è più. Ignora questa risposta.)


Il team di Google Gears ha riscontrato il problema della mancanza di dati binari e ha tentato di risolverlo:

API BLOB

JavaScript ha un tipo di dati integrato per le stringhe di testo, ma nulla per i dati binari. L'oggetto BLOB tenta di risolvere questa limitazione.

Forse puoi tesserlo in qualche modo.


Qual è lo stato dei BLOB in Javascript e JSON? È stato lasciato cadere?
chmike,

w3.org/TR/FileAPI/#blob-section Non performante come base64 per lo spazio, se scorri verso il basso scopri che codifica usando utf8 map (come quella dell'opzione mostrata dalla risposta di hobbs). E nessun supporto JSON, per quanto ne so
Daniele Cruciani,

3

Dato che stai cercando la possibilità di inserire i dati binari in un formato strettamente basato su testo e molto limitato, penso che l'overhead di Base64 sia minimo rispetto alla comodità che ti aspetti di mantenere con JSON. Se la potenza di elaborazione e la velocità effettiva sono un problema, probabilmente dovresti riconsiderare i tuoi formati di file.


2

Solo per aggiungere il punto di vista delle risorse e della complessità alla discussione. Dal momento che si esegue PUT / POST e PATCH per l'archiviazione di nuove risorse e la loro modifica, si dovrebbe ricordare che il trasferimento del contenuto è una rappresentazione esatta del contenuto archiviato e che viene ricevuto eseguendo un'operazione GET.

Un messaggio in più parti viene spesso utilizzato come salvatore, ma per motivi di semplicità e per compiti più complessi, preferisco l'idea di dare il contenuto nel suo insieme. Si spiega da sé ed è semplice.

E sì, JSON è qualcosa di paralizzante, ma alla fine JSON stesso è prolisso. E il sovraccarico della mappatura su BASE64 è un modo troppo piccolo.

Utilizzando correttamente i messaggi multiparte è necessario smantellare l'oggetto da inviare, utilizzare un percorso di proprietà come nome del parametro per la combinazione automatica o sarà necessario creare un altro protocollo / formato per esprimere semplicemente il payload.

Apprezzando anche l'approccio BSON, questo non è così ampiamente e facilmente supportato come si vorrebbe che fosse.

Fondamentalmente, qui ci manca solo qualcosa, ma incorporare i dati binari come base64 è ben definito e ben fatto a meno che tu non abbia davvero identificato la necessità di effettuare il vero trasferimento binario (cosa che spesso non accade).


1

Scavo un po 'di più (durante l'implementazione di base128 ) ed espongo che quando inviamo caratteri i cui codici ascii sono più grandi di 128, il browser (chrome) in effetti invia DUE caratteri (byte) invece uno :( . Il motivo è che JSON di default usa utf8 caratteri per i quali i caratteri con codici ASCII superiori a 127 sono codificati da due byte, ciò che è stato menzionato dalla risposta di Chmike . Ho fatto il test in questo modo: digita chrome url bar chrome: // net-export / , seleziona "Includi raw byte ", avvia l'acquisizione, invia le richieste POST (utilizzando lo snippet in basso), interrompe l'acquisizione e salva il file json con i dati delle richieste non elaborate. Quindi guardiamo all'interno di quel file json:

  • Possiamo trovare la nostra richiesta base64 trovando stringa di cui 4142434445464748494a4b4c4d4eè codifica esadecimale ABCDEFGHIJKLMNe lo vedremo "byte_count": 639per questo.
  • Possiamo trovare la nostra richiesta above127 trovando string che C2BCC2BDC380C381C382C383C384C385C386C387C388C389C38AC38Bsono codici utf8 di richiesta-esadecimale di caratteri ¼½ÀÁÂÃÄÅÆÇÈÉÊË(comunque sono i codici esadecimali ascii di questi caratteri c1c2c3c4c5c6c7c8c9cacbcccdce). Il "byte_count": 703così è 64bytes superiore base64 richiesta perché caratteri con codici ASCII sopra 127 sono codice da 2 byte richiesta :(

Quindi in effetti non abbiamo alcun profitto con l'invio di caratteri con codici> 127 :(. Per le stringhe di base64 non osserviamo un comportamento così negativo (probabilmente anche per base85 - non lo controllo) - tuttavia potrebbe essere una soluzione per questo problema invio di dati nella parte binaria di dati POST multipart / form descritti nella risposta Ælex (tuttavia di solito in questo caso non è necessario utilizzare alcuna codifica di base ...).

L'approccio alternativo può basarsi sulla mappatura di una porzione di dati di due byte in un carattere utf8 valido codificandolo usando qualcosa come base65280 / base65k ma probabilmente sarebbe meno efficace di base64 a causa della specifica utf8 ...


0

Il tipo di dati è davvero preoccupante. Ho testato diversi scenari sull'invio del payload da una risorsa RESTful. Per la codifica ho usato Base64 (Apache) e per la compressione GZIP (java.utils.zip. *). Il payload contiene informazioni su film, un'immagine e un file audio. Ho compresso e codificato i file di immagine e audio che hanno degradato drasticamente le prestazioni. La codifica prima della compressione è risultata buona. Il contenuto di immagini e audio è stato inviato come byte codificati e compressi [].


0

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

Descrive un modo per trasferire i dati binari tra un client CDMI e un server utilizzando operazioni di "tipo di contenuto CDMI" senza richiedere la conversione base64 dei dati binari.

Se è possibile utilizzare l'operazione "Tipo di contenuto non CDMI", è ideale per trasferire "dati" a / da un oggetto. Successivamente i metadati possono essere aggiunti / recuperati all'oggetto / dall'oggetto come successiva operazione di "tipo di contenuto CDMI".


-1

La mia soluzione ora, XHR2 sta usando ArrayBuffer. ArrayBuffer come sequenza binaria contiene contenuto multipart, video, audio, grafico, testo e così via con più tipi di contenuto. Risposta tutto in uno.

Nel browser moderno, con DataView, StringView e Blob per diversi componenti. Vedi anche: http://rolfrost.de/video.html per maggiori dettagli.


Farai crescere i tuoi dati + 100% serializzando una matrice di byte
Sharcoux,

@Sharcoux wot ??
Mihail Malostanidis,

La serializzazione di un array di byte in JSON è qualcosa di simile: [16, 2, 38, 89]molto inefficiente.
Sharcoux,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.