当前位置: 首页 > 后端技术 > Java

JSON这么萌,千字文来过一遍吧!

时间:2023-04-01 13:15:37 Java

JSON,伟大的协议,前端工程师的伟大发明!相信99%的程序员都知道JSON。作为流行的前后端交互协议,以其易懂、简单、灵活和超强的可读性受到了互联网的广泛欢迎,甚至很多微服务之间的传输也使用该协议.但是笔者在为Go开发JSON编解码库的过程中,除了涉水各种奇怪的问题之外,也意识到广大程序员对JSON的各种奇怪的用法和姿势。在处理了这些问题之后,作者萌生了进一步普及和引入JSON的想法。相信我,看完这篇文章,你就能理解这个可爱的JSON。这不是一篇4字长的文章,所以请向我保证,不要TLDR(太长,请勿阅读)好吗?什么是JSON的问题似乎很容易回答:JavaScriptObjectNotation,直译就是JavaScript对象表示。但是,这个命名中的“JavaScript”非常具有误导性,让人以为JSON是依附于JavaScript的。事实上,JSON是一种完全独立于任何语言的对象表示协议。即使从我个人的角度来看,它也不是很“JS”。关于JSON的“常识”从大家的认知来看,相信以下几点是常识:JSON可以是一个对象(object),用{...}格式包裹JSON可以是一个数组(array),使用[...]formatwrappedJSON值可以是string,boolean,number,也可以是进一步嵌套的object并且arrayJSON也有需要转义的特殊字符,最明显的就是双引号”,反斜杠\,换行符\n、\rJSON对象的键(key)必须是字符串格式,JSON可以通过对象和数组类型实现无限层级嵌套,好了,如果你理解了以上几点,其实你就能理解90%甚至99%的应用JSONScenario.程序员足以实现简单的JSON编码逻辑。如果你想知道剩下的1%让人掉牙,欢迎往下阅读;如果你想自己开发一个JSON编解码库,下面的内容也可以让你少走很多弯路:JSON标准有什么规定在了解JSON的各种坑之前,我们先来了解一下JSON标准本身。当前的JSON标准是ECMA-404。本协议一共14页,但除封面、封底、目录、介绍、版权声明外,只有5页正文,3页中绝大部分为图片。因此,作者建议所有程序员通读本文档。恐怕这是唯一能被大多数人完全读懂的主流协议(狗头)。所以,“可爱”的JSON真的不是标题党——试想这么短的协议,怎么能叫可爱呢!通读文档,我们可以发现,除了上面提到的常识外,还有几个知识点是人们很少关注的:JSON用于携带unicode字符,在标准的JSON标准中有明确提及事实上,并没有boolean类型,而是将true和false列为两个独立的类型。作为最外层的JSON类型,它不限于对象或数组。事实上,string、boolean、number,甚至null在JSON中也是可以接受的数字表示可以使用科学计数法。很多人在实际应用中可能没有注意到JSON。它明确指出不支持+/-Inf和NaN。IEEE754中规定的这两组特殊值。下面我们来解读一下以上知识点。影响:unicode字符:JSON应该传输可视字符而不是不可读的二进制数据。也就是说,请尽量不要使用JSON来传输二进制数据。没有布尔类型。这不是什么大问题,主要是针对各种库的使用——有些库提供boolean类型分类,而有些库根据标准协议分为true和false两种类型。请注意,外部类型不受限制。其实这作用不大,但是却让JSON多了一个额外的特性:当我们想把包含换行符的文本压缩成一行,但又要保持高可读性时,我们可以将文本序列化为JSON。此功能在记录日志时特别有用。科学计数法:这主要是因为在解析JSON数据时,需要注意与特殊浮点值的兼容性:这个问题可大可小,大多数情况下不会遇到,但一旦遇到,整个序列化过程将失败。开发人员需要小心浮点数,我会进一步提到JSON没有指定什么?JSON没有严格限制文本的编码格式JSON数字都是十进制的,没有绝对值大小的限制,也没有小数点后位数的限制JSON没有明确规定传输格式ASCII控制字符和不可见字符JSON不限制对象的键JSON使用的字符没有明确表示对象的键是有序的还是无序的。为什么列出这些要点?让我们继续往下看: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转义。但是在实际操作中,这种转义方式太浪费字节序列了。各种语言在对字符串类型进行操作时,习惯性的将内存中自己字符串的默认编码格式复制到JSON序列化。如果JSON编码端不能保证或协调对端解码器的编码格式,请统一使用\uXXXX转义。JSON中的UTF-16如果读者不需要自己编码或解析JSON数据,可以跳过本节;否则,这部分是必需的。对于编码值大于127的字符,如上图,我们可以转义为\uXXXX格式。那么直接写sprintf("\\u04X",aByte)是不是可以呢?如果你这样做,那么作为一个通用的库...2fps3.tgbus.com%2fUploadFiles%2f201206%2f20120619174916338.jpg&ehk=wYBPVaiaHHF33Liake9fWy32femV5sBtaHR4BkfWcYs%3d&risl=&pid=ImgRaw&r=0&sres=1&sresct=1"width="25%"height="25%">严格来说,\uXXXX其实是Transliteration用于UTF-16编码。这是一种比较少用的编码格式。我们都知道UTF-8是一种变长编码格式,编码的基本单位是1字节。受早期Windows16位wchar的影响,有些人可能会误认为UTF-16是定长2字节。事实上,情况并非如此。对于大于65535的unicode码位,UTF-16采用4字节编码,而JSON只需要将编码后的两个半字(halfworld)按顺序使用\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为例,我们会发现这个2.1通过JSON输出时变成了2.0999999046325684。从本质上讲,这是因为单精度数转换为双精度后,其二进制有效位被补零。当转换为十进制时,对于双精度浮点数,这不再是双精度有效数字。2.1来了。也就是说,在处理浮点数时,开发者需要考虑不同精度的浮点数在精度处理上的差异,尤其是对于金融相关的数据计算和传输,如果这样会造成大量的对账错误他们不小心。特殊浮点数前面提到,JSON明确表示不支持IEEE754中规定的+/-Inf和NaN这两组特殊值。但是有一些数学库会在计算后输出奇点为+/-Inf或者NaN,而对于很多JSON编码库来说,遇到这样的值会导致整个数据编码失败。因此,开发者需要针对这种情况进行特殊处理。我开发的jsonvalue里面有这样一个题目。毕竟是作者在实际操作中趟过的坑……JSON规范中有序的K-V,明确强调了数组类型的子值顺序的重要性(这个很好理解)。但是对于对象类型,没有提到键的顺序。在实际操作中,我发现在很多应用场景中,对象的K-V也是作为有序数据进行操作的——这在很多我使用代码简单拼接JSON字符串的场景中,出乎意料的普遍。另请注意:JSON对象,我们默认是无序的。如果需要传递一系列有序的KV对,一定要使用array类型而不是object类型。这绝对不是常见的做法。在这一点上,我也犯了一个很低级的错误:JSON数据的幂等性校验和数据校验。年少无知,曾经设计过一个模块接收上游传来的各种事件信息。为了保证事件得到处理,当下游响应不及时时,上游可能会重复发送同一个事件。此时,我需要对事件进行幂等计算,保证同一个事件不会被重复处理。一开始我只是简单的对上游数据进行哈希计算。但是经过一段时间的实际操作,出现了一个bug,原因很简单。我们看下面两条数据:{"time":1601539200,"event_id":10,"openid":"abcdefg"}{"event_id":10,"time":1601539200,"openid":"abcdefg"}这两条数据只是key的顺序不同,但是如果按照上面的逻辑,数据是一模一样的!我们应该始终注意:如果我们没有明确同意上游,那么请不要对上游做任何假设;即使你使用文档约束,你仍然必须检查各种异常。结果如何解决?上游受限?是不是好像我的能力不够好(狗头,主要是不想让上游知道我的bug这么低),所以我就干脆自己这边把解析出来的key排序一下(没有许多键并且无论如何都没有嵌套),然后重新计算哈希来解决它。结语本文从JSON标准出发,结合自己的一些工作经验,整理出JSON编解码过程中的一些陷阱和注意点。本文如有谬误,欢迎指正;如果读者遇到其他坑,欢迎补充。另外,如果读者中有Go开发者,也欢迎大家了解我的jsonvalue库。非常欢迎您点个星或提交问题给我。Unicode的UTF音译,为什么这个字符集在国内不是强制的?gojson踩坑记录axgle/mahoniagolang:gbk/gb18030编码字符串和utf8字符串转成GB18030,跟Unicode有关系吗?详细信息:Unicode、UTF-8、UTF-16、UTF-32、UCS-2、UCS-4UnicodeUTF-16GB18030本文根据知识共享署名-非商业性使用-共享相同4.0国际许可协议获得许可。原作者:amc,原文发表于云+社区,也是我的博客。欢迎转载,但转载请注明出处。原标题:《JSON 这么可爱,让我们用千字短文吃透它吧!》发布日期:2022-10-21