重学JSON.stringify

重学JSON.stringify

曾经碰到过一个问题,使用JSON.stringify时发现一些key在序列化后空值会被忽略

let signInfo = [{fieldId: 539,value: undefined},{fieldId: 540,value: undefined},{fieldId: 546,value: undefined},
]
// 经过JSON.stringify之后的数据,少了value key,导致后端无法读取value值进行报错
// 具体原因是`undefined`、`任意的函数`以及`symbol值`,出现在`非数组对象`的属性值中时在序列化过程中会被忽略
console.log(JSON.stringify(signInfo))
// '[{"fieldId":539},{"fieldId":540},{"fieldId":546}]'

解决方法

let newSignInfo = signInfo.map(item => ({ ...item, value: item.value === undefined ? '' : item.value }));console.log(JSON.stringify(newSignInfo))
// '[{"fieldId":539,"value":""},{"fieldId":540,"value":""},{"fieldId":546,"value":""}]'

或者直接用JSON.stringify的第二个参数

// 判断到value为undefined,返回空字符串即可
JSON.stringify(signInfo, (key, value) => value === undefined ? '' : value)
// '[{"fieldId":539,"value":""},{"fieldId":540,"value":""},{"fieldId":546,"value":""}]'

语法

JSON.stringify(value[, replacer [, space]])

参数

  • value
    1. 将要序列化成 一个 JSON 字符串的值。
  • replacer 可选
    1. 如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;
    2. 如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;
    3. 如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。
  • space 可选
    1. 指定缩进用的空白字符串,用于美化输出(pretty-print);
    2. 如果参数是个数字,它代表有多少的空格;上限为10。
    3. 该值若小于1,则意味着没有空格;
    4. 如果该参数为字符串(当字符串长度超过10个字母,取其前10个字母),该字符串将被作为空格;
    5. 如果该参数没有提供(或者为 null),将没有空格。

返回值

一个表示给定值的JSON字符串。

异常

  1. JSON.stringify可以转换对象或者值(平常用的更多的是转换对象)
  2. 可以指定replacer为函数选择性地替换
  3. 也可以指定replacer为数组,可转换指定的属性

这里仅仅是MDN上关于JSON.stringify其中最基础的说明,咱们先打个码试试这几个特性

// 1. 转换对象
console.log(JSON.stringify({ name: '张三', sex: 'boy' })) // '{"name":"张三","sex":"boy"}'// 2. 转换普通值
console.log(JSON.stringify('张三')) // "张三"
console.log(JSON.stringify(1)) // "1"
console.log(JSON.stringify(true)) // "true"
console.log(JSON.stringify(null)) // "null"// 3. 指定replacer函数
console.log(JSON.stringify({ name: '张三', sex: 'boy', age: 100 }, (key, value) => {return typeof value === 'number' ? undefined : value
}))
// '{"name":"张三","sex":"boy"}'// 4. 指定数组
console.log(JSON.stringify({ name: '张三', sex: 'boy', age: 100 }, [ 'name' ]))
// '{"name":"张三"}'// 5. 指定space(美化输出)
console.log(JSON.stringify({ name: '张三', sex: 'boy', age: 100 }))
// '{"name":"张三","sex":"boy","age":100}'
console.log(JSON.stringify({ name: '张三', sex: 'boy', age: 100 }, null , 2))
/*
{"name": "张三","sex": "boy","age": 100
}
*/

9大特性

特性一

  1. undefined任意的函数以及symbol值,出现在非数组对象的属性值中时在序列化过程中会被忽略
  2. undefined任意的函数以及symbol值出现在数组中时会被转换成 null
  3. undefined任意的函数以及symbol值被单独转换时,会返回 undefined
// 1. 对象中存在这三种值会被忽略
console.log(JSON.stringify({name: '张三',sex: 'boy',// 函数会被忽略showName () {console.log('张三')},// undefined会被忽略age: undefined,// Symbol会被忽略symbolName: Symbol('张三')
}))
// '{"name":"张三","sex":"boy"}'// 2. 数组中存在着三种值会被转化为null
console.log(JSON.stringify(['张三','boy',// 函数会被转化为nullfunction showName () {console.log('张三')},//undefined会被转化为nullundefined,//Symbol会被转化为nullSymbol('张三')
]))
// '["张三","boy",null,null,null]'// 3.单独转换会返回undefined
console.log(JSON.stringify(function showName () {console.log('张三')}
)) // undefined
console.log(JSON.stringify(undefined)) // undefined
console.log(JSON.stringify(Symbol('张三'))) // undefined

特性二

布尔值数字字符串的包装对象在序列化过程中会自动转换成对应的原始值。

console.log(JSON.stringify([new Number(1), new String("张三"), new Boolean(false)]))
// '[1,"张三",false]'

特性三

所有以symbol为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。

console.log(JSON.stringify({[Symbol('张三')]: '张三'}
)) 
// '{}'
console.log(JSON.stringify({[ Symbol('张三') ]: '张三',
}, (key, value) => {if (typeof key === 'symbol') {return value}
}))
// undefined

特性四

NaNInfinity 格式的数值及 null 都会被当做 null

console.log(JSON.stringify({age: NaN,age2: Infinity,name: null
}))
// '{"age":null,"age2":null,"name":null}'

特性五

转换值如果有 toJSON() 方法,该方法定义什么值将被序列化。

const toJSONObj = {name: '张三',toJSON () {return 'JSON.stringify'}
}console.log(JSON.stringify(toJSONObj))
// "JSON.stringify"

特性六

Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理。

const d = new Date()console.log(d.toJSON()) // 2021-10-05T14:01:23.932Z
console.log(JSON.stringify(d)) // "2021-10-05T14:01:23.932Z"

特性七

对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。

let cyclicObj = {name: '张三',
}cyclicObj.obj = cyclicObjconsole.log(JSON.stringify(cyclicObj))
// Converting circular structure to JSON

特性八

其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性

let enumerableObj = {}Object.defineProperties(enumerableObj, {name: {value: '张三',enumerable: true},sex: {value: 'boy',enumerable: false},
})console.log(JSON.stringify(enumerableObj))
// '{"name":"张三"}'

特性九

当尝试去转换 BigInt 类型的值会抛出错误

const alsoHuge = BigInt(9007199254740991)console.log(JSON.stringify(alsoHuge))
// TypeError: Do not know how to serialize a BigInt

手写一个JSON.stringify

终于重新学完JSON.stringify的众多特性啦!咱们根据这些特性来手写一个简单版本的吧(无replacer函数和space)

源码实现
const jsonstringify = (data) => {// 确认一个对象是否存在循环引用const isCyclic = (obj) => {// 使用Set数据类型来存储已经检测过的对象let stackSet = new Set()let detected = falseconst detect = (obj) => {// 不是对象类型的话,可以直接跳过if (obj && typeof obj != 'object') {return}// 当要检查的对象已经存在于stackSet中时,表示存在循环引用if (stackSet.has(obj)) {return detected = true}// 将当前obj存如stackSetstackSet.add(obj)for (let key in obj) {// 对obj下的属性进行挨个检测if (obj.hasOwnProperty(key)) {detect(obj[key])}}// 平级检测完成之后,将当前对象删除,防止误判/*例如:对象的属性指向同一引用,如果不删除的话,会被认为是循环引用let tempObj = {name: '张三'}let obj4 = {obj1: tempObj,obj2: tempObj}*/stackSet.delete(obj)}detect(obj)return detected
}// 特性七:// 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。if (isCyclic(data)) {throw new TypeError('Converting circular structure to JSON')}// 特性九:// 当尝试去转换 BigInt 类型的值会抛出错误if (typeof data === 'bigint') {throw new TypeError('Do not know how to serialize a BigInt')}const type = typeof dataconst 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// 特性四:// NaN 和 Infinity 格式的数值及 null 都会被当做 null。if ([NaN, Infinity, null].includes(data)) {result = 'null'// 特性一:// `undefined`、`任意的函数`以及`symbol值`被`单独转换`时,会返回 undefined} else if (commonKeys1.includes(type)) {// 直接得到undefined,并不是一个字符串'undefined'return undefined} else if (type === 'string') {result = '"' + data + '"'}return String(result)} else if (type === 'object') {// 特性五:// 转换值如果有 toJSON() 方法,该方法定义什么值将被序列化// 特性六:// Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理。if (typeof data.toJSON === 'function') {return jsonstringify(data.toJSON())} else if (Array.isArray(data)) {let result = data.map((it) => {// 特性一:// `undefined`、`任意的函数`以及`symbol值`出现在`数组`中时会被转换成 `null`return commonKeys1.includes(typeof it) ? 'null' : jsonstringify(it)})return `[${result}]`.replace(/'/g, '"')} else {// 特性二:// 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。if (['boolean', 'number'].includes(getType(data))) {return String(data)} else if (getType(data) === 'string') {return '"' + data + '"'} else {let result = []// 特性八// 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性Object.keys(data).forEach((key) => {// 特性三:// 所有以symbol为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。if (typeof key !== 'symbol') {const value = data[key]// 特性一// `undefined`、`任意的函数`以及`symbol值`,出现在`非数组对象`的属性值中时在序列化过程中会被忽略if (!commonKeys1.includes(typeof value)) {result.push(`"${key}":${jsonstringify(value)}`)}}})return `{${result}}`.replace(/'/, '"')}}}
}


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部