JSON.stringify()配列のPrototype.jsでの奇妙さ


88

私はjsonのシリアライズで何が問題になっているのかを理解しようとしています、私のアプリの現在のバージョンと古いバージョンを使用していて、JSON.stringify()の動作方法に驚くべき違いを見つけています(json.orgのJSONライブラリを使用しています) )。

私のアプリの古いバージョンでは:

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

これをくれ;

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

新しいバージョンでは、

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

これをくれ;

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

同じライブラリで新しいバージョンの配列の括弧を引用符で囲むように変更した可能性のあるアイデアはありますか?


4
新しいバージョンで導入したプロトタイプライブラリと競合しているようです。プロトタイプの下で配列を含むjsonオブジェクトを文字列化する方法はありますか?
morgancodes 2009

26
そのため、人々は(プロトタイプフレームワークと同様に)グローバルな組み込みオブジェクトの扱いを控えるべきです
Gerardo Lima

回答:


81

最近、JSON.stringifyが一部のブラウザーに同梱されているため、PrototypeのtoJSONの代わりに使用することをお勧めします。次に、window.JSON && window.JSON.stringifyをチェックし、それ以外の場合(document.createElement('script')… を介して)json.orgライブラリのみを含めます。非互換性を解決するには、以下を使用します。

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

独自のコードでwindow.JSONを確認する必要はありません-json.orgスクリプトはそれ自体を行います
zcrar70

それはそうかもしれませんが、それでも必要ない場合でも、スクリプトファイル全体をロードする必要があります。
Raphael Schweikert

11
実際、質問に対処するために必要な唯一のステートメントは次のとおりです。deleteArray.prototype.toJSON
Jean Vincent

1
どうもありがとうございます。私が現在取り組んでいる会社では、現在でもコードの多くでプロトタイプを使用しており、これはより新しいライブラリを使用するための救命手段でした。
クローブ2013

1
私はこの答えをDAYSの間検索して、それを理解しようとする2つの異なるSO質問を投稿しました。私が3番目に入力しているときに、これを関連質問として見ました。どうもありがとうございます!
マシューハーブスト2014

78

ECMAScript 5以上で定義された関数JSON.stringify()(ページ201-JSONオブジェクト、疑似コードページ205)は、オブジェクトで使用可能な場合、関数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

15

他のプロトタイプの依存関係に影響を与えない可能な解決策は次のとおりです。

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

これは、配列toJSONとJSON.stringifyの非互換性を処理し、他のプロトタイプライブラリが依存している可能性があるため、toJSON機能も保持します。


このスニペットをウェブサイトで使用しました。問題を引き起こしています。その結果、配列のtoJSONプロパティが未定義になります。その上で何かポインタはありますか?
Sourabh 2013年

1
上記のスニペットを使用してJSON.stringifyを再定義する前に、Array.prototype.toJSONが定義されていることを確認してください。私のテストでは問題なく動作します。
akkishore 2013年

2
に包み込みましたif(typeof Prototype !== 'undefined' && parseFloat(Prototype.Version.substr(0,3)) < 1.7 && typeof Array.prototype.toJSON !== 'undefined')。出来た。
Sourabh 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を拡張して、JSONオブジェクトが上記のコードで呼び出すtoJSONメソッドを含めることです。JSONオブジェクトが配列値に到達すると、Prototypeで定義されている配列に対してtoJSONを呼び出し、そのメソッドは配列の文字列バージョンを返します。したがって、配列の括弧を囲む引用符。

ArrayオブジェクトからtoJSONを削除すると、JSONライブラリが正しく機能するはずです。または、JSONライブラリを使用します。


2
これはライブラリのバグではありません。これは、JSON.stringify()がECMAScript 5で定義されている正確な方法であるためです。問題はprototype.jsにあり、解決策は次のとおりです。Array.prototype.toJSONを削除しますこれにはいくつかの側面がありますプロトタイプのtoJSONのシリアル化のための効果は、私はプロトタイプがECMAScriptの5と互換性があることの点でこれらのマイナーを発見した
ジャン・ヴァンサン

PrototypeライブラリはObject.prototypeではなくArray.prototypeを拡張しますが、JavaScriptのtypeof配列も「オブジェクト」を返しますが、「コンストラクタ」とプロトタイプは同じではありません。この問題を解決するには、「delete Array.prototype.toJSON;」を実行する必要があります。
Jean Vincent、

@Jean公平を期すために、PrototypeはObjectを含むすべての基本ネイティブオブジェクトを拡張します。しかし、わかりました、私はあなたの要点を再び見ます:)私の答えがより良くなるのを助けてくれてありがとう
ボブ

プロトタイプは、問題のfor ..を回避するために、長い間「Object.prototype」の拡張を停止しました(どのバージョンか覚えていません)。現在は、オブジェクトの静的プロパティ(はるかに安全)を名前空間としてのみ拡張しています。api.prototypejs.org
Jean Vincent

ジーン、実際にはライブラリのバグです。オブジェクトにtoJSONがある場合、それを呼び出してその結果を使用する必要がありますが、引用符で囲まれてはなりません。
gr

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()が利用可能な場合は保持されるため、古いブラウザーとの互換性が高まります。


渡された「値」がオブジェクトの場合、JSON.stringifyバージョンは機能しません。代わりにこれを行う必要があります。JSON.stringify= function(value){return Object.toJSON(value); };
akkishore

2

私はプロトタイプにはそれほど流暢ではありませんが、ドキュメントでこれを見ました:

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

しかし、これが現在のエンコーディングと同じ問題を抱えているかどうかはわかりません。

プロトタイプでのJSONの使用についてのより長いチュートリアルもあります。


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、最後にそれを復元することです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.