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 을 살펴 보았습니다 . 프로토 타입에 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.

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


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

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를 사용 합니다 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
경우 사람이 자신의 링커 오류와 이름 충돌에주의를 지불 :의 대체물 옵션을 사용하는 경우, 다른 매개 변수 이름을 선택해야합니다 keyfunction 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은 사실이지만 바람직 할 수도 있습니다. 내가 뭘 영업 이익이 원하는 것은 확인하기 위해 단지 생각 messagestackjson으로 포함되어 있습니다.
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.