Невозможно ли stringify ошибку с помощью JSON.преобразовать в строки?

воспроизведение проблемы

я сталкиваюсь с проблемой при попытке передать сообщения об ошибках с помощью веб-сокетов. Я могу повторить проблему, с которой я сталкиваюсь, используя 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);
    '{}'

проблема в том, что я в конечном итоге с пустой объект.

что я пробовал

- браузеры

Я сначала попытался покинуть узел.js и запуск его в различных браузерах. Версия Chrome 28 дает мне тот же результат, и что интересно, Firefox, по крайней мере, делает попытку, но оставил сообщение:

>>> JSON.stringify(error); // Firebug, Firefox 23
{"fileName":"debug eval code","lineNumber":1,"stack":"@debug eval code:1\n"}

функции заменитель

затем я посмотрел на ошибка.прототип. Он показывает, что прототип содержит такие методы, как toString и toSource. Зная, что функции не могут быть stringified, я включил функции заменитель при вызове JSON.stringify для удаления всех функции, но потом понял, что у него тоже было какое-то странное поведение:

var error = new Error('simple error message');
JSON.stringify(error, function(key, value) {
    console.log(key === ''); // true (?)
    console.log(value === error); // true (?)
});

он, похоже, не зацикливается на объекте, как обычно, и поэтому я не могу проверить, является ли ключ функцией и игнорировать его.

Вопрос

есть ли способ stringify собственные сообщения об ошибках с JSON.stringify? Если нет, то почему происходит такое поведение?

способы обойти это

  • Stick с простой Строковой ошибкой сообщения или создавать личные объекты ошибок и не полагаться на собственный объект ошибок.
  • Pull свойства: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.

принятый ответ обеспечивает обходной путь для этого проблема.

7 ответов


вы можете определить 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 Не будучи собственность.


об изменении Error.prototype, а toJSON() не может быть определена для Errors конкретно,метод по-прежнему стандартизирован для объектов в целом (см. Шаг 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));

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

Кажется, что работает

[из комментария /u /ub3rgeek на/R / javascript] и комментарий felixfbecker ниже


изменение большого ответа Джонатана, чтобы избежать исправления обезьяны:

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'));

вы также можете просто переопределить эти не перечисляемые свойства, чтобы быть перечисляемыми.

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

а может быть stack тоже собственность.


есть отличный узел.пакет JS для этого: serialize-error.

Он хорошо обрабатывает даже вложенные объекты ошибок, что мне на самом деле было нужно в моем проекте.

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


ни один из ответов выше, казалось, правильно сериализовать свойства, которые находятся на прототипе ошибки (потому что 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}}}  

как никто не говорит о почему часть, я собираюсь ответить на эти

Q: есть ли способ stringify собственные сообщения об ошибках с JSON.преобразовать в строки?

нет.

Q: если нет, то почему возникает такое поведение?

из документа в формате JSON.преобразовать в строки(),

для всех других экземпляров объектов (включая Map, Set, WeakMap и WeakSet), только их перечисляемые свойства будут сериализованы.

и