No es posible stringify un error usando JSON.stringify?


Reproduciendo el problema

Me estoy topando con un problema al intentar pasar mensajes de error usando sockets web. Puedo replicar el problema que estoy enfrentando usando JSON.stringify para atender a un público más amplio:

// node v0.10.15
> var error = new Error('simple error message');
    undefined

> error
    [Error: simple error message]

> Object.getOwnPropertyNames(error);
    [ 'stack', 'arguments', 'type', 'message' ]

> JSON.stringify(error);
    '{}'

El problema es que termino con un objeto vacío.

Lo que he intentado

Navegadores

Primero intenté dejar el nodo.js y ejecutarlo en varios navegadores. Chrome versión 28 me da el mismo resultado, y lo suficientemente interesante, Firefox al menos hace un intento, pero dejó fuera el mensaje:

>>> JSON.stringify(error); // Firebug, Firefox 23
{"fileName":"debug eval code","lineNumber":1,"stack":"@debug eval code:1\n"}

Función de reemplazo

Luego miré el error .prototype . Muestra que el prototipo contiene métodos como toStringy toSource. Sabiendo que las funciones no se pueden stringificar, he incluido una función replacer al llamar a JSON.stringify para eliminar todas las funciones, pero luego se dio cuenta de que también tenía un comportamiento extraño:

var error = new Error('simple error message');
JSON.stringify(error, function(key, value) {
    console.log(key === ''); // true (?)
    console.log(value === error); // true (?)
});

It no parece recorrer el objeto como lo haría normalmente, y por lo tanto no puedo comprobar si la tecla es una función e ignorarla.

La pregunta

¿Hay alguna manera de stringify mensajes de error nativos con JSON.stringify? Si no, ¿por qué ocurre este comportamiento?

Métodos para sortear esto

  • Siga con mensajes de error simples basados en cadenas, o cree objetos de error personales y no confíe en el objeto de error nativo.
  • Propiedades de extracción: JSON.stringify({ message: error.message, stack: error.stack })

Actualizaciones

@Ray Toal Sugirió en un comentario que eche un vistazo a los descriptores de propiedades . Ahora está claro por qué no funciona:

var error = new Error('simple error message');
var propertyNames = Object.getOwnPropertyNames(error);
var descriptor;
for (var property, i = 0, len = propertyNames.length; i < len; ++i) {
    property = propertyNames[i];
    descriptor = Object.getOwnPropertyDescriptor(error, property);
    console.log(property, descriptor);
}

Salida:

stack { get: [Function],
  set: [Function],
  enumerable: false,
  configurable: true }
arguments { value: undefined,
  writable: true,
  enumerable: false,
  configurable: true }
type { value: undefined,
  writable: true,
  enumerable: false,
  configurable: true }
message { value: 'simple error message',
  writable: true,
  enumerable: false,
  configurable: true }

Clave: enumerable: false.

Respuesta aceptada proporciona una solución para este problema.

Author: Community, 2013-08-23

7 answers

Puede definir un Error.prototype.toJSON para recuperar un Object plano que representa el Error:

if (!('toJSON' in Error.prototype))
Object.defineProperty(Error.prototype, 'toJSON', {
    value: function () {
        var alt = {};

        Object.getOwnPropertyNames(this).forEach(function (key) {
            alt[key] = this[key];
        }, this);

        return alt;
    },
    configurable: true,
    writable: true
});
var error = new Error('testing');
error.detail = 'foo bar';

console.log(JSON.stringify(error));
// {"message":"testing","detail":"foo bar"}

Utilizando Object.defineProperty() añade toJSON sin que sea una propiedad enumerable en sí.


Con respecto a la modificación de Error.prototype, mientras que toJSON() puede no definirse específicamente para Error, el método sigue estandarizado para los objetos en general (ref: paso 3). Por lo tanto, el riesgo de colisiones o conflictos es mínimo.

Sin embargo, para evitarlo completamente, JSON.stringify()'s replacer el parámetro se puede usar en su lugar:

function replaceErrors(key, value) {
    if (value instanceof Error) {
        var error = {};

        Object.getOwnPropertyNames(value).forEach(function (key) {
            error[key] = value[key];
        });

        return error;
    }

    return value;
}

var error = new Error('testing');
error.detail = 'foo bar';

console.log(JSON.stringify(error, replaceErrors));
 114
Author: Jonathan Lonowski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-04 02:31:51
JSON.stringify(err, Object.getOwnPropertyNames(err))

Parece funcionar

[de un comentario de / u / ub3rgeek en / r / javascript ] y el comentario de felixfbecker a continuación

 132
Author: laggingreflex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-28 07:27:18

Modificando la gran respuesta de Jonathan para evitar parches de mono:

var stringifyError = function(err, filter, space) {
  var plainObject = {};
  Object.getOwnPropertyNames(err).forEach(function(key) {
    plainObject[key] = err[key];
  });
  return JSON.stringify(plainObject, filter, space);
};

var error = new Error('testing');
error.detail = 'foo bar';

console.log(stringifyError(error, null, '\t'));
 41
Author: Bryan Larsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-05 16:48:46

También puede redefinir esas propiedades no enumerables para que sean enumerables.

Object.defineProperty(Error.prototype, 'message', {
    configurable: true,
    enumerable: true
});

Y tal vez stack propiedad también.

 9
Author: cheolgook,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-15 21:35:26

Hay un gran Nodo.paquete js para eso: serialize-error.

Maneja bien incluso los objetos de error anidados, lo que realmente necesitaba mucho en mi proyecto.

Https://www.npmjs.com/package/serialize-error

 5
Author: Lukasz Czerwinski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-09 14:03:20

Ninguna de las respuestas anteriores parecía serializar correctamente las propiedades que están en el prototipo de Error (porque getOwnPropertyNames() no incluye propiedades heredadas). Tampoco fui capaz de redefinir las propiedades como una de las respuestas sugeridas.

Esta es la solución que se me ocurrió - utiliza lodash, pero podría reemplazar lodash con versiones genéricas de esas funciones.

 function recursivePropertyFinder(obj){
    if( obj === Object.prototype){
        return {};
    }else{
        return _.reduce(Object.getOwnPropertyNames(obj), 
            function copy(result, value, key) {
                if( !_.isFunction(obj[value])){
                    if( _.isObject(obj[value])){
                        result[value] = recursivePropertyFinder(obj[value]);
                    }else{
                        result[value] = obj[value];
                    }
                }
                return result;
            }, recursivePropertyFinder(Object.getPrototypeOf(obj)));
    }
}


Error.prototype.toJSON = function(){
    return recursivePropertyFinder(this);
}

Aquí está la prueba que hice en Chrome:

var myError = Error('hello');
myError.causedBy = Error('error2');
myError.causedBy.causedBy = Error('error3');
myError.causedBy.causedBy.displayed = true;
JSON.stringify(myError);

{"name":"Error","message":"hello","stack":"Error: hello\n    at <anonymous>:66:15","causedBy":{"name":"Error","message":"error2","stack":"Error: error2\n    at <anonymous>:67:20","causedBy":{"name":"Error","message":"error3","stack":"Error: error3\n    at <anonymous>:68:29","displayed":true}}}  
 3
Author: Elliott Palermo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-30 18:59:34

Como nadie está hablando de la parte por qué , voy a responder a estos

P: ¿Hay alguna manera de stringify mensajes de error nativos con JSON.stringify?

No.

P: Si no, ¿por qué ocurre este comportamiento?

Del documento de JSON.stringify(),

Para todas las demás instancias de objetos (incluyendo Map, Set, WeakMap y WeakSet), solo se serializarán sus propiedades enumerables.

Y Error el objeto no tiene sus propiedades enumerables, por eso imprime un objeto vacío.

 3
Author: Sanghyun Lee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-22 01:26:37