与Prototype.js的JSON.stringify()数组奇异性

时间:2009-04-02 16:48:12

标签: javascript json prototypejs

我正在试图弄清楚我的json序列化出了什么问题,将我的应用程序的当前版本与旧版本一起使用,并且在JSON.stringify()的工作方式中发现了一些令人惊讶的差异(使用来自JSON库的JSON库) json.org)。

在旧版本的应用中:

 JSON.stringify({"a":[1,2]})

给了我这个;

"{\"a\":[1,2]}"

在新版本中,

 JSON.stringify({"a":[1,2]})

给了我这个;

"{\"a\":\"[1, 2]\"}"

任何想法可以改变什么来使同一个库在新版本的数组括号周围加上引号?

11 个答案:

答案 0 :(得分:76)

由于JSON.stringify最近已经在某些浏览器中发布,我建议使用它而不是Prototype的toJSON。然后你会检查window.JSON&& window.JSON.stringify,否则只包含json.org库(通过document.createElement('script') ...)。要解决不兼容问题,请使用:

if(window.Prototype) {
    delete Object.prototype.toJSON;
    delete Array.prototype.toJSON;
    delete Hash.prototype.toJSON;
    delete String.prototype.toJSON;
}

答案 1 :(得分:75)

ECMAScript 5 and above (Page 201 - the JSON Object, pseudo-code Page 205)中定义的函数JSON.stringify()在对象上可用时使用函数toJSON()。

因为Prototype.js(或您正在使用的另一个库)定义了一个Array.prototype.toJSON()函数,所以首先使用Array.prototype.toJSON()转换为字符串,然后使用JSON.stringify()引用的字符串因此数组周围的引号不正确。

因此,解决方案是直截了当的(这是Raphael Schweikert答案的简化版本):

delete Array.prototype.toJSON

这会产生对依赖于数组的toJSON()函数属性的库的副作用。但考虑到与ECMAScript 5的不兼容性,我发现这有点不便。

必须注意的是,ECMAScript 5中定义的JSON对象在现代浏览器中有效实现,因此最佳解决方案是符合标准并修改现有库。

答案 2 :(得分:14)

不会影响其他Prototype依赖关系的可能解决方案是:

var _json_stringify = JSON.stringify;
JSON.stringify = function(value) {
    var _array_tojson = Array.prototype.toJSON;
    delete Array.prototype.toJSON;
    var r=_json_stringify(value);
    Array.prototype.toJSON = _array_tojson;
    return r;
};

这会解决Array toJSON与JSON.stringify的不兼容问题,并且还会保留toJSON功能,因为其他Prototype库可能依赖于它。

答案 3 :(得分:7)

编辑以使其更准确:

代码的问题关键位在JSON.org的JSON库中(以及ECMAScript 5的JSON对象的其他实现):

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

问题是Prototype库扩展了Array以包含一个toJSON方法,JSON对象将在上面的代码中调用该方法。当JSON对象命中数组值时,它会调用Prototype中定义的数组上的JSON,并且该方法返回数组的字符串版本。因此,数组括号周围的引号。

如果从Array对象中删除toJSON,JSON库应该可以正常工作。或者,只需使用JSON库。

答案 4 :(得分:4)

我认为更好的解决方案是在原型加载后加入

JSON = JSON || {};

JSON.stringify = function(value) { return value.toJSON(); };

JSON.parse = JSON.parse || function(jsonsring) { return jsonsring.evalJSON(true); };

这使得原型函数可用作标准JSON.stringify()和JSON.parse(),但如果它可用则保留原生JSON.parse(),这样可以使旧版浏览器更兼容。

答案 5 :(得分:2)

我对Prototype不是很流利,但我在docs中看到了这一点:

Object.toJSON({"a":[1,2]})

我不确定当前编码是否会出现同样的问题。

关于在原型中使用JSON还有更长的tutorial

答案 6 :(得分:2)

这是我用于同一问题的代码:

var clicks = 0;
$('input:radio[name=phoneSelect]').click(function() {
    clicks++;
    if(clicks < 2) {
       console.log('First function now');
    } else{
       console.log('Second function now');   
    }
});

检查Prototype是否存在,然后检查版本。如果旧版本在所有其他情况下使用Object.toJSON(如果已定义)则回退到JSON.stringify()

答案 7 :(得分:1)

以下是我处理它的方式。

var methodCallString =  Object.toJSON? Object.toJSON(options.jsonMethodCall) :  JSON.stringify(options.jsonMethodCall);

答案 8 :(得分:1)

我的容忍解决方案检查Array.prototype.toJSON是否对JSON stringify有害,并在可能的情况下保留它以使周围的代码按预期工作:

var dummy = { data: [{hello: 'world'}] }, test = {};

if(Array.prototype.toJSON) {
    try {
        test = JSON.parse(JSON.stringify(dummy));
        if(!test || dummy.data !== test.data) {
            delete Array.prototype.toJSON;
        }
    } catch(e) {
        // there only hope
    }
}

答案 9 :(得分:1)

正如人们所指出的,这是由于Prototype.js - 特别是1.7之前的版本。我有类似的情况,但必须有代码操作Prototype.js是否存在;这意味着我不能只删除Array.prototype.toJSON,因为我不确定它依赖于它。对于这种情况,这是我提出的最佳解决方案:

function safeToJSON(item){ 
    if ([1,2,3] === JSON.parse(JSON.stringify([1,2,3]))){
        return JSON.stringify(item); //sane behavior
    } else { 
        return item.toJSON(); // Prototype.js nonsense
    }
}

希望它会帮助别人。

答案 10 :(得分:0)

如果您不想杀死所有内容,并且在大多数浏览器上都有可以使用的代码,您可以这样做:

(function (undefined) { // This is just to limit _json_stringify to this scope and to redefine undefined in case it was
  if (true ||typeof (Prototype) !== 'undefined') {
    // First, ensure we can access the prototype of an object.
    // See http://stackoverflow.com/questions/7662147/how-to-access-object-prototype-in-javascript
    if(typeof (Object.getPrototypeOf) === 'undefined') {
      if(({}).__proto__ === Object.prototype && ([]).__proto__ === Array.prototype) {
        Object.getPrototypeOf = function getPrototypeOf (object) {
          return object.__proto__;
        };
      } else {
        Object.getPrototypeOf = function getPrototypeOf (object) {
          // May break if the constructor has been changed or removed
          return object.constructor ? object.constructor.prototype : undefined;
        }
      }
    }

    var _json_stringify = JSON.stringify; // We save the actual JSON.stringify
    JSON.stringify = function stringify (obj) {
      var obj_prototype = Object.getPrototypeOf(obj),
          old_json = obj_prototype.toJSON, // We save the toJSON of the object
          res = null;
      if (old_json) { // If toJSON exists on the object
        obj_prototype.toJSON = undefined;
      }
      res = _json_stringify.apply(this, arguments);
      if (old_json)
        obj_prototype.toJSON = old_json;
      return res;
    };
  }
}.call(this));

这看起来很复杂,但这只是处理大多数用例的复杂问题。 主要思想是覆盖JSON.stringify从作为参数传递的对象中删除toJSON,然后调用旧的JSON.stringify,最后将其恢复。