重现问题

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

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

评论

您是否检查了错误对象中属性的属性描述符?

对我来说,问题是“为什么”,我发现答案在问题的底部。为您自己的问题发布答案没有错,这样您将获得更多的信誉。 :-)

serialize-error程序包将为您处理此问题:npmjs.com/package/serialize-error

#1 楼

您可以定义Error.prototype.toJSON来检索表示Object的普通Error:使用Object.defineProperty()

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"}
添加toJSON本身并不具有enumerable属性。


关于修改Error.prototype,虽然可能没有为toJSON()专门定义Error,但是该方法通常还是针对对象进行了标准化(参考:step 3)。因此,发生冲突或冲突的风险很小。

尽管为了完全避免冲突,可以使用JSON.stringify()replacer参数代替:

function replaceErrors(key, value) {
    if (value instanceof Error) {
        var error = {};

        Object.getOwnPropertyNames(value).forEach(function (key) {
            error[key] = value[key];
        });

        return error;
    }

    return value;
}

var error = new Error('testing');
error.detail = 'foo bar';

console.log(JSON.stringify(error, replaceErrors));


评论


如果使用.getOwnPropertyNames()而不是.keys(),则将获得不可枚举的属性,而无需手动定义它们。

–user2437417
13年8月22日在21:56

最好不要将其添加到Error.prototype中,否则在将来的JavaScrip版本中Error.prototype实际上具有toJSON函数时可能会引起问题。

–乔·德·琼(Jos de Jong)
2014年12月2日,11:34

小心!此解决方案打破了本机节点mongodb驱动程序中的错误处理:jira.mongodb.org/browse/NODE-554

–塞巴斯蒂安·诺瓦克(Sebastian Nowak)
16-2-25在15:34

万一有人注意自己的链接器错误和命名冲突:如果使用replacer选项,则应为函数replaceErrors(key,value)中的键选择一个不同的参数名称,以避免与.forEach(function(key){的命名冲突。 。});在此答案中未使用replaceErrors键参数。

–找不到404
17-4-20在22:58



在此示例中,键的阴影虽然被允许,但可能会造成混淆,因为它使人们怀疑作者是否打算引用外部变量。对于内部循环,propName将是一个更具表达性的选择。 (顺便说一句,我认为@ 404NotFound的意思是“ linter”(静态分析工具),而不是“ linker”。)在任何情况下,使用自定义替换函数都是一个很好的解决方案,因为它可以在一个适当的位置解决问题,并且不会改变本地/全局行为。

–雅各布
19-09-20在13:15



#2 楼

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


似乎可以工作

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

评论


梳理答案,JSON.stringify(err,Object.getOwnPropertyNames(err))

–felixfbecker
16年1月6日在13:47

这对于本机ExpressJS Error对象工作正常,但不适用于Mongoose错误。猫鼬错误具有ValidationError类型的嵌套对象。这不会对ValidationError类型的Mongoose错误对象中的嵌套错误对象进行字符串化处理。

–蒸汽动力
16年3月14日在22:49

这应该是答案,因为这是最简单的方法。

–欢
16-10-6在10:23

@felixfbecker只查找一级的属性名称。如果您有var spam = {a:1,b:{b:2,b2:3}};并运行Object.getOwnPropertyNames(spam),您将得到[“ a”,“ b”]-在这里具有欺骗性,因为b对象拥有它自己的b。您可能会在stringify通话中同时遇到这两个问题,但是会错过spam.b.b2。那很糟。

–松饼
17年6月2日在15:07

@ruffin是正确的,但它甚至是可取的。我认为OP想要的只是确保消息和堆栈包含在JSON中。

–felixfbecker
17年7月23日在15:37

#3 楼

由于没有人在谈论为什么原因,我要回答。

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

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


回答

从JSON.stringify()文档中,


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


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

评论


奇怪的是没有人打扰。只要修复有效,我就认为:)

–伊利亚·切尔诺莫迪克(Ilya Chernomordik)
18年8月6日在12:02

该答案的第一部分不正确。有一种使用其replacer参数使用JSON.stringify的方法。

–托德·查菲(Todd Chaffee)
19年6月1日在21:37



@ToddChaffee很好。我已经确定了答案。请检查并随时进行改进。谢谢。

–李圣贤
19年6月2日在9:20

#4 楼

修改乔纳森(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'));


评论


第一次我听到猴子打补丁:)

–克里斯·普林斯
16年6月18日在17:27

@ChrisPrince但这不是最后一次了,特别是在JavaScript中!这是有关猴子补丁的Wikipedia,仅供将来的人们参考。 (根据Chris的理解,在乔纳森的答案中,您正在直接向Error的原型添加JSON新功能,这通常不是一个好主意。也许已经有人对此进行了检查,但是您不知道要做什么。其他版本的版本。或者,如果有人意外获得了您的版本,或者假设Error的原型具有特定的属性,那么事情可能会变得很糟糕。)

–松饼
17年6月6日在15:22

很好,但是省略了错误堆栈(在控制台中显示)。不确定细节,如果这是与Vue相关或什么,只想提一下。

– phil294
19年3月13日在23:54



#5 楼

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

评论


否,但是可以进行翻译。看到这个评论。

–雅各布
19-09-20在13:59

这是正确的答案。序列化错误不是一个小问题,库的作者(一个出色的开发人员,拥有许多受欢迎的软件包)竭尽全力来处理极端情况,如README所示:“保留了自定义属性。不可枚举属性保持不可枚举(名称,消息,堆栈)。可枚举的属性保持枚举(除不可枚举的所有属性)。处理循环引用。”

– Dan Dascalescu
5月19日5:04



#6 楼

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

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


也许还有stack属性。

评论


不要更改您不拥有的对象,这可能会破坏应用程序的其他部分,并祝您好运。

–fregante
19 Mar 17 '19 4:58



#7 楼

我们需要序列化一个任意的对象层次结构,其中层次结构中的根或任何嵌套属性都可以是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)) 




#8 楼

我正在为日志追加程序处理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。

–lakshman.pasala
4月22日9:59

#9 楼

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


#10 楼

您可以使用普通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一起使用。

评论


那是一个很长的班轮!

– Ishan Madhusanka
9月30日下午5:43

哦,是的,它很漂亮。泪流满面,我感到非常自豪。

– savram
9月30日13:04

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

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