【问题标题】:Is it not possible to stringify an Error using JSON.stringify?不能使用 JSON.stringify 对错误进行字符串化吗?
【发布时间】:2013-08-25 19:39:49
【问题描述】:

重现问题

我在尝试使用 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 以删除所有函数时包含了replacer function,但后来意识到它也有一些奇怪的行为:

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 在评论中建议我看看property descriptors。现在很清楚为什么它不起作用:

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

标签: javascript json node.js error-handling


【解决方案1】:
JSON.stringify(err, Object.getOwnPropertyNames(err))

似乎有效

[from a comment by /u/ub3rgeek on /r/javascript] 和下面 felixfbecker 的评论

【讨论】:

  • 梳理答案,JSON.stringify(err, Object.getOwnPropertyNames(err))
  • 这适用于原生 ExpressJS 错误对象,但不适用于 Mongoose 错误。 Mongoose 错误包含 ValidationError 类型的嵌套对象。这不会对 ValidationError 类型的 Mongoose 错误对象中的嵌套 errors 对象进行字符串化。
  • 这应该是答案,因为这是最简单的方法。
  • @felixfbecker 这只会查找一层深度的属性名称。如果你有var spam = { a: 1, b: { b: 2, b2: 3} }; 并运行Object.getOwnPropertyNames(spam),你会得到["a", "b"]——这里有欺骗性,因为b 对象有它自己的b。你会在你的 stringify 调用中得到这两个,但是你会错过spam.b.b2。这很糟糕。
  • @ruffin 没错,但它甚至可能是可取的。我认为 OP 想要的只是确保 messagestack 包含在 JSON 中。
【解决方案2】:

您可以定义一个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() 可能不会专门为Errors 定义,the method is still standardized 用于一般对象(参考:步骤 3)。因此,碰撞或冲突的风险很小。

不过,为了完全避免这种情况,可以使用 JSON.stringify()'s replacer parameter 代替:

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(),您将获得不可枚举的属性,而无需手动定义它们。
  • 最好不要添加到 Error.prototype 中,如果在 JavaScrip 的未来版本中 Error.prototype 实际上有 toJSON 函数,可能会出现问题。
  • 小心!此解决方案破坏了本机节点 mongodb 驱动程序中的错误处理:jira.mongodb.org/browse/NODE-554
  • 如果有人注意他们的链接器错误和命名冲突:如果使用替换选项,您应该为function replaceErrors(key, value)中的key选择不同的参数名称,以避免与.forEach(function (key) { .. })发生命名冲突; replaceErrors key 参数在此答案中未使用。
  • 在这个例子中key 的阴影虽然被允许,但可能会造成混淆,因为它让人怀疑作者是否打算引用外部变量。 propName 将是内部循环更具表现力的选择。 (顺便说一句,我认为@404NotFound 的意思是"linter"(静态分析工具)而不是"linker")无论如何,使用自定义replacer 函数是一个很好的解决方案,因为它可以在一个适当的地方解决问题并且确实不改变本机/全局行为。
【解决方案3】:

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

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

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

回答

来自JSON.stringify()的文档,

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

Error 对象没有可枚举的属性,这就是它打印空对象的原因。

【讨论】:

  • 奇怪没有人打扰。只要修复工作我认为:)
  • 这个答案的第一部分不正确。有一种方法可以使用 JSON.stringifyreplacer 参数。
  • @ToddChaffee 说得好。我已经确定了我的答案。请检查并随时改进。谢谢。
  • 这并没有真正回答这个问题。为什么决定让 Error 对象的属性不可枚举?这背后的理由是什么?这是不一致的、令人困惑的,也是另一个需要注意的 JS 坑,好像还不够。
【解决方案4】:

修改乔纳森的最佳答案以避免猴子修补:

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 :)
  • @ChrisPrince 但这不会是最后一次,尤其是在 JavaScript 中!这是Monkey Patching 上的维基百科,仅供未来人们参考。 (在 Jonathan's answer 中,正如 Chris 所理解的那样,您正在添加一个新函数 toJSON直接添加到 Error 的原型,这通常不是一个好主意。也许其他人已经有,这会检查,但是你不知道其他版本的作用。或者如果有人意外得到你的,或者假设 Error 的原型具有特定属性,事情可能会失败。)
  • 这很好,但省略了错误堆栈(显示在控制台中)。不确定细节,如果这是与 Vue 相关的还是什么,只是想提一下。
【解决方案5】:

有一个很棒的 Node.js 包:serialize-error

npm install serialize-error

它甚至可以很好地处理嵌套的错误对象。

import {serializeError} from 'serialize-error';

JSON.stringify(serializeError(error));

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

【讨论】:

  • 否,但它可以被转译为这样做。见this comment
  • 这个是正确答案。序列化错误不是一个小问题,库的作者(拥有许多非常受欢迎的软件包的优秀开发人员)竭尽全力处理边缘情况,如自述文件中所示:“保留自定义属性。不可枚举属性保持不可枚举(名称、消息、堆栈)。可枚举属性保持可枚举(除不可枚举属性之外的所有属性)。循环引用被处理。”
  • @DanDascalescu 谢谢,这是一个很棒的包!它适用于错误的嵌套属性,替换缓冲区并删除循环引用异常!这应该是答案。
【解决方案6】:

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

【讨论】:

    【解决方案7】:

    我正在为日志附加程序开发 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 属性。
    • 我已经在节点 6 到 16 上尝试过这个。我在所有节点中都得到了堆栈跟踪,@ps2goat 你使用的是什么版本?
    • 我使用的是 16.0.4,但我发现了我的问题。我错过了格式化程序函数的name 参数。修复后,此解决方案仍将所有属性串在一起。 name: message: stacktrace。如果您想保留错误对象的结构并防止其他一些问题(日志记录缓冲区、循环引用等),则 serialize-error 包似乎更适合
    【解决方案8】:

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

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

    也许还有stack 属性。

    【讨论】:

    • 不要更改不属于你的对象,这会破坏你应用程序的其他部分,祝你好运找到原因。
    【解决方案9】:

    上面的答案似乎都没有正确序列化错误原型上的属性(因为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】:

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

      Link 用于文档,link 用于示例用法。

      以及在主题How can I get the full object in Node.js's console.log(), rather than '[Object]'?here中讨论堆栈溢出。

      【讨论】:

        【解决方案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; }, {})
        

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

        【讨论】:

          【解决方案12】:

          您可以使用纯 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

          【讨论】:

          • 那是一条长单线!
          • 哦,是的,它很漂亮。让我热泪盈眶,我很自豪。
          【解决方案13】:

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2018-05-06
            • 2019-12-01
            • 2018-04-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多