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文字列にエンコードするためのより良い方法と標準的な方法はありますか?
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
私は同じ問題にぶつかり、解決策を共有すると思った:multipart/form-data。
マルチパートフォームを送信することにより、最初に文字列としてJSONメタデータを送信し、次に、 Content-Disposition名前。
素敵な チュートリアル obj-cでこれを行う方法について説明します。 ブログ記事 は、フォーム境界で文字列データを分割し、分離する方法を説明していますバイナリデータから。
本当に必要な変更はサーバー側のみです。 POSTされたバイナリデータを適切に参照するメタデータをキャプチャする必要があります(Content-Disposition境界を使用して)。
当然、サーバー側で追加の作業が必要ですが、多数の画像または大きな画像を送信する場合、これは価値があります。必要に応じて、これをgzip圧縮と組み合わせます。
私見では、base64でエンコードされたデータを送信することはハッキングです。 RFC multipart/form-dataは、次のような問題のために作成されました。バイナリデータをテキストまたはメタデータと組み合わせて送信します。
BSON(Binary JSON)はあなたのために働くかもしれません。 http://en.wikipedia.org/wiki/BSON
編集:FYI。NETライブラリ json.net あなたがいくつかのC#サーバー側の愛を探しているなら、bsonの読み書きをサポートします。
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が最高だということです。
帯域幅の問題に対処する場合は、まずクライアント側でデータを圧縮してから、base64-itで圧縮してください。
そのような魔法の良い例は http://jszip.stuartk.co.uk/ そしてこのトピックに関するより多くの議論は にあります GzipのJavaScript実装 - /
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よりも劣ります。
エンコード、デコード、コンパクト化が非常に速い
速度比較(Javaベースだがそれでもなお意味がある): https://github.com/eishay/jvm-serializers/wiki/
また、バイト配列のbase64エンコーディングをスキップできるようにするのはJSONの拡張です
スペースが重要な場合は、スマイルエンコードされた文字列をgzipすることができます。
Base64の拡張率が約33%であることは事実ですが、処理のオーバーヘッドがこれを大幅に上回ることは必ずしも正しくありません。実際に使用しているJSONライブラリ/ツールキットによって異なります。エンコードとデコードは単純な単純な操作であり、文字エンコードに対して最適化することもできます(JSONはUTF-8/16/32しかサポートしていないため) - base64文字はJSON文字列エントリに対して常にシングルバイトです。たとえば、Javaプラットフォームでは、かなり効率的に作業を実行できるライブラリがあるため、オーバーヘッドの大部分はサイズの拡大によるものです。
私は以前の二つの答えに賛成です。
(7年後の編集: Google Gearsはなくなりました。この回答は無視してください。)
GoogleのGearsのチームが不足-のバイナリ・データ・タイプの問題に遭遇し、それに対処しようとしています:
JavaScriptは、バイナリデータのための組み込みのデータのテキスト文字列の型が、何を持っています。 Blobオブジェクトは、この制限に対処しようと試みます。
たぶん、あなたはどうにかしてそれを織ることができます。
バイナリデータを厳密にテキストベースで非常に限られた形式に変換する機能を探しているので、Base64のオーバーヘッドは、JSONで保守することを期待している利便性と比較して最小限であると思います。処理能力とスループットが問題になる場合は、おそらくファイル形式を再検討する必要があります。
ディスカッションにリソースと複雑さの観点を追加するだけです。新しいリソースを格納し、それらを変更するためにPUT/POSTおよびPATCHを実行するので、コンテンツ転送は格納され、GET操作を発行することによって受信されるコンテンツの正確な表現であることを忘れないでください。
マルチパートメッセージはしばしば救世主として使用されますが、単純さの理由とより複雑なタスクのために、私は内容を全体として与えるという考えを好みます。それは自己説明的で簡単です。
そしてはい、JSONは何かが不自由ですが、結局はJSON自体は冗長です。そしてBASE64へのマッピングのオーバーヘッドは小さくするための方法です。
マルチパートメッセージを正しく使用するには、送信するオブジェクトを解体するか、自動結合用のパラメータ名としてプロパティパスを使用するか、ペイロードを表すためだけに別のプロトコル/フォーマットを作成する必要があります。
またBSONのアプローチが好きで、これはそれが望まれるほど広くは簡単にサポートされていません。
基本的に、ここではなにか見逃しているだけですが、実際にバイナリ転送を行う必要性を実際に確認しない限り、base64としてバイナリデータを埋め込むことは確立されており、進むべき道です。
データ型は本当に重要です。私はRESTfulリソースからペイロードを送信することについてさまざまなシナリオをテストしました。エンコードにはBase64(Apache)、圧縮にはGZIP(Java.utils.Zip。*)を使用しましたペイロードにはフィルム、画像、オーディオファイルに関する情報が含まれています。画像と音声のファイルを圧縮してエンコードしたため、パフォーマンスが大幅に低下しました。圧縮前のエンコーディングはうまくいきました。画像と音声の内容は、エンコードされ圧縮されたbytes []として送信されました。
私はもう少し掘り下げて( base128 の実装中)、ASCIIコードが128よりも大きい文字を送信するときに実際にブラウザ(クローム)でそれを公開します 1つではなく2つの文字(バイト)を送信します:(。理由は、defaulによるJSONはutf8文字を使用しており、127を超えるASCIIコードの文字は2バイトでコード化されているためです chmike answer。この方法でテストを行いました:chrome url barchrome:// net-export /を入力して、「生を含む」を選択しますバイト」、キャプチャの開始、POSTリクエストの送信(下部のスニペットを使用)、キャプチャの停止、生のリクエストデータを含むjsonファイルの保存を行います。
4142434445464748494a4b4c4d4e
を見つけることで見つけることができます。これはABCDEFGHIJKLMN
の16進コードであり、"byte_count": 639
であることがわかります。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>
参照してください: http://snia.org/sites/default/files/Multi-part%20MIME%20Extension%20v1.0g.pdf
バイナリデータのbase64変換を必要とせずに、「CDMIコンテンツタイプ」操作を使用してCDMIクライアントとサーバー間でバイナリデータを転送する方法について説明します。
'Non-CDMI content type'操作を使用できる場合は、オブジェクトとの間で 'data'を転送するのが理想的です。その後、メタデータは、その後の「CDMIコンテンツタイプ」操作として、後でオブジェクトに追加またはオブジェクトから取得できます。
もしNodeを使っているなら、最も効率的で簡単な方法はUTF16に変換することだと思います。
Buffer.from(data).toString('utf16le');
次の方法でデータを取り戻すことができます。
Buffer.from(s, 'utf16le');