Ist es nicht möglich, einen Fehler mit JSON.stringify zu stringifizieren?

328

Das Problem reproduzieren

Beim Versuch, Fehlermeldungen mithilfe von Web-Sockets weiterzugeben, tritt ein Problem auf. Ich kann das Problem, mit dem ich konfrontiert bin JSON.stringify, wiederholen, um ein breiteres Publikum anzusprechen:

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

Das Problem ist, dass ich am Ende ein leeres Objekt habe.

Was ich versucht habe

Browser

Ich habe zuerst versucht, node.js zu verlassen und es in verschiedenen Browsern auszuführen. Die Chrome-Version 28 liefert das gleiche Ergebnis, und interessanterweise unternimmt Firefox zumindest einen Versuch, lässt jedoch die folgende Meldung aus:

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

Ersatzfunktion

Ich habe mir dann den Error.prototype angesehen . Es zeigt, dass der Prototyp Methoden wie toString und toSource enthält . Da ich wusste, dass Funktionen nicht stringifiziert werden können, habe ich beim Aufrufen von JSON.stringify eine Ersetzungsfunktion eingefügt, um alle Funktionen zu entfernen, aber dann festgestellt, dass auch diese ein seltsames Verhalten aufweist:

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

Es scheint das Objekt nicht wie gewohnt zu durchlaufen, und daher kann ich nicht überprüfen, ob die Taste eine Funktion ist, und sie ignorieren.

Die Frage

Gibt es eine Möglichkeit, native Fehlermeldungen mit zu kennzeichnen JSON.stringify? Wenn nicht, warum tritt dieses Verhalten auf?

Methoden, um dies zu umgehen

  • Halten Sie sich an einfache stringbasierte Fehlermeldungen oder erstellen Sie persönliche Fehlerobjekte und verlassen Sie sich nicht auf das native Fehlerobjekt.
  • Pull-Eigenschaften: JSON.stringify({ message: error.message, stack: error.stack })

Aktualisierung

@ Ray Toal In einem Kommentar vorgeschlagen, dass ich mir die Eigenschaftsbeschreibungen anschaue . Es ist jetzt klar, warum es nicht funktioniert:

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

Ausgabe:

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 }

Schlüssel : enumerable: false.

Die akzeptierte Antwort bietet eine Problemumgehung für dieses Problem.

JayQuerie.com
quelle
3
Haben Sie die Eigenschaftsbeschreibungen für die Eigenschaften im Fehlerobjekt untersucht?
Ray Toal
3
Die Frage für mich war 'warum' und ich fand die Antwort am Ende der Frage. Es ist nichts Falsches daran, eine Antwort auf Ihre eigene Frage zu veröffentlichen, und auf diese Weise erhalten Sie wahrscheinlich mehr Glaubwürdigkeit. :-)
Michael Scheper

Antworten:

178

Sie können a definieren Error.prototype.toJSON, um eine Ebene abzurufen, die Folgendes Objectdarstellt 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"}

Verwenden von Object.defineProperty()Adds, toJSONohne dass es sich um eine enumerableEigenschaft handelt.


In Bezug auf das Modifizieren Error.prototypeist die Methode , obwohl sie toJSON()möglicherweise nicht Errorspeziell für s definiert ist, für Objekte im Allgemeinen standardisiert (siehe Schritt 3). Das Risiko von Kollisionen oder Konflikten ist also minimal.

Obwohl, noch sie vollständig zu vermeiden, JSON.stringify()‚s - replacerParameter können stattdessen verwendet werden:

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));
Jonathan Lonowski
quelle
3
Wenn Sie .getOwnPropertyNames()anstelle von verwenden .keys(), erhalten Sie die nicht aufzählbaren Eigenschaften, ohne sie manuell definieren zu müssen.
8
Besser nicht zum Error.prototype hinzufügen, kann Probleme verursachen, wenn in einer zukünftigen Version von JavaScrip der Error.prototype tatsächlich eine toJSON-Funktion hat.
Jos de Jong
3
Vorsichtig! Diese Lösung unterbricht die Fehlerbehandlung im Native Node Mongodb-Treiber: jira.mongodb.org/browse/NODE-554
Sebastian Nowak
5
Falls jemand auf seine Linkerfehler und Namenskonflikte achtet: Wenn Sie die Ersetzungsoption verwenden, sollten Sie einen anderen Parameternamen für keyin wählen function replaceErrors(key, value), um Namenskonflikte mit zu vermeiden .forEach(function (key) { .. }). Der replaceErrors keyParameter wird in dieser Antwort nicht verwendet.
404 Nicht gefunden
2
Das Abschatten keyin diesem Beispiel ist zwar zulässig, aber möglicherweise verwirrend, da Zweifel daran bestehen, ob der Autor beabsichtigte, auf die äußere Variable zu verweisen oder nicht. propNamewäre eine ausdrucksstärkere Wahl für die innere Schleife. (Übrigens, ich denke, @ 404NotFound bedeutete "linter" (statisches Analysetool), nicht "Linker". ) In jedem Fall ist die Verwendung einer benutzerdefinierten replacerFunktion eine hervorragende Lösung dafür, da das Problem an einem geeigneten Ort gelöst wird und sich nicht ändert / globales Verhalten.
iX3
260
JSON.stringify(err, Object.getOwnPropertyNames(err))

scheint zu funktionieren

[ aus einem Kommentar von / u / ub3rgeek zu / r / javascript ] und dem Kommentar von felixfbecker unten

Nachlaufreflex
quelle
56
Die Antworten JSON.stringify(err, Object.getOwnPropertyNames(err))
kämmen
5
Dies funktioniert gut für ein natives ExpressJS-Fehlerobjekt, funktioniert jedoch nicht mit einem Mungofehler. Mungofehler haben verschachtelte Objekte für ValidationErrorTypen. Dadurch wird das verschachtelte errorsObjekt in einem Mongoose- Fehlerobjekt vom Typ nicht stringifiziert ValidationError.
Steampowered
4
Dies sollte die Antwort sein, da dies der einfachste Weg ist.
Huan
7
@felixfbecker Das sucht nur eine Ebene tief nach Eigenschaftsnamen . Wenn Sie haben var spam = { a: 1, b: { b: 2, b2: 3} };und laufen Object.getOwnPropertyNames(spam), werden Sie ["a", "b"]hier täuschen, weil das bObjekt sein eigenes hat b. Sie würden beide in Ihrem Stringify-Anruf erhalten, aber Sie würden vermissenspam.b.b2 . Das ist schlecht.
Ruffin
1
@ Ruffin das stimmt, aber es könnte sogar wünschenswert sein. Ich denke , was OP wollte , war nur um sicherzugehen , messageund stacksind in der JSON enthalten.
Felixfbecker
74

Da niemand über das Warum spricht , werde ich darauf antworten.

Warum gibt dies JSON.stringifyein leeres Objekt zurück?

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

Antworten

Aus dem Dokument von JSON.stringify () ,

Für alle anderen Objektinstanzen (einschließlich Map, Set, WeakMap und WeakSet) werden nur ihre aufzählbaren Eigenschaften serialisiert.

und ErrorObjekt hat nicht seine aufzählbaren Eigenschaften, deshalb druckt es ein leeres Objekt.

Sanghyun Lee
quelle
4
Seltsamerweise störte es niemanden. Solange Fix funktioniert, nehme ich an :)
Ilya Chernomordik
1
Der erste Teil dieser Antwort ist nicht korrekt. Es gibt eine Möglichkeit, JSON.stringifyden replacerParameter zu verwenden.
Todd Chaffee
1
@ToddChaffee das ist ein guter Punkt. Ich habe meine Antwort korrigiert. Bitte überprüfen Sie es und zögern Sie nicht, es zu verbessern. Vielen Dank.
Sanghyun Lee
52

Ändern Sie Jonathans großartige Antwort, um das Patchen von Affen zu vermeiden:

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'));
Bryan Larsen
quelle
3
Zum ersten Mal habe ich gehört monkey patching:)
Chris Prince
2
@ChrisPrince Aber es wird nicht das letzte Mal sein, besonders in JavaScript! Hier ist Wikipedia über Monkey Patching , nur für zukünftige Leute. (In Jonathans Antwort , wie Chris versteht, du bist das Hinzufügen einer neuen Funktion, toJSON, direkt auf Error‚s - Prototyp , die oft keine gute Idee. Vielleicht hat jemand anderes schon hat, der diese Kontrollen, aber dann wissen Sie nicht , was diese andere Version tut es. Oder wenn jemand unerwartet Ihre bekommt oder annimmt, dass der Prototyp von Error bestimmte Eigenschaften hat, könnten die Dinge schief gehen.)
Ruffin
Das ist schön, lässt aber den Stapel des Fehlers weg (der in der Konsole angezeigt wird). Ich bin mir der Details nicht sicher, ob dies mit Vue zusammenhängt oder was, wollte dies nur erwähnen.
Phil294
22

Dafür gibt es ein großartiges Node.js-Paket : serialize-error.

Es verarbeitet auch verschachtelte Fehlerobjekte gut, was ich eigentlich in meinem Projekt sehr gebraucht habe.

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

Lukasz Czerwinski
quelle
Nein, aber es kann transpiliert werden, um dies zu tun. Siehe diesen Kommentar .
iX3
Dies ist die richtige Antwort. Das Serialisieren von Fehlern ist kein triviales Problem, und der Autor der Bibliothek (ein ausgezeichneter Entwickler mit vielen sehr beliebten Paketen) hat erhebliche Anstrengungen unternommen, um Randfälle zu behandeln, wie in der README zu sehen ist: "Benutzerdefinierte Eigenschaften bleiben erhalten. Nicht aufzählbar Eigenschaften werden nicht aufzählbar gehalten (Name, Nachricht, Stapel). Aufzählbare Eigenschaften werden aufzählbar gehalten (alle Eigenschaften außer den nicht aufzählbaren). Zirkelverweise werden behandelt. "
Dan Dascalescu
9

Sie können diese nicht aufzählbaren Eigenschaften auch einfach neu definieren, damit sie aufzählbar sind.

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

und vielleicht auch stackEigentum.

Cheolgook
quelle
9
Ändern Sie keine Objekte, die Sie nicht besitzen, da dies andere Teile Ihrer Anwendung beschädigen kann.
Fregante
7

Wir mussten eine beliebige Objekthierarchie serialisieren, wobei die Wurzel oder eine der verschachtelten Eigenschaften in der Hierarchie Fehlerinstanzen sein könnten.

Unsere Lösung bestand darin, den replacerParameter zu verwenden JSON.stringify(), z.

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

Joel Malone
quelle
5

Keine der obigen Antworten schien Eigenschaften, die sich auf dem Prototyp des Fehlers befinden, ordnungsgemäß zu serialisieren (da getOwnPropertyNames()ererbte Eigenschaften nicht enthält). Ich war auch nicht in der Lage, die Eigenschaften wie in einer der vorgeschlagenen Antworten neu zu definieren.

Dies ist die Lösung, die ich mir ausgedacht habe - sie verwendet lodash, aber Sie könnten lodash durch generische Versionen dieser Funktionen ersetzen.

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

Hier ist der Test, den ich in Chrome durchgeführt habe:

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}}}  
Elliott Palermo
quelle
2

Ich habe an einem JSON-Format für Protokoll-Appender gearbeitet und bin hier gelandet, um ein ähnliches Problem zu lösen. Nach einer Weile wurde mir klar, dass ich Node einfach dazu bringen konnte, die Arbeit zu erledigen:

const util = require("util");
...
return JSON.stringify(obj, (name, value) => {
    if (value instanceof Error) {
        return util.format(value);
    } else {
        return value;
    }
}
Jason
quelle
1
Es sollte sein instanceofund nicht instanceOf.
lakshman.pasala