web-dev-qa-db-ja.com

JSON文字列のバイナリデータ。 Base64より優れたもの

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文字列にエンコードするためのより良い方法と標準的な方法はありますか?

540
dmeister

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

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

最終的な判定:私の意見では、base64は、交換が必要であり、一般的で、簡単で、悪くない十分なという理由で勝ちます。

参照: Base91

425
hobbs

私は同じ問題にぶつかり、解決策を共有すると思った:multipart/form-data。

マルチパートフォームを送信することにより、最初に文字列としてJSONメタデータを送信し、次に、 Content-Disposition名前。

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

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

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

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

221
Ælex

BSON(Binary JSON)はあなたのために働くかもしれません。 http://en.wikipedia.org/wiki/BSON

編集:FYI。NETライブラリ json.net あなたがいくつかのC#サーバー側の愛を探しているなら、bsonの読み書きをサポートします。

31
DarcyThomas

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

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

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

Base64エンコードは、初期サイズの133%までしか拡大しません。そのため、Base64エンコードはより効率的です。

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

理論的には、エンコードサイズを8/log 2(93)= 8 * log 10(2)/ log 10(93)= 122%に拡大するBase93エンコードを定義できます。しかし、Base93エンコーディングはBase64エンコーディングほど便利ではありません。 Base64では、単純なビット単位演算がうまく機能するように、入力バイトシーケンスを6ビットのチャンクにカットする必要があります。 133%以外は122%を超えていません。

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

純粋なバイナリエンコーディングよりもパフォーマンスが重要な場合は、JSONの置き換えとして検討する必要があります。しかしJSONでは、私の結論はBase64が最高だということです。

27
chmike

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

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

18
andrej

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

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

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

ただし、yEncは8ビットエンコーディングであるため、元のバイナリデータを格納するのと同じ問題がJSON文字列に格納されるのと同じ問題があります。単純な方法で行うと、100%の拡張が行われ、base64よりも劣ります。

17
richardtallent

スマイルフォーマット

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

速度比較(Javaベースだがそれでもなお意味がある): https://github.com/eishay/jvm-serializers/wiki/

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

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

9
Stefano Fratini

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

私は以前の二つの答えに賛成です。

  • base64はシンプルで一般的に使用されている標準なので、JSONで使用するための特別なものを見つけることはほとんどありません(base-85はPostScriptなどで使用されていますが、考えてみるとメリットはごくわずかです)
  • エンコード前(およびデコード後)の圧縮は、使用するデータによっては非常に意味があります。
7
StaxMan

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


GoogleのGearsのチームが不足-のバイナリ・データ・タイプの問題に遭遇し、それに対処しようとしています:

Blob API

JavaScriptは、バイナリデータのための組み込みのデータのテキスト文字列の型が、何を持っています。 Blobオブジェクトは、この制限に対処しようと試みます。

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

4
a paid nerd

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

3
jsoverson

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

マルチパートメッセージはしばしば救世主として使用されますが、単純さの理由とより複雑なタスクのために、私は内容を全体として与えるという考えを好みます。それは自己説明的で簡単です。

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

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

またBSONのアプローチが好きで、これはそれが望まれるほど広くは簡単にサポートされていません。

基本的に、ここではなにか見逃しているだけですが、実際にバイナリ転送を行う必要性を実際に確認しない限り、base64としてバイナリデータを埋め込むことは確立されており、進むべき道です。

2
Martin Kersten

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

0
Koushik

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

  • Base64リクエストは、文字列4142434445464748494a4b4c4d4eを見つけることで見つけることができます。これはABCDEFGHIJKLMNの16進コードであり、"byte_count": 639であることがわかります。
  • 上記の127リクエストは、文字列C2BCC2BDC380C381C382C383C384C385C386C387C388C389C38AC38Bを見つけることで見つけることができます。これは、文字のリクエスト16進utf8コード¼½ÀÁÂÃÄÅÆÇÈÉÊËです(ただし、この文字のASCII 16進コードはc1c2c3c4c5c6c7c8c9cacbcccdceです)。 "byte_count": 703は、base64リクエストより64バイト長くなります。127を超えるASCIIコードの文字はリクエストで2バイトのコードになるためです:(

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

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

function postBase64() {
  let formData = new FormData();
  let req = new XMLHttpRequest();

  formData.append("base64ch", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
  req.open("POST", '/testBase64ch');
  req.send(formData);
}


function postAbove127() {
  let formData = new FormData();
  let req = new XMLHttpRequest();

  formData.append("above127", "¼½ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüý");
  req.open("POST", '/testAbove127');
  req.send(formData);
}
<button onclick=postBase64()>POST base64 chars</button>
<button onclick=postAbove127()>POST chars with codes>127</button>
0

参照してください: http://snia.org/sites/default/files/Multi-part%20MIME%20Extension%20v1.0g.pdf

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

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

0

もしNodeを使っているなら、最も効率的で簡単な方法はUTF16に変換することだと思います。

Buffer.from(data).toString('utf16le');

次の方法でデータを取り戻すことができます。

Buffer.from(s, 'utf16le');
0
Sharcoux