id: 0b62c5fac77d4d659fb3e5bda80d887d
parent_id: 
item_type: 1
item_id: 1cdb56758d444652a81749a912de7884
item_updated_time: 1764345606353
title_diff: "[{\"diffs\":[[1,\"JSON 这么可爱，让我们用千字短文吃透它吧-今日头条\"]],\"start1\":0,\"start2\":0,\"length1\":0,\"length2\":27}]"
body_diff: "[{\"diffs\":[[1,\"<html>\\\r\\\n<head>\\\r\\\n  <title>JSON 这么可爱，让我们用千字短文吃透它吧-今日头条</title>\\\r\\\n  <basefont face=\\\"微软雅黑\\\" size=\\\"2\\\" />\\\r\\\n  <meta http-equiv=\\\"Content-Type\\\" content=\\\"text/html;charset=utf-8\\\" />\\\r\\\n  <meta name=\\\"exporter-version\\\" content=\\\"YXBJ Windows/608924 (zh-CN, DDL); Windows/10.0.0 (Win64); EDAMVersion=V2;\\\"/>\\\r\\\n  <style>\\\r\\\n\\tbody, td {\\\r\\\n\\t  font-family: 微软雅黑;\\\r\\\n\\t  font-size: 12pt;\\\r\\\n\\t}\\\r\\\n  </style>\\\r\\\n</head>\\\r\\\n<body>\\\r\\\n<a name=\\\"29407\\\"></a>\\\r\\\n<h1>JSON 这么可爱，让我们用千字短文吃透它吧-今日头条</h1>\\\r\\\n\\\r\\\n<div><span style=\\\"margin:0px;\\\"><div><div style=\\\"font-size: 18px; font-family: PingFangSC-Regular; letter-spacing: 1.2px; line-height: 30px; text-align: justify;\\\"><div style=\\\"\\\"><div><p style=\\\"\\\">作者：andruzhang，腾讯IEG后台开发工程师</p><p style=\\\"\\\"><span style=\\\"color: rgb(15, 149, 235); --tt-darkmode-color: #0F95EB; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(15, 149, 235);\\\"><span style=\\\"background-color: rgb(245, 244, 244); --tt-darkmode-bgcolor: #BFBEBE; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(15, 149, 235);\\\">| 导语</span></span><span style=\\\"background-color: rgb(245, 244, 244); --tt-darkmode-bgcolor: #BFBEBE; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(34, 34, 34);\\\"> JSON，一个伟大的协议，前端工程师的卓越发明！相信 99% 的程序员都认识 JSON。但这么简单的 JSON，也有坑！相信我，看完这篇文章，你就可以吃透这个可爱的 JSON 了。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">相信 99% 的程序员都认识 JSON，它作为前后端交互的热门协议，因其易理解、简单、灵活和超强的可读性，得到了互联网的广泛欢迎，甚至很多微服务之间的传输协议中也得到应用。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">但是笔者在开发一个 Go 的 JSON 编解码库的过程中，除了自己趟过各种奇奇怪怪的问题之外，也认识到广大程序员们对 JSON 各种奇奇怪怪的用法和使用姿势。在处理解决这些问题之后，笔者萌生了对 JSON 进行进一步科普和介绍的想法。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">相信我，看完这篇文章，你就可以吃透这个可爱的 JSON 了。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">这不是万字长文，所以答应我，不要 </span><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">TLDR</span></span>（too long, don't read） 好吗？</p><hr/><h1>JSON 是什么</h1><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">这个问题似乎很容易回答：JavaScript Object Notation，直译就是 JavaScript 对象表示。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">然而，这个命名中的 “JavaScript” 是个很大的误导，让人以为 JSON 是附属于 JavaScript 的。其实不然，JSON 是完全独立于任何语言之上的一个对象表示协议，甚至从我个人的角度来说，它非常的不 “JS”。</span></p><h1>关于 JSON 的 “常识”</h1><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">从大家的认知中，相信以下的几点是常识：</span></p><ul><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 可以是对象（object），使用 </span><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">{...}</span></span> 格式包起来</li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 可以是是数组（array），使用 </span><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">[...]</span></span> 格式包起来</li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 内的值可以是 string, boolean, number，也可以进一步嵌套 object 和 array</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 也有特殊字符需要转义，最显而易见的就是双引号 </span><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">&quot;</span></span>、反斜杠 <span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">\\\\</span></span>、换行符 <span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">\\\\\n</span></span>、<span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">\\\\\r</span></span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON object 的键（key）必须是 string 格式</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 可以通过 object 和 array 类型实现无限层级的嵌套</span></li></ul><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">好了，懂了上面几点，其实也就弄懂了 JSON 90% 甚至是 99% 的应用场景了。程序员们也足以可以实现简单的 JSON 编码逻辑。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">如果你想知道剩下那些让人掉大牙的 1%，欢迎你往下看；如果你想要自己开发一个 JSON 编解码库，以下内容也能够让你少走很多弯路：</span></p><hr/><h1>JSON 标准规定了什么</h1><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">在了解各种 JSON 的坑之前，我们先来了解一下 JSON 标准本身。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">现行通行的 JSON 标准是 ECMA-404（也可参见附件），这篇协议总共有14页，但除去封面、封底、目录、简介、版权声明，正文只有5页，并且其中3页大部分是图片。所以笔者推荐所有的程序员都把这篇文档通读一遍，恐怕这是大部分人唯一能完整读完的主流协议了（狗头）。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><em><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">所以啊，“可爱” 的 JSON 可真不是标题党——试想这么短小的协议，怎不可谓可爱呢！</span></em></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">通读了文档之后我们可以发现，除了前文提及的几个常识之外，下面有几个知识点估计大家很少留意：</span></p><ul><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 是用来承载 unicode 字符的，这一点在标准中明确提及</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 标准中其实并没有 boolean 这个类型，但是 </span><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">true</span></span> 和 <span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">false</span></span> 被并列为单独的两个类型</li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">作为最外层的 JSON 类型，并不限定为 object 或 array，实际上 string, boolean, number, 甚至 null 也是可以的</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 数字表示可以使用科学计数法，可能许多人在实际应用中没留意过</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 明确说明不支持 +/-Inf 和 NaN 这两组在 IEEE 754 中规定的特殊数值</span></li></ul><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">我们解读一下上面这些知识点带来的影响：</span></p><p style=\\\"\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 传输的应当是可视化字符，而不应该也无法承载不可读的二进制数据</span></p><p style=\\\"\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">换句话说，请尽量不要用 JSON 来传输二进制数据</span></p><p style=\\\"\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">这个问题不大，主要是对各种库的使用上——有些库提供了 boolean 类型分类，而有些库则按照标准协议分为 true 和 false 两种类型，请注意区分</span></p><p style=\\\"\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">其实这影响不大，但是这使得 JSON 多了一个额外功能: 当我们要把包含换行符的文本压在一行内，但又要保持高可读性的时候，我们可以将文本序列化为 JSON</span></p><p style=\\\"\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">这个特性在打日志的时候特别有用</span></p><p style=\\\"\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">这主要是在解析 JSON 数据时，需要注意兼容</span></p><p style=\\\"\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">这个问题可大可小，大部分情况下不会遇到，但是一旦出现了，会导致整个序列化过程失败。开发者们需要谨慎处理浮点数，下文会进一步提及</span></p><hr/><h1>JSON 没有规定什么？</h1><ul><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 并没有严格限定文本的编码格式</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 数字是十进制的，没有限制绝对值大小，也没有限制小数点后的位数</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 没有明确规定 ASCII 的控制字符和不可见字符的传输格式</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 没有限制 object 的 key 所使用的字符</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 没有明确说明 object 的 key 之间是有序的还是无序的</span></li></ul><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">为什么列出这几点？让我们继续往下看:</span></p><hr/><h1>JSON 的常见 “坑位”</h1><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 如此简单，但也正因为它的特性，我们会不知不觉地落入一些圈套中。下面我列几个常见的坑，读者看看能不能对号入座：</span></p><h1>没搞明白编码格式导致解码出错</h1><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">前面说到，JSON 明确声明自己是用于承载 unicode 的。但是，unicode 除了规定每个字符码的含义（码点）之外，还包含另外一个重要规范，那就是如何将这些字符串成字符流，这就是我们常说的 UTF-8、UTF-16BE、 UTF-16LE 等等概念。而 JSON 并</span><strong><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">没有</span></strong><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">对此作明确限定。这就导致了在 JSON 的编码与解码端，如果没有约定好，那么就会出现乱码。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">笔者曾经与一个合作伙伴的开发工程师对接过 JSON，对方使用 Java 解码我发出的原始数据时出现乱码。我告诉对方，应该用 UTF-8 格式解码，但是对方不明白 UTF-8 是什么，只是不停的告诉我他使用的是哪一个 Java 函数。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">我的解决方案不敢说万能，但应该即便是上古的解码器都能处理——这个方案就是指定各编码器在编码时，对大于 ASCII 范围的字符均作转义处理为 </span><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">\\\\uXXXX</span></span> 格式。这么一通操作后，我的合作伙伴表示：程序通了。</p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">其实在 JSON 规范中，列举了不少篇幅说明大于 U+00FF 的码点应该如何转义，包括大于 U+FFFF 的。所以从笔者的个人观点看来，如果我们严格按照 JSON 规范的话，什么 UTF-8，GB18030 等编码格式都是未被允许的，唯一严格允许的就是 </span><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">\\\\uXXXX</span></span> 转义。但是在实际操作中，这种转义太浪费字节序列了，各种语言对 string 类型进行操作时，习惯性地按照本身的字符串在内存中的默认编码格式照搬到 JSON 序列化上了。</p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">如果 JSON 的编码端无法确保或协调对端解码器的编码格式，那么请统一使用 </span><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">\\\\uXXXX</span></span> 转义。</p><h1>JSON 中的 UTF-16</h1><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">如果读者不需要自行编码或解析 JSON 数据的话，可以跳过这一小节；否则，这一段是必修课。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">对于编码值大于 127 的字符来说，如上文所示，我们可以转义为 </span><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">\\\\uXXXX</span></span> 格式。那么是不是直接写 <span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">sprintf(&quot;\\\\\\\\u04X&quot;, aByte)</span></span> 就可以呢？</p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">如果你这么做，那么作为一个通用库来说……</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">严格来说，</span><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">\\\\uXXXX</span></span> 其实是对 <span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">UTF-16</span></span> 编码的转写。这是一个比较少用的编码格式。我们都知道，<span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">UTF-8</span></span> 是一个变长的编码格式，它编码的基本单位是 1 个字节。受早期 Windows 16位 <span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">wchar</span></span> 的影响，有些人可能会误以为<span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">UTF-16</span></span> 是定长的 2 个字节。其实并不然，对于大于 65535 的 unicode 码点，<span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">UTF-16</span></span> 使用 4 个字节编码，而 JSON 只需要将编码后的两个半字（half world）按顺序使用 <span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">\\\\uXXXX</span></span> 转写出来就可以了。</p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">对 JSON 具体需要转义的字符，以及 </span><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">UTF-16</span></span> 的相关内容，笔者之前也写过一篇文章专门说明，欢迎移步。</p><h1>ASCII 控制字符</h1><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">按理说，JSON 只应该承载可见字符。但是按照 JSON 的规范，JSON 承载的是 unicode，而 ASCII 控制字符也是 unicode 的一部分，所以 JSON 也是可以承载 ASCII 控制字符的。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">其实这个问题并不大，即便把这些控制字符原封不动地包装在 JSON 序列化之后的数据流中，对端也是可以正确解析出来的。大家要注意的是，如果带控制字符的话，数据渲染到终端时，某些控制字符可能不会被渲染出来。如果此时你从终端复制一段数据，在粘贴到别处，这些字符可能就都丢失了。</span></p><hr/><h1>老生常谈的浮点数</h1><h1>精度问题</h1><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">众所周知，在许多语言的内部处理逻辑中，带小数部分的数字是使用浮点数来处理的。对于小数部分无法被 2 除尽的十进制数，系统（为了照顾 “你们人类”）而使用二进制浮点数的近似值来表示。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">具体到 JSON 中，坑在哪里？其实吧这里不算是 JSON 的坑，而是一个通用的问题。我简单提一下吧：</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">首先我们知道，对很多强类型语言来说，浮点数往往可以细分为单精度和双精度两种，前者使用 4 个字节，后者使用 8 个字节。单精度在有效位数方面比双精度数小一大截，但是在具体实践中，考虑到数据传输、计算效率、数值范围，往往单精度就足矣。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">这个时候，如果一个浮点数在系统内部经过各种不同精度的转换之后，在转换成 JSON 时会有什么问题呢？我们来考虑一下的过程：</span></p><ul><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">一个十进制精确定点数值 </span><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">2.1</span></span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">使用单精度浮点数表示，</span><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">f = float32(2.1)</span></span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">调用某些接口，可能接口本身是不支持单精度数，因此转成了双精度处理 </span><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">d = float64(f)</span></span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">将这个双精度数填入一个结构体并且格式化为 JSON 小数输出</span></li></ul><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">此时，我们会得到什么数字呢？根据不同的语言，输出可能会不同。如果不指定精度的话，很多 JSON 编码库是支持根据浮点数的具体数值，猜测并且格式化为一个最接近的十进制小数。以 Go 为例子，我们会发现通过 JSON 输出的时候，这个 </span><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">2.1</span></span> 变成了 <span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">2.0999999046325684</span></span>。</p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">这在本质上，是因为单精度数经过一次类型转换为双精度后，其二进制有效位数以零填充，转为十进制时，对于双精度浮点数，这就不再是双精度有效数字下的 </span><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">2.1</span></span> 了。</p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">换句话说，开发者们在处理浮点数时，需要考虑不同精度浮点数的精度处理差异，特别是金融相关的数据计算和传输，一不小心就会造成大量的对账错误。</span></p><h1>特殊浮点数</h1><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">前文提及，JSON 明确说明不支持 +/-Inf 和 NaN 这两组在 IEEE 754 中规定的特殊数值。但有一些数学运算库，在计算之后会将奇点输出为 +/-Inf 或 NaN，对于很多 JSON 编码库来说，遇到这种数值会导致整个数据编码失败。因此开发者需要针对这种情况特殊处理。我开发的 jsonvalue 中就有这样的一个专题。毕竟是笔者在实际操作中趟过的坑……</span></p><hr/><h1>有顺序的 K-V</h1><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">在 JSON 规范中，明确强调 array 类型的子值顺序的重要性（这很好理解）。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">但是针对 object 类型，key 的顺序则未提及。在实际操作中我发现不少应用场景中把 object 的 K-V 也当作有序数据来操作了——这在很多自己使用代码简单拼接 JSON 串的场景中，出乎意料地很常见。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">还请各位明确注意：JSON 的 object，我们应当默认它是无序的。如果需要传递一系列有序的 KV 对，那么请务必使用 array 类型，不要再用 object 了，这绝对不是一个通用的做法。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">在这一点上，我自己也犯过一个很低级的错误：</span></p><h1>JSON 数据的幂等检查和数据校验</h1><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">年少无知的我有一次设计过一个模块，接收上游发来的各种事件信息。为了确保事件都被处理，因此当下游响应不及时时，上游可能会将同一事件重复发出。此时我需要对事件进行幂等计算，确保同一事件不会被重复处理。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">一开始我这是简单对上游数据进行 hash 计算。但是在实际运作了一段时间，出了 bug，而原因也很简单，我们看看下面两段数据：</span></p><ul><li><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">{&quot;time&quot;:1601539200,&quot;event_id&quot;:10,&quot;openid&quot;:&quot;abcdefg&quot;}</span></span></li><li><span style=\\\"color: rgb(248, 83, 83); --tt-darkmode-color: #F85353; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\"><span style=\\\"background-color: rgb(229, 229, 229); --tt-darkmode-bgcolor: #2C2C2C; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(248, 83, 83);\\\">{&quot;event_id&quot;:10,&quot;time&quot;:1601539200,&quot;openid&quot;:&quot;abcdefg&quot;}</span></span></li></ul><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">这两段数据仅仅是 key 的顺序不同而已，但如果使用上面的逻辑，这数据根本就是一模一样的！我们永远要注意：如果我们没有明确地与上游约定好的话，那么请永远不要对上游做任何假设；即便使用文档约束，也依然要多多检查各种例外情况。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">结果怎么解决？约束上游？这岂不是显得我能力不行嘛（狗头，主要是不想让上游知道我的 bug 这么 low），所以我在我自己这边简单对解析出来的 key 排序之后（反正 key 不多且无嵌套），再重新计算 hash 来解决。</span></p><hr/><h1>结语</h1><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">本文从 JSON 标准出发，结合自己的一些工作经验，整理了 JSON 编解码过程中的一些坑和注意点。如果本文有谬误，还请不吝指正；如果读者还遇到了其他的坑，也欢迎补充。</span></p><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">此外，如果读者中有 Go 开发者的话，也欢迎了解一下我的 jsonvalue 库，点个 star 或者给我提 issue 都非常欢迎～～</span></p><h1>参考资料</h1><p style=\\\"text-align: left; display: block; box-sizing: content-box; padding: 0px; margin: 20px 0px; border: 0px none rgb(34, 34, 34);\\\"><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">JSON 相关资料：</span></p><ul><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">Python JSON模块解码中文的BUG</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">既然 GB18030 可以是 Unicode 的 UTF 转写，为什么中国境内不强制使用该字符集？</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">Go json 踩坑记录</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">axgle/mahonia</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">golang：gbk/gb18030编码字符串与utf8字符串互转</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">GB 18030 根上跟 Unicode 有关系吗？</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">细说：Unicode, UTF-8, UTF-16, UTF-32, UCS-2, UCS-4</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">Unicode</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">UTF-16</span></li><li><span style=\\\"color: rgb(0, 0, 0); --tt-darkmode-color: #A3A3A3; display: inline; box-sizing: content-box; padding: 0px; margin: 0px; border: 0px none rgb(0, 0, 0);\\\">GB 18030</span></li></ul></div></div></div></div></span>\\\n</div></body></html>\\u0000\"]],\"start1\":0,\"start2\":0,\"length1\":0,\"length2\":40226}]"
metadata_diff: {"new":{"id":"1cdb56758d444652a81749a912de7884","parent_id":"f67288c3a0be4374b5d532b5dcc3aabc","latitude":"0.00000000","longitude":"0.00000000","altitude":"0.0000","author":"","source_url":"","is_todo":0,"todo_due":0,"todo_completed":0,"source":"joplin-desktop","source_application":"net.cozic.joplin-desktop","application_data":"","order":1764348820812,"markup_language":2,"is_shared":0,"share_id":"","conflict_original_id":"","master_key_id":"","user_data":"","deleted_time":0},"deleted":[]}
encryption_cipher_text: 
encryption_applied: 0
updated_time: 2025-11-28T21:03:40.919Z
created_time: 2025-11-28T21:03:40.919Z
type_: 13