js如何在json中存储函数
原文链接:https://www.cnblogs.com/yalong/p/15768087.html
背景
最近在做可视化搭建系统,其中涉及到对接口字段的处理,需求是要用户可以自定义添加过滤函数,可视化搭建的最终产物其实就一个大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
存在一些问题:
- 转换值如果有 toJSON() 方法,那么由 toJson() 定义什么值将被序列化
- 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中
- 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值
- undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时);函数、undefined 被单独转换时,会返回 undefined,如JSON.stringify(function(){}) or JSON.stringify(undefined)
- 所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们
- Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理
- NaN 和 Infinity 格式的数值及 null 都会被当做 null
- 其他类型的对象,包括 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 ${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]