是否无法使用 JSON.stringify 将错误字符串化?

IT技术 javascript json node.js error-handling
2021-02-05 19:38:45

重现问题

我在尝试使用 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它表明原型包含诸如toStringtoSource 之类的方法明知功能不能被字符串化,我包括一个替代品函数调用JSON.stringify时卸下的所有功能,但后来意识到它也有一些怪异的行为:

var error = new Error('simple error message');
JSON.stringify(error, function(key, value) {
    console.log(key === ''); // true (?)
    console.log(value === error); // true (?)
});

它似乎不像往常那样循环遍历对象,因此我无法检查键是否为函数并忽略它。

问题

有没有办法用字符串化本机错误消息JSON.stringify如果不是,为什么会发生这种行为?

解决这个问题的方法

  • 坚持使用简单的基于字符串的错误消息,或创建个人错误对象,不要依赖本机 Error 对象。
  • 拉属性: JSON.stringify({ message: error.message, stack: error.stack })

更新

@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

接受的答案提供了解决此问题的方法。

6个回答
JSON.stringify(err, Object.getOwnPropertyNames(err))

似乎工作

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

这应该是答案,因为这是最简单的方法。
2021-03-27 19:38:45
梳理答案, JSON.stringify(err, Object.getOwnPropertyNames(err))
2021-04-04 19:38:45
@ruffin 这是真的,但它甚至可能是可取的。我认为 OP 想要的只是确保messagestack包含在 JSON 中。
2021-04-05 19:38:45
这适用于原生 ExpressJS 错误对象,但不适用于 Mongoose 错误。Mongoose 错误具有嵌套的ValidationError类型对象这不会字符串化errors类型为 Mongoose 错误对象中的嵌套对象ValidationError
2021-04-06 19:38:45
@felixfbecker 只查找一级深的属性名称如果你有var spam = { a: 1, b: { b: 2, b2: 3} };并运行Object.getOwnPropertyNames(spam),你会得到["a", "b"]- 在这里具有欺骗性,因为b对象有它自己的b. 你会在你的 stringify 调用中得到两者,但你会错过spam.b.b2. 那很糟。
2021-04-08 19:38:45

您可以定义 aError.prototype.toJSON来检索Object表示 的文本Error

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专门s 定义,但该方法通常仍然针对对象进行标准化(参考:步骤 3)。因此,碰撞或冲突的风险很小。

但是,为了仍然完全避免它,可以使用JSON.stringify()'sreplacer参数代替:

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.prototype 中,当在 JavaScrip 的未来版本中 Error.prototype 实际上有一个 toJSON 函数时会出现问题。
2021-03-11 19:38:45
如果有人注意到他们的链接器错误和命名冲突:如果使用替换选项,你应该为keyin选择不同的参数名称function replaceErrors(key, value)以避免命名冲突.forEach(function (key) { .. })replaceErrors key此答案中未使用参数。
2021-03-24 19:38:45
key本示例中的阴影虽然允许,但可能令人困惑,因为它让人怀疑作者是否打算引用外部变量。propName将是内循环更具表现力的选择。(顺便说一句,我认为@404NotFound 的意思是 linter (静态分析工具)而不是“链接器”)无论如何,使用自定义replacer函数是一个很好的解决方案,因为它可以在一个适当的地方解决问题,并且不会改变本机/全局行为。
2021-03-25 19:38:45
小心!此解决方案破坏了本机节点 mongodb 驱动程序中的错误处理:jira.mongodb.org/browse/NODE-554
2021-03-30 19:38:45
如果使用.getOwnPropertyNames()代替.keys(),您将获得不可枚举的属性,而无需手动定义它们。
2021-04-09 19:38:45

由于没有人在谈论为什么部分,我将回答它。

为什么这会JSON.stringify返回一个空对象?

> JSON.stringify(error);
'{}'

回答

JSON.stringify()的文档中

对于所有其他 Object 实例(包括 Map、Set、WeakMap 和 WeakSet),只会序列化它们的可枚举属性。

并且Errorobject 没有其可枚举的属性,这就是它打印空对象的原因。

奇怪的是没人理会。只要修复工作,我假设:)
2021-03-19 19:38:45
这个答案的第一部分不正确。有一种方法可以使用JSON.stringify它的replacer参数。
2021-03-22 19:38:45
@ToddChaffee 这是一个很好的观点。我已经修复了我的答案。请检查它并随时改进它。谢谢。
2021-03-30 19:38:45
但这并不能真正回答问题。为什么当时决定使 Error 对象属性不可枚举?这背后的原理是什么?这是不一致的,令人困惑的,还有另一个需要注意的 JS 坑,好像还不够。
2021-04-04 19:38:45

修改乔纳森的好答案以避免猴子补丁:

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'));
@ChrisPrince 但这不会是最后一次,尤其是在 JavaScript 中!这是关于Monkey Patching的维基百科,仅供未来人们的信息。(在乔纳森的回答,克里斯明白,你添加新的功能,toJSON直接Error的原型,这往往不是一个好主意。也许别人已经有了,此检查,但你不知道是什么其他版本可以。或者如果有人意外得到你的,或者假设 Error 的原型具有特定属性,事情可能会很糟糕。)
2021-03-15 19:38:45
这很好,但省略了错误堆栈(显示在控制台中)。不确定细节,如果这是与Vue相关的还是什么,只想提一下。
2021-03-29 19:38:45
第一次听说monkey patching:)
2021-03-30 19:38:45

有一个伟大的Node.js包为:serialize-error

npm install serialize-error

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

import {serializeError} from 'serialize-error';

JSON.stringify(serializeError(error));

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

不,但它可以被转换为这样做。看到这个评论
2021-03-24 19:38:45
@DanDascalescu 谢谢,这是一个很棒的包!它适用于错误的嵌套属性,替换缓冲区并删除循环引用异常!这应该是答案。
2021-03-31 19:38:45
是正确答案。序列化错误不是一个小问题,库的作者(拥有许多非常受欢迎的软件包的优秀开发者)竭尽全力处理边缘情况,如自述文件中所示:“自定义属性被保留。不可枚举属性保持不可枚举(名称、消息、堆栈)。可枚举属性保持可枚举(除不可枚举属性之外的所有属性)。循环引用被处理。”
2021-04-07 19:38:45