JSON格式本身不支持二进制数据。 必须对二进制数据进行转义,以便可以将其放入JSON中的字符串元素(即使用反斜杠转义的双引号中的零个或多个Unicode字符)。
逃脱二进制数据的一种明显方法是使用Base64。 但是,Base64具有较高的处理开销。 它还将3个字节扩展为4个字符,这导致数据大小增加了约33%。
一个用例是CDMI云存储API规范的v0.8草案。 您可以使用JSON通过REST-Webservice创建数据对象,例如
PUT /MyContainer/BinaryObject HTTP/1.1
Host:
Accept: application/+json
Content-Type: application/+json
X-CDMI-Specification-Version: 1.0
{
"mimetype" : "application/octet-stream",
"metadata" : [ ],
"value" : "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz
IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg
dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu
dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo
ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=",
}
是否有更好的方法和标准方法将二进制数据编码为JSON字符串?
#1楼
yEnc可能为您工作:
/wiki/Yenc
“ yEnc是一种用于在[text]中传输二进制文件的二进制到文本编码方案。它通过使用8位扩展ASCII编码方法,比以前的基于US-ASCII的编码方法减少了开销。yEnc的开销通常是(如果每个字节的值平均以大约相同的频率出现)仅为1-2%,而uuencode和Base64等6位编码方法的开销为33%-40%。...到2003年,yEnc成为事实上的标准Usenet上二进制文件的编码系统。”
但是,yEnc是8位编码,因此将其存储在JSON字符串中与存储原始二进制数据存在相同的问题-天真地实现意味着100%的扩展,这比base64差。
#2楼
由于您正在寻求将二进制数据转换为严格基于文本且非常有限的格式的功能,因此与您期望通过JSON维护的便利相比,我认为Base64的开销是最小的。 如果需要考虑处理能力和吞吐量,那么您可能需要重新考虑文件格式。
#3楼
( 7年后编辑: Google Gears消失了。忽略此答案。)
Google Gears小组遇到了二进制数据类型不足的问题,并试图解决该问题:
Blob API
JavaScript具有用于文本字符串的内置数据类型,而对于二进制数据则没有。 Blob对象尝试解决此限制。
也许您可以用某种方式编织它。
#4楼
根据JSON规范,有94个Unicode字符可以表示为一个字节(如果JSON以UTF-8格式传输)。 考虑到这一点,我认为您可以在空间上做到最好的是base85 , 它将85个字节表示为四个字节。 但是,与base64相比,这仅提高了7%,计算成本更高,并且实现比base64少,因此这可能不是胜利。
您还可以简单地将每个输入字节映射到U + 0000-U + 00FF中的相应字符,然后执行JSON标准要求的最小编码以传递这些字符; 这样做的好处是所需的解码比内置函数要少,但是空间效率很差-扩展105%(如果所有输入字节均等的可能性),而base85为25%,base64为33%。
最终判决:BASE64胜,在我看来,其理由是它的普通,简单,不差足以保证更换。
另请参阅: Base91
#5楼
请参阅: http : ///sites/default/files/Multi-part%20MIME%20Extension%20v1.
它描述了一种使用“ CDMI内容类型”操作在CDMI客户端和服务器之间传输二进制数据而无需对二进制数据进行base64转换的方法。
如果可以使用“非CDMI内容类型”操作,则理想的是将“数据”与对象之间进行传输。 然后可以将元数据作为后续的“ CDMI内容类型”操作添加到对象或从对象检索。
#6楼
UTF-8的问题在于它不是最节省空间的编码。 同样,某些随机二进制字节序列是无效的UTF-8编码。 因此,您不能仅将随机二进制字节序列解释为某些UTF-8数据,因为它将是无效的UTF-8编码。 这种对UTF-8编码的限制的好处是,它使它变得强大并且可以定位多字节字符的开始和结束,无论我们开始看什么字节。
结果,如果在UTF-8编码中对[0..127]范围内的字节值进行编码仅需要一个字节,则对[128..255]范围内的字节值进行编码将需要2个字节! 比那更糟。 在JSON中,不允许将控制字符“和\\出现在字符串中。因此,二进制数据需要进行一些转换才能正确编码。
让我们看看。 如果我们在二进制数据中假设均匀分布的随机字节值,那么平均而言,一半字节将被编码为一个字节,另一半字节将被编码为两个字节。 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个字符。
因此,从理论上讲,我们可以定义Base93编码,该编码将使编码后的大小增加到8 / log2(93)= 8 * log10(2)/ log10(93)= 122%。 但是Base93编码不如Base64编码方便。 Base64需要将输入字节序列切成6位的块,简单的按位操作就可以很好地工作。 超过133%不会超过122%。
这就是为什么我独立得出一个普遍的结论,即Base64实际上是在JSON中编码二进制数据的最佳选择。 我的回答为此辩护。 我同意从性能的角度来看它不是很吸引人,但是还要考虑使用JSON的好处,因为它具有易于在所有编程语言中操作的人类可读字符串表示形式。
如果性能至关重要,则应将纯二进制编码视为JSON的替代。 但是使用JSON,我的结论是Base64是最好的。
#7楼
现在我的解决方案是XHR2使用ArrayBuffer。 ArrayBuffer作为二进制序列包含多部分内容,视频,音频,图形,文本等,具有多种内容类型。 一站式回应。
在现代浏览器中,具有用于不同组件的DataView,StringView和Blob。 另请参阅: http : ///,以获取更多详细信息。
#8楼
虽然base64的扩展率约为33%,但这并不一定意味着处理开销要远远超过此:它确实取决于您使用的JSON库/工具包。 编码和解码是简单直接的操作,甚至可以使用字符编码进行优化(因为JSON仅支持UTF-8 / 16/32)-base64字符始终是JSON String条目的单字节。 例如,在Java平台上,有一些库可以相当有效地完成这项工作,因此开销主要是由于扩展大小。
我同意较早的两个答案:
- base64是简单且常用的标准,因此不太可能找到更适合与JSON一起使用的东西(base-85用于后记等;但是考虑到这一点,其好处至多是微不足道的)
- 编码之前(和解码之后)的压缩可能很有意义,具体取决于您使用的数据
#9楼
我遇到了同样的问题,并认为我会共享一个解决方案: multipart / form-data。
通过发送多部分形式,您首先以字符串形式发送JSON元数据 ,然后分别以原始二进制文件(图像,wav等)发送,并由Content-Disposition名称索引。
这是一个很好的教程 ,说明如何在obj-c中执行此操作,这是一篇博客文章 ,介绍了如何使用形式边界对字符串数据进行分区,并将其与二进制数据分开。
您真正需要做的唯一更改是在服务器端。 您将必须捕获元数据,该元数据应适当地引用POST的二进制数据(通过使用Content-Disposition边界)。
当然,这需要在服务器端进行其他工作,但是如果要发送许多图像或大图像,这是值得的。 如果需要,可以将其与gzip压缩结合使用。
恕我直言,发送base64编码的数据是一种攻击; RFC multipart / form-data是针对以下问题创建的:将二进制数据与文本或元数据结合发送。
#10楼
只是为了增加讨论的资源和复杂性。 由于进行PUT / POST和PATCH来存储新资源并对其进行更改,因此应该记住,内容传输是存储的内容的精确表示,并通过发出GET操作来接收。
通常将多部分消息用作救星,但出于简单原因和更复杂的任务,我更倾向于将内容作为整体给出。 这是不言自明的,很简单。
是的,JSON有点令人毛骨悚然,但最终,JSON本身很冗长。 映射到BASE64的开销是减小开销的一种方法。
正确使用Multi-Part消息,要么必须拆除要发送的对象,要么使用属性路径作为参数名称以进行自动组合,或者需要创建另一种协议/格式以仅表示有效负载。
同样喜欢BSON的方法,它并没有像人们所希望的那样得到广泛而轻松的支持。
基本上,我们只是在这里遗漏了一些东西,但是嵌入二进制数据是因为base64已经很好地确立并且是可行的方法,除非您确实确定需要进行真正的二进制传输(这种情况很少见)。
#11楼
如果你正在使用Node,我认为最有效和最简单的方法是转换为UTF16:
(data).toString('utf16le');
您可以通过以下方式获取数据:
(s, 'utf16le');
#12楼
如果要处理带宽问题,请先尝试在客户端压缩数据,然后再压缩base64-it。
此类魔术的好例子在/上 ,关于此主题的更多讨论在Gzip的JavaScript实现上
#13楼
我进一步挖掘了一点(在实现base128的过程中 ),并揭露了当我们发送ASCII代码大于128的字符时,浏览器(chrome)实际上发送了两个字符(字节),而不是一个:( 。原因是JSON通过默认使用utf8字符,其中ASCII码高于127的字符由两个字节编码,这是chmike答案所提到的。我以此方式进行了测试:在chrome网址栏中输入chrome:// net-export / ,选择“包括原始个字节”,开始捕获,发送POST请求(使用底部的代码段),停止捕获并将json文件与原始请求数据一起保存。然后我们查看该json文件:
- 我们可以通过找到字符串
4142434445464748494a4b4c4d4e
来找到base64请求,这是ABCDEFGHIJKLMN
十六进制编码,我们将看到"byte_count": 639
。 - 我们可以找到我们通过查找字符串above127要求
C2BCC2BDC380C381C382C383C384C385C386C387C388C389C38AC38B
这是字符的请求-十六进制代码UTF8¼½ÀÁÂÃÄÅÆÇÈÉÊË
(然而,这字符的ASCII十六进制代码是c1c2c3c4c5c6c7c8c9cacbcccdce
)。"byte_count": 703
因此比base64请求长64个字节,因为在请求中,ASCII码高于127的字符是2个字节的代码:(
因此,实际上,发送代码> 127 :(。的字符对我们没有好处。对于base64字符串,我们也没有观察到这种负面行为(可能也是针对base85的,我也没有检查过)-但是,对于该问题可能有一些解决方案在Ælex答案中描述的POST多部分/表单数据的二进制部分中发送数据(但是通常在这种情况下,我们根本不需要使用任何基本编码...)。
另一种方法可能是依靠将两个字节的数据部分映射为一个有效的utf8字符,方法是使用base65280 / base65k之类的代码对其进行编码,但由于utf8规范,它的效果可能不如base64 ...
function postBase64() { let formData = new FormData(); let req = new XMLHttpRequest(); ("base64ch", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); ("POST", '/testBase64ch'); (formData); } function postAbove127() { let formData = new FormData(); let req = new XMLHttpRequest(); ("above127", "¼½ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüý"); ("POST", '/testAbove127'); (formData); }
<button onclick=postBase64()>POST base64 chars</button> <button onclick=postAbove127()>POST chars with codes>127</button>
#14楼
BSON(二进制JSON)可能适合您。 /wiki/BSON
编辑:仅供参考。如果您正在寻找一些C#服务器端的爱,.NET库支持读写bson。
#15楼
微笑格式
编码,解码和压缩非常快
速度比较(基于Java但仍然有意义): https : ///eishay/jvm-serializers/wiki/
也是JSON的扩展,允许您跳过字节数组的base64编码
当空间很紧迫时,可以将微笑编码的字符串压缩
#16楼
数据类型确实令人担忧。 我已经测试了从RESTful资源发送有效负载的不同方案。 为了进行编码,我使用了Base64(Apache),并使用了压缩GZIP(。*)。有效负载包含有关电影,图像和音频文件的信息。 我已经对图像和音频文件进行了压缩和编码,这极大地降低了性能。 压缩前的编码效果很好。 图像和音频内容以编码和压缩字节[]的形式发送。