Skip to content

Commit

Permalink
Add maxDepth option (#43)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <[email protected]>
  • Loading branch information
ltburruel and sindresorhus authored Apr 19, 2021
1 parent 71bb117 commit f15af7d
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 12 deletions.
27 changes: 25 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,29 @@ export type ErrorObject = {
code?: string;
} & JsonObject;

export interface Options {
/**
The maximum depth of properties to preserve when serializing/deserializing.
@default Number.POSITIVE_INFINITY
@example
```
import {serializeError} from 'serialize-error';
const error = new Error('🦄');
error.one = {two: {three: {}}};
console.log(serializeError(error, {maxDepth: 1}));
//=> {name: 'Error', message: '…', one: {}}
console.log(serializeError(error, {maxDepth: 2}));
//=> {name: 'Error', message: '…', one: { two: {}}}
```
*/
readonly maxDepth?: number;
}

/**
Serialize an `Error` object into a plain object.
Expand Down Expand Up @@ -55,7 +78,7 @@ console.log(serializeError(error));
// => {date: '1970-01-01T00:00:00.000Z', message: '🦄', name, stack}
```
*/
export function serializeError<ErrorType>(error: ErrorType): ErrorType extends Primitive
export function serializeError<ErrorType>(error: ErrorType, options?: Options): ErrorType extends Primitive
? ErrorType
: ErrorObject;

Expand Down Expand Up @@ -83,4 +106,4 @@ console.log(error);
// at <anonymous>:1:13
```
*/
export function deserializeError(errorObject: ErrorObject | unknown): Error;
export function deserializeError(errorObject: ErrorObject | unknown, options?: Options): Error;
34 changes: 28 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,18 @@ const destroyCircular = ({
from,
seen,
to_,
forceEnumerable
forceEnumerable,
maxDepth,
depth
}) => {
const to = to_ || (Array.isArray(from) ? [] : {});

seen.push(from);

if (depth >= maxDepth) {
return to;
}

if (typeof from.toJSON === 'function' && from[isCalled] !== true) {
return toJSON(from);
}
Expand All @@ -69,10 +75,14 @@ const destroyCircular = ({
}

if (!seen.includes(from[key])) {
depth++;

to[key] = destroyCircular({
from: from[key],
seen: seen.slice(),
forceEnumerable
forceEnumerable,
maxDepth,
depth
});
continue;
}
Expand All @@ -94,12 +104,16 @@ const destroyCircular = ({
return to;
};

const serializeError = value => {
const serializeError = (value, options = {}) => {
const {maxDepth = Number.POSITIVE_INFINITY} = options;

if (typeof value === 'object' && value !== null) {
return destroyCircular({
from: value,
seen: [],
forceEnumerable: true
forceEnumerable: true,
maxDepth,
depth: 0
});
}

Expand All @@ -112,14 +126,22 @@ const serializeError = value => {
return value;
};

const deserializeError = value => {
const deserializeError = (value, options = {}) => {
const {maxDepth = Number.POSITIVE_INFINITY} = options;

if (value instanceof Error) {
return value;
}

if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
const newError = new Error(); // eslint-disable-line unicorn/error-message
destroyCircular({from: value, seen: [], to_: newError});
destroyCircular({
from: value,
seen: [],
to_: newError,
maxDepth,
depth: 0
});
return newError;
}

Expand Down
5 changes: 3 additions & 2 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {expectType} from 'tsd';
import {serializeError, deserializeError, ErrorObject} from './index.js';
import {expectType, expectAssignable} from 'tsd';
import {serializeError, deserializeError, ErrorObject, Options} from './index.js';

const error = new Error('unicorn');

expectType<number>(serializeError(1));
expectType<ErrorObject>(serializeError(error));
expectAssignable<Options>({maxDepth: 1});

expectType<Error>(deserializeError({
message: 'error message',
Expand Down
28 changes: 26 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ console.log(deserialized);

## API

### serializeError(value)
### serializeError(value, options?)

Type: `Error | unknown`

Expand Down Expand Up @@ -76,7 +76,7 @@ console.log(serializeError(error));
// => {date: '1970-01-01T00:00:00.000Z', message: '🦄', name, stack}
```

### deserializeError(value)
### deserializeError(value, options?)

Type: `{[key: string]: unknown} | unknown`

Expand All @@ -86,3 +86,27 @@ Deserialize a plain object or any value into an `Error` object.
Non-error values are wrapped in a `NonError` error.
Custom properties are preserved.
Circular references are handled.

### options

Type: `object`

#### maxDepth

Type: `number`\
Default: `Number.POSITIVE_INFINITY`

The maximum depth of properties to preserve when serializing/deserializing.

```js
const {serializeError} = require('serialize-error');

const error = new Error('🦄');
error.one = {two: {three: {}}};

console.log(serializeError(error, {maxDepth: 1}));
//=> {name: 'Error', message: '…', one: {}}

console.log(serializeError(error, {maxDepth: 2}));
//=> {name: 'Error', message: '…', one: { two: {}}}
```
53 changes: 53 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,41 @@ test('deserialized name, stack and message should not be enumerable, other props
}
});

test('should deserialize properties up to `Options.maxDepth` levels deep', t => {
const error = new Error('errorMessage');
const object = {
message: error.message,
name: error.name,
stack: error.stack,
one: {
two: {
three: {}
}
}
};

const levelZero = deserializeError(object, {maxDepth: 0});
const emptyError = new Error('test');
emptyError.message = '';
t.is(levelZero instanceof Error, true);
t.deepEqual(levelZero, emptyError);

const levelOne = deserializeError(object, {maxDepth: 1});
error.one = {};
t.is(levelOne instanceof Error, true);
t.deepEqual(levelOne, error);

const levelTwo = deserializeError(object, {maxDepth: 2});
error.one = {two: {}};
t.is(levelTwo instanceof Error, true);
t.deepEqual(levelTwo, error);

const levelThree = deserializeError(object, {maxDepth: 3});
error.one = {two: {three: {}}};
t.is(levelThree instanceof Error, true);
t.deepEqual(levelThree, error);
});

test('should serialize Date as ISO string', t => {
const date = {date: new Date(0)};
const serialized = serializeError(date);
Expand Down Expand Up @@ -290,3 +325,21 @@ test('should serialize custom error with `.toJSON` defined with `serializeError`
});
t.not(stack, undefined);
});

test('should serialize properties up to `Options.maxDepth` levels deep', t => {
const error = new Error('errorMessage');
error.one = {two: {three: {}}};
const {message, name, stack} = error;

const levelZero = serializeError(error, {maxDepth: 0});
t.deepEqual(levelZero, {});

const levelOne = serializeError(error, {maxDepth: 1});
t.deepEqual(levelOne, {message, name, stack, one: {}});

const levelTwo = serializeError(error, {maxDepth: 2});
t.deepEqual(levelTwo, {message, name, stack, one: {two: {}}});

const levelThree = serializeError(error, {maxDepth: 3});
t.deepEqual(levelThree, {message, name, stack, one: {two: {three: {}}}});
});

0 comments on commit f15af7d

Please sign in to comment.