diff --git a/index.d.ts b/index.d.ts index f7d09d6..55ec263 100644 --- a/index.d.ts +++ b/index.d.ts @@ -38,6 +38,13 @@ export interface Options { ``` */ readonly maxDepth?: number; + + /** + Indicate whether to use a `.toJSON()` method if encountered in the object. This is useful when a custom error implements its own serialization logic via `.toJSON()` but you prefer to not use it. + + @default true + */ + readonly useToJSON?: boolean; } /** diff --git a/index.js b/index.js index 28b26f3..c16542d 100644 --- a/index.js +++ b/index.js @@ -46,6 +46,7 @@ const toJSON = from => { return json; }; +// eslint-disable-next-line complexity const destroyCircular = ({ from, seen, @@ -53,6 +54,7 @@ const destroyCircular = ({ forceEnumerable, maxDepth, depth, + useToJSON, }) => { const to = to_ || (Array.isArray(from) ? [] : {}); @@ -62,7 +64,7 @@ const destroyCircular = ({ return to; } - if (typeof from.toJSON === 'function' && from[toJsonWasCalled] !== true) { + if (useToJSON && typeof from.toJSON === 'function' && from[toJsonWasCalled] !== true) { return toJSON(from); } @@ -97,6 +99,7 @@ const destroyCircular = ({ forceEnumerable, maxDepth, depth, + useToJSON, }); continue; } @@ -119,7 +122,10 @@ const destroyCircular = ({ }; export function serializeError(value, options = {}) { - const {maxDepth = Number.POSITIVE_INFINITY} = options; + const { + maxDepth = Number.POSITIVE_INFINITY, + useToJSON = true, + } = options; if (typeof value === 'object' && value !== null) { return destroyCircular({ @@ -128,6 +134,7 @@ export function serializeError(value, options = {}) { forceEnumerable: true, maxDepth, depth: 0, + useToJSON, }); } diff --git a/readme.md b/readme.md index 4f88658..0fa3944 100644 --- a/readme.md +++ b/readme.md @@ -112,12 +112,19 @@ const error = new Error('🦄'); error.one = {two: {three: {}}}; console.log(serializeError(error, {maxDepth: 1})); -//=> {name: 'Error', message: '…', one: {}} +//=> {name: 'Error', message: '🦄', one: {}} console.log(serializeError(error, {maxDepth: 2})); -//=> {name: 'Error', message: '…', one: { two: {}}} +//=> {name: 'Error', message: '🦄', one: { two: {}}} ``` +#### useToJSON + +Type: `boolean`\ +Default: `true` + +Indicate whether to use a `.toJSON()` method if encountered in the object. This is useful when a custom error implements [its own serialization logic via `.toJSON()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#tojson_behavior) but you prefer to not use it. + ### isErrorLike(value) Predicate to determine whether a value looks like an error, even if it's not an instance of `Error`. It must have at least the `name`, `message`, and `stack` properties. diff --git a/test.js b/test.js index bfb24e3..51ccad6 100644 --- a/test.js +++ b/test.js @@ -305,6 +305,7 @@ test('should serialize custom error with `.toJSON`', t => { }; } } + const error = new CustomError(); const serialized = serializeError(error); t.deepEqual(serialized, { @@ -366,6 +367,32 @@ test('should serialize custom error with `.toJSON` defined with `serializeError` t.not(stack, undefined); }); +test('should ignore `.toJSON` methods if set in the options', t => { + class CustomError extends Error { + constructor() { + super('foo'); + this.name = this.constructor.name; + this.value = 10; + } + + toJSON() { + return { + message: this.message, + amount: `$${this.value}`, + }; + } + } + + const error = new CustomError(); + const serialized = serializeError(error, {useToJSON: false}); + t.like(serialized, { + name: 'CustomError', + message: 'foo', + value: 10, + }); + t.truthy(serialized.stack); +}); + test('should serialize properties up to `Options.maxDepth` levels deep', t => { const error = new Error('errorMessage'); error.one = {two: {three: {}}};