是否有可能有JSON.对保存函数进行字符串化

Is there any possibility to have JSON.stringify preserve functions?

本文关键字:字符串 函数 保存 可能有 JSON 是否      更新时间:2023-09-26

取这个对象:

x = {
 "key1": "xxx",
 "key2": function(){return this.key1}
}

如果我这样做:

y = JSON.parse( JSON.stringify(x) );

则y将返回{ "key1": "xxx" }。有什么方法可以通过stringify传递函数吗?创建带有附加函数的对象可以使用"旧的eval()",但是打包它呢?

json-stringify-function是一个类似的帖子。

通过该帖子发现的片段可能对任何偶然发现此答案的人有用。它通过使用JSON中的replacer参数来工作。JSON.parse.

中的stringify和reviver参数

更具体地说,当一个值恰好是函数类型时,通过replacer对其调用.toString()。当需要解析时,当函数以字符串形式出现时,通过reviver执行eval()

var JSONfn;
if (!JSONfn) {
    JSONfn = {};
}
(function () {
  JSONfn.stringify = function(obj) {
    return JSON.stringify(obj,function(key, value){
            return (typeof value === 'function' ) ? value.toString() : value;
        });
  }
  JSONfn.parse = function(str) {
    return JSON.parse(str,function(key, value){
        if(typeof value != 'string') return value;
        return ( value.substring(0,8) == 'function') ? eval('('+value+')') : value;
    });
  }
}());

代码片段取自Vadim Kiryukhin的JSONfn.js或参见主页的文档

我最近也有类似的需求。为了清楚,输出看起来像JSON,但实际上只是javascript。

JSON.stringify在大多数情况下工作良好,但在函数中"失败"。

我用一些小技巧使它工作:

  1. 利用replacer (JSON.stringify()的第二个参数)
  2. 使用func.toString()获取函数的JS代码
  3. 记住哪些函数已被字符串化,并在结果
  4. 中直接替换它们

它是这样的:

// our source data
const source = {
    "aaa": 123,
    "bbb": function (c) {
        // do something
        return c + 1;
    }
};
// keep a list of serialized functions
const functions = [];
// json replacer - returns a placeholder for functions
const jsonReplacer = function (key, val) {
    if (typeof val === 'function') {
  	    functions.push(val.toString());
        
        return "{func_" + (functions.length - 1) + "}";
    }
        
    return val;
};
// regex replacer - replaces placeholders with functions
const funcReplacer = function (match, id) {
   return functions[id];
};
const result = JSON
    .stringify(source, jsonReplacer)               // generate json with placeholders
    .replace(/"'{func_('d+)'}"/g, funcReplacer);   // replace placeholders with functions
// show the result
document.body.innerText = result;
body { white-space: pre-wrap; font-family: monospace; }

重要提示:注意占位符的格式-确保它不是太通用。如果要更改它,也要根据需要更改正则表达式。

从技术上讲,这不是JSON,我也很难想象为什么你想这样做,但尝试以下hack:

x.key2 = x.key2.toString();
JSON.stringify(x)  //"{"key1":"xxx","key2":"function (){return this.key1}"}"

当然,第一行可以通过递归迭代对象来自动完成。反向操作更难——函数只是一个字符串,eval可以工作,但是你必须猜测给定的键是否包含字符串化的函数代码。

不能打包函数,因为它们封闭的数据对任何序列化器都是不可见的。即使Mozilla的uneval也不能正确打包闭包。

你最好的选择是使用一个恢复器和一个替换器。

https://yuilibrary.com/yui/docs/json/json-freeze-thaw.html

传递给JSON的reviver函数。Parse应用于原始解析对象中的所有键:值对,从最深层的键到最高级。在我们的示例中,这意味着名称和发现的属性将通过检索器传递,然后包含这些键的对象将通过检索器传递。

我就是这么做的https://gist.github.com/Lepozepo/3275d686bc56e4fb5d11d27ef330a8ed

function stringifyWithFunctions(object) {
  return JSON.stringify(object, (key, val) => {
    if (typeof val === 'function') {
      return `(${val})`; // make it a string, surround it by parenthesis to ensure we can revive it as an anonymous function
    }
    return val;
  });
};
function parseWithFunctions(obj) {
  return JSON.parse(obj, (k, v) => {
    if (typeof v === 'string' && v.indexOf('function') >= 0) {
      return eval(v);
    }
    return v;
  });
};

我已经提出了这个解决方案,它将照顾函数的转换(没有eval)。您所要做的就是将这些代码放在使用JSON方法之前。用法完全相同,但现在只需要一个参数value来转换为JSON字符串,所以如果你传递剩余的replacerspace参数,它们将被忽略。

void function () {
    window.JSON = Object.create(JSON)
    JSON.stringify = function (obj) {
        return JSON.__proto__.stringify(obj, function (key, value) {
            if (typeof value === 'function') {
                return value.toString()
            }
            return value
        })
    }
    JSON.parse = function (obj) {
        return JSON.__proto__.parse(obj, function (key, value) {
            if (typeof value === 'string' && value.slice(0, 8) == 'function') {
                return Function('return ' + value)()
            }
            return value
        })
    }
}()

// YOUR CODE GOES BELOW HERE
x = {
 "key1": "xxx",
 "key2": function(){return this.key1}
}
const y = JSON.parse(JSON.stringify(x))
console.log(y.key2())

顽皮但有效的方法是:

Function.prototype.toJSON = function() { return this.toString(); }

虽然你真正的问题(除了修改Function的原型)将是反序列化没有使用eval .

完全有可能从字符串创建函数没有eval()

var obj = {a:function(a,b){
    return a+b;
}};
var serialized = JSON.stringify(obj, function(k,v){
    //special treatment for function types
    if(typeof v === "function")
        return v.toString();//we save the function as string
    return v;
});
/*output:
"{"a":"function (a,b){'n        return a+b;'n    }"}"
*/

现在有一些神奇的将字符串转换为函数的功能

var compileFunction = function(str){
    //find parameters
    var pstart = str.indexOf('('), pend = str.indexOf(')');
    var params = str.substring(pstart+1, pend);
    params = params.trim();
    //find function body
    var bstart = str.indexOf('{'), bend = str.lastIndexOf('}');
    var str = str.substring(bstart+1, bend);
    return Function(params, str);
}

现在使用JSON。解析

var revivedObj = JSON.parse(serialized, function(k,v){
    // there is probably a better way to determ if a value is a function string
    if(typeof v === "string" && v.indexOf("function") !== -1)
        return compileFunction(v);
    return v;
});
//output:
 revivedObj.a
 function anonymous(a,b
 /**/) {
    return a+b;
 }
 revivedObj.a(1,2)
3

据我所知,在任何语言中都没有持久化函数的序列化库。序列化是为了保存数据而做的事情。编译是用来保存函数的。

似乎在这里着陆的人正在处理结构,如果不是因为它们包含函数,它们将是有效的JSON。那么我们如何处理这些结构的字符串化呢?

我在编写修改RequireJS配置的脚本时遇到了这个问题。我是这样做的。首先,前面有一些代码确保内部使用的占位符(">>>F<<<")不会在RequireJS配置中显示为值。不太可能发生,但安全总比后悔好。输入配置作为JavaScript Object读取,该对象可能包含数组、原子值、其他Object和函数。如果函数不存在,它可以直接字符串化为JSON。这个配置是下面代码中的config对象:

// Holds functions we encounter.
var functions = [];
var placeholder = ">>>F<<<";
// This handler just records a function object in `functions` and returns the 
// placeholder as the value to insert into the JSON structure.
function handler(key, value) {
    if (value instanceof Function) {
        functions.push(value);
        return placeholder;
    }
    return value;
}
// We stringify, using our custom handler.    
var pre = JSON.stringify(config, handler, 4);
// Then we replace the placeholders in order they were encountered, with
// the functions we've recorded.
var post = pre.replace(new RegExp('"' + placeholder + '"', 'g'),
                       functions.shift.bind(functions));

post变量包含最终值。这段代码依赖于这样一个事实,即调用handler的顺序与最终JSON中各个数据块的顺序相同。我检查了ECMAScript第5版,它定义了字符串化算法,没有发现排序问题的情况。如果该算法在将来的版本中发生更改,则可以为函数使用唯一占位符,并使用这些占位符引用将存储在将唯一占位符映射到函数的关联数组中的函数。