Ich habe ein Problem, wenn ich versuche, Fehlermeldungen über Web-Sockets zu senden. Ich kann das Problem, mit dem ich konfrontiert bin, mit 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 mit einem leeren Objekt ende.
Browser
Ich habe zuerst versucht, node.js zu verlassen und in verschiedenen Browsern auszuführen. Die Chrome-Version 28 liefert mir dasselbe Ergebnis. Interessanterweise versucht Firefox zumindest einen Versuch, lässt aber die Nachricht aus:
>>> JSON.stringify(error); // Firebug, Firefox 23
{"fileName":"debug eval code","lineNumber":1,"stack":"@debug eval code:1\n"}
Replacer-Funktion
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, fügte ich beim Aufruf von JSON.stringify ein Ersetzungsfunktion ein, um alle Funktionen zu entfernen, erkannte jedoch, dass auch sie ein seltsames Verhalten hatte:
var error = new Error('simple error message');
JSON.stringify(error, function(key, value) {
console.log(key === ''); // true (?)
console.log(value === error); // true (?)
});
Es scheint sich nicht wie üblich über das Objekt zu bewegen, und daher kann ich nicht überprüfen, ob der Schlüssel eine Funktion ist, und ihn ignorieren.
Gibt es eine Möglichkeit, native Fehlermeldungen mit JSON.stringify
zu stringifizieren? Wenn nicht, warum tritt dieses Verhalten auf?
JSON.stringify({ message: error.message, stack: error.stack })
@Ray Toal In einem Kommentar vorgeschlagen, betrachte ich die Eigenschaftsdeskriptoren . 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.
Sie können einen Error.prototype.toJSON
definieren, um einen einfachen Object
namen__ abzurufen, der den Error
namen__ darstellt:
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"}
Mit Object.defineProperty()
wird toJSON
hinzugefügt, ohne dass es sich um eine enumerable
-Eigenschaft handelt.
In Bezug auf das Ändern von Error.prototype
, während toJSON()
möglicherweise nicht speziell für Error
name__s definiert ist, die Methode ist immer noch standardisiert für Objekte im Allgemeinen (siehe Schritt 3). Das Risiko von Kollisionen oder Konflikten ist also minimal.
Um dies dennoch vollständig zu vermeiden, kann stattdessen der Parameter replacer
von JSON.stringify()
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));
JSON.stringify(err, Object.getOwnPropertyNames(err))
scheint zu funktionieren
[ aus einem Kommentar von/u/ub3rgeek zu/r/javascript ] und dem Kommentar von felixfbecker
Jonathans großartige Antwort modifizieren, 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'));
Da niemand über den warum - Teil spricht, werde ich diese beantworten
Q: Gibt es eine Möglichkeit, native Fehlermeldungen mit JSON.stringify zu stringifizieren?
Nein.
F: Wenn nicht, warum tritt dieses Verhalten auf?
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 Error
object hat keine aufzählbaren Eigenschaften, deshalb gibt es ein leeres Objekt aus.
Sie können auch diese nicht aufzählbaren Eigenschaften einfach neu definieren, um sie aufzuzählen.
Object.defineProperty(Error.prototype, 'message', {
configurable: true,
enumerable: true
});
und vielleicht auch stack
-Eigenschaft.
Dafür gibt es ein tolles Node.js-Paket: serialize-error
.
Es behandelt auch verschachtelte Fehlerobjekte gut, was ich eigentlich in meinem Projekt sehr brauchte.
Keine der obigen Antworten schien Eigenschaften, die sich auf dem Prototyp von Error befinden, richtig zu serialisieren (da getOwnPropertyNames()
keine geerbten Eigenschaften enthält). Ich konnte die Eigenschaften auch nicht wie in einer der vorgeschlagenen Antworten neu 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}}}
Wir mussten eine beliebige Objekthierarchie serialisieren, wobei der Stamm oder eine der verschachtelten Eigenschaften in der Hierarchie Fehler sein könnte.
Unsere Lösung bestand darin, den Parameter replacer
von JSON.stringify()
zu verwenden, 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))