web-dev-qa-db-ja.com

JSON.stringifyを使用してエラーを文字列化することはできませんか?

問題を再現する

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で文字列化する方法はありますか?そうでない場合、なぜこのような動作が起こるのですか?

これを回避する方法

  • 単純な文字列ベースのエラーメッセージに固執するか、個人的なエラーオブジェクトを作成してネイティブの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);
}

出力:

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

受け入れられた答えはこの問題のための回避策を提供します。

253
JayQuerie.com

Objectを表すプレーンなErrorを取得するためにError.prototype.toJSONを定義できます。

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()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));
135
JSON.stringify(err, Object.getOwnPropertyNames(err))

動作しているようです

[ / r/javascriptの/ u/ub3rgeekによるコメントから ]と下のfelixfbeckerのコメント

183
laggingreflex

猿のパッチを避けるために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'));
44
Bryan Larsen

なぜ部分について誰も話していないので、それに答えようと思います。

このJSON.stringifyが空のオブジェクトを返すのはなぜですか?

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

答え

JSON.stringify() の文書から、

他のすべてのObjectインスタンス(Map、Set、WeakMap、WeakSetを含む)では、列挙可能なプロパティだけがシリアル化されます。

Errorオブジェクトには列挙可能なプロパティがないため、空のオブジェクトが出力されます。

22
Sanghyun Lee

そのためのすばらしいNode.jsパッケージがあります:serialize-error

それはネストされたErrorオブジェクトでさえもうまく処理します、私が実際に私が私のプロジェクトで多くを必要としたもの。

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

13

列挙不可能なプロパティを列挙可能に再定義することもできます。

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

そしておそらくstackプロパティも。

8
cheolgook

上記の答えはどれも、Errorのプロトタイプにあるプロパティを正しくシリアル化しているようには見えませんでした(getOwnPropertyNames()は継承されたプロパティを含まないため)。私はまた提案された答えの1つのように特性を再定義することができなかった。

これが私が思いついた解決策です - それは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}}}  
4
Elliott Palermo

階層内のルートまたはネストされたプロパティのいずれかが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))
1
Joel Malone