重现问题

我遇到了一个问题时,试图传递错误消息周围使用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。

公认的答案为这个问题提供了一个变通办法。


当前回答

我正在为日志追加器编写JSON格式,最后在这里尝试解决类似的问题。过了一段时间,我意识到我可以让Node来做这项工作:

const util = require("util");
...
return JSON.stringify(obj, (name, value) => {
    if (value instanceof Error) {
        return util.format(value);
    } else {
        return value;
    }
}

其他回答

上面的答案似乎都没有正确地序列化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}}}  

对此,有一个很棒的Node.js包:serialize-error。

npm install serialize-error

它甚至可以很好地处理嵌套的Error对象。

import {serializeError} from 'serialize-error';

const stringifiedError = serializeError(error);

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

你可以在纯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);

它也适用于domexception。

字符串构造函数应该能够stringify错误

try { 
  throw new Error("MY ERROR MSG")
} catch (e) {
  String(e) // returns 'Error: MY ERROR MSG'
}

您还可以将那些不可枚举的属性重新定义为可枚举的。

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

也许还有堆栈属性。