使用JSON.stringify无法将错误字符串化吗?


330

重现问题

尝试使用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吗?如果没有,为什么会出现这种现象?

解决这个问题的方法

  • 坚持使用基于字符串的简单错误消息,或者创建个人错误对象,而不依赖于本地错误对象。
  • 拉属性: 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

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


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

3
对我来说,问题是“为什么”,我发现答案在问题的底部。为您自己的问题发布答案没有错,这样您可能会得到更多的信任。:-)
Michael Scheper 2014年

Answers:


178

您可以定义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本身作为属性。


关于修改Error.prototype,尽管toJSON()可能没有Error专门针对它进行定义,但通常该方法仍针对对象进行了标准化(参考:步骤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));

3
如果使用.getOwnPropertyNames()代替.keys(),则将获得非枚举属性,而无需手动定义它们。

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

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

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

2
key在允许的情况下,此示例中的阴影可能会造成混淆,因为这使作者不确定是否打算引用外部变量。propName对于内循环,这将是一个更具表现力的选择。(顺便说一句,我认为@ 404NotFound的意思是 linter (静态分析工具),而不是“ linker”。)无论如何,使用自定义replacer函数是一个很好的解决方案,因为它可以在一个适当的位置解决问题,并且不会改变本机/ global行为。
iX3

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

似乎有效

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


57
梳理答案,JSON.stringify(err, Object.getOwnPropertyNames(err))
felixfbecker 2016年

5
这对于本机ExpressJS Error对象工作正常,但不适用于Mongoose错误。猫鼬错误为ValidationError类型嵌套了对象。这不会将嵌套errors对象字符串化为类型为Mongoose的错误对象ValidationError
蒸汽动力

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

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

1
@ruffin是正确的,但它甚至是可取的。我认为OP想要的只是确保它messagestack包含在JSON中。
felixfbecker

74

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

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

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

回答

JSON.stringify()的文档中,

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

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


4
奇怪的是没有人打扰。只要修复工作我都认为:)
Ilya Chernomordik '18

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

1
@ToddChaffee很好。我已经解决了。请检查并随时进行改进。谢谢。
Sanghyun Lee,

52

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

3
第一次我听到monkey patching:)
克里斯·普林斯

2
@ChrisPrince但这不是最后一次了,特别是在JavaScript中!这是有关猴子补丁的Wikipedia ,仅供将来的人们参考。(在乔纳森的回答,克里斯明白,你添加新的功能,toJSON直接Error的原型,这往往不是一个好主意。也许别人已经有了,此检查,但你不知道是什么其他版本的版本。或者,如果有人意外获得了您的版本,或者假定Error的原型具有特定的属性,则可能会出错。)
ruffin

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

23

有一个伟大的Node.js包为:serialize-error

它甚至可以很好地处理嵌套的Error对象,这实际上是我在项目中急需的。

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


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

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

9

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

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

也许还有stack财产。


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

7

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


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

2

我正在为日志追加程序处理JSON格式,最终在这里试图解决类似的问题。过了一会儿,我意识到我可以让Node来工作:

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

1
应该是instanceof,不是instanceOf
lakshman.pasala
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.