本文比较完整地整理一下 JSON 编码中的转义,以及 JSON 对 Unicode 编码的处理。
其实这是我上一篇文章的姊妹篇。在研究 Unicode 颜文字的时候,由于我们的数据传输是通过 JSON 串来完成的,在对颜文字进行转码传输的过程中,也发现了一个问题。解决问题之后,便有了本总结文。
个人认为,JSON 是目前针对程序员而言可读性(readability)最佳的数据传输格式之一,并且 JSON 完整地考虑到了数据传输中的转义,避免出现各种注入风险。当对 JSON 进行序列化操作时(Go 中称为 marshal
),根据 JSON 标准的说明,需要对字符串中的以下字符进行转义:
符号 | 名称 | 转义后的字符串 |
---|---|---|
| 双引号 |
|
| 斜杠 |
|
| 反斜杠 |
|
| 退格符 |
|
| 垂直制表符 |
|
Tab | 水平制表符 |
|
| 回车 |
|
| 换行符 |
|
| 左尖括号 |
|
| 右尖括号 |
|
| And 符号 |
|
另外针对 Go 语言,个人建议再转义一个百分号 %
为 \u0025
,原因是在 Go 的各种字符串格式化操作中,百分号是一个关键字符,这样可以避免在打日志或者其他设计格式化的操作时出现错误。
这里所说的 Unicode 字符,准确而言指的是在 ASCII 范围之外的字符,也就是值大于 0x7F 的 Unicode 字符。
其实大部分情况下,UTF-8 已经成为现代编程语言约定俗成的标准了,因此在 JSON 序列化时,只要简单地对 Unicode 字符的值转为二进制然后按照网络字节序打包就可以了。
但是在某些情况下,当对端采用的不是 UTF-8,或者是对端采用的不是网络字节序时(比如对方是技术底下/落后、但却话语权强大的客户/合作商/集成商),这个时候,大家统一采用 ASCII 编码,就能够避免这些问题了。
那么 JSON 是怎么使用 ASCII 编码来传输 Unicode 的呢?从前文的转义其实就可以一窥端倪了——JSON 采用的是 \uXXXX
的形式来表示一个 Unicode 字符的。每个 Unicode 字符表示法中,XXXX
必须是4个十六进制数,即便高位为0也需要补全。通过这种方式,编码和传输 Unicode 字符。在 ASCII 为主的数据传输中,这种编码方式比较稳妥,并且不会额外增加过多的数据量。当然对于 Unicode 字符比较多的情况下(比如大量的中文),这就需要程序员考虑一下额外带来的网络花销了。
读者可能会注意到了,\uXXXX
格式最大只能支持到 0xFFFF,但 Unicode 早就已经超过了这个范围。大于 65535 的字符要怎么表示呢?首先,绝对不是简单地采用 \uXXXXX,这会导致编码错误。
针对大于 65535 的字符,JSON 采用的是 UTF-16 编码。UTF-16 采用了 Unicode 的一个特性:不超过20位。
比如我们用 u
代表这样的一个字符,UTF-16 的处理方法如下:
u = u - 0x10000
hi
等于做了减法之后的 u 的高10位:hi = (u & 0xFFC00) >> 10
lo
等于做了减法之后的 u 的低10位:lo = u & 0x003FF
0xD800
后进行 \u
编码0xDC00
后进行 \u
编码举例说明:代表地球的颜文字符号 “🌍”,其编码值为 0x1F30D,按照 UTF-16 编码过程为:
u = 0x1F30D - 0x10000 = 0xF30D
,二进制:1111 0011 0000 1101
0000111100
,低10位等于 1100001101
0x03C
做加法之后等于 0xD83C
0x30D
做加法之后等于 0xDF0D
\uD83C\uDF0D
比如以下的 JSON:
{
"string":"我是地球🌍"
}
按照 ASCII 序列化之后,结果为:
{"string":"\u6211\u662F\u5730\u7403\uD83C\uDF0D"}
本文章采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
原作者: amc,欢迎转载,但请注明出处。
原文标题:JSON 序列化中的转义和 Unicode 编码
发布日期:2020-05-09
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。