Bizzarria dell'array JSON.stringify () con Prototype.js


89

Sto cercando di capire cosa è andato storto con la mia serializzazione json, ho la versione corrente della mia app con e quella vecchia e sto trovando alcune sorprendenti differenze nel modo in cui funziona JSON.stringify () (utilizzando la libreria JSON da json.org ).

Nella vecchia versione della mia app:

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

mi dà questo;

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

nella nuova versione,

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

mi dà questo;

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

qualche idea su cosa sarebbe potuto cambiare per fare in modo che la stessa libreria mettesse le virgolette attorno alle parentesi dell'array nella nuova versione?


4
sembra che sia un conflitto con la libreria Prototype, che abbiamo introdotto nella versione più recente. Qualche idea su come stringere un oggetto JSON contenente un array in Prototype?
Morgancodes

26
ecco perché le persone dovrebbero astenersi dal manipolare oggetti globali incorporati (come fa il framework prototipo)
Gerardo Lima

Risposte:


82

Poiché JSON.stringify è stato fornito con alcuni browser ultimamente, suggerirei di usarlo al posto di toJSON di Prototype. Dovresti quindi controllare window.JSON && window.JSON.stringify e includere solo la libreria json.org altrimenti (tramite document.createElement('script')...). Per risolvere le incompatibilità, utilizzare:

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

Non c'è bisogno di controllare window.JSON nel tuo codice - lo script json.org lo fa da solo
zcrar70

Potrebbe essere così, ma poi l'intero file di script deve essere caricato anche se non sarà necessario.
Raphael Schweikert

11
In realtà, l'unica dichiarazione necessaria per affrontare la domanda è: eliminare Array.prototype.toJSON
Jean Vincent

1
Grazie mille. L'azienda per cui lavoro in questo momento utilizza ancora il prototipo in gran parte del nostro codice e questo è stato un salvavita per l'utilizzo di librerie più moderne, altrimenti tutto si sarebbe rotto.
krob

1
Ho cercato questa risposta per DAYS e ho pubblicato due diverse domande SO cercando di capirlo. L'ho vista come una domanda correlata mentre ne stavo digitando una terza. Grazie mille!
Matthew Herbst

80

La funzione JSON.stringify () definita in ECMAScript 5 e versioni successive (Pagina 201 - Oggetto JSON, pseudo-codice Pagina 205) , utilizza la funzione toJSON () quando disponibile sugli oggetti.

Poiché Prototype.js (o un'altra libreria che stai utilizzando) definisce una funzione Array.prototype.toJSON (), gli array vengono prima convertiti in stringhe utilizzando Array.prototype.toJSON () quindi stringa citata da JSON.stringify (), da cui il virgolette extra errate intorno agli array.

La soluzione è quindi semplice e banale (questa è una versione semplificata della risposta di Raphael Schweikert):

delete Array.prototype.toJSON

Ciò produce ovviamente effetti collaterali sulle librerie che si basano su una proprietà della funzione toJSON () per gli array. Ma trovo questo un piccolo inconveniente considerando l'incompatibilità con ECMAScript 5.

Va notato che l'oggetto JSON definito in ECMAScript 5 è implementato in modo efficiente nei browser moderni e quindi la soluzione migliore è conformarsi allo standard e modificare le librerie esistenti.


5
Questa è la risposta più concisa di ciò che sta accadendo con la citazione extra dell'array.
tmarthal

15

Una possibile soluzione che non influenzerà altre dipendenze del prototipo sarebbe:

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

Questo si prende cura dell'incompatibilità di Array toJSON con JSON.stringify e mantiene anche la funzionalità toJSON poiché altre librerie di prototipi potrebbero dipendere da esso.


Ho usato questo frammento in un sito web. Sta causando problemi. Risulta che la proprietà toJSON dell'array non è definita. Qualche suggerimento su questo?
Sourabh

1
Assicurati che il tuo Array.prototype.toJSON sia definito prima di utilizzare lo snippet sopra per ridefinire JSON.stringify. Funziona bene nel mio test.
akkishore

2
Mi sono avvolto in if(typeof Prototype !== 'undefined' && parseFloat(Prototype.Version.substr(0,3)) < 1.7 && typeof Array.prototype.toJSON !== 'undefined'). Ha funzionato.
Sourabh

1
Grande. Solo fino a Prototype 1.7 questo è un problema. Si prega di
votare a

1
Il problema riguarda le versioni <1.7
Sourabh

9

Modifica per rendere un po 'più preciso:

Il bit chiave del problema è nella libreria JSON di JSON.org (e altre implementazioni dell'oggetto JSON di ECMAScript 5):

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

Il problema è che la libreria Prototype estende Array per includere un metodo toJSON, che l'oggetto JSON chiamerà nel codice sopra. Quando l'oggetto JSON raggiunge il valore dell'array, chiama toJSON sull'array definito in Prototype e tale metodo restituisce una versione di stringa dell'array. Quindi, le virgolette attorno alle parentesi dell'array.

Se elimini toJSON dall'oggetto Array, la libreria JSON dovrebbe funzionare correttamente. Oppure usa semplicemente la libreria JSON.


2
Questo non è un bug nella libreria, perché questo è il modo esatto in cui JSON.stringify () è definito in ECMAScript 5. Il problema è con prototype.js e la soluzione è: elimina Array.prototype.toJSON Questo avrà un lato effetti per la serializzazione da prototipo a JSON, ma ho trovato questi minori per quanto riguarda l'incompatibilità che il prototipo ha con ECMAScript 5.
Jean Vincent

la libreria Prototype non estende Object.prototype ma Array.prototype, sebbene typeof array in JavaScript restituisca anche "object", non hanno lo stesso "costruttore" e prototipo. Per risolvere il problema è necessario: "delete Array.prototype.toJSON;"
Jean Vincent

@Jean Per essere onesti, Prototype estende tutti gli oggetti nativi di base, incluso Object. Ma ok, vedo di nuovo il tuo punto :) Grazie per aver aiutato la mia risposta a essere migliore
Bob

Prototype ha smesso di estendere "Object.prototype" da molto tempo (non ricordo quale versione però) per evitare problemi di .. in. Ora estende solo le proprietà statiche di Object (che è molto più sicuro) come spazio dei nomi: api.prototypejs.org/language/Object
Jean Vincent

Jean, in realtà è esattamente un bug nella libreria. Se un oggetto ha toJSON, deve essere chiamato e il suo risultato deve essere utilizzato, ma non deve essere quotato.
grr

4

Penso che una soluzione migliore sarebbe includerla subito dopo che il prototipo è stato caricato

JSON = JSON || {};

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

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

Questo rende la funzione prototipo disponibile come JSON.stringify () e JSON.parse () standard, ma mantiene il JSON.parse () nativo se è disponibile, quindi questo rende le cose più compatibili con i browser meno recenti.


la versione JSON.stringify non funziona se il "valore" passato è un oggetto. Dovresti invece fare questo: JSON.stringify = function (value) {return Object.toJSON (value); };
akkishore

2

Non sono così fluente con Prototype, ma l'ho visto nei suoi documenti :

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

Non sono sicuro se questo avrebbe lo stesso problema della codifica corrente, però.

C'è anche un tutorial più lungo sull'uso di JSON con Prototype.


2

Questo è il codice che ho usato per lo stesso problema:

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

Controlli se Prototype esiste, quindi controlli la versione. Se la vecchia versione usa Object.toJSON (se è definito) in tutti gli altri casi fallback a JSON.stringify ()


1

Ecco come lo sto affrontando.

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

1

La mia soluzione tollerante controlla se Array.prototype.toJSON è dannoso per la stringa JSON e lo mantiene quando possibile per far funzionare il codice circostante come previsto:

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

Come le persone hanno sottolineato, ciò è dovuto a Prototype.js, in particolare alle versioni precedenti alla 1.7. Ho avuto una situazione simile ma dovevo avere un codice che funzionasse indipendentemente dal fatto che Prototype.js fosse presente o meno; questo significa che non posso semplicemente eliminare Array.prototype.toJSON perché non sono sicuro di cosa si basi su di esso. Per quella situazione questa è la migliore soluzione che ho trovato:

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

Si spera che possa aiutare qualcuno.


0

Se non vuoi uccidere tutto e hai un codice che andrebbe bene sulla maggior parte dei browser, puoi farlo in questo modo:

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

Sembra complesso, ma è complesso solo per gestire la maggior parte dei casi d'uso. L'idea principale è quella JSON.stringifydi rimuovere toJSONdall'oggetto passato come argomento, quindi chiamare il vecchio JSON.stringifye infine ripristinarlo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.