456

重现问题

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

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

4

13 回答 13

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

似乎工作

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

于 2014-10-05T04:57:05.017 回答
225

您可以定义 aError.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()可能没有专门为Errors 定义,但该方法仍然是一般对象的标准化方法(参考:步骤 3)。因此,碰撞或冲突的风险很小。

不过,为了完全避免它,可以使用JSON.stringify()'sreplacer参数:

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));
于 2013-08-22T21:48:08.910 回答
163

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

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

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

回答

JSON.stringify()的文档中,

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

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

于 2018-06-07T09:50:33.137 回答
66

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

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'));
于 2013-12-05T16:48:46.313 回答
65

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

npm install serialize-error

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

import {serializeError} from 'serialize-error';

JSON.stringify(serializeError(error));

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

于 2017-11-09T14:03:20.057 回答
11

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

const util = require("util");
...
return JSON.stringify(obj, (name, value) => {
    if (value instanceof Error) {
        return util.format(value);
    } else {
        return value;
    }
}
于 2020-02-24T06:47:48.487 回答
11

We needed to serialise an arbitrary object hierarchy, where the root or any of the nested properties in the hierarchy could be instances of Error.

Our solution was to use the replacer param of JSON.stringify(), e.g.:

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

于 2018-12-05T02:50:45.840 回答
9

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

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

也许还有stack财产。

于 2014-07-15T21:35:26.197 回答
6

上面的答案似乎都没有正确序列化错误原型上的属性(因为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}}}  
于 2017-03-30T18:59:34.367 回答
3

只需转换为常规对象

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

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

于 2020-11-06T17:02:44.023 回答
3

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

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

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

于 2021-06-03T14:06:33.240 回答
0

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

于 2020-09-16T02:56:52.503 回答
-1

String constructor should be able to stringify error

try { 
  throw new Error("MY ERROR MSG")
} catch (e) {
  String(e) // returns 'Error: MY ERROR MSG'
}
于 2022-01-05T21:15:02.037 回答