web-dev-qa-db-fra.com

N'est-il pas possible de codifier une erreur à l'aide de JSON.stringify?

Reproduire le problème

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.

Ce que j'ai essayé

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.

La question

Existe-t-il un moyen de codifier les messages d'erreur natifs avec JSON.stringify? Si non, pourquoi ce problème se produit-il?

Méthodes de contournement

  • Respectez les simples messages d'erreur basés sur des chaînes ou créez des objets d'erreur personnels sans vous fier à l'objet d'erreur natif.
  • Propriétés Pull: JSON.stringify({ message: error.message, stack: error.stack })

Mises à jour

_ { @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.

225
querie.cc

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 Errors 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));
130
Jonathan Lonowski
JSON.stringify(err, Object.getOwnPropertyNames(err))

semble fonctionner

[ d'un commentaire de/u/ub3rgeek sur/r/javascript ] et le commentaire de felixfbecker ci-dessous

164
laggingreflex

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'));
42
Bryan Larsen

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.

12
Sanghyun Lee

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.

9
cheolgook

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.

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

7

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}}}  
4
Elliott Palermo

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

0
Joel Malone