首页 > 其他分享 >我的朋友因为 JSON.stringify 差点丢了奖金

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

时间:2022-11-08 17:31:32浏览次数:53  
标签:stringify return undefined value 奖金 JSON data

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

英文 | 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字段,导致服务端接口出错。

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

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

事情就是这样。

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

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

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

这些字段是可选的。

通常,数据应如下所示:

let data = {
signInfo: [
{
"fieldId": 539,
"value": "silver card"
},
{
"fieldId": 540,
"value": "2021-03-01"
},
{
"fieldId": 546,
"value": "10:30"
}
]
}

然后它们应该转换为:

我的朋友因为 JSON.stringify 差点丢了奖金_数据_03

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

let data = {
signInfo: [
{
"fieldId": 539,
"value": undefined
},
{
"fieldId": 540,
"value": undefined
},
{
"fieldId": 546,
"value": undefined
}
]
}

他们将变成这样:

我的朋友因为 JSON.stringify 差点丢了奖金_数据_04

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

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

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

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

let signInfo = [
{
fieldId: 539,
value: undefined
},
{
fieldId: 540,
value: undefined
},
{
fieldId: 546,
value: undefined
},
]
let newSignInfo = signInfo.map((it) => {
const value = typeof it.value === 'undefined' ? '' : it.value
return {
...it,
value
}
})
console.log(JSON.stringify(newSignInfo))
// '[{"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 字符串:

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

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

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

我的朋友因为 JSON.stringify 差点丢了奖金_字段_07

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

我的朋友因为 JSON.stringify 差点丢了奖金_字段_08

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

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


我的朋友因为 JSON.stringify 差点丢了奖金_字段_10

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

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

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

我的朋友因为 JSON.stringify 差点丢了奖金_字段_12

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

我的朋友因为 JSON.stringify 差点丢了奖金_字段_13

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

我的朋友因为 JSON.stringify 差点丢了奖金_数据_14

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

我的朋友因为 JSON.stringify 差点丢了奖金_字段_15

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

我的朋友因为 JSON.stringify 差点丢了奖金_数据_16

自己实现 JSON.stringify

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

const jsonstringify = (data) => {
// Check if an object has a circular reference
const isCyclic = (obj) => {
// Use a Set to store the detected objects
let stackSet = new Set()
let detected = false


const detect = (obj) => {
// If it is not an object, we can skip it directly
if (obj && typeof obj != 'object') {
return
}
// When the object to be checked already exists in the stackSet,
// it means that there is a circular reference
if (stackSet.has(obj)) {
return detected = true
}
// save current obj to stackSet
stackSet.add(obj)


for (let key in obj) {
// check all property of `obj`
if (obj.hasOwnProperty(key)) {
detect(obj[key])
}
}
// After the detection of the same level is completed,
// the current object should be deleted to prevent misjudgment
/*
For example: different properties of an object may point to the same reference,
which will be considered a circular reference if not deleted


let tempObj = {
name: 'bytefish'
}
let obj4 = {
obj1: tempObj,
obj2: tempObj
}
*/
stackSet.delete(obj)
}


detect(obj)


return detected
}


// Throws a TypeError ("cyclic object value") exception when a circular reference is found.
if (isCyclic(data)) {
throw new TypeError('Converting circular structure to JSON')
}


// Throws a TypeError when trying to stringify a BigInt value.
if (typeof data === 'bigint') {
throw new TypeError('Do not know how to serialize a BigInt')
}


const type = typeof data
const commonKeys1 = ['undefined', 'function', 'symbol']
const getType = (s) => {
return Object.prototype.toString.call(s).replace(/\[object (.*?)\]/, '$1').toLowerCase()
}


if (type !== 'object' || data === null) {
let result = data
// The numbers Infinity and NaN, as well as the value null, are all considered null.
if ([NaN, Infinity, null].includes(data)) {
result = 'null'


// undefined, arbitrary functions, and symbol values are converted individually and return undefined
} else if (commonKeys1.includes(type)) {


return undefined
} else if (type === 'string') {
result = '"' + data + '"'
}


return String(result)
} else if (type === 'object') {
// If the target object has a toJSON() method, it's responsible to define what data will be serialized.


// The instances of Date implement the toJSON() function by returning a string (the same as date.toISOString()). Thus, they are treated as strings.
if (typeof data.toJSON === 'function') {
return jsonstringify(data.toJSON())
} else if (Array.isArray(data)) {
let result = data.map((it) => {
// 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).
return commonKeys1.includes(typeof it) ? 'null' : jsonstringify(it)
})


return `[${result}]`.replace(/'/g, '"')
} else {
// 2# Boolean, Number, and String objects are converted to the corresponding primitive values during stringification, in accord with the traditional conversion semantics.
if (['boolean', 'number'].includes(getType(data))) {
return String(data)
} else if (getType(data) === 'string') {
return '"' + data + '"'
} else {
let result = []
// 7# All the other Object instances (including Map, Set, WeakMap, and WeakSet) will have only their enumerable properties serialized.
Object.keys(data).forEach((key) => {
// 4# All Symbol-keyed properties will be completely ignored
if (typeof key !== 'symbol') {
const value = data[key]
// 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).
if (!commonKeys1.includes(typeof value)) {
result.push(`"${key}":${jsonstringify(value)}`)
}
}
})


return `{${result}}`.replace(/'/, '"')
}
}
}
}

写在最后

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

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

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

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


标签:stringify,return,undefined,value,奖金,JSON,data
From: https://blog.51cto.com/u_15809510/5833895

相关文章

  • 6.Json交互处理
    6.Json交互处理1.什么是JsonJSON(JavaScriptObjectNotation,JS对象标记)是一种轻量级的数据交换格式,目前使用特别广泛。采用完全独立于编程语言的文本格式来存储......
  • JSONPath 处理特殊字符
    JSONPath处理特殊字符先来看看jsonpath的基础语法$表示文档的根元素@表示文档的当前元素.node_name或['node_name']匹配下级节点[index]检索数组中的元......
  • vscode常用配置的json文件
    {"editor.parameterHints":true,"editor.quickSuggestions":{"other":true,"comments":true,"strings":true},"wind......
  • webpack中配置CSS兼容性时报错 Failed to parse package.json data
      是因为在package.json中添加了注释正确webpack配置CSS兼容性的步骤:npmipostcss-loaderpostcss-preset-env-D/webpack.config.jsmodule:{    ru......
  • Long数据类型序列化Json后传递给前端,产生的精度丢失的问题解决
    问题产生的原因Long类型的数据,如果我们在后端将结果序列化为json,直接传给前端的话,在Long长度大于17位时会出现精度丢失的问题。java中的long能表示的范围比js中number大,......
  • Python Ujson
    UJson主要记录其安装方式,能使用pipinstallujson进行安装不过好像有点慢,反正我等了很久,也可以使用Python命令进行安装会快一点,命令如下:python-mpipinstallujson ......
  • json格式的数组去重
    vararr=[{key:'01',value:'乐乐'},{key:'02',value:'博博'},{key:'03',value:'淘淘'},{key:'......
  • package.json中dependencies与devDependencies
    dependenciesdependencies:{key:value}声明的是项目中生产环境中所需的依赖包,如element-ui、jsmid、pinia等程序运行时需要的依赖。使用npminstall【xxx】|npmi......
  • js把json格式化
    1字符串转json对象2json对象转格式化的字符串<html><head></head><body><inputtype="button"οnclick="aa()"/><textarearows="13"cols="220"id="t"></textare......
  • JSON字符串与JSON对象的区别
    Q:什么是"JSON字符串",什么是"JSON对象",两者的区别?a.JSON对象是直接可以使用JQuery操作的格式,如js中可以用对象(类名)点出属性(方法)一样b.JSON字符串仅仅只是一个字符串,一个......