日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

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 的特性并自己實現了它。

今天我與你分享這個故事,是希望你以后遇到這個問題,知道怎么處理,不要也犯同樣的錯誤。

如果你覺得有用的話,請點贊我,關注我,最后,感謝你的閱讀,編程愉快!
 

分享到:
標簽:JSON
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定