Чи не можливо впорядкувати помилку за допомогою 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 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? Якщо ні, то чому така поведінка відбувається?

Методи подолання цього

  • Дотримуйтесь простих рядкових повідомлень про помилки або створюйте особисті об’єкти помилок і не покладайтеся на нативний об'єкт 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

3
Питання для мене було "чому", і я виявив, що відповідь була внизу питання. Немає нічого поганого в опублікуванні відповіді на власне запитання, і ви, мабуть, отримаєте більше кредитів таким чином. :-)
Майкл Шепер

Відповіді:


178

Ви можете визначити a, 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конкретно для s, метод все ще стандартизований для об'єктів загалом (див .: крок 3). Отже, ризик зіткнень чи конфліктів мінімальний.

Хоча, по - , як і раніше уникати його повністю, JSON.stringify()«s 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
Краще не addit до Error.prototype, може створювати проблеми, коли в майбутній версії JavaScrip Error.prototype насправді має функцію toJSON.
Джос де Йонг

3
Обережно! Це рішення порушує обробку помилок у вбудованому драйвері mongodb: jira.mongodb.org/browse/NODE-554
Sebastian Nowak,

5
У разі , якщо хто -то звертає увагу на свої помилки компоновщика і конфлікти імен: при використанні опції замінника, ви повинні вибрати інше ім'я параметра для keyв , function replaceErrors(key, value)щоб уникнути конфлікту імен з .forEach(function (key) { .. }); replaceErrors keyпараметр не використовується в цій відповіді.
404 Не знайдено

2
Затінення keyв цьому прикладі, хоча це дозволено, потенційно заплутане, оскільки залишає сумніви в тому, чи мав намір автор посилатися на зовнішню змінну чи ні. propNameбув би більш виразним вибором для внутрішньої петлі. (BTW, я думаю, що @ 404NotFound означав "лінтер" (інструмент статичного аналізу), а не "лінкер" ) У будь-якому випадку використання спеціальної replacerфункції є прекрасним рішенням для цього, оскільки воно вирішує проблему в одному, відповідному місці та не змінює нативне / глобальна поведінка.
iX3

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

здається, працює

[ з коментаря / u / ub3rgeek on / r / javascript ] та коментаря felixfbecker нижче


57
JSON.stringify(err, Object.getOwnPropertyNames(err))
Збирання

5
Це добре працює для нативного об’єкта Error ExpressJS, але він не працюватиме з помилкою Mongoose. Помилки мангустів мають вкладені об’єкти для ValidationErrorтипів. Це не впорядкує вкладений errorsоб'єкт у тип помилки об'єкта Mongoose ValidationError.
підключений парою

4
це має бути відповіддю, адже це найпростіший спосіб зробити це.
Хуан

7
@felixfbecker Це шукає лише імена властивостей на один рівень глибокі . Якщо ти маєш var spam = { a: 1, b: { b: 2, b2: 3} };і біжиш Object.getOwnPropertyNames(spam), тут потрапиш ["a", "b"]- оманливий, бо bоб’єкт має своє b. Ви отримаєте обоє у своєму виклику, але ви пропуститеspam.b.b2 . Це погано.
ruffin

1
@ruffin це правда, але це може бути навіть бажано. Я думаю , що ОП хотів тільки , щоб переконатися , що messageі stackвключені в JSON.
felixfbecker

74

Оскільки ніхто не говорить про те, чому частина, я відповім на це.

Чому це JSON.stringifyповертає порожній об’єкт?

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

Відповідь

З документа JSON.stringify () ,

Для всіх інших примірників об’єктів (включаючи Map, Set, WeakMap та WeakSet) буде серіалізовано лише їх перелічені властивості.

і Errorоб'єкт не має своїх перелічених властивостей, тому він друкує порожній об'єкт.


4
Дивно, що ніхто навіть не турбував. Поки виправлення працює, я припускаю :)
Ілля Черномордик

1
Перша частина цієї відповіді невірна. Існує спосіб використання, JSON.stringifyвикористовуючи його replacerпараметр.
Todd Chaffee

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! Ось Вікіпедія про мавпочки , лише для інформації майбутніх людей. (В відповідь Джонатана , так як розуміє , Кріс, ви додаєте нову функцію, toJSON, безпосередньо Errorпрототип «S , який часто не гарна ідея. Може бути , хто - то вже є, яких це перевіряє, але ви не знаєте , що або, якщо хтось несподівано отримає вашу, або припускає, що прототип помилки має специфічні властивості, речі можуть причаїтися.)
ruffin

це приємно, але опускає стек помилки (яка показана на консолі). не впевнений у деталях, якщо це пов’язано з Vue чи що, просто хотів згадати про це.
phil294

23

Існує великий пакет Node.js для цього: serialize-error.

Він добре обробляє навіть вкладені об'єкти помилок, що мені насправді було потрібно в моєму проекті.

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


Ні, але це можна зробити так, щоб це зробити. Дивіться цей коментар .
iX3

Це правильна відповідь. Серіалізація помилок не є тривіальною проблемою, і автор бібліотеки (відмінний розробник з багатьма високопопулярними пакетами) пішов на значну довжину для обробки крайових справ, як це можна побачити в README: "Спеціальні властивості зберігаються. Не перелічуються властивості зберігаються не перелічуваними (ім'я, повідомлення, стек). Незмірні властивості зберігаються незліченними (усі властивості, крім ненумерованих). Обробляються циркулярні посилання. "
Дан Даскалеску

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.