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

时间:2013-08-22 21:34:01

标签: javascript json node.js error-handling

重现问题

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

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

12 个答案:

答案 0 :(得分:179)

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

似乎有用

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

答案 1 :(得分:135)

您可以定义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,但通常为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 (key) {
            error[key] = value[key];
        });

        return error;
    }

    return value;
}

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

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

答案 2 :(得分:43)

修改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 :(得分:20)

由于没有人在谈论为什么部分,我会回答这些

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

没有

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

来自JSON.stringify()的文件,

  

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

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

答案 4 :(得分:12)

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

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

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

答案 5 :(得分:9)

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

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

答案 6 :(得分:8)

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

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

也许stack属性。

答案 7 :(得分:4)

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

答案 8 :(得分:1)

我们需要序列化任意对象层次结构,其中层次结构中的根或任何嵌套属性都可以是Error的实例。

我们的解决方案是使用replacer的{​​{1}}参数,例如:

JSON.stringify()

答案 9 :(得分: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一起使用。

答案 10 :(得分:0)

使其可序列化

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

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

答案 11 :(得分:0)

如果使用 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 主题中也有讨论。