JSON字符串中的二进制数据。

Binary Data in JSON String. Something better than Base64

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

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

这方面的一个用例是cdmi云存储API规范的v0.8草案。您可以使用JSON通过RESTWebService创建数据对象,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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规范,有94个Unicode字符可以表示为一个字节(如果您的JSON是以UTF-8传输的)。考虑到这一点,我认为在空间方面最好的做法是base85,它将四个字节表示为五个字符。然而,与base64相比,这仅仅是7%的改进,计算成本更高,而且实现比base64更不常见,所以这可能不是一个胜利。

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

最终结论:在我看来,base64赢了,理由是它很普通,容易,而且还不至于糟糕到需要替换。

另请参见:base91


我遇到了同样的问题,我想我应该共享一个解决方案:多部分/表单数据。

通过发送一个多部分表单,您首先以字符串形式发送JSON元数据,然后以内容处置名称索引的原始二进制(图像、wav等)形式单独发送。

这里有一个关于如何在obj-c中实现这一点的很好的教程,这里有一篇博客文章,解释了如何使用表单边界划分字符串数据,并将其与二进制数据分离。

您真正需要做的唯一更改是在服务器端;您必须捕获元数据,该元数据应该适当地引用已发布的二进制数据(通过使用内容处置边界)。

当然,它需要在服务器端进行额外的工作,但是如果您要发送许多图像或大型图像,这是值得的。如果需要,可以将其与gzip压缩结合起来。

imho发送base64编码数据是一种黑客行为;RFC多部分/表单数据是为如下问题创建的:将二进制数据与文本或元数据结合发送。


BSON(二进制JSON)可能适用于您。http://en.wikipedia.org/wiki/bson网站

编辑:仅供参考.NET库json.net支持读写BSON,如果您正在寻找一些C服务器端的爱。


UTF-8的问题在于它不是最节省空间的编码。另外,一些随机的二进制字节序列是无效的UTF-8编码。因此,不能将随机的二进制字节序列解释为一些UTF-8数据,因为它将是无效的UTF-8编码。这种对UTF-8编码的约束的好处在于,它使定位多字节字符变得健壮,并且可以开始和结束我们开始看到的任何字节。

因此,如果对范围[0..127]中的字节值进行编码,在UTF-8编码中只需要一个字节,则对范围[128..255]中的字节值进行编码需要2个字节!比这更糟。在JSON中,控制字符"和不允许出现在字符串中。所以二进制数据需要一些转换才能正确编码。

让我们看看。如果我们假设二进制数据中的随机字节值是均匀分布的,那么平均来说,一半字节将被编码成一个字节,另一半则被编码成两个字节。UTF-8编码的二进制数据将有初始大小的150%。

base64编码只增长到初始大小的133%。所以base64编码更有效。

使用另一种基本编码怎么样?在UTF-8中,编码128个ASCII值是最节省空间的。在8位中,您可以存储7位。因此,如果我们将二进制数据切成7位块,将它们存储在UTF-8编码字符串的每个字节中,那么编码数据将只增长到初始大小的114%。比base64好。不幸的是,我们不能使用这个简单的技巧,因为JSON不允许使用一些ASCII字符。必须排除ASCII([0..31]和127)的33个控制字符以及"和"。这只剩下我们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是最好的。


如果处理带宽问题,请先尝试压缩客户端的数据,然后再压缩base64。

这种魔力的好例子在http://jszip.stuartk.co.uk/上,关于这个主题的更多讨论在gzip的javascript实现上。


Yenc可能为您工作:

网址:http://en.wikipedia.org/wiki/yenc

"yEnc is a binary-to-text encoding scheme for transferring binary
files in . It reduces the overhead over previous US-ASCII-based
encoding methods by using an 8-bit Extended ASCII encoding method.
yEnc's overhead is often (if each byte value appears approximately
with the same frequency on average) as little as 1–2%, compared to
33%–40% overhead for 6-bit encoding methods like uuencode and Base64.
... By 2003 yEnc became the de facto standard encoding system for
binary files on Usenet."

然而,Yenc是一种8位编码,因此将其存储在JSON字符串中与存储原始二进制数据有相同的问题—是不是使用NA?ve-way意味着大约100%的扩展,这比base64更糟糕。


微笑格式

编码、解码和压缩都很快

速度比较(基于Java但有意义):HTTPS:/GIHUUBCOM/EISHYY/JVM SerialZiSers/Wiki/

它也是JSON的扩展,允许您跳过字节数组的base64编码。

当空间很关键时,可以对微笑编码的字符串进行gzip处理。


虽然base64的扩展率约为33%,但处理开销不一定比这高出很多:它实际上取决于您使用的JSON库/工具箱。编码和解码是简单的直接操作,甚至可以优化WRT字符编码(因为JSON只支持UTF-8/16/32)——对于JSON字符串条目,base64字符总是单字节的。例如,在Java平台上,有一些库可以高效地完成工作,因此开销很大程度上是由于扩展的大小。

我同意前面两个答案:

  • base64是一种简单、常用的标准,因此不太可能找到更好的专门与JSON一起使用的标准(base 85由PostScript等使用;但是当您考虑它时,好处最好是微乎其微的)。
  • 根据所使用的数据,编码前(和解码后)的压缩可能很有意义。

(7年后编辑:GoogleGears消失了。忽略此答案。)

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

Blob API

JavaScript has a built-in data type for text strings, but nothing for binary data. The Blob object attempts to address this limitation.

也许你可以用某种方法把它编织起来。


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


只是为了在讨论中添加资源和复杂性的观点。由于执行了存储新资源并对其进行修改的Put/Post和Patch操作,因此应该记住,内容传输是存储内容的精确表示,并通过发出get操作接收这些内容。

多部分消息通常被用作救世主,但出于简单的原因和更复杂的任务,我更喜欢将内容作为一个整体提供。这是自我解释,而且很简单。

是的,JSON是一个严重的问题,但最终JSON本身是冗长的。映射到base64的开销很小。

正确地使用多部分消息,要么拆除要发送的对象,使用属性路径作为参数名进行自动组合,要么需要创建另一个协议/格式来表示有效负载。

同样喜欢BSON方法,这并不像人们希望的那样广泛和容易支持。

基本上,我们在这里遗漏了一些东西,但是将二进制数据嵌入base64已经很好地建立起来了,而且还有很长的路要走,除非您确实确定需要执行真正的二进制传输(这种情况很少发生)。


我更深入地挖掘了一点(在base128的实现过程中),揭示了当我们发送ASCII码大于128的字符时,浏览器(chrome)实际上发送了两个字符(字节),而不是一个:()。原因是,json通过defaul使用了utf8字符,其中ASCII代码高于127的字符由chmike answer提到的两个字节编码。我用这种方式进行了测试:键入chrome url bar chrome://net export/,选择"include raw bytes",开始捕获,发送post请求(使用底部的代码段),停止捕获并保存带有raw请求数据的json文件。然后我们查看json文件:

  • 我们可以通过查找字符串4142434445464748494a4b4c4d4e找到base64请求,这是ABCDEFGHIJKLMN的十六进制编码,我们将看到它的"byte_count": 639
  • 我们可以通过查找字符串C2BCC2BDC380C381C382C383C384C385C386C387C388C389C38AC38B找到上述127个请求,这是字符??àá??????èéê?的十六进制UTF8请求代码(但是,这些字符的ASCII十六进制代码是c1c2c3c4c5c6c7c8c9cacbcccdce)。"byte_count": 703因此它比base64请求长64字节,因为ASCII代码高于127的字符在请求中的代码是2字节:(

所以事实上,发送代码大于127的字符是没有利润的:(。对于base64字符串,我们没有观察到这样的负面行为(可能对base85也是如此-我不检查它)-但是,对于这个问题,可能有一些解决方案将以二进制形式发送数据,该二进制部分是后多部分/表单数据,如中所述?lex应答(不过通常在这种情况下,我们根本不需要使用任何基本编码…)。

另一种方法可能依赖于使用类似base65280/base65k的代码将两个字节的数据部分映射为一个有效的utf8字符,但由于utf8规范的原因,它可能不如base64有效…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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","??àá??????èéê?ìí??D?òó???×?ùú?üYT?àáa?????èéê?ìí??e?òó???÷?ùú?üy");
  req.open("POST", '/testAbove127');
  req.send(formData);
}
1
2
<button onclick=postBase64()>POST base64 chars</button>
<button onclick=postAbove127()>POST chars with codes>127</button>


请参阅:http://snia.org/sites/default/files/multi-part%20time%20extension%20v1.0g.pdf

它描述了一种使用"cdmi content type"操作在cdmi客户机和服务器之间传输二进制数据的方法,而不需要对二进制数据进行base64转换。

如果您可以使用"非cdmi内容类型"操作,则最好将"数据"传输到对象或从对象传输。然后可以将元数据作为后续的"cdmi内容类型"操作添加/检索到对象或从对象中检索元数据。


如果您使用的是node,我认为最有效和最简单的方法是使用以下代码转换成utf16:

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

您可以通过以下方式取回数据:

1
Buffer.from(s, 'utf16le');

数据类型确实令人担忧。我已经测试了从RESTful资源发送有效负载的不同场景。对于编码,我使用了Base64(Apache)和压缩Gzip(Java.UTL.zip)。有效载荷包含关于胶片、图像和音频文件的信息。我压缩和编码了图像和音频文件,这大大降低了性能。压缩前的编码效果很好。图像和音频内容以编码和压缩字节[]的形式发送。


我的解决方案是xhr2使用arraybuffer。arrayBuffer作为二进制序列包含多部分内容、视频、音频、图形、文本等,具有多种内容类型。一应俱全。

在现代浏览器中,为不同的组件提供数据视图、字符串视图和BLOB。有关详细信息,请参阅:http://rolfrost.de/video.html。