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

Posted

技术标签:

【中文标题】JSON.stringify() 数组的怪异与 Prototype.js【英文标题】:JSON.stringify() array bizarreness with Prototype.js 【发布时间】:2012-11-25 02:06:51 【问题描述】:

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

在我的应用程序的旧版本中:

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

给我这个;

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

在新版本中,

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

给我这个;

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

知道什么可以改变以使相同的库在新版本中的数组括号周围加上引号?

【问题讨论】:

看起来与我们在较新版本中引入的 Prototype 库有冲突。任何想法如何在 Prototype 下对包含数组的 json 对象进行字符串化? 这就是为什么人们应该避免使用全局内置对象(就像原型框架所做的那样) 【参考方案1】:

由于 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 脚本会自行完成这项工作 可能是这样,但是即使不需要,也必须加载整个脚本文件。 其实处理这个问题唯一需要的语句就是:delete Array.prototype.toJSON 非常感谢。我现在工作的公司目前仍在我们的大部分代码中使用原型,这是使用更现代的库的救命稻草,否则一切都会崩溃。 我一直在寻找 DAYS 的这个答案,并发布了两个不同的 SO 问题试图弄清楚。当我输入第三个时,将此视为一个相关问题。非常感谢!【参考方案2】:

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 对象在现代浏览器中是有效实现的,因此最好的解决方案是符合标准并修改现有的库。

【讨论】:

这是对数组的额外引用的最简洁的答案。【参考方案3】:

一个不会影响其他原型依赖的可能解决方案是:

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 库可能依赖它。

【讨论】:

我在一个网站中使用了这个 sn-p。它正在引起问题。它导致数组的 toJSON 属性未定义。对此有任何指示吗? 在使用上面的 sn-p 重新定义 JSON.stringify 之前,请确保您的 Array.prototype.toJSON 已定义。它在我的测试中运行良好。 我进入了if(typeof Prototype !== 'undefined' && parseFloat(Prototype.Version.substr(0,3)) < 1.7 && typeof Array.prototype.toJSON !== 'undefined')。它奏效了。 太棒了。直到 Prototype 1.7 才有这个问题。请点赞:) 问题是针对版本 【参考方案4】:

编辑以使其更准确:

问题的关键代码位在 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 中定义的数组上调用 toJSON,并且该方法返回数组的字符串版本。因此,数组括号周围的引号。

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

【讨论】:

这不是库中的错误,因为这是在 ECMAScript 5 中定义 JSON.stringify() 的确切方式。问题出在prototype.js 上,解决方案是:删除数组。 prototype.toJSON 这会对原型 toJSON 序列化产生一些副作用,但我发现这些是关于原型与 ECMAScript 5 不兼容的小问题。 Prototype 库没有扩展 Object.prototype 而是 Array.prototype,虽然 javascript 中的 typeof 数组也返回“object”,但它们没有相同的“构造函数”和原型。要解决这个问题你需要:“delete Array.prototype.toJSON;” @Jean 公平地说,Prototype 扩展了所有基础原生对象,包括 Object。但是好的,我再次明白了你的意思:) 感谢您帮助我的回答变得更好 Prototype 已经停止扩展“Object.prototype”很长时间了(我不记得是哪个版本)以避免 for .. in 问题。它现在仅将 Object 的静态属性(更安全)扩展为命名空间:api.prototypejs.org/language/Object Jean,实际上这正是库中的一个错误。如果一个对象有 toJSON,则必须调用它并且必须使用它的结果,但它不应该被引用。【参考方案5】:

我认为更好的解决方案是在加载原型后立即包含它

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(),因此这使得它与旧浏览器更兼容。

【讨论】:

如果传入的“值”是对象,则 JSON.stringify 版本不起作用。你应该这样做: JSON.stringify = function(value) return Object.toJSON(value); ;【参考方案6】:

我对 Prototype 不太熟悉,但我在它的 docs 中看到了这一点:

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

不过,我不确定这是否会出现与当前编码相同的问题。

还有一个较长的tutorial 关于将 JSON 与 Prototype 结合使用。

【讨论】:

【参考方案7】:

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

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)

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

【讨论】:

【参考方案8】:

这就是我的处理方式。

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

【讨论】:

【参考方案9】:

我的宽容解决方案检查 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
    

【讨论】:

【参考方案10】:

正如人们所指出的,这是由于 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
    

希望它对某人有所帮助。

【讨论】:

【参考方案11】:

如果你不想杀死所有东西,并且有一个在大多数浏览器上都可以使用的代码,你可以这样做:

(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://***.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,最后恢复它。

【讨论】:

以上是关于JSON.stringify() 数组的怪异与 Prototype.js的主要内容,如果未能解决你的问题,请参考以下文章

JSON.stringify与JSON.parse

JSON.stringify 应用

JSON.stringify()

JSON.stringify()

json.stringify()与json.parse()

data-* 属性中的 encodeURIComponent() 与 JSON.stringify()