Libon

JSON.stringify

3Mins #JavaScript
实现一个和 JSON.stringify 一样的方法
1
function jsonStringify (data) {
2
// 检查对象是否有循环引用
3
const isCyclic = (obj) => {
4
// 使用Set数据类型存储检测到的对象
5
const stackSet = new Set()
6
let detected = false
7
8
const detect = (obj) => {
9
// 如果它不是对象类型,可以直接跳过它
10
if (obj && typeof obj !== 'object') {
107 collapsed lines
11
return
12
}
13
14
// 当要检查的对象在stackSet中已经存在时,这意味着存在循环引用
15
if (stackSet.has(obj)) {
16
return (detected = true)
17
}
18
// 将当前 obj 保存在Set
19
stackSet.add(obj)
20
21
for (const key in obj) {
22
if (Object.prototype.hasOwnProperty.call(obj, key)) {
23
detect(obj[key])
24
}
25
}
26
// 级别检测完成后,请删除当前对象,以免误判
27
/*
28
对象的属性指向同一个引用, 如未删除,则视为循环引用
29
let tempObj = {
30
name: 'fatfish'
31
}
32
let obj4 = {
33
obj1: tempObj,
34
obj2: tempObj
35
}
36
*/
37
stackSet.delete(obj)
38
}
39
40
detect(obj)
41
42
return detected
43
}
44
45
// 在包含循环引用的对象上执行此方法将引发错误。
46
if (isCyclic(data)) {
47
throw new TypeError('Converting circular structure to JSON')
48
}
49
50
// 在尝试转换BigInt类型的值时会抛出一个错误
51
if (typeof data === 'bigint') {
52
throw new TypeError('Do not know how to serialize a BigInt')
53
}
54
55
const type = typeof data
56
const commonKeys1 = ['undefined', 'function', 'symbol']
57
const getType = (s) => {
58
return Object.prototype.toString.call(s).replace(/\[object (.*?)\]/, '$1').toLowerCase()
59
}
60
61
// not an object
62
if (type !== 'object' || data === null) {
63
let result = data
64
65
// 数字Infinity和NaN,以及值null,都被认为是null。
66
if ([NaN, Infinity, null].includes(data)) {
67
result = 'null'
68
// undefined、Function和Symbol不是有效的JSON值。
69
// 如果在转换过程中遇到任何这样的值,它们要么被省略(当在对象中找到时),要么被更改为null(当在数组中找到时).
70
// 当传入像JSON.stringify(function(){})或JSON.stringify(undefined)这样的“纯”值时,JSON.stringify()可以返回undefined。
71
} else if (commonKeys1.includes(type)) {
72
return undefined
73
} else if (type === 'string') {
74
result = '"' + data + '"'
75
}
76
77
return String(result)
78
} else if (type === 'object') {
79
// 如果值具有toJSON()方法,则它负责定义将序列化的数据。
80
// Date的实例通过返回一个字符串(与Date . toisostring()相同)来实现toJSON()函数。
81
// 因此,它们被视为字符串。
82
if (typeof data.toJSON === 'function') {
83
return jsonstringify(data.toJSON())
84
} else if (Array.isArray(data)) {
85
const result = data.map((it) => {
86
// 如果在转换过程中遇到任何这样的值,它们要么被省略(当在对象中找到时),要么被更改为null(当在数组中找到时)
87
return commonKeys1.includes(typeof it) ? 'null' : jsonstringify(it)
88
})
89
90
return `[${result}]`.replace(/'/g, '"')
91
} else {
92
// Boolean、Number和String对象在字符串化过程中按照传统的转换语义转换为相应的基元值。
93
if (['boolean', 'number'].includes(getType(data))) {
94
return String(data)
95
} else if (getType(data) === 'string') {
96
return '"' + data + '"'
97
} else {
98
const result = []
99
100
// 所有其他Object实例(包括Map、Set、WeakMap和WeakSet)将只序列化它们的可枚举属性。
101
Object.keys(data).forEach((key) => {
102
// 所有以符号为键的属性将被完全忽略,即使在使用替换函数时也是如此。
103
if (typeof key !== 'symbol') {
104
const value = data[key]
105
106
// undefined、Function和Symbol不是有效的JSON值。
107
if (!commonKeys1.includes(typeof value)) {
108
result.push(`"${key}":${jsonstringify(value)}`)
109
}
110
}
111
})
112
113
return `{${result}}`.replace(/'/, '"')
114
}
115
}
116
}
117
}

CD ..