web-dev-qa-db-ja.com

Prototype.jsを使用したJSON.stringify()配列の奇妙さ

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

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

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

これをくれた。

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

新しいバージョンでは、

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

これをくれた。

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

同じライブラリが新しいバージョンの配列括弧の周りに引用符を付けるように変更することができたアイデアはありますか?

81
morgancodes

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

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オブジェクトは最新のブラウザーに効率的に実装されているため、標準に準拠して既存のライブラリを変更することが最善のソリューションであることに注意する必要があります。

75
Jean Vincent

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

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

これは、JSON.stringifyとArray toJSONの非互換性を処理し、他のPrototypeライブラリが依存する可能性があるため、toJSON機能も保持します。

14
akkishore

編集してもう少し正確にします:

問題の主要なコードは、JSON.orgのJSONライブラリ(およびECMAScript 5のJSONオブジェクトの他の実装)にあります。

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

問題は、PrototypeライブラリーがArrayを拡張してtoJSONメソッドを含めることです。toJSONメソッドは、JSONオブジェクトが上記のコードで呼び出します。 JSONオブジェクトが配列値にヒットすると、Prototypeで定義されている配列のtoJSONを呼び出し、そのメソッドは配列の文字列バージョンを返します。したがって、配列括弧を囲む引用符。

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

7
Bob

より良い解決策は、プロトタイプがロードされた直後にこれを含めることだと思います

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

4
AJenbo

これは私が同じ問題に使用したコードです:

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()へのフォールバック

2
Memos

私はPrototypeにはそれほど流ではありませんが、 docs でこれを見ました:

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

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

また、JSONをプロトタイプで使用することについて、より長い tutorial があります。

2
Powerlord

私の寛容なソリューションは、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
highmaintenance

人々が指摘しているように、これは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
    }
}

うまくいけば、それは誰かを助けるでしょう。

1
polm23

これが私がどのように対処しているかです。

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

すべてを強制終了したくなく、ほとんどのブラウザで問題ないコードがある場合は、次のようにできます。

(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、最後に復元します。

0
Jerska