是否不能使用JSON.stringify对错误进行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删除所有函数,但随后意识到它也有一些奇怪的行为:

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对象。
  • Pull属性:JSON.stringify({ message: error.message, stack: error.stack })

更新

@Ray Toal在评论中建议我看看属性描述符. c。现在很清楚为什么它不起作用了:

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

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

175527 次浏览

你可以定义一个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()可能没有专门为__abc2定义,但这种方法仍然是标准化的一般为对象定义(参考:步骤3)。因此,冲突或冲突的风险是最小的。

不过,为了完全避免它,可以使用__ABC0的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));

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

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

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

可能还有stack属性。

JSON.stringify(err, Object.getOwnPropertyNames(err))

似乎有效

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

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


JSON.stringify(serializeError(error));

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

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

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

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

回答

JSON.stringify ()的文档中,

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

Error对象没有它的可枚举属性,这就是为什么它打印一个空对象。

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

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

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

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

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

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

只需将其转换为常规对象

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

如果你想从子进程、worker或者网络发送这个对象,就不需要字符串化。它将像任何其他正常对象一样自动进行字符串化和解析

如果使用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.

链接为文档,链接为示例用法。

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

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

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

我扩展了这个答案:是否不能使用JSON.stringify对错误进行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) };
}