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

Jay*_*com 289 javascript error-handling json node.js

再现问题

尝试使用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);
    '{}'
Run Code Online (Sandbox Code Playgroud)

问题是我最终得到一个空对象.

我试过的

浏览器

我首先尝试离开node.js并在各种浏览器中运行它.Chrome版本28给了我相同的结果,有趣的是,Firefox至少做了一次尝试,但遗漏了消息:

>>> JSON.stringify(error); // Firebug, Firefox 23
{"fileName":"debug eval code","lineNumber":1,"stack":"@debug eval code:1\n"}
Run Code Online (Sandbox Code Playgroud)

替换功能

然后我查看了Error.prototype.它表明原型包含诸如toStringtoSource之类的方法.知道函数不能被字符串化,我在调用JSON.stringify时包含了一个replacer函数来删除所有函数,但后来意识到它也有一些奇怪的行为:

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

它似乎不像通常那样循环遍历对象,因此我无法检查密钥是否是函数并忽略它.

问题

有没有办法用字符串化本机错误消息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);
}
Run Code Online (Sandbox Code Playgroud)

输出:

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 }
Run Code Online (Sandbox Code Playgroud)

关键:enumerable: false.

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

lag*_*lex 214

JSON.stringify(err, Object.getOwnPropertyNames(err))
Run Code Online (Sandbox Code Playgroud)

似乎工作

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

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

Jon*_*ski 156

您可以定义一个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
});
Run Code Online (Sandbox Code Playgroud)
var error = new Error('testing');
error.detail = 'foo bar';

console.log(JSON.stringify(error));
// {"message":"testing","detail":"foo bar"}
Run Code Online (Sandbox Code Playgroud)

使用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));
Run Code Online (Sandbox Code Playgroud)

  • 最好不要添加到Error.prototype,可以在JavaScrip的未来版本中提供问题,Error.prototype实际上有一个toJSON函数. (6认同)
  • 如果有人关注他们的链接器错误和命名冲突:如果使用replacer选项,你应该在`function replaceErrors(key,value)`中为`key`选择一个不同的参数名,以避免命名与`.forEach(函数)冲突(关键){..})`; 在这个答案中没有使用`replaceErrors``key`参数. (5认同)
  • 如果使用`.getOwnPropertyNames()`而不是`.keys()`,您将获得不可枚举的属性,而无需手动定义它们. (3认同)
  • 小心!此解决方案打破了本机节点mongodb驱动程序中的错误处理:https://jira.mongodb.org/browse/NODE-554 (3认同)
  • 本例中“key”的阴影虽然允许,但可能会造成混淆,因为它让人怀疑作者是否打算引用外部变量。对于内部循环来说,“propName”将是一个更具表现力的选择。(顺便说一句,我认为 @404NotFound 意味着 ["linter"](https://en.wikipedia.org/wiki/Linter_(software)) (静态分析工具)而不是 ["linker"](https://en.wikipedia .org/wiki/Linker_%28computing%29)) 在任何情况下,使用自定义“replacer”函数都是一种很好的解决方案,因为它可以在一个适当的位置解决问题,并且不会改变本机/全局行为。 (2认同)

Bry*_*sen 45

修改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'));
Run Code Online (Sandbox Code Playgroud)

  • 我第一次听到`猴子补丁':) (2认同)
  • @ChrisPrince 但这不会是最后一次,尤其是在 JavaScript 中!这是关于 [Monkey Patching](https://en.wikipedia.org/wiki/Monkey_patch) 的维基百科,仅供未来人们参考。(在 [Jonathan 的回答](/sf/answers/1287398031/) 中,正如 Chris 所理解的,您正在添加一个新函数 `toJSON`,*直接添加到 `Error` 的原型中*,这通常不是一个好主意。也许其他人已经有了,这会检查,但是你不知道其他版本做了什么。或者如果有人意外得到你的,或者假设 Error 的原型具有特定属性,事情可能会很糟糕。) (2认同)

San*_*Lee 42

由于没有人在谈论原因,我会回答这些问题

问:有没有办法用JSON.stringify对本机错误消息进行字符串化?

没有.

问:如果没有,为什么会出现这种情况?

JSON.stringify()的文档,

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

JSON.stringifyobject没有它的可枚举属性,这就是它打印一个空对象的原因.

  • 但这并不能真正回答问题。为什么当时决定使 Error 对象属性不可枚举?这背后的理由是什么?这是不一致的、令人困惑的,也是另一个需要警惕的 JS 坑,就好像还不够。 (9认同)
  • 奇怪的是没有人打扰。只要修复有效,我就认为:) (3认同)
  • 这个答案的第一部分不正确。有一种方法可以通过其“replacer”参数来使用“JSON.stringify”。 (2认同)
  • @ToddChaffee 这是一个很好的观点。我已经修复了我的答案。请检查它并随时改进它。谢谢。 (2认同)

Luk*_*ski 15

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

它甚至可以处理嵌套的Error对象,我在项目中实际需要的东西.

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

  • **这**是正确答案。序列化错误不是一个小问题,该库的作者(一位优秀的开发人员,拥有许多非常流行的软件包)花费了很大的精力来处理边缘情况,如自述文件中所示:*“保留了自定义属性。非-可枚举属性保持不可枚举(名称、消息、堆栈)。可枚举属性保持可枚举(除不可枚举属性之外的所有属性)。处理循环引用。"* (10认同)
  • @DanDascalescu 谢谢,这是一个很棒的包!它适用于错误的嵌套属性,替换缓冲区,并删除循环引用异常!这应该是答案。 (2认同)
  • 截至“serialize-error”包文档 - 我认为不需要“JSON.stringify()”部分。 (2认同)

Joe*_*one 10

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


Jas*_*son 10

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

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


Dav*_*kal 10

如果使用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.
Run Code Online (Sandbox Code Playgroud)

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

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


小智 8

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

Object.defineProperty(Error.prototype, 'message', {
    configurable: true,
    enumerable: true
});
Run Code Online (Sandbox Code Playgroud)

也许stack属性也是.

  • **请勿更改您不拥有的对象,**可能会破坏应用程序的其他部分,并祝您好运。 (4认同)

Dee*_*hal 7

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

try { 
  throw new Error("MY ERROR MSG")
} catch (e) {
  String(e) // returns 'Error: MY ERROR MSG'
}
Run Code Online (Sandbox Code Playgroud)

  • 我不知道为什么你被否决了,你的回答对我很有用。 (2认同)

小智 6

上面的答案似乎都没有正确序列化 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);
}
Run Code Online (Sandbox Code Playgroud)

这是我在 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}}}  
Run Code Online (Sandbox Code Playgroud)


Paw*_*wel 5

只需转换为常规对象即可

// 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; }, {})
Run Code Online (Sandbox Code Playgroud)

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