Je rencontre un problème lorsque j'essaie de transmettre des messages d'erreur à l'aide de sockets Web. Je peux reproduire le problème auquel je suis confronté en utilisant JSON.stringify
pour répondre à un public plus large:
// 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);
'{}'
Le problème est que je me retrouve avec un objet vide.
Navigateurs
J'ai d'abord essayé de quitter node.js et de l'exécuter dans différents navigateurs. La version 28 de Chrome me donne le même résultat et, chose intéressante, Firefox fait au moins une tentative mais laisse de côté le message:
>>> JSON.stringify(error); // Firebug, Firefox 23
{"fileName":"debug eval code","lineNumber":1,"stack":"@debug eval code:1\n"}
Fonction de remplacement
J'ai ensuite regardé le Error.prototype . Il montre que le prototype contient des méthodes telles que toString et toSource . Sachant que les fonctions ne peuvent pas être chaînées, j'ai inclus un fonction de remplacement lorsque j'ai appelé JSON.stringify pour supprimer toutes les fonctions, mais j'ai ensuite réalisé qu'il avait également un comportement étrange:
var error = new Error('simple error message');
JSON.stringify(error, function(key, value) {
console.log(key === ''); // true (?)
console.log(value === error); // true (?)
});
Il ne semble pas boucler sur l'objet comme d'habitude et je ne peux donc pas vérifier si la clé est une fonction et l'ignorer.
Existe-t-il un moyen de codifier les messages d'erreur natifs avec JSON.stringify
? Si non, pourquoi ce problème se produit-il?
JSON.stringify({ message: error.message, stack: error.stack })
_ { @Ray Toal } _ Dans un commentaire, je jette un coup d'œil au descripteur de propriété . Il est clair maintenant pourquoi cela ne fonctionne pas:
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);
}
Sortie:
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 }
Clé: enumerable: false
.
La réponse acceptée fournit une solution de contournement à ce problème.
Vous pouvez définir un Error.prototype.toJSON
pour extraire une Object
pure représentant la 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"}
Utiliser Object.defineProperty()
ajoute toJSON
sans que ce soit une propriété enumerable
elle-même.
En ce qui concerne la modification de Error.prototype
, alors que toJSON()
peut ne pas être défini pour Error
s spécifiquement, la méthode est toujours normalisée pour les objets en général (réf. Étape 3). Le risque de collision ou de conflit est donc minime.
Cependant, pour l'éviter complètement, le paramètre replacer
de JSON.stringify()
peut être utilisé à la place:
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))
semble fonctionner
[ d'un commentaire de/u/ub3rgeek sur/r/javascript ] et le commentaire de felixfbecker ci-dessous
Modifier la bonne réponse de Jonathan pour éviter les corrections de singe:
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'));
Comme personne ne parle de la partie pourquoi, je vais répondre à ces
Q: Existe-t-il un moyen de codifier les messages d'erreur natifs avec JSON.stringify?
Non.
Q: Si non, pourquoi ce problème se produit-il?
Extrait du document de JSON.stringify () ,
Pour toutes les autres instances d'objet (y compris Map, Set, WeakMap et WeakSet), seules leurs propriétés énumérables seront sérialisées.
et Error
n’a pas ses propriétés énumérables, c’est pourquoi il affiche un objet vide.
Vous pouvez également simplement redéfinir ces propriétés non énumérables pour être énumérables.
Object.defineProperty(Error.prototype, 'message', {
configurable: true,
enumerable: true
});
et peut-être aussi la propriété stack
.
Il existe un excellent package Node.js pour cela: serialize-error
.
Il gère bien même les objets d'erreur imbriqués, ce dont j'avais réellement besoin dans mon projet.
Aucune des réponses ci-dessus ne semble sérialiser correctement les propriétés figurant sur le prototype de Error (car getOwnPropertyNames()
n'inclut pas les propriétés héritées). Je ne pouvais pas non plus redéfinir les propriétés comme l’a suggéré l’une des réponses.
C’est la solution que j’ai proposée - elle utilise lodash, mais vous pouvez la remplacer par des versions génériques de ces fonctions.
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);
}
Voici le test que j'ai fait dans 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}}}
Nous devions sérialiser une hiérarchie d'objet arbitraire, où la racine ou l'une des propriétés imbriquées dans la hiérarchie pourraient être des instances d'erreur.
Notre solution consistait à utiliser le paramètre replacer
de JSON.stringify()
, par exemple:
function jsonFriendlyErrorReplacer(key, value) {
if (value instanceof Error) {
return {
// Pull all enumerable properties, supporting properties on custom Errors
...value,
// Explicitly pull Error's non-enumerable properties
name: value.name,
message: value.message,
stack: value.stack,
}
}
return value
}
let obj = {
error: new Error('nested error message')
}
console.log('Result WITHOUT custom replacer:', JSON.stringify(obj))
console.log('Result WITH custom replacer:', JSON.stringify(obj, jsonFriendlyErrorReplacer))