您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

我的朋友因为 JSON.stringify 差点丢了奖金

时间:2022-11-07 17:02:07  来源:今日头条  作者:Echa攻城狮

b66c81373bfb902b4ddb6c8c953acbb9.png
英文 | https://medium.com/frontend-canteen/my-friend-almost-lost-his-year-end-bonus-because-of-json-stringify-9da86961eb9e

翻译 | 杨小爱

这是发生在我朋友身上的真实故事,他的绰号叫胖头。由于JSON.stringify的错误使用,他负责的其中一个业务模块上线后出现了bug,导致某个页面无法使用,进而影响用户体验,差点让他失去年终奖。

在这篇文章中,我将分享这个悲伤的故事。然后我们还将讨论 JSON.stringify 的各种功能,以帮助您避免将来也犯同样的错误。

我们现在开始

故事是这样的。

他所在的公司,有一位同事离开了,然后胖头被要求接受离开同事的工作内容。

没想到,在他接手这部分业务后不久,项目中就出现了一个bug。

当时,公司的交流群里,很多人都在讨论这个问题。

产品经理先是抱怨:项目中有一个bug,用户无法提交表单,客户抱怨这个。请开发组尽快修复。

然后测试工程师说:我之前测试过这个页面,为什么上线后就不行了?

而后端开发者说:前端发送的数据缺少value字段,导致服务端接口出错。

找到同事抱怨后,问题出在他负责的模块上,我的朋友胖头真的很头疼。

经过一番检查,我的朋友终于找到了这个错误。

事情就是这样。

发现页面上有一个表单允许用户提交数据,然后前端应该从表单中解析数据并将数据发送到服务器。

表格是这样的:(下面是我的模拟)

25c82f627e8d6fcc2e45312b4972e1ed.png

这些字段是可选的。

通常,数据应如下所示:


 
  1. let data = {
  2. signInfo: [
  3. {
  4. "fieldId": 539,
  5. "value": "silver card"
  6. },
  7. {
  8. "fieldId": 540,
  9. "value": "2021-03-01"
  10. },
  11. {
  12. "fieldId": 546,
  13. "value": "10:30"
  14. }
  15. ]
  16. }

然后它们应该转换为:

f4688da1a27da7febc59481dd9a609f8.png

但问题是,这些字段是可选的。如果用户没有填写某些字段,那么数据会变成这样:


 
  1. let data = {
  2. signInfo: [
  3. {
  4. "fieldId": 539,
  5. "value": undefined
  6. },
  7. {
  8. "fieldId": 540,
  9. "value": undefined
  10. },
  11. {
  12. "fieldId": 546,
  13. "value": undefined
  14. }
  15. ]
  16. }

他们将变成这样:

7ca09a08e371bf7ed8f5e567375b852b.png

JSON.stringify 在转换过程中忽略其值为undefined的字段。

因此,此类数据上传到服务器后,服务器无法解析 value 字段,进而导致错误。

一旦发现问题,解决方案就很简单,为了在数据转换为 JSON 字符串后保留 value 字段,我们可以这样做:

b24aa3fdc317fa3d32263d1700e13f27.png


 
  1. let signInfo = [
  2. {
  3. fieldId: 539,
  4. value: undefined
  5. },
  6. {
  7. fieldId: 540,
  8. value: undefined
  9. },
  10. {
  11. fieldId: 546,
  12. value: undefined
  13. },
  14. ]
  15. let newSignInfo = signInfo.map((it) => {
  16. const value = typeof it.value === 'undefined' ? '' : it.value
  17. return {
  18. ...it,
  19. value
  20. }
  21. })
  22. console.log(JSON.stringify(newSignInfo))
  23. // '[{"fieldId":539,"value":""},{"fieldId":540,"value":""},{"fieldId":546,"value":""}]'

如果发现某个字段的值为undefined,我们将该字段的值更改为空字符串。

虽然问题已经解决了,但是,我们还需要思考这个问题是怎么产生的。

本来这是一个已经上线好几天的页面,为什么突然出现这个问题?仔细排查,原来是产品经理之前提出了一个小的优化点,然后,胖头对代码做了一点改动。但是胖头对 JSON.stringify 的特性并不熟悉,同时,他认为改动比较小,所以没有进行足够的测试,最终导致项目出现 bug。

好在他发现问题后,很快就解决了问题。这个bug影响的用户少,所以老板没有责怪他,我的朋友奖金没有丢掉,不然,影响大的话,估计奖金真的就没有了,甚至还会让他直接离开。

接着,我们一起来了解一下 JSON.stringify,它为啥那么“厉害”,差点把我朋友的奖金都给弄丢了。

了解一下 JSON.stringify

其实,这个bug主要是因为胖头对JSON.stringify不熟悉造成的,所以,这里我们就一起来分析一下这个内置函数的一些特点。

基本上,JSON.stringify() 方法将 JAVAScript 对象或值转换为 JSON 字符串:

8c236068569b8bdae2073a34351647f7.png

同时,JSON.stringify 有以下规则。

1、如果目标对象有toJSON()方法,它负责定义哪些数据将被序列化。

494b0b6ed82fb36be17922aad24bf1a3.png

2、 Boolean、Number、String 对象在字符串化过程中被转换为对应的原始值,符合传统的转换语义。

cdcc9b0fbe5288ec8c38f8c2861ea0fc.png

3、 undefined、Functions 和 Symbols 不是有效的 JSON 值。如果在转换过程中遇到任何此类值,则它们要么被忽略(在对象中找到),要么被更改为 null(当在数组中找到时)。

d790f8afaa0aefeaebabf1c881a6318c.png

ca8ee1d038ccd528058614118da5943a.png

4、 所有 Symbol-keyed 属性将被完全忽略

f7fc9ba44aa40c37810b6aa92ebb7fc5.png

5、 Date的实例通过返回一个字符串来实现toJSON()函数(与date.toISOString()相同)。因此,它们被视为字符串。

1d385aaed9000ad32dfff5677c8db945.png

6、 数字 Infinity 和 NaN 以及 null 值都被认为是 null。

774c3d97adda1ffeb8c392462f4db392.png

7、 所有其他 Object 实例(包括 Map、Set、WeakMap 和 WeakSet)将仅序列化其可枚举的属性。

e7ecd3a4626dce00aaf1038bc4d0b561.png

8、找到循环引用时抛出TypeError(“循环对象值”)异常。

be1479a0b522933ad7636a158194c9d9.png

9、 尝试对 BigInt 值进行字符串化时抛出 TypeError(“BigInt 值无法在 JSON 中序列化”)。

af612b440a5d4104e48915c76af1caa9.png

自己实现 JSON.stringify

理解一个函数的最好方法是自己实现它。下面我写了一个模拟 JSON.stringify 的简单函数。


 
  1. const jsonstringify = (data) => {
  2. // Check if an object has a circular reference
  3. const isCyclic = (obj) => {
  4. // Use a Set to store the detected objects
  5. let stackSet = new Set()
  6. let detected = false
  7.  
  8.  
  9. const detect = (obj) => {
  10. // If it is not an object, we can skip it directly
  11. if (obj && typeof obj != 'object') {
  12. return
  13. }
  14. // When the object to be checked already exists in the stackSet,
  15. // it means that there is a circular reference
  16. if (stackSet.has(obj)) {
  17. return detected = true
  18. }
  19. // save current obj to stackSet
  20. stackSet.add(obj)
  21.  
  22.  
  23. for (let key in obj) {
  24. // check all property of `obj`
  25. if (obj.hasOwnProperty(key)) {
  26. detect(obj[key])
  27. }
  28. }
  29. // After the detection of the same level is completed,
  30. // the current object should be deleted to prevent misjudgment
  31. /*
  32. For example: different properties of an object may point to the same reference,
  33. which will be considered a circular reference if not deleted
  34.  
  35.  
  36. let tempObj = {
  37. name: 'bytefish'
  38. }
  39. let obj4 = {
  40. obj1: tempObj,
  41. obj2: tempObj
  42. }
  43. */
  44. stackSet.delete(obj)
  45. }
  46.  
  47.  
  48. detect(obj)
  49.  
  50.  
  51. return detected
  52. }
  53.  
  54.  
  55. // Throws a TypeError ("cyclic object value") exception when a circular reference is found.
  56. if (isCyclic(data)) {
  57. throw new TypeError('Converting circular structure to JSON')
  58. }
  59.  
  60.  
  61. // Throws a TypeError when trying to stringify a BigInt value.
  62. if (typeof data === 'bigint') {
  63. throw new TypeError('Do not know how to serialize a BigInt')
  64. }
  65.  
  66.  
  67. const type = typeof data
  68. const commonKeys1 = ['undefined', 'function', 'symbol']
  69. const getType = (s) => {
  70. return Object.prototype.toString.call(s).replace(/[object (.*?)]/, '$1').toLowerCase()
  71. }
  72.  
  73.  
  74. if (type !== 'object' || data === null) {
  75. let result = data
  76. // The numbers Infinity and NaN, as well as the value null, are all considered null.
  77. if ([NaN, Infinity, null].includes(data)) {
  78. result = 'null'
  79.  
  80.  
  81. // undefined, arbitrary functions, and symbol values are converted individually and return undefined
  82. } else if (commonKeys1.includes(type)) {
  83.  
  84.  
  85. return undefined
  86. } else if (type === 'string') {
  87. result = '"' + data + '"'
  88. }
  89.  
  90.  
  91. return String(result)
  92. } else if (type === 'object') {
  93. // If the target object has a toJSON() method, it's responsible to define what data will be serialized.
  94.  
  95.  
  96. // The instances of Date implement the toJSON() function by returning a string (the same as date.toISOString()). Thus, they are treated as strings.
  97. if (typeof data.toJSON === 'function') {
  98. return jsonstringify(data.toJSON())
  99. } else if (Array.isArray(data)) {
  100. let result = data.map((it) => {
  101. // 3# undefined, Functions, and Symbols are not valid JSON values. If any such values are encountered during conversion they are either omitted (when found in an object) or changed to null (when found in an array).
  102. return commonKeys1.includes(typeof it) ? 'null' : jsonstringify(it)
  103. })
  104.  
  105.  
  106. return `[${result}]`.replace(/'/g, '"')
  107. } else {
  108. // 2# Boolean, Number, and String objects are converted to the corresponding primitive values during stringification, in accord with the traditional conversion semantics.
  109. if (['boolean', 'number'].includes(getType(data))) {
  110. return String(data)
  111. } else if (getType(data) === 'string') {
  112. return '"' + data + '"'
  113. } else {
  114. let result = []
  115. // 7# All the other Object instances (including Map, Set, WeakMap, and WeakSet) will have only their enumerable properties serialized.
  116. Object.keys(data).forEach((key) => {
  117. // 4# All Symbol-keyed properties will be completely ignored
  118. if (typeof key !== 'symbol') {
  119. const value = data[key]
  120. // 3# undefined, Functions, and Symbols are not valid JSON values. If any such values are encountered during conversion they are either omitted (when found in an object) or changed to null (when found in an array).
  121. if (!commonKeys1.includes(typeof value)) {
  122. result.push(`"${key}":${jsonstringify(value)}`)
  123. }
  124. }
  125. })
  126.  
  127.  
  128. return `{${result}}`.replace(/'/, '"')
  129. }
  130. }
  131. }
  132. }

写在最后

从一个 bug 开始,我们讨论了 JSON.stringify 的特性并自己实现了它。

今天我与你分享这个故事,是希望你以后遇到这个问题,知道怎么处理,不要也犯同样的错误。

如果你觉得有用的话,请点赞我,关注我,最后,感谢你的阅读,编程愉快!
 



Tags:JSON   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
如何在Rust中操作JSON,你学会了吗?
sonic-rs ​还具有一些额外的方法来进行惰性评估和提高速度。例如,如果我们想要一个 JSON​ 字符串文字,我们可以在反序列化时使用 LazyValue​ 类型将其转换为一个仍然带有斜...【详细内容】
2024-02-27  Search: JSON  点击:(47)  评论:(0)  加入收藏
.NET配置文件大揭秘:轻松读取JSON、XML、INI和环境变量
概述:.NET中的IConfiguration接口提供了一种多源读取配置信息的灵活机制,包括JSON、XML、INI文件和环境变量。通过示例,清晰演示了从这些不同源中读取配置的方法,使配置获取变得...【详细内容】
2023-12-28  Search: JSON  点击:(92)  评论:(0)  加入收藏
使用 Mapstructure 解析 Json,你学会了吗?
背景前几天群里的小伙伴问了一个这样的问题:图片其实质就是在面对 value 类型不确定的情况下,怎么解析这个 json?我下意识就想到了 [mapstructure](https://github.com/mitchel...【详细内容】
2023-12-27  Search: JSON  点击:(153)  评论:(0)  加入收藏
JSON非常慢:这里有更快的替代方案!
是的,你没听错!JSON,这种在网络开发中普遍用于数据交换的格式,可能正在拖慢我们的应用程序。在速度和响应性至关重要的世界里,检查 JSON 的性能影响至关重要。在这篇博客中,深入探...【详细内容】
2023-11-21  Search: JSON  点击:(252)  评论:(0)  加入收藏
Json格式弊端及优化方案
Json介绍Json(JavaScript Object Notation)是一种轻量级的数据交换格式,常用于前后端数据传输和存储。它使用简洁的文本格式来表示结构化的数据,易于阅读和编写,并且可以被多种编...【详细内容】
2023-11-13  Search: JSON  点击:(298)  评论:(0)  加入收藏
web服务器json-serve详解
简介JSON-Server 是一个 Node 模块,运行 Express 服务器,你可以指定一个 json 文件作为 api 的数据源。一、安装 JSON-Serve (Install JSON Server)使用 npm 或 yarn 工具安装...【详细内容】
2023-11-09  Search: JSON  点击:(320)  评论:(0)  加入收藏
Python文件操作:JSON、CSV、TSV、Excel和Pickle文件序列化
文件操作是Python编程的重要部分,它涉及处理各种文件格式,包括JSON、CSV、TSV、Excel和Pickle。一、JSON文件操作1.1 什是JSON?JSON(JavaScript Object Notation)是一种轻量级数...【详细内容】
2023-10-26  Search: JSON  点击:(77)  评论:(0)  加入收藏
程序开发中是使用XML还是使用JSON作为数据传输格式好?
在程序开发中,使用XML还是JSON作为传输对象是一个常见的问题。两者都是常用的数据交换格式,但在不同的情况下,使用XML或JSON可能会有不同的优势和适用性。XML(可扩展标记语言)是...【详细内容】
2023-10-24  Search: JSON  点击:(25)  评论:(0)  加入收藏
程序开发中使用XML还是JSON作为数据传输格式好?
在程序开发中,使用XML还是JSON作为传输对象是一个常见的问题。两者都是常用的数据交换格式,但在不同的情况下,使用XML或JSON可能会有不同的优势和适用性。XML(可扩展标记语言)是...【详细内容】
2023-10-24  Search: JSON  点击:(187)  评论:(0)  加入收藏
Python开发者的宝典:CSV和JSON数据处理技巧大公开!
在Python中处理CSV和JSON数据时,需要深入了解这两种数据格式的读取、写入、处理和转换方法。下面将详细介绍如何在Python中处理CSV和JSON数据,并提供一些示例和最佳实践。CSV...【详细内容】
2023-10-17  Search: JSON  点击:(217)  评论:(0)  加入收藏
▌简易百科推荐
Netflix 是如何管理 2.38 亿会员的
作者 | Surabhi Diwan译者 | 明知山策划 | TinaNetflix 高级软件工程师 Surabhi Diwan 在 2023 年旧金山 QCon 大会上发表了题为管理 Netflix 的 2.38 亿会员 的演讲。她在...【详细内容】
2024-04-08    InfoQ  Tags:Netflix   点击:(2)  评论:(0)  加入收藏
即将过时的 5 种软件开发技能!
作者 | Eran Yahav编译 | 言征出品 | 51CTO技术栈(微信号:blog51cto) 时至今日,AI编码工具已经进化到足够强大了吗?这未必好回答,但从2023 年 Stack Overflow 上的调查数据来看,44%...【详细内容】
2024-04-03    51CTO  Tags:软件开发   点击:(7)  评论:(0)  加入收藏
跳转链接代码怎么写?
在网页开发中,跳转链接是一项常见的功能。然而,对于非技术人员来说,编写跳转链接代码可能会显得有些困难。不用担心!我们可以借助外链平台来简化操作,即使没有编程经验,也能轻松实...【详细内容】
2024-03-27  蓝色天纪    Tags:跳转链接   点击:(13)  评论:(0)  加入收藏
中台亡了,问题到底出在哪里?
曾几何时,中台一度被当做“变革灵药”,嫁接在“前台作战单元”和“后台资源部门”之间,实现企业各业务线的“打通”和全域业务能力集成,提高开发和服务效率。但在中台如火如荼之...【详细内容】
2024-03-27  dbaplus社群    Tags:中台   点击:(9)  评论:(0)  加入收藏
员工写了个比删库更可怕的Bug!
想必大家都听说过删库跑路吧,我之前一直把它当一个段子来看。可万万没想到,就在昨天,我们公司的某位员工,竟然写了一个比删库更可怕的 Bug!给大家分享一下(不是公开处刑),希望朋友们...【详细内容】
2024-03-26  dbaplus社群    Tags:Bug   点击:(5)  评论:(0)  加入收藏
我们一起聊聊什么是正向代理和反向代理
从字面意思上看,代理就是代替处理的意思,一个对象有能力代替另一个对象处理某一件事。代理,这个词在我们的日常生活中也不陌生,比如在购物、旅游等场景中,我们经常会委托别人代替...【详细内容】
2024-03-26  萤火架构  微信公众号  Tags:正向代理   点击:(11)  评论:(0)  加入收藏
看一遍就理解:IO模型详解
前言大家好,我是程序员田螺。今天我们一起来学习IO模型。在本文开始前呢,先问问大家几个问题哈~什么是IO呢?什么是阻塞非阻塞IO?什么是同步异步IO?什么是IO多路复用?select/epoll...【详细内容】
2024-03-26  捡田螺的小男孩  微信公众号  Tags:IO模型   点击:(9)  评论:(0)  加入收藏
为什么都说 HashMap 是线程不安全的?
做Java开发的人,应该都用过 HashMap 这种集合。今天就和大家来聊聊,为什么 HashMap 是线程不安全的。1.HashMap 数据结构简单来说,HashMap 基于哈希表实现。它使用键的哈希码来...【详细内容】
2024-03-22  Java技术指北  微信公众号  Tags:HashMap   点击:(11)  评论:(0)  加入收藏
如何从头开始编写LoRA代码,这有一份教程
选自 lightning.ai作者:Sebastian Raschka机器之心编译编辑:陈萍作者表示:在各种有效的 LLM 微调方法中,LoRA 仍然是他的首选。LoRA(Low-Rank Adaptation)作为一种用于微调 LLM(大...【详细内容】
2024-03-21  机器之心Pro    Tags:LoRA   点击:(12)  评论:(0)  加入收藏
这样搭建日志中心,传统的ELK就扔了吧!
最近客户有个新需求,就是想查看网站的访问情况。由于网站没有做google的统计和百度的统计,所以访问情况,只能通过日志查看,通过脚本的形式给客户导出也不太实际,给客户写个简单的...【详细内容】
2024-03-20  dbaplus社群    Tags:日志   点击:(4)  评论:(0)  加入收藏
站内最新
站内热门
站内头条