【问题标题】:Is there any possibility to have JSON.stringify preserve functions?是否有可能让 JSON.stringify 保留函数?
【发布时间】:2011-12-07 05:38:48
【问题描述】:

拿走这个物品:

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

如果我这样做:

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

然后 y 将返回 { "key1": "xxx" }。有什么办法可以通过 stringify 传递函数吗?使用“ye goode olde eval()”可以创建带有附加函数的对象,但是打包它是什么?

【问题讨论】:

  • 为什么不在你的函数周围加上引号,然后eval()呢?
  • JSON 不允许函数。如果是这样,它不会比eval 更好或更安全。
  • 这是toJSON 的用途吗?
  • JSON !== javascript Object
  • 一个帖子演示:medium.com/@oprearocks/…

标签: javascript json object


【解决方案1】:

json-stringify-function 与此类似。

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

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

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 的代码片段或查看Home Page 的文档

【讨论】:

    【解决方案2】:

    我最近也有类似的要求。需要明确的是,输出 看起来 像 JSON,但实际上只是 javascript。

    JSON.stringify 在大多数情况下运行良好,但在函数中“失败”。

    我使用了一些技巧:

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

    下面是它的样子:

    // 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; }

    重要提示:注意占位符格式 - 确保它不太通用。如果您更改它,请同时更改适用的正则表达式。

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

        【解决方案4】:

        这就是我所做的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(v) 很容易被反序列化的恶意函数利用,我更愿意将这些函数作为字符串发出,因此任何实现都在parseWithFunctions 之外显式完成。
        【解决方案5】:

        您不能打包函数,因为它们关闭的数据对任何序列化程序都不可见。 甚至 Mozilla 的 uneval 也无法正确打包闭包。

        最好的办法是使用再生剂和替代剂。

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

        传递给 JSON.parse 的 reviver 函数应用于原始解析对象中从最深键到最高级别的所有键:值对。在我们的例子中,这意味着 name 和 found 属性将通过 reviver 传递,然后包含这些键的对象将通过。

        【讨论】:

          【解决方案6】:

          顽皮但有效的方法是:

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

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

          【讨论】:

            【解决方案7】:

            完全可以从字符串创建函数没有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.parse 与 reviver 一起使用

            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
            

            【讨论】:

              【解决方案8】:

              据我所知,没有任何语言的序列化库可以持久化函数。序列化是为了保存数据所做的。编译是为了保留函数所做的。

              【讨论】:

                【解决方案9】:

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

                我在编写脚本来修改 RequireJS 配置时遇到了问题。我就是这样做的。首先,前面有一段代码确保内部使用的占位符 (">>>F<<<") 不会在 RequireJS 配置中显示为值。不太可能发生,但比抱歉更安全。输入配置被读取为 JavaScript 对象,其中可能包含数组、原子值、其他 Objects 和函数。如果函数不存在,它将直接作为 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 版,它定义了字符串化算法,找不到会出现排序问题的情况。如果此算法在未来的版本中发生更改,则修复方法是使用唯一的占位符作为函数,并使用这些来引用函数,这些函数将存储在关联数组中,将唯一的占位符映射到函数。

                【讨论】:

                  猜你喜欢
                  • 2014-08-06
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-03-04
                  • 2013-07-14
                  • 2020-09-02
                  • 2017-08-10
                  • 1970-01-01
                  相关资源
                  最近更新 更多