JSON.stringify () Array-Bizarrheit mit Prototype.js

88

Ich versuche herauszufinden, was mit meiner JSON-Serialisierung schief gelaufen ist, habe die aktuelle Version meiner App mit und die alte und finde einige überraschende Unterschiede in der Funktionsweise von JSON.stringify () (mithilfe der JSON-Bibliothek von json.org) ).

In der alten Version meiner App:

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

gibt mir das;

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

in der neuen Version,

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

gibt mir das;

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

Haben Sie eine Idee, was sich geändert haben könnte, damit dieselbe Bibliothek in der neuen Version Anführungszeichen um die Array-Klammern setzt?

Morgancodes
quelle
4
Es sieht so aus, als wäre es ein Konflikt mit der Prototype-Bibliothek, die wir in der neueren Version eingeführt haben. Irgendwelche Ideen, wie man ein json-Objekt, das ein Array enthält, unter Prototype stringifiziert?
Morgancodes
26
Deshalb sollten die Leute es unterlassen, mit global eingebauten Objekten zu zerfleischen (wie es das Prototyp-Framework tut)
Gerardo Lima,

Antworten:

81

Da JSON.stringify in letzter Zeit mit einigen Browsern ausgeliefert wurde, würde ich empfehlen, es anstelle von Prototyps toJSON zu verwenden. Sie würden dann nach window.JSON && window.JSON.stringify suchen und ansonsten nur die json.org-Bibliothek einschließen (via document.createElement('script')…). Verwenden Sie zum Beheben der Inkompatibilitäten:

if(window.Prototype) {
    delete Object.prototype.toJSON;
    delete Array.prototype.toJSON;
    delete Hash.prototype.toJSON;
    delete String.prototype.toJSON;
}
Raphael Schweikert
quelle
Sie müssen in Ihrem eigenen Code nicht nach window.JSON suchen - das Skript json.org erledigt dies selbst
zcrar70
Das mag sein, aber dann muss die gesamte Skriptdatei geladen werden, auch wenn sie nicht benötigt wird.
Raphael Schweikert
11
Eigentlich ist die einzige Aussage, die benötigt wird, um sich mit der Frage zu befassen: Löschen Sie Array.prototype.toJSON
Jean Vincent
1
Ich danke dir sehr. Das Unternehmen, für das ich gerade arbeite, verwendet derzeit noch Prototypen in einem Großteil unseres Codes. Dies war ein Lebensretter für die Verwendung moderner Bibliotheken, da sonst alles kaputt gehen würde.
Krob
1
Ich habe nach dieser Antwort für DAYS gesucht und zwei verschiedene SO-Fragen gestellt, um es herauszufinden. Sah dies als eine verwandte Frage, als ich eine dritte tippte. Ich danke dir sehr!
Matthew Herbst
78

Die in ECMAScript 5 und höher definierte Funktion JSON.stringify () (Seite 201 - das JSON-Objekt, Pseudocode Seite 205) verwendet die Funktion toJSON (), wenn sie für Objekte verfügbar ist.

Da Prototype.js (oder eine andere Bibliothek, die Sie verwenden) eine Array.prototype.toJSON () -Funktion definiert, werden Arrays zuerst mit Array.prototype.toJSON () in Zeichenfolgen konvertiert und dann mit JSON.stringify () als Zeichenfolge angegeben falsche zusätzliche Anführungszeichen um die Arrays.

Die Lösung ist daher einfach und trivial (dies ist eine vereinfachte Version der Antwort von Raphael Schweikert):

delete Array.prototype.toJSON

Dies führt natürlich zu Nebenwirkungen bei Bibliotheken, die für Arrays auf einer toJSON () - Funktionseigenschaft beruhen. Angesichts der Inkompatibilität mit ECMAScript 5 finde ich dies jedoch eine kleine Unannehmlichkeit.

Es ist zu beachten, dass das in ECMAScript 5 definierte JSON-Objekt in modernen Browsern effizient implementiert wird. Daher besteht die beste Lösung darin, dem Standard zu entsprechen und vorhandene Bibliotheken zu ändern.

Jean Vincent
quelle
5
Dies ist die präziseste Antwort darauf, was mit dem zusätzlichen Zitieren des Arrays passiert.
Tmarthal
15

Eine mögliche Lösung, die andere Prototypabhängigkeiten nicht beeinflusst, wäre:

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

Dies behebt die Inkompatibilität von Array zu JSON mit JSON.stringify und behält auch die JSON-Funktionalität bei, da andere Prototype-Bibliotheken davon abhängen können.

akkishore
quelle
Ich habe dieses Snippet auf einer Website verwendet. Es verursacht Probleme. Dies führt dazu, dass die toJSON-Eigenschaft des Arrays undefiniert ist. Irgendwelche Hinweise dazu?
Sourabh
1
Stellen Sie sicher, dass Ihr Array.prototype.toJSON definiert ist, bevor Sie das obige Snippet verwenden, um JSON.stringify neu zu definieren. In meinem Test funktioniert es gut.
Akkishore
2
Ich wickelte mich ein if(typeof Prototype !== 'undefined' && parseFloat(Prototype.Version.substr(0,3)) < 1.7 && typeof Array.prototype.toJSON !== 'undefined'). Es funktionierte.
Sourabh
1
Toll. Nur bis Prototyp 1.7 ist dies ein Problem. Bitte upvote :)
akkishore
1
Das Problem ist für Versionen <1.7
Sourabh
9

Bearbeiten, um etwas genauer zu machen:

Das Problemschlüsselbit des Codes befindet sich in der JSON-Bibliothek von JSON.org (und anderen Implementierungen des JSON-Objekts von ECMAScript 5):

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

Das Problem ist, dass die Prototype-Bibliothek Array um eine toJSON-Methode erweitert, die das JSON-Objekt im obigen Code aufruft. Wenn das JSON-Objekt den Array-Wert erreicht, ruft es JSON für das in Prototype definierte Array auf, und diese Methode gibt eine Zeichenfolgenversion des Arrays zurück. Daher die Anführungszeichen um die Array-Klammern.

Wenn Sie toJSON aus dem Array-Objekt löschen, sollte die JSON-Bibliothek ordnungsgemäß funktionieren. Oder verwenden Sie einfach die JSON-Bibliothek.

Bob
quelle
2
Dies ist kein Fehler in der Bibliothek, da JSON.stringify () genau so in ECMAScript 5 definiert ist. Das Problem liegt bei prototype.js und die Lösung lautet: delete Array.prototype.toJSON Dies hat einige Seiten Effekte für den Prototyp zur JSON-Serialisierung, aber ich fand diese geringfügig in Bezug auf die Inkompatibilität, die der Prototyp mit ECMAScript 5 hat.
Jean Vincent
Die Prototypbibliothek erweitert nicht Object.prototype, sondern Array.prototype. Obwohl der Arraytyp in JavaScript auch "object" zurückgibt, haben sie nicht denselben "Konstruktor" und Prototyp. Um das Problem zu lösen, müssen Sie: "Array.prototype.toJSON löschen;"
Jean Vincent
@Jean Um fair zu sein, erweitert Prototype alle nativen Basisobjekte, einschließlich Object. Aber ok, ich verstehe deinen Standpunkt wieder :) Danke, dass du mir geholfen hast, meine Antwort besser zu machen
Bob
Prototype hat die Erweiterung von "Object.prototype" schon lange eingestellt (ich erinnere mich jedoch nicht an welche Version), um die Probleme mit for .. zu vermeiden. Es erweitert jetzt nur die statischen Eigenschaften von Object (was viel sicherer ist) als Namespace: api.prototypejs.org/language/Object
Jean Vincent
Jean, eigentlich ist es genau ein Fehler in der Bibliothek. Wenn ein Objekt toJSON hat, muss es aufgerufen und sein Ergebnis verwendet werden, aber es sollte nicht in Anführungszeichen gesetzt werden.
grr
4

Ich denke, eine bessere Lösung wäre, dies direkt nach dem Laden des Prototyps aufzunehmen

JSON = JSON || {};

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

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

Dadurch wird die Prototypfunktion als Standard-JSON.stringify () und JSON.parse () verfügbar, die native JSON.parse () bleibt jedoch erhalten, sofern verfügbar, sodass die Kompatibilität mit älteren Browsern verbessert wird.

Benjamin
quelle
Die Version JSON.stringify funktioniert nicht, wenn der übergebene 'Wert' ein Objekt ist. Sie sollten dies stattdessen tun: JSON.stringify = function (value) {return Object.toJSON (value); };
Akkishore
2

Ich spreche nicht so fließend mit Prototype, aber ich habe dies in den Dokumenten gesehen :

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

Ich bin mir nicht sicher, ob dies das gleiche Problem haben würde, das die aktuelle Codierung hat.

Es gibt auch ein längeres Tutorial zur Verwendung von JSON mit Prototype.

Powerlord
quelle
2

Dies ist der Code, den ich für dasselbe Problem verwendet habe:

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

Sie überprüfen, ob Prototyp vorhanden ist, und überprüfen dann die Version. Wenn die alte Version Object.toJSON verwendet (falls definiert), greifen Sie in allen anderen Fällen auf JSON.stringify () zurück.

Memos
quelle
1

So gehe ich damit um.

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

Meine tolerante Lösung prüft, ob Array.prototype.toJSON für JSON stringify schädlich ist, und behält es bei, wenn möglich, den umgebenden Code wie erwartet funktionieren zu lassen:

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
    }
}
Ben Sinclair
quelle
1

Wie bereits erwähnt, liegt dies an Prototype.js - speziell an Versionen vor 1.7. Ich hatte eine ähnliche Situation, musste aber Code haben, der funktionierte, ob Prototype.js da war oder nicht; Dies bedeutet, dass ich die Datei Array.prototype.toJSON nicht einfach löschen kann, da ich nicht sicher bin, was davon abhängt. Für diese Situation ist dies die beste Lösung, die ich gefunden habe:

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

Hoffentlich hilft es jemandem.

polm23
quelle
0

Wenn Sie nicht alles töten möchten und einen Code haben, der in den meisten Browsern in Ordnung ist, können Sie dies folgendermaßen tun:

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

Dies scheint komplex zu sein, ist jedoch nur für die meisten Anwendungsfälle komplex. Die Hauptidee besteht JSON.stringifydarin, toJSONdas als Argument übergebene Objekt zu entfernen , dann das alte aufzurufen JSON.stringifyund es schließlich wiederherzustellen.

Jerska
quelle