JSON文字列のバイナリデータ。Base64より良いもの


614

JSON形式は、ネイティブバイナリデータをサポートしていません。バイナリデータは、JSONの文字列要素(バックスラッシュエスケープを使用して二重引用符で囲まれた0個以上のUnicode文字)に配置できるようにエスケープする必要があります。

バイナリデータをエスケープする明白な方法は、Base64を使用することです。ただし、Base64には高い処理オーバーヘッドがあります。また、3バイトを4文字に拡張するため、データサイズが約33%増加します。

この使用例の1つは、CDMIクラウドストレージAPI仕様のv0.8ドラフトです。JSONを使用してREST Webサービスを介してデータオブジェクトを作成します。

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

バイナリデータをJSON文字列にエンコードするより良い方法と標準的な方法はありますか?


30
アップロードの場合:一度だけ行うので、それほど大したことではありません。ダウンロードの場合、base64 がgzipでどの程度圧縮されているかは驚くかもしれません。そのため、サーバーでgzipが有効になっている場合も、おそらく問題ありません。
cloudfeet 2014年

2
ハードコアオタク向けのもう1つの価値あるソリューションmsgpack.org:github.com/msgpack/msgpack/blob/master/spec.md
nicolallias

2
@cloudfeet、アクションごとにユーザーごとに1回。非常に大きな取引です。
Pacerier 2017年

2
通常、文字はそれぞれ2バイトのメモリです。したがって、base64は回線上で+ 33%(4/3)のオーバーヘッドを与える可能性がありますが、そのデータを回線上に配置し、それを取得して利用するには、+ 166%(8/3)のオーバーヘッド必要になります。適例:Javascript文字列の最大長が100k文字の場合、base64を使用して表現できるのは37.5kバイトのデータのみであり、75kバイトのデータを表すことはできません。これらの数値は、アプリケーションJSON.parseなど、アプリケーションの多くの部分でボトルネックになる可能性があります。……
Pacerier

5
@Pacerier "通常は2バイトのメモリ[文字ごと]"は正確ではありません。たとえば、v8にはOneByteおよびTwoByte文字列があります。2バイト文字列は、グロテスクなメモリ消費を避けるために必要な場合にのみ使用されます。Base64は1バイト文字列でエンコード可能です。
ZachB 2018

回答:


459

JSON仕様に従って1バイトとして表すことができる94のUnicode文字があります(JSONがUTF-8として送信される場合)。そのことを念頭に置いて、スペースで実行できる最善の方法は、4バイトを5文字で表すbase85だと思います。ただし、これはbase64に比べて7%の改善にすぎず、計算にコストがかかり、実装はbase64よりも一般的ではないため、おそらく成功しません。

また、単純にすべての入力バイトをU + 0000-U + 00FFの対応する文字にマップしてから、JSON標準でこれらの文字を渡すために必要な最小限のエンコードを行うこともできます。ここでの利点は、必要なデコードが組み込み関数を超えてnilであるということですが、スペース効率が悪いです(105%の拡張(すべての入力バイトが同等である場合))に対して、base85の25%またはbase64の33%。

最終的な評決:base64は、私の意見では、一般的で、簡単で、交換を保証するほど悪くないという理由で勝っています。

参照:Base91およびBase122


5
引用文字をエンコードしながら実際のバイトを105%展開し、base64を33%だけ使用するのはどうですか?base64 133%ではありませんか?
jjxtra 2013

17
Base91は、引用符がアルファベットで含まれているため、JSONには適していません。JSONエンコード後の最悪のケース(すべての引用符の出力)では、元のペイロードの245%です。
jarnoh 2013

25
Python 3.4にはbase64.b85encode()b85decode()現在も含まれています。単純なエンコード+デコードのタイミング測定は、b85がb64より13倍以上遅いことを示しています。したがって、7%のサイズの勝利がありますが、1300%のパフォーマンス損失があります。
Pieter Ennes 14

3
@hobbs JSONは、制御文字はエスケープする必要があると述べていますRFC20セクション5.2ではDEL、制御文字として定義されています。
Tino

2
@Tino ECMA-404は、エスケープする必要のある文字を具体的に示しています。二重引用符U + 0022、バックスラッシュU + 005C、および「制御文字U + 0000からU + 001F」です。
ホブ、2015年

249

私は同じ問題に遭遇し、解決策を共有したいと思いました:multipart / form-data。

マルチパートフォームを送信する場合は、最初にJSONメタデータを文字列として送信し、次にContent-Disposition名でインデックス付けされた生のバイナリ(画像、wavなど)として個別に送信します。

これはobj-cでこれを行う方法に関する素晴らしいチュートリアルです。ここでは、フォームの境界で文字列データを分割し、バイナリデータから分離する方法を説明するブログ記事があります。

実際に行う必要がある唯一の変更はサーバー側です。POSTされたバイナリデータを適切に参照するメタデータを(Content-Disposition境界を使用して)キャプチャする必要があります。

サーバー側で追加の作業が必要になることは確かですが、多数の画像または大きな画像を送信する場合、これは価値があります。必要に応じて、これをgzip圧縮と組み合わせてください。

IMHOがbase64でエンコードされたデータを送信することはハックです。RFC multipart / form-dataは、バイナリデータをテキストまたはメタデータと組み合わせて送信するなどの問題のために作成されました。


4
ちなみに、GoogleドライブAPIはこの方法でそれをやっている:developers.google.com/drive/v2/reference/files/update#examples
マティアスConradt

2
丸い(バイナリ)ペグを四角い(ASCII)穴に押し込むのではなく、ネイティブ機能を使用すると、この回答が非常に低くなるのはなぜですか?
Mark K Cowan

5
base64でエンコードされたデータの送信はハックであり、multipart / form-dataも同様です。リンクしたブログ記事でさえ、Content-Type multipart / form-dataを使用することで、送信するのは実際にはフォームであると述べています。そうではありません。そのため、base64ハックは実装がはるかに簡単であるだけでなく、信頼性も高いと思います。マルチパート/フォームデータのコンテンツタイプがハードコーディングされたライブラリ(Pythonなど)をいくつか見ました。
t3chb0t 2018

4
@ t3chb0t multipart / form-dataメディアタイプは、フォームデータを転送するために生まれましたが、今日では特にHTTP / HTMLの外部で、特に電子メールコンテンツのエンコードに広く使用されています。今日、それは一般的なエンコーディング構文として提案されています。tools.ietf.org/html/rfc7578
ロレンツォ

3
@MarkKCowanこれは質問の目的には役立ちますが、「JSONで使用するためのバイナリからテキストへのエンコーディングのオーバーヘッドが低い」という質問には答えないため、この回答はJSONを完全に排除します。
Chinoto Vokro

34

UTF-8の問題は、それが最もスペース効率の良いエンコーディングではないことです。また、一部のランダムバイナリバイトシーケンスは無効なUTF-8エンコーディングです。したがって、ランダムなバイナリバイトシーケンスをUTF-8データとして解釈することはできません。これは、UTF-8エンコーディングが無効になるためです。UTF-8エンコーディングに対するこの制約の利点は、マルチバイト文字を見つけやすくして、見始めたバイトを開始および終了できることです。

結果として、[0..127]の範囲のバイト値をエンコードする場合、UTF-8エンコードで1バイトしか必要ない場合、[128..255]の範囲のバイト値をエンコードする場合は2バイトが必要になります。それより悪いです。JSONでは、制御文字「」と「\」を文字列に含めることはできません。そのため、バイナリデータを適切にエンコードするには、何らかの変換が必要になります。

見てみましょう。バイナリデータでランダムに分散されたバイト値を想定すると、平均して、バイトの半分が1バイトに、残りの半分が2バイトにエンコードされます。UTF-8でエンコードされたバイナリデータは、初期サイズの150%になります。

Base64エンコーディングは、初期サイズの133%までしか増加しません。そのため、Base64エンコーディングの方が効率的です。

別のBaseエンコーディングの使用についてはどうですか?UTF-8では、128のASCII値をエンコードするのが最もスペース効率が良いです。8ビットでは7ビットを格納できます。したがって、バイナリデータを7ビットのチャンクに切り分けて、UTF-8エンコードされた文字列の各バイトに格納すると、エンコードされたデータは初期サイズの114%までしか増加しません。Base64よりも優れています。残念ながら、JSONでは一部のASCII文字を使用できないため、この簡単なトリックは使用できません。ASCIIの33の制御文字([0..31]と127)および "と\は除外する必要があります。これにより、128〜35 = 93文字しか残りません。

したがって、理論的には、エンコードされたサイズを8 / log2(93)= 8 * log10(2)/ log10(93)= 122%に増やすBase93エンコードを定義できます。ただし、Base93エンコーディングは、Base64エンコーディングほど便利ではありません。Base64では、入力バイトシーケンスを6ビットのチャンクにカットする必要があります。このため、単純なビット単位の演算がうまく機能します。133%の横は122%を超えません。

これが、Base64が実際にバイナリデータをJSONでエンコードするための最良の選択であるという共通の結論に私が単独で気付いた理由です。私の答えはそれの正当化を提示します。パフォーマンスの観点からはそれほど魅力的ではないことに同意しますが、JSONをすべてのプログラミング言語で操作しやすい人間が読み取れる文字列表現で使用することの利点も考慮してください。

パフォーマンスが重要な場合は、純粋なバイナリエンコーディングをJSONの代わりと見なす必要があります。しかし、JSONの場合、私の結論は、Base64が最高だということです。


Base128について何が、その後のJSONシリアライザは"と\私はユーザーがJSONパーサー実装を使用することを期待するのが妥当だと思い脱出させる?
jcalfee314

1
@ jcalfee314残念ながら、これは不可能です。32未満のASCIIコードの文字は、JSON文字列では許可されません。ベースが64から128のエンコーディングはすでに定義されていますが、必要な計算はbase64よりも高くなっています。エンコードされたテキストサイズの増加は、それだけの価値はありません。
chmike

base64(1000としましょう)で大量の画像をロードする場合、または非常に低速な接続でロードする場合、base85またはbase93はネットワークトラフィックの削減(gzip付きまたはw / oなし)の費用を負担しますか?よりコンパクトなデータが別の方法の1つを正当化する点があるかどうか、私は興味があります。
vol7ron

計算時間は送信時間よりも重要だと思います。画像は明らかにサーバー側で事前計算する必要があります。とにかく、JSONはバイナリデータに適していないと結論付けています。
chmike

Base64エンコーディングは、初期サイズの133%までしか大きくならないので、Base64エンコーディングの方が効率的です」については、文字は通常それぞれ2バイトであるため、これは完全に間違っています。詳細については、stackoverflow.com
questions / 1443158 /…を

34

BSON(バイナリJSON)が役立つかもしれません。 http://en.wikipedia.org/wiki/BSON

編集:FYI .NETライブラリjson.netは、C#サーバー側の愛を探している場合、bsonの読み取りと書き込みをサポートしています。


1
「長さのプレフィックスと明示的な配列のインデックスが原因で、BSONがJSONよりも多くのスペースを使用する場合があります。」en.wikipedia.org/wiki/BSON
Pawel Cioch 2017年

朗報:BSONは、Binary、Datetimeなどのタイプをネイティブでサポートしています(特にMongoDBを使用している場合に役立ちます)。悪いニュース:エンコーディングはバイナリバイトです...したがって、OPに対する回答ではありません。ただし、RabbitMQメッセージ、ZeroMQメッセージ、カスタムTCPまたはUDPソケットなど、ネイティブでバイナリをサポートするチャネルでは便利です。
Dan H

19

帯域幅の問題に対処する場合は、最初にクライアント側でデータを圧縮してから、base64-itで圧縮してみてください。

そのような魔法の良い例はhttp://jszip.stuartk.co.uk/にあり、このトピックに関する詳細な議論はGzipのJavaScript実装にあります


2
ここではJavaScriptのzip実装だと主張し、より良い性能:zip.js
ヤヌスTroelsen

Content-Encodingbase64はかなりうまく圧縮するので、その後も(通常はを介して)圧縮できます(そして、そうすべきです)。
Mahmoud Al-Qudsi

@ MahmoudAl-Qudsiあなたはあなたがbase64(zip(base64(zip(data)))))であることを意味しましたか?別のzipを追加してからそれをbase64で(データとして送信できるようにするために)追加するのが良い考えかどうかはわかりません。
andrej

18

yEncはあなたのために働くかもしれません:

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

「yEncは、[テキスト]でバイナリファイルを転送するためのバイナリからテキストへのエンコードスキームです。8ビット拡張ASCIIエンコード方式を使用することにより、以前のUS-ASCIIベースのエンコード方式よりもオーバーヘッドが削減されます。yEncのオーバーヘッドは、各バイト値は、平均でほぼ同じ頻度で表示されます)uuencodeやBase64などの6ビットエンコーディング方式のオーバーヘッドが33%〜40%であるのに対し、わずか1〜2%。...2003年までに、yEncが事実上の標準になりました。 Usenet上のバイナリファイルのエンコーディングシステム。」

ただし、yEncは8ビットエンコーディングであるため、JSON文字列に保存すると、元のバイナリデータを保存する場合と同じ問題が発生します。これを行うと、約100%の拡張となり、base64よりも悪くなります。


42
多くの人がまだこの質問を表示しているようですので、yEncが実際に役立つとは思わないことを述べておきます。yEncは8ビットエンコーディングであるため、JSON文字列に格納すると、元のバイナリデータを格納する場合と同じ問題が発生します。これを行うと、約100%の拡張となり、base64よりも悪くなります。
ホブ、

JSONデータを含む大きなアルファベットでyEncのようなエンコーディングを使用することが許容できると見なされる場合、escapelessは、既知の固定のオーバーヘッドを提供する優れた代替手段として機能する場合があります。
Ivan Kosarev

10

base64の拡張率が最大33%であることは事実ですが、処理オーバーヘッドがこれより大幅に大きいとは限りません。実際に使用しているJSONライブラリ/ツールキットに依存します。エンコーディングとデコーディングは単純明快な操作であり、文字エンコーディング(JSONはUTF-8 / 16/32のみをサポートするため)に対しても最適化できます。base64文字は、JSON文字列エントリでは常に1バイトです。たとえば、Javaプラットフォームには、かなり効率的にジョブを実行できるライブラリーがあるため、オーバーヘッドの大部分はサイズの拡張によるものです。

以前の2つの回答に同意します。

  • base64はシンプルで、一般的に使用される標準であるため、JSONでの使用に特に適したものを見つけることはほとんどありません(base-85はポストスクリプトなどで使用されますが、メリットについて考えると、限界に達しています)。
  • 使用するデータによっては、エンコード前(およびデコード後)の圧縮が意味をなす場合があります

10

笑顔のフォーマット

エンコード、デコード、コンパクト化が非常に速い

速度の比較(Javaベースですが、それでも意味があります):https : //github.com/eishay/jvm-serializers/wiki/

また、バイト配列のbase64エンコーディングをスキップできるようにするJSONの拡張機能です。

スペースが重要な場合は、スマイルエンコードされた文字列をgzip圧縮できます


3
...そしてリンクは死んでいます。これは最新のようです:github.com/FasterXML/smile-format-specification
Zero3

4

7年後の編集: Google Gearsはなくなりました。この答えは無視してください。)


Google Gearsチームは、バイナリデータタイプの不足の問題に遭遇し、それに対処しようとしました:

Blob API

JavaScriptには、テキスト文字列用の組み込みデータ型がありますが、バイナリデータ用のものはありません。Blobオブジェクトは、この制限に対処しようとします。

たぶん、どうにかしてそれを織ることができます。


それで、JavaScriptとjsonのblobのステータスは何ですか?落とされましたか?
2015年

w3.org/TR/FileAPI/#blob-sectionスペースのbase64ほどパフォーマンスが良くありません。下にスクロールすると、(ホッブズの回答で示されているオプションの1つとして)utf8マップを使用してエンコードされていることがわかります。そして、私が知る限り、jsonのサポートはありません
ダニエレクルチャニ

3

バイナリデータを厳密にテキストベースの非常に制限された形式に変換する機能を探しているので、Base64のオーバーヘッドは、JSONで維持することを期待している利便性と比較して最小限であると思います。処理能力とスループットが問題になる場合は、おそらくファイル形式を再検討する必要があります。


2

リソースと複雑さの観点をディスカッションに追加するだけです。新しいリソースを格納してそれらを変更するためにPUT / POSTおよびPATCHを行うので、コンテンツ転送は、格納され、GET操作を発行することによって受信されるコンテンツの正確な表現であることを覚えておく必要があります。

マルチパートメッセージは救世主としてよく使用されますが、単純化の理由から、およびより複雑なタスクのために、コンテンツを全体として提供するという考えを好みます。それは自明であり、それは簡単です。

そして、はいJSONは不自由なものですが、結局JSON自体は冗長です。そして、BASE64へのマッピングのオーバーヘッドは、小さな方法です。

マルチパートメッセージを正しく使用するには、送信するオブジェクトを解体するか、プロパティパスを自動結合のパラメーター名として使用するか、ペイロードを表現するための別のプロトコル/フォーマットを作成する必要があります。

また、BSONアプローチを好むので、これは、希望どおりに広く簡単にサポートされるわけではありません。

基本的に、ここで何かが欠けていますが、実際にバイナリ転送を行う必要性を本当に確認していない限り(ほとんどの場合)、base64としてのバイナリデータの埋め込みは十分に確立されており、進む方法です。


1

私はもう少し掘り下げ(base128の実装中)、ASCIIコードが128より大きい文字を送信すると、ブラウザ(クロム)は実際には1つではなく2つの文字(バイト)を送信します:(理由はそのJSONですデフォルトで、127を超えるASCIIコードの文字がchmikeの回答で言及されたものを2バイトでコード化するutf8文字を使用します。この方法でテストを行いました:chrome url bar chrome:// net-export /と入力し、「未加工のまま含める」を選択しますバイト」、キャプチャを開始し、POSTリクエストを送信し(下部のスニペットを使用)、キャプチャを停止し、生のリクエストデータを含むjsonファイルを保存します。次に、そのjsonファイル内を調べます。

  • 私たちは、文字列を見つけることによって、当社のbase64要求を見つけることができ4142434445464748494a4b4c4d4e、これはの進コード化されABCDEFGHIJKLMN、我々はそれを見ることができます"byte_count": 639それのために。
  • 上記の127リクエストを見つけるには、文字列を検索します。C2BCC2BDC380C381C382C383C384C385C386C387C388C389C38AC38Bこれは、文字のリクエスト16進utf8コードです¼½ÀÁÂÃÄÅÆÇÈÉÊË(ただし、この文字のASCII 16進コードはですc1c2c3c4c5c6c7c8c9cacbcccdce)。その"byte_count": 703ため、127を超えるASCIIコードの文字はリクエストで2バイトずつコード化されるため、base64リクエストよりも64バイト長くなります:(

したがって、実際には、コード> 127の文字を送信しても利益がありません:(。base64文字列の場合、このような否定的な動作は観察されません(おそらくbase85の場合も-チェックしていません)。Ælexの回答で説明されているPOST multipart / form-dataのバイナリパートでデータを送信します(ただし、通常この場合、ベースコーディングを使用する必要はありません...)。

代替のアプローチは、base65280 / base65kなどを使用してコード化することにより、2バイトのデータ部分を1つの有効なutf8文字にマッピングすることに依存する場合がありますが、utf8仕様のため、おそらくbase64よりも効果が低くなります...


0

データ型は本当に関係があります。RESTfulリソースからペイロードを送信するさまざまなシナリオをテストしました。エンコーディングにはBase64(Apache)と圧縮GZIP(java.utils.zip。*)を使用しました。ペイロードには、フィルム、画像、オーディオファイルに関する情報が含まれています。パフォーマンスを大幅に低下させる画像と音声ファイルを圧縮してエンコードしました。圧縮前のエンコーディングはうまくいきました。画像と音声のコンテンツは、エンコードされ圧縮されたバイトとして送信されました[]。


0

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

バイナリデータのbase64変換を必要とせずに、「CDMIコンテンツタイプ」操作を使用してCDMIクライアントとサーバー間でバイナリデータを転送する方法について説明します。

「非CDMIコンテンツタイプ」操作を使用できる場合は、オブジェクトとの間で「データ」を転送するのが理想的です。その後、メタデータは、後続の「CDMIコンテンツタイプ」操作としてオブジェクトに追加/オブジェクトから取得できます。


-1

私のソリューションでは、XHR2はArrayBufferを使用しています。バイナリシーケンスとしてのArrayBufferには、マルチパートコンテンツ、ビデオ、オーディオ、グラフィック、テキストなど、複数のコンテンツタイプが含まれています。オールインワンレスポンス。

最新のブラウザでは、さまざまなコンポーネントに対してDataView、StringView、Blobがあります。詳細については、http//rolfrost.de/video.htmlも参照してください。


バイトの配列をシリアル化することにより、データを100%増加させます
Sharcoux

@Sharcoux wot ??
Mihail Malostanidis

JSONでのバイト配列のシリアル化は次のようなものです。[16, 2, 38, 89]これは非常に非効率的です。
Sharcoux
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.