web-dev-qa-db-fra.com

JSON.stringify () bizarreries du tableau avec Prototype.js

J'essaie de comprendre ce qui n'a pas fonctionné avec ma sérialisation json, d'avoir la version actuelle de mon application avec l'ancienne et de trouver des différences surprenantes dans le fonctionnement de JSON.stringify () (Utilisation de la bibliothèque JSON de json.org ).

Dans l'ancienne version de mon application:

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

me donne ceci;

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

dans la nouvelle version,

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

me donne ceci;

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

une idée de ce qui aurait pu changer pour que la même bibliothèque place des guillemets autour des crochets du tableau dans la nouvelle version?

81
morgancodes

Étant donné que JSON.stringify est livré avec certains navigateurs récemment, je suggère de l'utiliser au lieu de toJSON de Prototype. Vous devriez alors rechercher window.JSON && window.JSON.stringify et n'inclure la bibliothèque json.org que dans le cas contraire (via document.createElement('script')…). Pour résoudre les incompatibilités, utilisez:

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

La fonction JSON.stringify () définie dans ECMAScript 5 et supérieur (Page 201 - l'objet JSON, pseudo-code page 205) , utilise la fonction toJSON () lorsqu'elle est disponible sur les objets.

Étant donné que Prototype.js (ou une autre bibliothèque que vous utilisez) définit une fonction Array.prototype.toJSON (), les tableaux sont d'abord convertis en chaînes à l'aide de Array.prototype.toJSON () puis chaîne citée par JSON.stringify (), d'où la citations supplémentaires incorrectes autour des tableaux.

La solution est donc simple et triviale (il s'agit d'une version simplifiée de la réponse de Raphael Schweikert):

delete Array.prototype.toJSON

Cela produit bien sûr des effets secondaires sur les bibliothèques qui s'appuient sur une propriété de fonction toJSON () pour les tableaux. Mais je trouve cela un inconvénient mineur compte tenu de l'incompatibilité avec ECMAScript 5.

Il faut noter que l'objet JSON défini dans ECMAScript 5 est efficacement implémenté dans les navigateurs modernes et donc la meilleure solution est de se conformer à la norme et de modifier les bibliothèques existantes.

75
Jean Vincent

Une solution possible qui n'affectera pas les autres dépendances du prototype serait:

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

Cela prend en charge l'incompatibilité du tableau toJSON avec JSON.stringify et conserve également la fonctionnalité toJSON car d'autres bibliothèques de prototypes peuvent en dépendre.

14
akkishore

Modifier pour rendre un peu plus précis:

Le bit de clé du problème se trouve dans la bibliothèque JSON de JSON.org (et dans d'autres implémentations de l'objet JSON d'ECMAScript 5):

        if (valeur && typeof valeur === 'objet' && 
 typeof value.toJSON === 'fonction') {
 value = value.toJSON (clé); 
}

Le problème est que la bibliothèque Prototype étend Array pour inclure une méthode toJSON, que l'objet JSON appellera dans le code ci-dessus. Lorsque l'objet JSON atteint la valeur du tableau, il appelle toJSON sur le tableau qui est défini dans Prototype, et cette méthode renvoie une version chaîne du tableau. Par conséquent, les guillemets autour des crochets du tableau.

Si vous supprimez toJSON de l'objet Array, la bibliothèque JSON devrait fonctionner correctement. Ou utilisez simplement la bibliothèque JSON.

7
Bob

Je pense qu'une meilleure solution serait de l'inclure juste après le chargement du prototype

JSON = JSON || {};

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

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

Cela rend la fonction prototype disponible en tant que JSON.stringify () et JSON.parse () standard, mais conserve le JSON.parse () natif s'il est disponible, ce qui rend les choses plus compatibles avec les anciens navigateurs.

4
AJenbo

Voici le code que j'ai utilisé pour le même problème:

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

Vous vérifiez si le prototype existe, puis vous vérifiez la version. Si l'ancienne version utilise Object.toJSON (si elle est définie) dans tous les autres cas, utilisez JSON.stringify ()

2
Memos

Je ne suis pas très à l'aise avec Prototype, mais je l'ai vu dans son docs :

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

Je ne sais pas si cela aurait le même problème que l'encodage actuel.

Il y a aussi un plus long tutoriel sur l'utilisation de JSON avec Prototype.

2
Powerlord

Ma solution tolérante vérifie si Array.prototype.toJSON est nuisible pour JSON stringify et la conserve lorsque cela est possible pour laisser le code environnant fonctionner comme prévu:

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

Comme les gens l'ont souligné, cela est dû à Prototype.js - en particulier aux versions antérieures à 1.7. J'ai eu une situation similaire, mais je devais avoir un code qui fonctionnait, que Prototype.js soit là ou non; cela signifie que je ne peux pas simplement supprimer le tableau.prototype.toJSON car je ne suis pas sûr de ce qui en dépend. Pour cette situation, c'est la meilleure solution que j'ai trouvée:

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

J'espère que cela aidera quelqu'un.

1
polm23

Voici comment je m'en occupe.

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

Si vous ne voulez pas tout tuer et avoir un code qui conviendrait à la plupart des navigateurs, vous pouvez le faire de cette façon:

(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));

Cela semble complexe, mais ce n'est complexe que pour gérer la plupart des cas d'utilisation. L'idée principale est de remplacer JSON.stringify pour supprimer toJSON de l'objet passé en argument, puis appelez l'ancien JSON.stringify, et enfin le restaurer.

0
Jerska