是否可以使用 JSON.stringify 字符串化错误?

Is it not possible to stringify an Error using JSON.stringify?

提问人:Jay 提问时间:8/23/2013 最后编辑:CommunityJay 更新时间:10/31/2023 访问量:208205

问:

重现问题

我在尝试使用 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

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

JavaScript JSON 节点 .js 错误处理

评论

6赞 Ray Toal 8/23/2013
您是否检查了错误对象中属性的属性描述符?
5赞 Michael Scheper 12/1/2014
对我来说,问题是“为什么”,我发现答案就在问题的底部。为自己的问题发布答案并没有错,这样你可能会得到更多的信任。:-)
1赞 tim-phillips 8/25/2020
该软件包会为您处理此问题:npmjs.com/package/serialize-errorserialize-error

答:

250赞 Jonathan Lonowski 8/23/2013 #1

您可以定义 a 来检索表示 :Error.prototype.toJSONObjectError

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() 添加它本身不是属性。toJSONenumerable


关于修改,虽然可能没有专门针对 s 定义,但该方法对于一般对象仍然是标准化的(参考:步骤 3)。因此,碰撞或冲突的风险很小。Error.prototypetoJSON()Error

不过,为了完全避免这种情况,可以使用 JSON.stringify()replacer 参数来代替:

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

评论

4赞 8/23/2013
如果使用 instead of ,则无需手动定义即可获得不可枚举的属性。.getOwnPropertyNames().keys()
9赞 Jos de Jong 12/2/2014
最好不要将其添加到 Error.prototype 中,当 Error.prototype 在未来版本的 JavaScrip 中实际上具有 toJSON 函数时,可能会出现问题。
5赞 Sebastian Nowak 2/25/2016
小心!此解决方案中断了本机节点 mongodb 驱动程序中的错误处理:jira.mongodb.org/browse/NODE-554
5赞 404 Not Found 4/21/2017
如果有人注意他们的链接器错误和命名冲突:如果使用 replacer 选项,您应该为 in 选择不同的参数名称以避免与 ;该参数在此答案中未使用。keyfunction replaceErrors(key, value).forEach(function (key) { .. })replaceErrorskey
2赞 jacobq 9/20/2019
这个例子中的阴影虽然是允许的,但可能会造成混淆,因为它让人怀疑作者是否打算引用外部变量。 对于内循环来说,将是一个更具表现力的选择。(顺便说一句,我认为@404NotFound是指“linter”(静态分析工具)而不是“linker”)无论如何,使用自定义函数都是一个很好的解决方案,因为它可以在一个适当的位置解决问题,并且不会改变本机/全局行为。keypropNamereplacer
69赞 Bryan Larsen 12/6/2013 #2

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

评论

10赞 Chris Prince 6/19/2016
我第一次听说:)monkey patching
2赞 ruffin 6/6/2017
@ChrisPrince 但这不会是最后一次,尤其是在 JavaScript 中!这里是关于猴子补丁的维基百科,仅供未来参考。(在 Jonathan 的回答中,正如 Chris 所理解的那样,你直接向 Error 的原型添加了一个新函数,这通常不是一个好主意。也许其他人已经有了,这会检查,但你不知道其他版本是做什么的。或者,如果有人意外地得到了你的,或者假设 Error 的原型具有特定的属性,事情可能会变得无聊。toJSON
1赞 phil294 3/14/2019
这很好,但省略了错误的堆栈(显示在控制台中)。不确定细节,这是否与 Vue 相关还是什么,只是想提一下这个。
9赞 cheolgook 7/16/2014 #3

也可以将这些不可枚举的属性重新定义为可枚举的属性。

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

也许还有财产。stack

评论

21赞 fregante 3/17/2019
不要更改你不拥有的对象,它可能会破坏你的应用程序的其他部分,祝你好运找到原因。
482赞 3 revs, 3 users 67%laggingreflex #4
JSON.stringify(err, Object.getOwnPropertyNames(err))

似乎有效

[来自 /u/ub3rgeek 在 /r/javascript 上的评论]和 Felixfbecker 在下面的评论

另请参阅“Sanghyun Lee”的回答,以解释为什么需要这样做。

评论

75赞 felixfbecker 1/6/2016
梳理答案,JSON.stringify(err, Object.getOwnPropertyNames(err))
10赞 steampowered 3/15/2016
这适用于本机 ExpressJS Error 对象,但不适用于 Mongoose 错误。猫鼬错误具有类型的嵌套对象。这不会将 Mongoose 错误对象中的嵌套对象字符串化。ValidationErrorerrorsValidationError
5赞 Huan 10/6/2016
这应该是答案,因为这是最简单的方法。
12赞 ruffin 6/2/2017
@felixfbecker 这只会查找一个级别的属性名称。如果你有 和 run ,你会得到 -- 这里是欺骗性的,因为对象有它自己的 。你会在 stringify 调用中得到两者,但你会错过 spam.b.b2。这很糟糕。var spam = { a: 1, b: { b: 2, b2: 3} };Object.getOwnPropertyNames(spam)["a", "b"]bb
3赞 felixfbecker 7/23/2017
@ruffin这是真的,但它甚至可能是可取的。我认为 OP 想要的只是确保并包含在 JSON 中。messagestack
6赞 Elliott Palermo 3/31/2017 #5

上面的答案似乎都没有正确序列化 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}}}  
92赞 Lukasz Czerwinski 11/9/2017 #6

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

评论

0赞 jacobq 9/20/2019
不可以,但可以进行转译。请参阅此评论
19赞 Dan Dascalescu 5/19/2020
这是正确的答案。序列化错误不是一个微不足道的问题,该库的作者(一个优秀的开发人员,拥有许多非常受欢迎的软件包)不遗余力地处理边缘情况,正如自述文件中所示:“保留了自定义属性。不可枚举的属性保持不可枚举(名称、消息、堆栈)。可枚举属性保持可枚举(除不可枚举属性之外的所有属性)。处理循环引用。
2赞 ps2goat 7/13/2021
@DanDascalescu谢谢,这是一个很棒的套餐!它适用于 Errors 的嵌套属性,替换缓冲区,并删除循环引用异常!这应该是答案。
2赞 Richard Trembecký 3/8/2022
截至软件包文档 - 我认为不需要该部分。serialize-errorJSON.stringify()
225赞 Sanghyun Lee 6/7/2018 #7

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

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

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

JSON.stringify() 的文档中,

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

object 没有任何可枚举的属性,这就是它打印空对象的原因。Error

可枚举属性的背景

在 Javascript 中,对象可以有两种类型的属性:

  • 可枚举属性
  • 不可枚举的属性

确切的区别有点棘手,但基本上:

  • “正常”属性,例如通过赋值 ( ) 创建的属性,是可枚举的,myobj= {}; myobj.prop1 = 4711;
  • “内部”属性(如数组的属性)是不可枚举的length

具体而言,an 具有不可枚举的属性。Error

有关详细信息,请参阅 MDN 上的可枚举性和属性所有权

评论

24赞 Ilya Chernomordik 8/6/2018
奇怪的是,甚至没有人打扰。只要修复有效,我就假设:)
2赞 Todd Chaffee 6/2/2019
这个答案的第一部分是不正确的。有一种方法可以使用其参数。JSON.stringifyreplacer
2赞 Sanghyun Lee 6/2/2019
@ToddChaffee这是一个很好的观点。我已经解决了我的答案。请检查它并随时改进它。谢谢。
9赞 user3700562 5/21/2021
不过,这并不能真正回答这个问题。为什么决定使 Error 对象属性不可枚举?这背后的理由是什么?这是不一致的,令人困惑的,并且又是一个需要注意的JS坑洼,好像还不够。
14赞 Joel Malone 12/5/2018 #8

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

我们的解决方案是使用 ,例如:replacerJSON.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))

评论

1赞 Alan Grainger 10/17/2023
在你发帖 5 年后,我不得不滚动浏览答案才能找到这个答案,但伙计,这是一个很好的解决方案!完美无缺。
13赞 Jason 2/24/2020 #9

我正在为日志追加器研究 JSON 格式,最终在这里试图解决类似的问题。过了一会儿,我意识到我可以让 Node 完成这项工作:

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

评论

1赞 lakshman.pasala 4/22/2020
它应该是而不是.instanceofinstanceOf
0赞 ps2goat 7/10/2021
看起来这只设置消息的格式,而不设置其他属性的格式。使用它时,我因错误而丢失了该属性。stack
0赞 Jason 7/13/2021
我已经在节点 6 到 16 上尝试过这个。我在所有这些中都得到了堆栈跟踪,@ps2goat您使用的是什么版本?
0赞 ps2goat 7/13/2021
我正在使用 16.0.4,但我发现了我的问题。我错过了格式化程序函数的参数。修复后,此解决方案仍会将所有属性串在一起。.如果您想保留错误对象的结构并防止其他一些问题(日志记录缓冲区、循环引用等),serialize-error 包似乎更适合namename: message: stacktrace
0赞 savram 9/16/2020 #10

你可以用一个普通的javascript中的one-line(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

评论

3赞 Ishan Madhusanka 9/30/2020
那是一句很长的单行字!
1赞 savram 9/30/2020
哦,是的,它很漂亮。让我热泪盈眶,我感到非常自豪。
4赞 Pawel 11/7/2020 #11

只需转换为常规对象即可

// 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; }, {})

如果要从子进程、工作线程或通过网络发送此对象,则无需字符串化。它将像任何其他普通对象一样自动字符串化和解析

10赞 David Navrkal 6/3/2021 #12

如果使用 nodejs,则使用原生 nodejs 有更可靠的方法。此外,您还可以指定将对象打印为无限深度。inspect

Typescript 示例:

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]'?

7赞 Deep Panchal 1/6/2022 #13

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

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

评论

2赞 Moebius 3/15/2022
我不知道你为什么被否决,你的回答对我有用。
3赞 Elron 6/22/2022 #14

我已经扩展了这个答案:是否可以使用 JSON.stringify 字符串化错误?

serializeError.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) };
}
1赞 gvlax 10/11/2023 #15

通常我声明一次:

  const cloneError = (err) => {
    return err ? { name: err.name, message: err.message, stack: err.stack, cause: err.cause } : {};
  };

然后我可以在任何地方使用它,例如:

...logger.log('An error occurred:', cloneError(err));