Repository: sindresorhus/serialize-error (js)

Issue:
Optionally ignore `toJSON`
`serialize-error` automatically uses `toJSON`:

https://github.com/sindresorhus/serialize-error/blob/855fe3dc67e0376070007d72ac5cf88bafdb7d36/index.js#L61-L63

Unfortunately this introduces two unwanted behaviors:

- code can be executed by passing `{toJSON() { /*h4xor*/ }}`
- serialization is decided by the object owner, with which we might disagree (e.g. it might [exclude arbitrary properties](https://github.com/pixiebrix/pixiebrix-extension/blob/22c2a0b3bae84c42253eb8adb97f5bed15d68cd3/src/services/errors.ts#L90-L92))

### Possible solution

```js
serializeError(error, {toJSON: false})
```


Pull request description:
Add `useToJSON` option
Closes #66 

Patch:
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.


Test patch:
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: {}}};