重现问题

我遇到了一个问题时,试图传递错误消息周围使用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.stringify(err, Object.getOwnPropertyNames(err))

似乎有效

[来自/u/ub3rgeek对/r/javascript的评论]和下面felixfbecker的评论

其他回答

你可以定义一个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));
JSON.stringify(err, Object.getOwnPropertyNames(err))

似乎有效

[来自/u/ub3rgeek对/r/javascript的评论]和下面felixfbecker的评论

我们需要序列化任意对象层次结构,其中层次结构中的根或任何嵌套属性都可能是Error的实例。

我们的解决方案是使用JSON.stringify()的替换参数,例如:

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

对此,有一个很棒的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

修改Jonathan的回答以避免猴子补丁:

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