关于json:Javascript toJSON自定义方法的Stackoverflow错误

Stackoverflow error on Javascript toJSON custom method

脚本

在阅读了这个答案之后,我意识到我可以从JSON文本开始创建对象。

所以我猜我可以用这个有用的JSON方法做相反的事情:JSON.stringify(myObject)

所以我这样做了:

1
2
3
4
5
6
7
8
9
10
11
function MyObject(id, value, desc)
{
  this.id = id;
  this.value = value;
  this.desc = desc;
  this.toJSON = function()
  {
    return JSON.stringify(this);
  }

}

但是当我运行这个东西(演示)时,会发生一个Maximum call stack size exceeded错误。

在谷歌搜索了一下之后,我发现了两个解释这种行为的参考资料:

  • mdn上的json.stringify()方法。
  • json.org上的javascript文章中的json

如果我说对了,.toJSON会取代.stringify。因此,如果第一个调用第二个,则生成一个循环。

问题

  • (常规)为什么选择这种设计?toJSON是一种保留的特殊关键字吗?
  • (具体)我解决了stackoverflow错误,将.toJSON名称改为.display名称。不太优雅。还有别的办法吗?

  • 认为这是因为toJSON是半保留的:stringify会检查对象,看它是否有一个名为toJSON的方法,然后尝试调用它来字符串化结果。

    解决方法可以是:(不确定此代码的可靠性)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var obj = {
        value: 1,
        name:"John",
        toJSON: function() {
            var ret,
                fn = this.toJSON;

            delete this.toJSON;

            ret = JSON.stringify(this);

            this.toJSON = fn;

            return ret;
        }
    }

    用途:

    1
    2
    3
    obj.toJSON(); //"{"value":1,"name":"John"}"
    obj.lastName ="Smith";
    obj.toJSON(); //"{"value":1,"name":"John","lastName":"Smith"}"

    也许使用Clousure更漂亮一点:(然后我想我可以说它是安全的)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var obj = {
        value: 1,
        name:"John",
        toJSON: (function() {
            function fn() {
                var ret;
                delete this.toJSON;

                ret = JSON.stringify(this);

                this.toJSON = fn;

                return ret;
            }
            return fn;
        })()
    }

    因此,在阅读了@filmor的评论后,我想到了另一种处理方法。不是很漂亮,但很管用。

    使用function.caller我可以检测是否使用JSON.stringify调用fn

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    var obj = {
        value: 1,
        name:"John",
        toJSON: (function() {
            return function fn() {
                var ret;

                delete this.toJSON;

                ret = JSON.stringify(this);

                if ( fn.caller === JSON.stringify ) {
                    ret = JSON.parse( ret );
                }

                this.toJSON = fn;

                return ret;
            }
        })()
    }


    问题1,toJSON是否保留?

    我不确定它是否保留,但例如,本机日期对象使用tojson创建一个字符串化的日期表示:

    1
    2
    (new Date()).toJSON();           // ->"2012-10-20T01:58:21.427Z"
    JSON.stringify({d: new Date()}); // -> {"d":"2012-10-20T01:58:21.427Z"}"

    问题2,一个简单的解决方案:

    创建忽略tojson方法的自定义stringify函数(您可以将其添加到已经存在的全局JSON中):

    1
    2
    3
    4
    5
    6
    7
    8
    JSON.customStringify = function (obj) {

        var fn = obj.toJSON;
        obj.toJSON = undefined;
        var json = JSON.stringify(obj);
        obj.toJSON = fn;
        return json;
    }

    现在很容易在所有对象中使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function MyObject(id, value, desc)
    {
      this.id = id;
      this.value = value;
      this.desc = desc;
      this.toJSON = function()
      {
        return JSON.customStringify(this);
      }
    }

    为了使其更简单,另外添加:

    1
    2
    3
    4
    JSON.customStringifyMethod = function () {

        return JSON.customStringify(this);
    }

    现在,您的对象可能看起来像:

    1
    2
    3
    4
    5
    6
    7
    function MyObject(id, value, desc)
    {
      this.id = id;
      this.value = value;
      this.desc = desc;
      this.toJSON = JSON.customStringifyMethod;
    }