重现问题
我遇到了一个问题时,试图传递错误消息周围使用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删除所有函数,但随后意识到它也有一些奇怪的行为:
var error = new Error('simple error message');
JSON.stringify(error, function(key, value) {
console.log(key === ''); // true (?)
console.log(value === error); // true (?)
});
它似乎不像通常那样遍历对象,因此我不能检查键是否为函数并忽略它。
这个问题
有什么方法来stringify本机错误消息与JSON.stringify?如果不是,为什么会发生这种行为?
解决这个问题的方法
坚持使用简单的基于字符串的错误消息,或者创建个人错误对象,不要依赖于本机error对象。
拉取属性:JSON。Stringify({消息:错误。消息,堆栈:错误。栈})
更新
@Ray Toal在评论中建议我看一下属性描述符。现在很清楚为什么它不起作用了:
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。
公认的答案为这个问题提供了一个变通办法。
你可以定义一个Error.prototype. tojson来检索一个表示错误的普通对象:
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()添加到json中,而它本身不是一个可枚举的属性。
关于修改Error。虽然toJSON()可能没有专门为Errors定义,但该方法仍然是一般对象的标准化方法(参考:步骤3)。因此,碰撞或冲突的风险是最小的。
但是,为了完全避免它,可以使用JSON.stringify()的替换参数:
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));
上面的答案似乎都没有正确地序列化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}}}