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


330

問題を再現する

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

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


3
エラーオブジェクトのプロパティのプロパティ記述子を調べましたか?
Ray Toal 2013

3
私にとっての質問は「なぜ」で、答えは質問の最後にあることがわかりました。あなた自身の質問に対する回答を投稿することに何の問題もありません、そしてあなたはおそらくそのようにもっと信用できるでしょう。:-)
Michael Scheper、2014

回答:


178

を定義して、Error.prototype.toJSONObject表すプレーンを取得できます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なく、adds を使用しますenumerable


修飾についてError.prototype、しばらくはtoJSON()のために定義されない場合がありError、具体的に、複数の方法がまだ標準化されて(ステップ3:REF)一般に、オブジェクトに対して。したがって、衝突や衝突のリスクは最小限です。

ただし、完全に回避するには、代わりに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));

3
.getOwnPropertyNames()代わりにを使用すると.keys()、手動で定義しなくても、列挙不可能なプロパティを取得できます。

8
Error.prototypeに追加しない方がよいでしょう。JavaScripの将来のバージョンでは、Error.prototypeに実際にtoJSON関数がある場合に問題が発生する可能性があります。
ジョス

3
気をつけて!このソリューションは、ネイティブノードのmongodbドライバーでのエラー処理を壊します:jira.mongodb.org/browse/NODE-554
Sebastian Nowak

5
誰かがリンカのエラーと名前の競合に注意を払っている場合:replacerオプションを使用する場合は、との名前の競合を避けるためにkeyin function replaceErrors(key, value)に別のパラメータ名を選択する必要があります.forEach(function (key) { .. })replaceErrors keyこの回答ではパラメーターは使用されていません。
404が見つかりません

2
keyこの例でのシャドウイングは許可されていますが、作成者が外部変数を参照するつもりであったかどうかについて疑問が残るため、混乱を招く可能性があります。propName内側のループのより表現力のある選択肢になります。(ところで、@ 404NotFoundはリンカー」ではなく「リンター」(静的分析ツール)を意味したと思います)いずれにしても、カスタムreplacer関数を使用することは、1つの適切な場所で問題を解決し、ネイティブを変更しないため、これに対する優れたソリューションです/グローバルな動作。
iX3

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

うまくいくようです

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


57
答えをJSON.stringify(err, Object.getOwnPropertyNames(err))
組み合わせる

5
これは、ネイティブのExpressJS Errorオブジェクトでは正常に機能しますが、Mongooseエラーでは機能しません。Mongooseエラーには、ValidationError型のオブジェクトがネストされています。これはerrors、タイプのMongooseエラーオブジェクト内のネストされたオブジェクトを文字列化しませんValidationError
2016年

4
これが最も簡単な方法なので、これが答えになるはずです。
フアン

7
@felixfbeckerこれは、1レベル下のプロパティ名のみを検索します。あなたが持っていvar spam = { a: 1, b: { b: 2, b2: 3} };て実行した場合Object.getOwnPropertyNames(spam)、あなたはそれを得るでしょう["a", "b"]- bオブジェクトはそれ自身を持っているので、ここでは欺瞞的ですb。stringify呼び出しで両方を取得しますが、見逃しspam.b.b2ます。それは良くないね。
ruffin

1
@ruffinは本当ですが、望ましい場合もあります。OPが欲しかったのは、確認のためだけであり、JSONに含まれているmessageと思いstackます。
felixfbecker 2017

74

なぜその部分については誰も話していないので、私はそれに答えます。

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

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

回答

JSON.stringify()のドキュメントから、

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

そしてErrorオブジェクトは空のオブジェクトを出力します理由です、その列挙可能な特性を持っていません。


4
奇妙なことに誰も気にしませんでした。修正が機能する限り、私は仮定します:)
Ilya Chernomordik

1
この回答の最初の部分は正しくありません。使用する方法がありJSON.stringify、その使用してreplacerパラメータが。
トッドチャフィー

1
@ToddChaffeeそれは良い点です。答えを直しました。是非チェックして、改善してください。ありがとう。
Sanghyun Lee

52

モンキーパッチを回避するためにジョナサンの素晴らしい答えを変更します:

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
初めて聞いたのはmonkey patching:)
クリスプリンス

2
@ChrisPrinceしかし、JavaScriptでは特に、これが最後ではありません。将来の人々の情報のためだけに、Monkey Patchingに関するWikipediaがあります。(でジョナサンの答え、クリスが理解するように、あなたはしているが、新しい機能を追加しtoJSON直接Errorのプロトタイプしばしば素晴らしいアイデアではありません、たぶん他の誰かがすでに持っている、あなたは何を知っていないそれはこのチェックが、または、誰かが予期せず自分のものを取得した場合、またはエラーのプロトタイプに特定のプロパティがあると想定した場合、問題が発生する可能性があります。)
ruffin

これは便利ですが、エラーのスタックが省略されます(コンソールに表示されます)。詳細がわからない場合、これがVue関連であるか、何であるかは、これについて言及したかっただけです。
phil294

23

そのための優れたNode.jsパッケージがありますserialize-error

ネストされたErrorオブジェクトも処理します。プロジェクトで実際に必要なものです。

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


いいえ。ただし、そうするために積み重ねることができます。このコメントを参照してください。
iX3

これが正解です。エラーのシリアライズはささいな問題ではなく、ライブラリの作者(非常に人気のある多くのパッケージを備えた優れた開発者)は、READMEに見られるように、エッジケースを処理するためにかなりの時間を費やしました:「カスタムプロパティは保持されます。列挙できないプロパティは列挙不可(名前、メッセージ、スタック)に保持されます。列挙可能なプロパティは列挙可能に保持されます(列挙不可のプロパティ以外のすべてのプロパティ)。循環参照が処理されます。
Dan Dascalescu

9

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

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

そしておそらくstack財産も。


9
所有していないオブジェクトは変更しないでください。変更すると、アプリケーションの他の部分が破損する可能性がありその理由を見つけることができます。
fregante

7

ルートまたは階層内のネストされたプロパティのいずれかがエラーのインスタンスである可能性がある任意のオブジェクト階層をシリアル化する必要がありました。

私たちの解決策はの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))


5

上記の回答のいずれも、エラーのプロトタイプにあるプロパティを適切にシリアル化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}}}  

2

私はログアペンダーのJSON形式に取り組んでおり、同様の問題を解決しようとしてここにたどり着きました。しばらくして、Nodeに機能させるだけでよいことに気づきました。

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

1
それがあるべきでinstanceofあり、そうでないべきinstanceOfです。
lakshman.pasala
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.