JSON.stringify를 사용하여 오류를 문자열화할 수 없습니까?


330

문제 재현

웹 소켓 사용과 관련된 오류 메시지를 전달하려고 할 때 문제가 발생했습니다. 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 을 살펴 보았습니다 . 프로토 타입에 toString 및 toSource 와 같은 메소드가 포함되어 있음을 보여줍니다 . 함수를 문자열 화 할 수 없다는 것을 알고 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.

허용되는 답변은이 문제에 대한 해결 방법을 제공합니다.


3
오류 객체의 속성에 대한 속성 설명자를 검사 했습니까?
— Ray Toal

3
나에게 대한 질문은 '왜'였으며, 그 답이 질문의 맨 아래에 있다는 것을 알았습니다. 자신의 질문에 대한 답변을 게시하는 데 아무런 문제가 없으며 아마도 그렇게 많은 신뢰를 얻을 것입니다. :-)
— Michael Scheper 2014

답변:


178

을 정의하여 다음 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 일반적으로 객체에 대해 되어 있습니다 (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));

3
.getOwnPropertyNames()대신에 를 사용 .keys()하면 열거 할 수없는 속성을 수동으로 정의하지 않고도 얻을 수 있습니다.

8
Error.prototype에 추가하지 않는 것이 좋습니다. 향후 JavaScrip 버전에서 Error.prototype에 실제로 toJSON 함수가있을 때 문제가 발생할 수 있습니다.
— Jos de Jong

3
꼼꼼한! 이 솔루션은 기본 노드 mongodb 드라이버에서 오류 처리를 중단합니다. jira.mongodb.org/browse/NODE-554
— Sebastian Nowak

5
경우 사람이 자신의 링커 오류와 이름 충돌에주의를 지불 :의 대체물 옵션을 사용하는 경우, 다른 매개 변수 이름을 선택해야합니다 key에 function replaceErrors(key, value)와 충돌을 명명 피하기 위해 .forEach(function (key) { .. }); replaceErrors key이 답변 에서는 매개 변수가 사용되지 않습니다.
— 404 Not Found

2
key이 예제에서 그림자는 허용되었지만, 저자가 외부 변수를 참조하려고했는지 여부에 대해 의문을 남길 수 있으므로 혼동 될 수 있습니다. propName내부 루프에 대해보다 표현적인 선택이 될 것입니다. (BTW, @ 404NotFound는 " 링커 " (정적 분석 도구)가 "링커"가 아님)를 의미한다고 생각 합니다. 어쨌든 사용자 정의 replacer함수를 사용 하면 문제를 한 곳에서 해결하고 적절한 위치에서 네이티브를 변경하지 않기 때문에 탁월한 해결책입니다. / 전역 행동.
— iX3

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

작동하는 것 같습니다

[ / r / javascript에 대한 / u / ub3rgeek의 의견] 및 felixfbecker의 의견


57
답변을 결합JSON.stringify(err, Object.getOwnPropertyNames(err))
— felixfbecker

5
이것은 기본 ExpressJS 오류 오브젝트에는 잘 작동하지만 몽구스 오류에는 작동하지 않습니다. 몽구스 오류에는 ValidationError유형에 대한 중첩 객체가 있습니다. 이것은 errors형식의 Mongoose 오류 개체에서 중첩 개체를 문자열 화하지 않습니다 ValidationError.
— steampowered

4
가장 간단한 방법이기 때문에 이것이 답이되어야합니다.
— Huan

7
@felixfbecker 한 수준 깊이의 속성 이름 만 찾습니다 . 당신이 가지고 var spam = { a: 1, b: { b: 2, b2: 3} };있고 실행 한다면 Object.getOwnPropertyNames(spam), 당신은 ["a", "b"]여기에 기만적 입니다. b객체는 자신의 것을 가지고 있기 때문 b입니다. 당신은 당신의 stringify 호출에서 둘 다 얻을 것이지만, 당신은 그리울 것spam.b.b2 입니다. 그 나쁜.
— ruffin

1
@ruffin은 사실이지만 바람직 할 수도 있습니다. 내가 뭘 영업 이익이 원하는 것은 확인하기 위해 단지 생각 message과 stackjson으로 포함되어 있습니다.
— felixfbecker

74

아무도 이유 에 대해 이야기하지 않기 때문에 부분 대답하겠습니다.

왜 이것이 JSON.stringify빈 객체를 반환합니까?

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

대답

JSON.stringify () 문서 에서

다른 모든 Object 인스턴스 (Map, Set, WeakMap 및 WeakSet 포함)의 경우 열거 가능한 속성 만 직렬화됩니다.

그리고 Error객체는 빈 객체를 인쇄 그 이유는, 그 열거 속성이 없습니다.


4
아무도 귀찮게하지 않았다. 만큼 수정이 작동하기 때문에 나는 :) 가정
— 일리아 Chernomordik

1
이 답변의 첫 부분이 올바르지 않습니다. 사용하는 방법이 JSON.stringify그 사용하여 replacer매개 변수.
— Todd Chaffee 2016 년

1
@ToddChaffee 좋은 지적입니다. 내 대답을 수정했습니다. 확인 후 자유롭게 개선하십시오. 감사.
— 이상현

52

원숭이 패치를 피하기 위해 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
내가 처음 들었을 때 monkey patching:)
— Chris Prince

2
@ChrisPrince하지만 JavaScript는 마지막이 아닙니다! 미래의 사람들을위한 정보를 제공 하는 Monkey Patching 위키 백과입니다 . ( Jonathan의 답변 에서 Chris가 이해하는 것처럼 새로운 기능인 toJSON, 직접에 Error의 프로토 타입 어쩌면 다른 사람이 이미이 종종 좋은 생각이 아니다., 어떤이 수표,하지만 당신은 모르는 것 누군가가 예기치 않게 귀하의 것을 얻거나 Error의 프로토 타입에 특정 속성이 있다고 가정하면 문제가
— 생길

이것은 좋지만 오류 스택을 생략합니다 (콘솔에 표시됨). Vue와 관련이 있거나 자세한 내용은 확실하지 않습니다.
— phil294

23

이를위한 훌륭한 Node.js 패키지가 있습니다 : serialize-error .

내 프로젝트에서 실제로 필요한 중첩 된 Error 객체도 잘 처리합니다.

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


아니요, 그러나 그렇게하기 위해 번역 될 수 있습니다. 이 의견을 참조하십시오 .
— iX3 2018 년

이것이 정답입니다. 직렬화 오류는 사소한 문제가 아니며 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() 하지 않는 것 같습니다 (상속 속성이 포함되어 있지 않기 ). 또한 제안 된 답변 중 하나와 같은 속성을 재정의 할 수 없었습니다.

이것이 내가 생각해 낸 해결책입니다-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.