带有Prototype.js的JSON.stringify()数组异常


88

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

在我的应用的旧版本中:

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

给我这个

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

在新版本中,

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

给我这个

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

任何想法都可能会发生什么变化,以使同一库在新版本中将引号放在数组括号内?


4
似乎与我们在较新版本中引入的Prototype库存在冲突。有什么想法如何对包含原型下的数组的json对象进行字符串化?
morgancodes

26
这就是为什么人们应该避免使用全局内置对象(就像原型框架一样)的原因
Gerardo Lima 2012年

Answers:


81

由于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;
}

无需在自己的代码中检查window.JSON-json.org脚本本身即可完成
zcrar70 2010年

可能是这样,但是即使不需要它,也必须加载整个脚本文件。
拉斐尔·施维克

11
实际上,处理该问题所需的唯一声明是:delete Array.prototype.toJSON
Jean Vincent

1
非常感谢。我现在工作的公司目前仍在我们的许多代码中使用原型,这可以节省使用更多现代库的生命,否则一切都会崩溃。
krob

1
我一直在寻找DAYS的答案,并发布了两个不同的SO问题试图找出答案。当我输入第三个字母时,将其视为一个相关问题。非常感谢!
马修·赫伯斯特

78

ECMAScript 5及更高版本(第201页 -JSON 对象,伪代码第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对象已在现代浏览器中有效实现,因此最好的解决方案是符合标准并修改现有库。


5
这是数组的额外引用所发生情况的最简洁答案。
tmarthal 2011年

15

不会影响其他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库可能依赖于此。


我在网站中使用了此代码段。这引起了问题。结果导致数组的toJSON属性未定义。有什么建议吗?
2013年

1
在使用上述代码段重新定义JSON.stringify之前,请确保已定义Array.prototype.toJSON。在我的测试中效果很好。
akkishore

2
我包裹进去了if(typeof Prototype !== 'undefined' && parseFloat(Prototype.Version.substr(0,3)) < 1.7 && typeof Array.prototype.toJSON !== 'undefined')。有效。
2013年

1
大。仅在原型1.7之前,这是一个问题。请
投票

1
问题是针对版本<1.7
Sourabh,2013年

9

编辑以使其更加准确:

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

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

问题在于Prototype库将Array扩展为包括toJSON方法,该JSON对象将在上面的代码中调用该方法。当JSON对象达到数组值时,它将在Prototype中定义的数组上调用toJSON,该方法将返回该数组的字符串版本。因此,用引号引起来。

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


2
这不是库中的错误,因为这是在ECMAScript 5中定义JSON.stringify()的确切方法。问题出在prototype.js上,解决方案是:delete Array.prototype.toJSON这将具有某些方面为原型的toJSON序列化的影响,但我发现这些小在原型具有的ECMAScript 5不相容的方面
让·文森特

Prototype库不会扩展Object.prototype而是Array.prototype,尽管JavaScript中的typeof数组也返回“对象”,但它们没有相同的“构造函数”和原型。要解决该问题,您需要:“删除Array.prototype.toJSON;”
Jean Vincent

@Jean公平地说,Prototype扩展了所有基本本机对象,包括Object。不过没关系,我再次看到你的观点:)感谢帮助我的回答会更好
鲍勃

原型已经很长时间停止扩展“ Object.prototype”了(尽管我不记得哪个版本),以避免出现问题中的for ..。现在,它仅将Object的静态属性(更安全)扩展为名称空间:api.prototypejs.org/language/Object
Jean Vincent

Jean,实际上这确实是库中的错误。如果对象具有toJSON,则必须调用该对象,并且必须使用其结果,但不应将其引起引用。
grr 2012年

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()(如果可用),因此这使它们与较旧的浏览器更加兼容。


如果传入的“值”是一个Object,则JSON.stringify版本不起作用。您应该改为这样做:JSON.stringify = function(value){return Object.toJSON(value); };
akkishore 2012年

2

我对Prototype不太熟练,但是我在其docs中看到了这一点:

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

我不确定这是否会与当前编码有相同的问题。

还有关于使用JSON与Prototype 的更长的教程


2

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

function stringify(object){
      var Prototype = window.Prototype
      if (Prototype && Prototype.Version < '1.7' &&
          Array.prototype.toJSON && Object.toJSON){
              return Object.toJSON(object)
      }
      return JSON.stringify(object)
}

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


1

这就是我的处理方式。

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

1

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

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

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

希望它将对某人有所帮助。


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,然后调用old JSON.stringify,最后将其还原。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.