Разве невозможно зашифровать ошибку с помощью JSON.stringify?
Воспроизведение проблемы
Я столкнулся с проблемой при попытке передать сообщения об ошибках с помощью веб-сокетов. Я могу повторить проблему, с которой сталкиваюсь, используя 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
? Если нет, почему это происходит?
Методы обойти это
- Придерживайтесь простых сообщений об ошибках на основе строк или создавайте личные объекты ошибок и не полагайтесь на собственный объект 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
,
Принятый ответ обеспечивает обходной путь для этой проблемы.
15 ответов
Вы можете определить 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()
"sreplacer
параметр может быть использован вместо:
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 ниже
Поскольку никто не говорит о том, почему, я собираюсь ответить на эти
Q: Есть ли способ привести в порядок собственные сообщения об ошибках с помощью JSON.stringify?
Нет.
В: Если нет, почему это происходит?
Из документа JSON.stringify (),
Для всех других экземпляров Object (включая Map, Set, WeakMap и WeakSet) будут сериализованы только их перечисляемые свойства.
а также Error
У объекта нет перечисляемых свойств, поэтому он печатает пустой объект.
Для этого есть отличный пакет Node.js: serialize-error
,
Он хорошо обрабатывает даже вложенные объекты Error, что мне действительно нужно в моем проекте.
Изменение отличного ответа Джонатана, чтобы избежать исправлений обезьяны:
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'));
Нам нужно было сериализовать произвольную иерархию объектов, где корень или любое из вложенных свойств в иерархии могут быть экземплярами Error.
Нашим решением было использовать 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))
Я работал над форматом JSON для приложений журналов и в итоге попытался решить аналогичную проблему. Через некоторое время я понял, что могу просто заставить Node выполнять работу:
const util = require("util");
...
return JSON.stringify(obj, (name, value) => {
if (value instanceof Error) {
return util.format(value);
} else {
return value;
}
}
Если вы используете nodejs, есть более надежный способ, используя собственный nodejs
inspect
. Также вы можете указать печать объектов на неограниченную глубину.
Пример машинописного текста:
import { inspect } from "util";
const myObject = new Error("This is error");
console.log(JSON.stringify(myObject)); // Will print {}
console.log(myObject); // Will print full error object
console.log(inspect(myObject, {depth: null})); // Same output as console.log plus it works as well for objects with many nested properties.
Ссылка на документацию, ссылка на пример использования.
И так же обсуждали в теме
How can I get the full object in Node.js's console.log(), rather than '[Object]'?
здесь в переполнении стека.
Вы также можете просто переопределить эти не перечисляемые свойства, чтобы они были перечисляемыми.
Object.defineProperty(Error.prototype, 'message', {
configurable: true,
enumerable: true
});
и возможно stack
собственность тоже.
Строковый конструктор должен иметь возможность преобразовать ошибку в строку
try {
throw new Error("MY ERROR MSG")
} catch (e) {
String(e) // returns 'Error: MY ERROR MSG'
}
Похоже, что ни один из приведенных выше ответов не мог правильно сериализовать свойства, относящиеся к прототипу ошибки 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}}}
Сделать сериализуемым
// example error
let err = new Error('I errored')
// one liner converting Error into regular object that can be stringified
err = Object.getOwnPropertyNames(err).reduce((acc, key) => { acc[key] = err[key]; return acc; }, {})
Если вы хотите отправить этот объект из дочернего процесса, рабочего процесса или через сеть, нет необходимости в строковой обработке. Он будет автоматически преобразован в строку и проанализирован, как любой другой нормальный объект.
Я расширил этот ответ: нельзя ли преобразовать ошибку в строку с помощью JSON.stringify?
сериализоватьError.ts
export function serializeError(err: unknown) {
return JSON.parse(JSON.stringify(err, Object.getOwnPropertyNames(err)))
}
И я могу использовать это так:
import { serializeError } from '../helpers/serializeError'; // Change to your path
try {
const res = await create(data);
return { status: 201 };
} catch (err) {
return { status: 400, error: serializeError(err) };
}
Обычно я объявляю один раз:
const cloneError = (err) => {
return err ? { name: err.name, message: err.message, stack: err.stack, cause: err.cause } : {};
};
Тогда везде я могу его использовать, например:
...logger.log('An error occurred:', cloneError(err));
Вы можете решить эту проблему с помощью однострочника (errStringified) в простом javascript:
var error = new Error('simple error message');
var errStringified = (err => JSON.stringify(Object.getOwnPropertyNames(Object.getPrototypeOf(err)).reduce(function(accumulator, currentValue) { return accumulator[currentValue] = err[currentValue], accumulator}, {})))(error);
console.log(errStringified);
Он также работает с DOMExceptions.