提问人:Jay 提问时间:8/23/2013 最后编辑:CommunityJay 更新时间:10/31/2023 访问量:208205
是否可以使用 JSON.stringify 字符串化错误?
Is it not possible to stringify an Error using 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。它显示原型包含 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 (?)
});
它似乎没有像往常一样循环对象,因此我无法检查键是否为函数并忽略它。
问题
有没有办法用 ?如果不是,为什么会发生这种行为?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
接受的答案提供了此问题的解决方法。
答:
您可以定义 a 来检索表示 :Error.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
关于修改,虽然可能没有专门针对 s 定义,但该方法对于一般对象仍然是标准化的(参考:步骤 3)。因此,碰撞或冲突的风险很小。Error.prototype
toJSON()
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));
评论
.getOwnPropertyNames()
.keys()
key
function replaceErrors(key, value)
.forEach(function (key) { .. })
replaceErrors
key
修改 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'));
评论
monkey patching
Error
的原型添加了一个新函数,这通常不是一个好主意。也许其他人已经有了,这会检查,但你不知道其他版本是做什么的。或者,如果有人意外地得到了你的,或者假设 Error 的原型具有特定的属性,事情可能会变得无聊。toJSON
也可以将这些不可枚举的属性重新定义为可枚举的属性。
Object.defineProperty(Error.prototype, 'message', {
configurable: true,
enumerable: true
});
也许还有财产。stack
评论
JSON.stringify(err, Object.getOwnPropertyNames(err))
似乎有效
[来自 /u/ub3rgeek 在 /r/javascript 上的评论]和 Felixfbecker 在下面的评论
另请参阅“Sanghyun Lee”的回答,以解释为什么需要这样做。
评论
JSON.stringify(err, Object.getOwnPropertyNames(err))
ValidationError
errors
ValidationError
spam.b.b2
。这很糟糕。var spam = { a: 1, b: { b: 2, b2: 3} };
Object.getOwnPropertyNames(spam)
["a", "b"]
b
b
message
stack
上面的答案似乎都没有正确序列化 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';
const stringifiedError = serializeError(error);
文档:https://www.npmjs.com/package/serialize-error
评论
serialize-error
JSON.stringify()
由于没有人在谈论为什么部分,我将回答它。
为什么这个 JSON.stringify
返回一个空对象?
> JSON.stringify(error);
'{}'
答
从 JSON.stringify() 的文档中,
对于所有其他 Object 实例(包括 Map、Set、WeakMap 和 WeakSet),将仅序列化其可枚举属性。
object 没有任何可枚举的属性,这就是它打印空对象的原因。Error
可枚举属性的背景
在 Javascript 中,对象可以有两种类型的属性:
- 可枚举属性
- 不可枚举的属性
确切的区别有点棘手,但基本上:
- “正常”属性,例如通过赋值 ( ) 创建的属性,是可枚举的,
myobj= {}; myobj.prop1 = 4711;
- “内部”属性(如数组的属性)是不可枚举的
length
具体而言,an 仅具有不可枚举的属性。Error
有关详细信息,请参阅 MDN 上的可枚举性和属性所有权。
评论
JSON.stringify
replacer
我们需要序列化任意对象层次结构,其中层次结构中的根或任何嵌套属性可能是 Error 的实例。
我们的解决方案是使用 ,例如:replacer
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))
评论
我正在为日志追加器研究 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
name
name: message: stacktrace
你可以用一个普通的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。
评论
只需转换为常规对象即可
// 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; }, {})
如果要从子进程、工作线程或通过网络发送此对象,则无需字符串化。它将像任何其他普通对象一样自动字符串化和解析
如果使用 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]'?
String 构造函数应该能够字符串化错误
try {
throw new Error("MY ERROR MSG")
} catch (e) {
String(e) // returns 'Error: MY ERROR MSG'
}
评论
我已经扩展了这个答案:是否可以使用 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) };
}
通常我声明一次:
const cloneError = (err) => {
return err ? { name: err.name, message: err.message, stack: err.stack, cause: err.cause } : {};
};
然后我可以在任何地方使用它,例如:
...logger.log('An error occurred:', cloneError(err));
评论
serialize-error