一、前言
目前在做一些项目,很多项目想实现动态扩展,比如对接口字段进行处理,用户可以自定义添加过滤函数,从而获取想要的字段内容,如何将自定义函数存在json中,从而实现后端存储。
二、实现思路剖析
一般情况下,把json转成字符串都是直接用JSON.stringify()
的, 以及平常我做简单对象的深度拷贝也是直接 JSON.parse(JSON.stringify(obj))
;不过JSON.stringify()
存在一些问题:
1、转换值如果有toJson()
方法,那么由toJson()
定义什么值将被序列化。
2、非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
3、布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
4、undefined
、任意的函数以及 symbol
值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null
(出现在数组中时);函数、undefined
被单独转换时,会返回 undefined
,如JSON.stringify(function(){})
or JSON.stringify(undefined)
。
5、所有以symbol
为属性键的属性都会被完全忽略掉,即便 replacer
参数中强制指定包含了它们。
6、Date
日期调用了 toJSON()
将其转换为了string
字符串(同Date.tolSOString()
),因此会被当做字符串处理。
7、NaN 和 Infinity 格式的数值及 null 都会被当做 null。
8、其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。
这里我们主要看针对第四条,则有如下输出结果:
let obj = {
name: [1,2, undefined, 3],
fn: () => {},
age: null,
sex: undefined
}
console.log(JSON.stringify(obj)) // {"name":[1,2,null,3],"age":null}
可以看到,函数
和 undefined
类型的会被干掉,数组中的undefined
会被转成null
,这几点在使用JSON.parse(JSON.stringify(obj))
做深度拷贝的时候也要注意。
三、解决方式
3.1 使用JSON.stringify
的第二个参数
具体实现如下:
JSON.stringify(obj, (k, v) => {
if(typeof v === 'function') {
return `${v}`
} else {
return v
}
})
思路就是不让JSON.stringify
把 函数去掉,咱们给他转成字符串
const stringify = (obj) => {
return JSON.stringify(obj, (k, v) => {
if(typeof v === 'function') {
return `${v}`
} else {
return v
}
})
}
let obj = {
arr: [1, 2, '11', '22'],
fn: function filterNumber (){
return this.arr.filter(item => {
return typeof item === 'number'
})
}
}
// 输出字符串结果
console.log(stringify(obj))
// {"arr":[1,2,"11","22"],"fn":"function filterNumber (){\n return this.arr.filter(item => {\n return typeof item === 'number'\n })\n }"}
3.2 上面可以把函数转为字符串,但是如何将字符串再变回原来的函数,这里则用到了new Function
new Function(str) 的语法如下:
let func = new Function ([arg1[, arg2[, ...argN]],] functionBody)
换句话说,函数的参数(或更确切地说,各参数的名称)首先出现,而函数体在最后。所有参数都写成字符串形式。通过查看示例,可以更容易理解。这是一个有两个参数的函数:
let sum = new Function('a', 'b', 'return a + b');
alert( sum(1, 2) ); // 3
也可以不传参数,直接传一个函数的字符串,如下:
let str = 'function say(){console.log(1)}'
let fn = new Function(`return ${str}`)()
fn() // 输出1
现在可以把函数转成字符串了,也可以把字符串转为函数了,但是json中那么多字符串,如何识别哪个才是由函数转成的呢?这时候只需在函数转字符串的时候,加一个前缀做为标示就可以,具体代码如下:
// 这里前缀的标识用 'FUNCTION_FLAG' 可根据需要自定修改
const stringify = (obj) => {
return JSON.stringify(obj, (k, v) => {
if(typeof v === 'function') {
return `FUNCTION_FLAG ${v}`
} else {
return v
}
})
}
解析字符串的时候,只需判断下就行,代码如下:
const parse = (jsonStr) => {
return JSON.parse(jsonStr, (key, value) => {
if(value && typeof value === 'string') {
return value.indexOf('FUNCTION_FLAG') > -1 ? new Function(`return ${value.replace('FUNCTION_FLAG', '')}`)() : value
}
return value
})
}
再加上try catch 处理,完整代码如下:
const stringify = (obj) => {
try {
return JSON.stringify(obj, (k, v) => {
if(typeof v === 'function') {
return `FUNCTION_FLAG ${v}`
} else {
return v
}
})
} catch (error) {
console.log(error)
return '出错了'
}
}
const parse = (jsonStr) => {
try {
return JSON.parse(jsonStr, (key, value) => {
if(value && typeof value === 'string') {
return value.indexOf('FUNCTION_FLAG') > -1 ? new Function(`return ${value.replace('FUNCTION_FLAG', '')}`)() : value
}
return value
})
} catch (error) {
console.log(error)
return '出错了'
}
}
let obj = {
arr: [1, 2, '11', '22'],
fn: function filterNumber (){
return this.arr.filter(item => {
return typeof item === 'number'
})
}
}
let str = stringify(obj)
let result = parse(str)
console.log(result.fn()) // [1,2]