JSON格式本身不支持二进制数据。二进制数据必须转义,以便可以将其放在JSON中的字符串元素中(即使用反斜杠转义的双引号中的零或多个Unicode字符)。

转义二进制数据的一个明显方法是使用Base64。然而,Base64有很高的处理开销。此外,它将3个字节扩展为4个字符,导致数据大小增加约33%。

其中一个用例是CDMI云存储API规范的0.8版草案。您可以使用JSON通过REST-Webservice创建数据对象,例如:

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字符串?


当前回答

另一个更新颖的想法是通过uuencode对数据进行编码。大多数情况下都不推荐使用,但它仍然可以作为一种替代方案。(虽然可能不是很严重。)

其他回答

在深度上

I dig a little bit more (during implementation of base128), and expose that when we send characters which ascii codes are bigger than 128 then browser (chrome) in fact send TWO characters (bytes) instead one :(. The reason is that JSON by defaul use utf8 characters for which characters with ascii codes above 127 are coded by two bytes what was mention by chmike answer. I made test in this way: type in chrome url bar chrome://net-export/ , select "Include raw bytes", start capturing, send POST requests (using snippet at the bottom), stop capturing and save json file with raw requests data. Then we look inside that json file:

We can find our base64 request by finding string 4142434445464748494a4b4c4d4e this is hex coding of ABCDEFGHIJKLMN and we will see that "byte_count": 639 for it. We can find our above127 request by finding string C2BCC2BDC380C381C382C383C384C385C386C387C388C389C38AC38B this are request-hex utf8 codes of characters ¼½ÀÁÂÃÄÅÆÇÈÉÊË (however the ascii hex codes of this characters are c1c2c3c4c5c6c7c8c9cacbcccdce). The "byte_count": 703 so it is 64bytes longer than base64 request because characters with ascii codes above 127 are code by 2 bytes in request :(

所以事实上,发送带有代码>127的字符并没有什么好处。对于base64字符串,我们没有观察到这样的负面行为(可能对于base85也是如此-我不检查它)-然而,这个问题的一些解决方案将以POST multipart/form-data的二进制部分发送数据,在Ælex回答中描述(然而通常在这种情况下,我们根本不需要使用任何基本编码…)

另一种方法可能依赖于通过使用base65280 / base65k之类的代码将两个字节的数据部分映射到一个有效的utf8字符,但由于utf8规范,它可能不如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>

(7年后编辑:谷歌Gears消失了。忽略这个答案。)


谷歌Gears团队遇到了缺少二进制数据类型的问题,并试图解决它:

Blob API JavaScript为文本字符串提供了内置的数据类型,但没有用于二进制数据的数据类型。Blob对象试图解决这个限制。

也许你可以想办法编进去。

另一个更新颖的想法是通过uuencode对数据进行编码。大多数情况下都不推荐使用,但它仍然可以作为一种替代方案。(虽然可能不是很严重。)

根据JSON规范,有94个Unicode字符可以表示为一个字节(如果您的JSON以UTF-8传输)。考虑到这一点,我认为最好的空格方式是base85,它将四个字节表示为五个字符。然而,这只比base64提高了7%,它的计算成本更高,实现也不像base64那么常见,所以它可能不是一个胜利。

您还可以简单地将每个输入字节映射到U+0000-U+00FF中的相应字符,然后执行JSON标准所需的最小编码来传递这些字符;这里的优点是,除了内置函数之外,所需的解码为nil,但空间效率很差——105%的扩展(如果所有输入字节的可能性相等),而base85为25%,base64为33%。

最终结论:在我看来,base64胜出,因为它是常见的、简单的,而且还没有坏到需要替换的地步。

参见:Base91和Base122

由于您正在寻找将二进制数据硬塞进严格基于文本且非常有限的格式的能力,我认为Base64的开销与您期望使用JSON维护的便利性相比是最小的。如果需要考虑处理能力和吞吐量,那么可能需要重新考虑文件格式。