js如何在json中存储函数
原⽂链接:
背景
最近在做可视化搭建系统,其中涉及到对接⼝字段的处理,需求是要⽤户可以⾃定义添加过滤函数,可视化搭建的最终产物其实就⼀个⼤json,
那么如何把这个⾃定义函数存在json中就是个问题了,最后得以解决,有此总结。
效果展⽰
这⾥是为了做demo展⽰⽅便,模拟展⽰过滤掉数组中的⾮数字类型,实际项⽬情况会⽐这种复杂,不过核⼼功能⼀样
// stringify parse 是两个⾃定义函数,后续会讲
// 这是原json对象
{
let obj = {
arr: [1, 2, '11', '22'],
fn: function filterNumber () {
return this.arr.filter(item => {
return typeof item === 'number'
})
}
}
// 把json对象转成字符串,就可以存到数据库中了
let str = stringify(obj)
console.log(str)
/
/ 把字符串解析成json对象
let result = parse(str)
// 这⾥就可以执⾏ function 了
console.log(result.fn())
可以看到输出结果如下图所⽰:
实现原理剖析
⼀般情况下,把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 字符串(同ISOString()),因此会被当做字符串处理
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))做深度拷贝的时候也要注意
JSON.stringify 的第⼆个参数
要想实现把function 保存在json中,就必须⽤到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  }"}
这样就把函数转成了字符串了,但是如何把字符串再变回原来的函数呢,这⾥就⽤到了 new Function
new Function(str)
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 ${place('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') {jsreplace函数
return value.indexOf('FUNCTION_FLAG') > -1 ? new Function(`return ${place('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]

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。