JSON.stringify () rareza de la matriz con Prototype.js

89

Estoy tratando de averiguar qué salió mal con mi serialización json, tengo la versión actual de mi aplicación con la anterior y encuentro algunas diferencias sorprendentes en la forma en que JSON.stringify () funciona (usando la biblioteca JSON de json.org ).

En la versión anterior de mi aplicación:

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

me da esto;

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

en la nueva versión,

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

me da esto;

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

¿Alguna idea de qué podría haber cambiado para que la misma biblioteca pusiera comillas alrededor de los corchetes de la matriz en la nueva versión?

códigos morgan
fuente
4
Parece que hay un conflicto con la biblioteca Prototype, que presentamos en la versión más reciente. ¿Alguna idea de cómo secuenciar un objeto json que contiene una matriz en Prototype?
morgancodes
26
es por eso que la gente debería abstenerse de manipular objetos integrados globales (como lo hace el marco prototipo)
Gerardo Lima

Respuestas:

82

Dado que JSON.stringify se ha estado enviando con algunos navegadores últimamente, sugeriría usarlo en lugar del toJSON de Prototype. Luego, verificaría window.JSON && window.JSON.stringify y solo incluiría la biblioteca json.org de lo contrario (a través de document.createElement('script')…). Para resolver las incompatibilidades, utilice:

if(window.Prototype) {
    delete Object.prototype.toJSON;
    delete Array.prototype.toJSON;
    delete Hash.prototype.toJSON;
    delete String.prototype.toJSON;
}
Raphael Schweikert
fuente
No es necesario verificar window.JSON en su propio código; el script json.org lo hace por sí mismo
zcrar70
Eso puede ser así, pero luego se debe cargar todo el archivo de secuencia de comandos incluso si no se necesita.
Raphael Schweikert
11
En realidad, la única declaración necesaria para abordar la pregunta es: eliminar Array.prototype.toJSON
Jean Vincent
1
Muchas gracias. La empresa para la que trabajo en este momento todavía usa prototipos en gran parte de nuestro código y esto fue un salvavidas para usar bibliotecas más modernas, de lo contrario, todo se rompería.
krob
1
He estado buscando esta respuesta durante DAYS y publiqué dos preguntas SO diferentes tratando de resolverlo. Vi esto como una pregunta relacionada mientras estaba escribiendo una tercera. Muchas gracias!
Matthew Herbst
78

La función JSON.stringify () definida en ECMAScript 5 y superior (página 201 - el objeto JSON, pseudocódigo página 205) , usa la función toJSON () cuando está disponible en objetos.

Debido a que Prototype.js (u otra biblioteca que esté usando) define una función Array.prototype.toJSON (), las matrices primero se convierten en cadenas usando Array.prototype.toJSON () y luego la cadena entre comillas JSON.stringify (), de ahí el comillas adicionales incorrectas alrededor de las matrices.

Por lo tanto, la solución es sencilla y trivial (esta es una versión simplificada de la respuesta de Raphael Schweikert):

delete Array.prototype.toJSON

Por supuesto, esto produce efectos secundarios en las bibliotecas que dependen de una propiedad de función toJSON () para matrices. Pero encuentro esto como un inconveniente menor considerando la incompatibilidad con ECMAScript 5.

Cabe señalar que el objeto JSON definido en ECMAScript 5 se implementa de manera eficiente en los navegadores modernos y, por lo tanto, la mejor solución es ajustarse al estándar y modificar las bibliotecas existentes.

Jean Vincent
fuente
5
Esta es la respuesta más concisa de lo que está sucediendo con las citas adicionales de la matriz.
tmarthal
15

Una posible solución que no afectará a otras dependencias de Prototype sería:

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

Esto se encarga de la incompatibilidad de Array toJSON con JSON.stringify y también conserva la funcionalidad de toJSON, ya que otras bibliotecas de Prototype pueden depender de ella.

Akkishore
fuente
Usé este fragmento en un sitio web. Está causando problemas. Resulta en que la propiedad toJSON de la matriz no está definida. ¿Algún consejo sobre eso?
Sourabh
1
Asegúrese de que su Array.prototype.toJSON esté definido antes de usar el fragmento anterior para redefinir JSON.stringify. Funciona bien en mi prueba.
Akkishore
2
Me envolví en if(typeof Prototype !== 'undefined' && parseFloat(Prototype.Version.substr(0,3)) < 1.7 && typeof Array.prototype.toJSON !== 'undefined'). Funcionó.
Sourabh
1
Excelente. Solo hasta el Prototype 1.7 es un problema.
Vota por
1
El problema es para las versiones <1.7
Sourabh
9

Edite para hacer un poco más preciso:

El bit de código clave del problema está en la biblioteca JSON de JSON.org (y otras implementaciones del objeto JSON de ECMAScript 5):

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

El problema es que la biblioteca Prototype extiende Array para incluir un método toJSON, que el objeto JSON llamará en el código anterior. Cuando el objeto JSON alcanza el valor de la matriz, llama a toJSON en la matriz que está definida en Prototype, y ese método devuelve una versión de cadena de la matriz. Por lo tanto, las comillas alrededor de los corchetes de la matriz.

Si elimina toJSON del objeto Array, la biblioteca JSON debería funcionar correctamente. O simplemente use la biblioteca JSON.

Beto
fuente
2
Esto no es un error en la biblioteca, porque esta es la forma exacta en que JSON.stringify () se define en ECMAScript 5. El problema es con prototype.js y la solución es: eliminar Array.prototype.toJSON Esto tendrá algún lado efectos para la serialización del prototipo a JSON, pero encontré estos menores con respecto a la incompatibilidad que el prototipo tiene con ECMAScript 5.
Jean Vincent
la biblioteca Prototype no extiende Object.prototype sino Array.prototype, aunque typeof array en JavaScript también devuelve "object", no tienen el mismo "constructor" y prototype. Para resolver el problema es necesario: "eliminar Array.prototype.toJSON;"
Jean Vincent
@Jean Para ser justos, Prototype extiende todos los objetos nativos base, incluido Object. Pero está bien, veo su punto de nuevo :) Gracias por ayudar a que mi respuesta sea mejor
Bob
Prototype ha dejado de extender "Object.prototype" durante mucho tiempo (aunque no recuerdo qué versión) para evitar problemas. Ahora solo extiende las propiedades estáticas de Object (que es mucho más seguro) como un espacio de nombres: api.prototypejs.org/language/Object
Jean Vincent
Jean, en realidad es exactamente un error en la biblioteca. Si un objeto tiene toJSON, debe ser llamado y su resultado debe usarse, pero no debe ser citado.
grr
4

Creo que una mejor solución sería incluir esto justo después de que se haya cargado el prototipo

JSON = JSON || {};

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

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

Esto hace que la función prototipo esté disponible como JSON.stringify () y JSON.parse () estándar, pero mantiene el JSON.parse () nativo si está disponible, por lo que esto hace que las cosas sean más compatibles con los navegadores más antiguos.

Benjamín
fuente
la versión JSON.stringify no funciona si el 'valor' pasado es un objeto. Debería hacer esto en su lugar: JSON.stringify = function (value) {return Object.toJSON (value); };
Akkishore
2

No soy tan fluido con Prototype, pero vi esto en sus documentos :

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

Sin embargo, no estoy seguro de si esto tendría el mismo problema que la codificación actual.

También hay un tutorial más largo sobre el uso de JSON con Prototype.

Señor de poder
fuente
2

Este es el código que usé para el mismo 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)
}

Verifica si el prototipo existe, luego verifica la versión. Si la versión anterior usa Object.toJSON (si está definida) en todos los demás casos, recurra a JSON.stringify ()

Memos
fuente
1

Así es como lo estoy afrontando.

var methodCallString =  Object.toJSON? Object.toJSON(options.jsonMethodCall) :  JSON.stringify(options.jsonMethodCall);
códigos morgan
fuente
1

Mi solución tolerante verifica si Array.prototype.toJSON es dañino para JSON stringify y lo mantiene cuando es posible para que el código circundante funcione como se esperaba:

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
    }
}
propenso
fuente
1

Como la gente ha señalado, esto se debe a Prototype.js, específicamente versiones anteriores a la 1.7. Tuve una situación similar pero tenía que tener un código que funcionara si Prototype.js estaba allí o no; esto significa que no puedo simplemente eliminar Array.prototype.toJSON ya que no estoy seguro de qué se basa en él. Para esa situación, esta es la mejor solución que se me ocurrió:

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

Ojalá ayude a alguien.

polm23
fuente
0

Si no desea eliminar todo y tiene un código que estaría bien en la mayoría de los navegadores, puede hacerlo de esta manera:

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

Esto parece complejo, pero es complejo solo para manejar la mayoría de los casos de uso. La idea principal es primordial JSON.stringifypara eliminar toJSONdel objeto pasado como argumento, luego llamar al antiguo JSON.stringifyy finalmente restaurarlo.

Jerska
fuente