Não é possível restringir um erro usando JSON.stringify?

Reproduzindo o problema

Eu estou correndo em um problema ao tentar passar mensagens de erro em torno de usar sockets da web. Eu posso replicar o problema que estou enfrentando usando JSON.stringify para atender a um público mais amplo:

 // 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); '{}' 

O problema é que acabo com um object vazio.

O que eu tentei

Navegadores

Primeiro tentei sair do node.js e executá-lo em vários navegadores. A versão 28 do Chrome me dá o mesmo resultado e, curiosamente, o Firefox pelo menos faz uma tentativa, mas deixou de fora a mensagem:

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

Função Replacer

Então eu olhei para o Error.prototype . Isso mostra que o protótipo contém methods como toString e toSource . Sabendo que as funções não podem ser esticadas, incluí uma function replacer ao chamar JSON.stringify para remover todas as funções, mas depois percebi que ele também tinha algum comportamento estranho:

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

Parece não fazer um loop sobre o object como normalmente faria e, portanto, não posso verificar se a chave é uma function e ignorá-la.

A questão

Existe alguma maneira de restringir mensagens de erro nativas com JSON.stringify ? Se não, por que esse comportamento ocorre?

Métodos de contornar isso

  • Fique com mensagens de erro simples baseadas em string ou crie objects de erro pessoais e não confie no object Error nativo.
  • Propriedades de JSON.stringify({ message: error.message, stack: error.stack }) : JSON.stringify({ message: error.message, stack: error.stack })

Atualizações

@Ray Toal Sugerido em um comentário que eu dou uma olhada nos descritores de propriedade . Está claro agora porque não 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); } 

Saída:

 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 } 

Chave: enumerable: false .

A resposta aceita fornece uma solução alternativa para esse problema.

Você pode definir um Error.prototype.toJSON para recuperar um Object simples representando o 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"} 

O uso de Object.defineProperty() adiciona toJSON sem que ele seja uma propriedade enumerable .


Em relação à modificação de Error.prototype , enquanto toJSON() não pode ser definido para Error s especificamente, o método ainda é padronizado para objects em geral (ref: etapa 3). Portanto, o risco de colisões ou conflitos é mínimo.

Porém, para ainda evitá-lo completamente, o parâmetro replacer JSON.stringify() pode ser usado em seu 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)); 
 JSON.stringify(err, Object.getOwnPropertyNames(err)) 

parece funcionar

[ de um comentário por / u / ub3rgeek em / r / javascript ] e o comentário de felixfbecker abaixo

Modificando a ótima resposta do Jonathan para evitar o monkey patch:

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

Você também pode apenas redefinir essas propriedades não enumeráveis ​​para serem enumeráveis.

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

e talvez a propriedade de stack também.

Existe um ótimo pacote Node.js para isso: serialize-error .

Ele lida bem até com objects Error nesteds, o que eu realmente precisava muito no meu projeto.

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

Nenhuma das respostas acima pareceu serializar adequadamente propriedades que estão no protótipo de Error (porque getOwnPropertyNames() não inclui propriedades herdadas). Também não consegui redefinir as propriedades como uma das respostas sugeridas.

Esta é a solução que eu criei – ela usa o lodash, mas você pode replace o lodash por versões genéricas dessas funções.

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

Aqui está o teste que fiz no 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 :66:15","causedBy":{"name":"Error","message":"error2","stack":"Error: error2\n at :67:20","causedBy":{"name":"Error","message":"error3","stack":"Error: error3\n at :68:29","displayed":true}}} 

Como ninguém está falando sobre a parte por que , eu vou responder a estas

Existe alguma maneira de restringir mensagens de erro nativas com JSON.stringify?

Não.

Se não, por que esse comportamento ocorre?

No documento de JSON.stringify () ,

Para todas as outras instâncias Object (incluindo Map, Set, WeakMap e WeakSet), somente suas propriedades enumeráveis ​​serão serializadas.

e o object Error não tem suas propriedades enumeráveis, é por isso que ele imprime um object vazio.