重现问题
我在尝试使用 Web 套接字传递错误消息时遇到了问题。我可以使用 JSON.stringify
复制我面临的问题以迎合更广泛的受众:
// 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);
'{}'
问题是我最终得到了一个空对象。
我试过的
浏览器
我首先尝试离开 node.js 并在各种浏览器中运行它。 Chrome 版本 28 给了我同样的结果,有趣的是,Firefox 至少做了一次尝试,但遗漏了这条信息:
>>> JSON.stringify(error); // Firebug, Firefox 23
{"fileName":"debug eval code","lineNumber":1,"stack":"@debug eval code:1\n"}
替换功能
然后我查看了Error.prototype。它表明原型包含 toString 和 toSource 等方法。知道函数不能被字符串化,我在调用 JSON.stringify 以删除所有函数时包含了一个 replacer function,但后来意识到它也有一些奇怪的行为:
var error = new Error('simple error message');
JSON.stringify(error, function(key, value) {
console.log(key === ''); // true (?)
console.log(value === error); // true (?)
});
它似乎没有像通常那样循环对象,因此我无法检查键是否是函数并忽略它。
问题
有没有办法用 JSON.stringify
对原生错误消息进行字符串化?如果不是,为什么会发生这种行为?
解决这个问题的方法
坚持使用简单的基于字符串的错误消息,或者创建个人错误对象而不依赖本机错误对象。
拉取属性:JSON.stringify({ message: error.message, stack: error.stack })
更新
@Ray Toal 在评论中建议我看看 property descriptors。现在很清楚为什么它不起作用:
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);
}
输出:
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 }
键:enumerable: false
。
接受的答案提供了解决此问题的方法。
serialize-error
包为您处理此问题:npmjs.com/package/serialize-error
JSON.stringify(err, Object.getOwnPropertyNames(err))
似乎工作
[from a comment by /u/ub3rgeek on /r/javascript] 和下面 felixfbecker 的评论
您可以定义 Error.prototype.toJSON
来检索表示 Error
的普通 Object
:
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"}
使用 Object.defineProperty()
添加 toJSON
而不是 enumerable
属性本身。
关于修改 Error.prototype
,而 toJSON()
可能没有专门为 Error
定义,the method is still standardized 一般用于对象(参考:步骤 3)。因此,碰撞或冲突的风险很小。
不过,为了完全避免它,可以使用 JSON.stringify()
's replacer
parameter 代替:
function replaceErrors(key, value) {
if (value instanceof Error) {
var error = {};
Object.getOwnPropertyNames(value).forEach(function (propName) {
error[propName] = value[propName];
});
return error;
}
return value;
}
var error = new Error('testing');
error.detail = 'foo bar';
console.log(JSON.stringify(error, replaceErrors));
.getOwnPropertyNames()
而不是 .keys()
,您将获得不可枚举的属性,而无需手动定义它们。
function replaceErrors(key, value)
中的 key
选择不同的参数名称,以避免与 .forEach(function (key) { .. })
命名冲突;此答案中未使用 replaceErrors
key
参数。
由于没有人谈论为什么部分,我会回答它。
为什么这个 JSON.stringify
返回一个空对象?
> JSON.stringify(error);
'{}'
回答
从JSON.stringify()的文档中,
对于所有其他 Object 实例(包括 Map、Set、WeakMap 和 WeakSet),只有它们的可枚举属性会被序列化。
并且 Error
对象没有它的可枚举属性,这就是它打印一个空对象的原因。
JSON.stringify
的 replacer
参数。
为此有一个很棒的 Node.js 包:serialize-error
。
npm install serialize-error
它甚至可以很好地处理嵌套的错误对象。
import {serializeError} from 'serialize-error';
JSON.stringify(serializeError(error));
文档:https://www.npmjs.com/package/serialize-error
serialize-error
包文档开始 - 我认为不需要 JSON.stringify()
部分。
修改乔纳森的最佳答案以避免猴子修补:
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'));
monkey patching
:)
toJSON
,直接添加到 Error
的原型,这通常不是一个好主意。也许其他人已经有,这会检查,但是你不知道其他版本做了什么。或者如果有人意外地得到你的,或者假设错误的原型具有特定的属性,事情可能会失败。)
我们需要序列化任意对象层次结构,其中根或层次结构中的任何嵌套属性都可能是 Error 的实例。
我们的解决方案是使用 JSON.stringify()
的 replacer
参数,例如:
function jsonFriendlyErrorReplacer(key, value) { if (value instanceof Error) { return { // 拉取所有可枚举属性,支持自定义 Errors 上的属性 ...value, // 显式拉取 Error 的不可枚举属性 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))
我正在为日志附加程序研究 JSON 格式,并最终在这里尝试解决类似的问题。过了一会儿,我意识到我可以让 Node 完成这项工作:
const util = require("util");
...
return JSON.stringify(obj, (name, value) => {
if (value instanceof Error) {
return util.format(value);
} else {
return value;
}
}
instanceof
而不是 instanceOf
。
stack
属性。
name
参数。修复后,此解决方案仍将所有属性串在一起。 name: message: stacktrace
。如果您想保留错误对象的结构并防止其他一些问题(记录缓冲区、循环引用等),serialize-error 包似乎更合适。
您也可以将那些不可枚举的属性重新定义为可枚举的。
Object.defineProperty(Error.prototype, 'message', {
configurable: true,
enumerable: true
});
也许还有 stack
属性。
上面的答案似乎都没有正确序列化 Error 原型上的属性(因为 getOwnPropertyNames()
不包括继承的属性)。我也无法像建议的答案之一那样重新定义属性。
这是我提出的解决方案 - 它使用 lodash,但您可以将 lodash 替换为这些函数的通用版本。
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);
}
这是我在 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}}}
如果使用 nodejs,则使用本机 nodejs inspect
有更好的可靠方法。您还可以指定将对象打印到无限深度。
打字稿示例:
import { inspect } from "util";
const myObject = new Error("This is error");
console.log(JSON.stringify(myObject)); // Will print {}
console.log(myObject); // Will print full error object
console.log(inspect(myObject, {depth: null})); // Same output as console.log plus it works as well for objects with many nested properties.
并且在主题 How can I get the full object in Node.js's console.log(), rather than '[Object]'?
here 中讨论了堆栈溢出。
只需转换为常规对象
// example error
let err = new Error('I errored')
// one liner converting Error into regular object that can be stringified
err = Object.getOwnPropertyNames(err).reduce((acc, key) => { acc[key] = err[key]; return acc; }, {})
如果您想从子进程、工作人员或通过网络发送此对象,则无需进行字符串化。它将像任何其他普通对象一样自动字符串化和解析
您可以使用纯 JavaScript 中的单行( errStringified )解决此问题:
var error = new Error('simple error message');
var errStringified = (err => JSON.stringify(Object.getOwnPropertyNames(Object.getPrototypeOf(err)).reduce(function(accumulator, currentValue) { return accumulator[currentValue] = err[currentValue], accumulator}, {})))(error);
console.log(errStringified);
它也适用于 DOMExceptions。
字符串构造函数应该能够字符串化错误
try {
throw new Error("MY ERROR MSG")
} catch (e) {
String(e) // returns 'Error: MY ERROR MSG'
}
我扩展了这个答案:Is it not possible to stringify an Error using JSON.stringify?
序列化错误.ts
export function serializeError(err: unknown) {
return JSON.parse(JSON.stringify(err, Object.getOwnPropertyNames(err)))
}
我可以这样使用它:
import { serializeError } from '../helpers/serializeError'; // Change to your path
try {
const res = await create(data);
return { status: 201 };
} catch (err) {
return { status: 400, error: serializeError(err) };
}
JSON.stringify(err, Object.getOwnPropertyNames(err))
ValidationError
类型的嵌套对象。这不会对ValidationError
类型的 Mongoose 错误对象中的嵌套errors
对象进行字符串化。var spam = { a: 1, b: { b: 2, b2: 3} };
并运行Object.getOwnPropertyNames(spam)
,您将得到["a", "b"]
- 这里具有欺骗性,因为b
对象有它自己的b
。你会在你的 stringify 调用中得到这两个,但是你会错过spam.b.b2
。那很糟。message
和stack
包含在 JSON 中。