ChatGPT解决这个技术问题 Extra ChatGPT

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

重现问题

我在尝试使用 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 以删除所有函数时包含了一个 replacer function,但后来意识到它也有一些奇怪的行为:

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

它似乎没有像通常那样循环对象,因此我无法检查键是否是函数并忽略它。

问题

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

解决这个问题的方法

坚持使用简单的基于字符串的错误消息,或者创建个人错误对象而不依赖本机错误对象。

拉取属性:JSON.stringify({ message: error.message, stack: error.stack })

更新

@Ray Toal 在评论中建议我看看 property descriptors。现在很清楚为什么它不起作用:

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

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

您是否检查了错误对象中属性的属性描述符?
我的问题是“为什么”,我发现答案就在问题的底部。为您自己的问题发布答案并没有错,而且您可能会以这种方式获得更多的信任。 :-)
serialize-error 包为您处理此问题:npmjs.com/package/serialize-error

2
2 revs, 2 users 80%
JSON.stringify(err, Object.getOwnPropertyNames(err))

似乎工作

[from a comment by /u/ub3rgeek on /r/javascript] 和下面 felixfbecker 的评论


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

您可以定义 Error.prototype.toJSON 来检索表示 Error 的普通 Object

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 定义,the method is still standardized 一般用于对象(参考:步骤 3)。因此,碰撞或冲突的风险很小。

不过,为了完全避免它,可以使用 JSON.stringify()'s replacer parameter 代替:

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

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

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

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

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

回答

JSON.stringify()的文档中,

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

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


奇怪的是,甚至没有人打扰。只要修复工作我认为:)
这个答案的第一部分是不正确的。有一种方法可以使用 JSON.stringifyreplacer 参数。
@ToddChaffee 这是一个很好的观点。我已经确定了我的答案。请检查并随时改进。谢谢。
但这并不能真正回答问题。为什么决定让 Error 对象的属性不可枚举?这背后的理由是什么?这是不一致的、令人困惑的,也是另一个需要注意的 JS 坑,好像还不够。
V
Viacheslav Dobromyslov

为此有一个很棒的 Node.js 包:serialize-error

npm install serialize-error

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

import {serializeError} from 'serialize-error';

JSON.stringify(serializeError(error));

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


不,但它可以被转译为这样做。请参阅this comment
这是正确的答案。序列化错误不是一个微不足道的问题,库的作者(拥有许多非常受欢迎的软件包的优秀开发人员)竭尽全力处理边缘情况,如自述文件中所示:“保留自定义属性。不可枚举属性保持不可枚举(名称、消息、堆栈)。可枚举属性保持可枚举(除了不可枚举的所有属性)。循环引用被处理。
@DanDascalescu 谢谢,这是一个很棒的包!它适用于错误的嵌套属性,替换缓冲区并删除循环引用异常!这应该是答案。
serialize-error 包文档开始 - 我认为不需要 JSON.stringify() 部分。
B
Bryan Larsen

修改乔纳森的最佳答案以避免猴子修补:

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

我第一次听到monkey patching :)
@ChrisPrince 但这不会是最后一次,尤其是在 JavaScript 中!这是 Monkey Patching 上的 Wikipedia,仅供未来人们参考。 (正如 Chris 所理解的,在 Jonathan's answer 中,您正在添加一个新函数 toJSON直接添加到 Error 的原型,这通常不是一个好主意。也许其他人已经有,这会检查,但是你不知道其他版本做了什么。或者如果有人意外地得到你的,或者假设错误的原型具有特定的属性,事情可能会失败。)
这很好,但省略了错误堆栈(显示在控制台中)。不确定细节,如果这是与 Vue 相关的还是什么,只想提一下。
J
Joel Malone

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

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

function jsonFriendlyErrorReplacer(key, value) { if (value instanceof Error) { return { // 拉取所有可枚举属性,支持自定义 Errors 上的属性 ...value, // 显式拉取 Error 的不可枚举属性 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))


J
Jason

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

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

它应该是 instanceof 而不是 instanceOf
看起来这只会格式化消息,而不是其他属性。使用它时,我的错误中缺少 stack 属性。
我已经在节点 6 到 16 上尝试过这个。我在所有节点中都得到了堆栈跟踪,@ps2goat 你使用的是什么版本?
我正在使用 16.0.4,但我发现了我的问题。我错过了格式化程序函数的 name 参数。修复后,此解决方案仍将所有属性串在一起。 name: message: stacktrace。如果您想保留错误对象的结构并防止其他一些问题(记录缓冲区、循环引用等),serialize-error 包似乎更合适。
c
cheolgook

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

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

也许还有 stack 属性。


不要更改您不拥有的对象,它可能会破坏您应用程序的其他部分,祝您好运找到原因。
E
Elliott Palermo

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

D
David Navrkal

如果使用 nodejs,则使用本机 nodejs inspect 有更好的可靠方法。您还可以指定将对象打印到无限深度。

打字稿示例:

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.

Link 到文档,link 到示例用法。

并且在主题 How can I get the full object in Node.js's console.log(), rather than '[Object]'? here 中讨论了堆栈溢出。


P
Pawel

只需转换为常规对象

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

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


s
savram

您可以使用纯 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);

它也适用于 DOMExceptions


那是一条很长的单线!
哦,是的,它只是美丽。泪流满面,我很自豪。
D
Deep Panchal

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

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

我不知道您为什么被否决,您的回答对我很有用。
E
Elron

我扩展了这个答案:Is it not possible to stringify an Error using JSON.stringify?

序列化错误.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) };
}