共计 5602 个字符,预计需要花费 15 分钟才能阅读完成。
JSON,一个平凡的协定,前端工程师的卓越创造!置信 99% 的程序员都意识 JSON,它作为前后端交互的热门协定,因其易了解、简略、灵便和超强的可读性,失去了互联网的宽泛欢送,甚至很多微服务之间的传输协定中也失去利用。
然而笔者在开发一个 Go 的 JSON 编解码库的过程中,除了本人趟过各种奇奇怪怪的问题之外,也意识到宽广程序员们对 JSON 各种奇奇怪怪的用法和应用姿态。在解决解决这些问题之后,笔者萌发了对 JSON 进行进一步科普和介绍的想法。
置信我,看完这篇文章,你就能够吃透这个可恶的 JSON 了。
这不是万字长文,所以许可我,不要 TLDR(too long, don’t read)好吗?
JSON 是什么
这个问题仿佛很容易答复:JavaScript Object Notation,直译就是 JavaScript 对象示意。
然而,这个命名中的“JavaScript”是个很大的误导,让人认为 JSON 是从属于 JavaScript 的。其实不然,JSON 是齐全独立于任何语言之上的一个对象示意协定,甚至从我集体的角度来说,它十分的不“JS”。
对于 JSON 的“常识”
从大家的认知中,置信以下的几点是常识:
- JSON 能够是对象(object),应用
{...}
格局包起来 - JSON 能够是是数组(array),应用
[...]
格局包起来 - JSON 内的值能够是 string, boolean, number,也能够进一步嵌套 object 和 array
- JSON 也有特殊字符须要本义,最不言而喻的就是双引号
"
、反斜杠\
、换行符\n
、\r
- JSON object 的键(key)必须是 string 格局
- JSON 能够通过 object 和 array 类型实现有限层级的嵌套
好了,懂了下面几点,其实也就弄懂了 JSON 90% 甚至是 99% 的利用场景了。程序员们也足以能够实现简略的 JSON 编码逻辑。
如果你想晓得剩下那些让人掉大牙的 1%,欢送你往下看;如果你想要本人开发一个 JSON 编解码库,以下内容也可能让你少走很多弯路:
JSON 标准规定了什么
在理解各种 JSON 的坑之前,咱们先来理解一下 JSON 规范自身。
现行通行的 JSON 规范是 ECMA-404,这篇协定总共有 14 页,但除去封面、封底、目录、简介、版权申明,注释只有 5 页,并且其中 3 页大部分是图片。所以笔者举荐所有的程序员都把这篇文档通读一遍,恐怕这是大部分人惟一能残缺读完的支流协定了(狗头)。
所以啊,“可恶”的 JSON 可真不是题目党——试想这么短小的协定,怎不堪称可恶呢!
通读了文档之后咱们能够发现,除了前文提及的几个常识之外,上面有几个知识点预计大家很少注意:
- JSON 是用来承载 unicode 字符的,这一点在规范中明确提及
- JSON 规范中其实并没有 boolean 这个类型,然而
true
和false
被并列为独自的两个类型 - 作为最外层的 JSON 类型,并不限定为 object 或 array,实际上 string, boolean, number, 甚至 null 也是能够的
- JSON 数字示意能够应用迷信计数法,可能许多人在理论利用中没注意过
- JSON 明确阐明不反对 +/-Inf 和 NaN 这两组在 IEEE 754 中规定的非凡数值
咱们解读一下下面这些知识点带来的影响:
unicode 字符:
- JSON 传输的该当是可视化字符,而不应该也无奈承载不可读的二进制数据
- 换句话说,请尽量不要用 JSON 来传输二进制数据
没有 boolean 类型
- 这个问题不大,次要是对各种库的应用上——有些库提供了 boolean 类型分类,而有些库则依照标准协议分为 true 和 false 两种类型,请留神辨别
外层类型不限定
- 其实这影响不大,然而这使得 JSON 多了一个额定性能: 当咱们要把蕴含换行符的文本压在一行内,但又要放弃高可读性的时候,咱们能够将文本序列化为 JSON
- 这个个性在打日志的时候特地有用
迷信计数法:
- 这次要是在解析 JSON 数据时,须要留神兼容
非凡浮点值:
- 这个问题可大可小,大部分状况下不会遇到,然而一旦呈现了,会导致整个序列化过程失败。开发者们须要审慎解决浮点数,下文会进一步提及
JSON 没有规定什么?
- JSON 并没有严格限定文本的编码格局
- JSON 数字是十进制的,没有限度绝对值大小,也没有限度小数点后的位数
- JSON 没有明确规定 ASCII 的控制字符和不可见字符的传输格局
- JSON 没有限度 object 的 key 所应用的字符
- JSON 没有明确阐明 object 的 key 之间是有序的还是无序的
为什么列出这几点?让咱们持续往下看:
JSON 的常见“坑位”
JSON 如此简略,但也正因为它的个性,咱们会人不知; 鬼不觉地落入一些陷阱中。上面我列几个常见的坑,读者看看能不能对号入座:
没搞明确编码格局导致解码出错
后面说到,JSON 明确申明本人是用于承载 unicode 的。然而,unicode 除了规定每个字符码的含意(码点)之外,还蕴含另外一个重要标准,那就是如何将这些字符串成字符流,这就是咱们常说的 UTF-8、UTF-16BE、
UTF-16LE 等等概念。而 JSON 并 没有 对此作明确限定。这就导致了在 JSON 的编码与解码端,如果没有约定好,那么就会呈现乱码。
笔者已经与一个合作伙伴的开发工程师对接过 JSON,对方应用 Java 解码我收回的原始数据时呈现乱码。我通知对方,应该用 UTF-8 格局解码,然而对方不明确 UTF-8 是什么,只是不停的通知我他应用的是哪一个 Java 函数。
我的解决方案不敢说万能,但应该即使是上古的解码器都能解决——这个计划就是指定各编码器在编码时,对大于 ASCII 范畴的字符均作本义解决为 \uXXXX
格局。这么一通操作后,我的合作伙伴示意:程序通了。
其实在 JSON 标准中,列举了不少篇幅阐明大于 U+00FF 的码点应该如何本义,包含大于 U+FFFF 的。所以从笔者的个人观点看来,如果咱们严格依照 JSON 标准的话,什么 UTF-8,GB18030 等编码格局都是未被容许的,惟一严格容许的就是 \uXXXX
本义。然而在实际操作中,这种本义太节约字节序列了,各种语言对 string 类型进行操作时,习惯性地依照自身的字符串在内存中的默认编码格局照搬到 JSON 序列化上了。
如果 JSON 的编码端无奈确保或协调对端解码器的编码格局,那么请对立应用 \uXXXX
本义。
JSON 中的 UTF-16
如果读者不须要自行编码或解析 JSON 数据的话,能够跳过这一大节;否则,这一段是必修课。
对于编码值大于 127 的字符来说,如上文所示,咱们能够本义为 \uXXXX
格局。那么是不是间接写 sprintf("\\u04X", aByte)
就能够呢?
如果你这么做,那么作为一个通用库来说……
<img src=”https://ts1.cn.mm.bing.net/th/id/R-C.f1ca9cac65236fb8944862661d3a915c?rik=3iTDjVXAAsd2lw&riu=http%3a%2f%2fps3.tgbus.com%2fUploadFiles%2f201206%2f20120619174916338.jpg&ehk=wYBPVaiaHHF33Liake9fWy32femV5sBtaHR4BkfWcYs%3d&risl=&pid=ImgRaw&r=0&sres=1&sresct=1″ width=”25%” height=”25%”>
严格来说,\uXXXX
其实是对 UTF-16
编码的转写。这是一个比拟少用的编码格局。咱们都晓得,UTF-8
是一个变长的编码格局,它编码的根本单位是 1 个字节。受晚期 Windows 16 位 wchar
的影响,有些人可能会误以为UTF-16
是定长的 2 个字节。其实并不然,对于大于 65535 的 unicode 码点,UTF-16
应用 4 个字节编码,而 JSON 只须要将编码后的两个半字(half world)按程序应用 \uXXXX
转写进去就能够了。
对 JSON 具体须要本义的字符,以及 UTF-16
的相干内容,笔者之前也写过一篇文章专门阐明,欢送移步。
ASCII 控制字符
按理说,JSON 只应该承载可见字符。然而依照 JSON 的标准,JSON 承载的是 unicode,而 ASCII 控制字符也是 unicode 的一部分,所以 JSON 也是能够承载 ASCII 控制字符的。
其实这个问题并不大,即使把这些控制字符一成不变地包装在 JSON 序列化之后的数据流中,对端也是能够正确解析进去的。大家要留神的是,如果带控制字符的话,数据渲染到终端时,某些控制字符可能不会被渲染进去。如果此时你从终端复制一段数据,在粘贴到别处,这些字符可能就都失落了。
陈词滥调的浮点数
精度问题
家喻户晓,在许多语言的外部解决逻辑中,带小数局部的数字是应用浮点数来解决的。对于小数局部无奈被 2 除尽的十进制数,零碎(为了关照“你们人类”)而应用二进制浮点数的近似值来示意。
具体到 JSON 中,坑在哪里?其实吧这里不算是 JSON 的坑,而是一个通用的问题。我简略提一下吧:
首先咱们晓得,对很多强类型语言来说,浮点数往往能够细分为单精度和双精度两种,前者应用 4 个字节,后者应用 8 个字节。单精度在有效位数方面比双精度数小一大截,然而在具体实际中,思考到数据传输、计算效率、数值范畴,往往单精度就足矣。
这个时候,如果一个浮点数在零碎外部通过各种不同精度的转换之后,在转换成 JSON 时会有什么问题呢?咱们来考虑一下的过程:
- 一个十进制准确定点数值
2.1
- 应用单精度浮点数示意,
f = float32(2.1)
- 调用某些接口,可能接口自身是不反对单精度数,因而转成了双精度解决
d = float64(f)
- 将这个双精度数填入一个构造体并且格式化为 JSON 小数输入
此时,咱们会失去什么数字呢?依据不同的语言,输入可能会不同。如果不指定精度的话,很多 JSON 编码库是反对依据浮点数的具体数值,猜想并且格式化为一个最靠近的十进制小数。以 Go 为例子,咱们会发现通过 JSON 输入的时候,这个 2.1
变成了 2.0999999046325684
。
这在实质上,是因为单精度数通过一次类型转换为双精度后,其二进制有效位数以零填充,转为十进制时,对于双精度浮点数,这就不再是双精度有效数字下的 2.1
了。
换句话说,开发者们在解决浮点数时,须要思考不同精度浮点数的精度解决差别,特地是金融相干的数据计算和传输,一不小心就会造成大量的对账谬误。
非凡浮点数
前文提及,JSON 明确阐明不反对 +/-Inf 和 NaN 这两组在 IEEE 754 中规定的非凡数值。但有一些数学运算库,在计算之后会将奇点输入为 +/-Inf 或 NaN,对于很多 JSON 编码库来说,遇到这种数值会导致整个数据编码失败。因而开发者须要针对这种状况非凡解决。我开发的 jsonvalue 中就有这样的一个专题。毕竟是笔者在实际操作中趟过的坑……
有程序的 K-V
在 JSON 标准中,明确强调 array 类型的子值程序的重要性(这很好了解)。
然而针对 object 类型,key 的程序则未提及。在实际操作中我发现不少利用场景中把 object 的 K-V 也当作有序数据来操作了——这在很多本人应用代码简略拼接 JSON 串的场景中,出其不意地很常见。
还请各位明确留神:JSON 的 object,咱们该当默认它是无序的。如果须要传递一系列有序的 KV 对,那么请务必应用 array 类型,不要再用 object 了,这相对不是一个通用的做法。
在这一点上,我本人也犯过一个很低级的谬误:
JSON 数据的幂等检查和数据校验
年少无知的我有一次设计过一个模块,接管上游发来的各种事件信息。为了确保事件都被解决,因而当上游响应不及时时,上游可能会将同一事件反复收回。此时我须要对事件进行幂等计算,确保同一事件不会被反复解决。
一开始我这是简略对上游数据进行 hash 计算。然而在理论运作了一段时间,出了 bug,而起因也很简略,咱们看看上面两段数据:
{"time":1601539200,"event_id":10,"openid":"abcdefg"}
{"event_id":10,"time":1601539200,"openid":"abcdefg"}
这两段数据仅仅是 key 的程序不同而已,但如果应用下面的逻辑,这数据根本就是截然不同的!咱们永远要留神:如果咱们没有明确地与上游约定好的话,那么请永远不要对上游做任何假如;即使应用文档束缚,也仍然要多多查看各种例外情况。
后果怎么解决?束缚上游?这岂不是显得我能力不行嘛(狗头,次要是不想让上游晓得我的 bug 这么 low),所以我在我本人这边简略对解析进去的 key 排序之后(反正 key 不多且无嵌套),再从新计算 hash 来解决。
结语
本文从 JSON 规范登程,联合本人的一些工作教训,整顿了 JSON 编解码过程中的一些坑和留神点。如果本文有舛误,还请不吝指正;如果读者还遇到了其余的坑,也欢送补充。
此外,如果读者中有 Go 开发者的话,也欢送理解一下我的 jsonvalue 库,点个 star 或者给我提 issue 都十分欢送~~
参考资料
JSON 相干材料:
- Python JSON 模块解码中文的 BUG
- 既然 GB18030 能够是 Unicode 的 UTF 转写,为什么中国境内不强制应用该字符集?
- Go json 踩坑记录
- axgle/mahonia
- golang:gbk/gb18030 编码字符串与 utf8 字符串互转
- GB 18030 根上跟 Unicode 有关系吗?
- 细说:Unicode, UTF-8, UTF-16, UTF-32, UCS-2, UCS-4
- Unicode
- UTF-16
- GB 18030
本文章采纳 常识共享署名 - 非商业性应用 - 雷同形式共享 4.0 国内许可协定 进行许可。
原作者:amc,原文公布于云 + 社区,也是自己的博客。欢送转载,但请注明出处。
原文题目:《JSON 这么可恶,让咱们用千字短文吃透它吧!》
公布日期:2022-10-21
原文链接:https://segmentfault.com/a/1190000042660224