modern-errors-serialize
v6.1.0
Published
`modern-errors` plugin to serialize/parse errors
Downloads
3,026
Maintainers
Readme
modern-errors
plugin to serialize/parse
errors.
This adds BaseError.serialize()
and
BaseError.parse()
to serialize/parse errors
to/from plain objects.
Hire me
Please reach out if you're looking for a Node.js API or CLI engineer (11 years of experience). Most recently I have been Netlify Build's and Netlify Plugins' technical lead for 2.5 years. I am available for full-time remote positions.
Features
- Ensures errors are safe to serialize with JSON
- Deep serialization/parsing
- Custom serialization/parsing (e.g. YAML or
process.send()
) - Keeps error classes
- Preserves errors' additional properties
- Works recursively with
AggregateError
- Safe: this never throws
Example
Adding the plugin to
modern-errors
.
import ModernError from 'modern-errors'
import modernErrorsSerialize from 'modern-errors-serialize'
export const BaseError = ModernError.subclass('BaseError', {
plugins: [modernErrorsSerialize],
})
// ...
Serializing errors to plain objects.
const error = new ExampleError('message', { props: { filePath } })
const errorObject = BaseError.serialize(error)
// { name: 'ExampleError', message: 'message', stack: '...', filePath: '...' }
const errorString = JSON.stringify(errorObject)
// '{"name":"ExampleError",...}'
Parsing errors from plain objects.
const newErrorObject = JSON.parse(errorString)
const newError = BaseError.parse(newErrorObject)
// ExampleError: message
// at ...
// filePath: '...'
Install
npm install modern-errors-serialize
This package works in both Node.js >=18.18.0 and browsers.
This is an ES module. It must be loaded using
an import
or import()
statement,
not require()
. If TypeScript is used, it must be configured to
output ES modules,
not CommonJS.
API
modernErrorsSerialize
Type: Plugin
Plugin object to pass to the
plugins
option of
ErrorClass.subclass()
.
BaseError.serialize(error)
error
: ErrorInstance
Return value: ErrorObject
Converts error
to an error plain object. All
error properties
are kept.
Plugin options are
also preserved.
BaseError.parse(errorObject)
errorObject
: ErrorObject
Return value: ErrorInstance
Converts errorObject
to an error instance. The original error classes are
preserved providing they are
subclasses of
BaseError
.
Options
Type: object
shallow
Type: boolean
Default: false
Unless this option is true
, nested errors are also serialized/parsed. They can
be inside other errors, plain objects or arrays.
const inner = new ExampleError('inner')
const error = new ExampleError('example', { props: { inner } })
BaseError.serialize(error).inner // { name: 'BaseError', message: 'inner', ... }
BaseError.serialize(error, { shallow: true }).inner // BaseError
const errorObject = BaseError.serialize(error)
BaseError.parse(errorObject).inner // BaseError
BaseError.parse(errorObject, { shallow: true }).inner // { name: '...', ... }
loose
Type: boolean
Default: false
By default, when the argument is not an Error
instance or an error plain
object, it is converted to one. If this option is true
, it is kept as is
instead.
BaseError.serialize('example') // { name: 'BaseError', message: 'example', ... }
BaseError.serialize('example', { loose: true }) // 'example'
BaseError.parse('example') // BaseError
BaseError.parse('example', { loose: true }) // 'example'
include
Type: string[]
During serialization, only pick specific properties.
BaseError.serialize(error, { include: ['message'] }) // { message: 'example' }
exclude
Type: string[]
During serialization, omit specific properties.
BaseError.serialize(error, { exclude: ['stack'] }) // { name: 'Error', message: 'example' }
transformObject(errorObject, errorInstance)
Type: (errorObject, errorInstance) => void
During serialization, transform each error plain object.
errorObject
is the error after serialization. It must be directly mutated.
errorInstance
is the error before serialization.
transformArgs(constructorArgs, errorObject, ErrorClass)
Type: (constructorArgs, errorObject, ErrorClass) => void
During parsing, transform the
arguments passed to each new Error()
.
constructorArgs
is the array of arguments. Usually, constructorArgs[0]
is
the
error message
and constructorArgs[1]
is the
constructor options object.
constructorArgs
must be directly mutated.
errorObject
is the error before parsing. ErrorClass
is its class.
transformInstance(errorInstance, errorObject)
Type: (errorInstance, errorObject) => void
During parsing, transform each
Error
instance.
errorInstance
is the error after parsing. It must be directly mutated.
errorObject
is the error before parsing.
Configuration
Options can apply to (in priority order):
- Any error: second argument to
ModernError.subclass()
export const BaseError = ModernError.subclass('BaseError', {
plugins: [modernErrorsSerialize],
serialize: options,
})
- Any error of a specific class (and its subclasses): second argument to
ErrorClass.subclass()
export const ExampleError = BaseError.subclass('ExampleError', {
serialize: options,
})
- A specific error: second argument to
new ErrorClass()
throw new ExampleError('...', { serialize: options })
- A specific
BaseError.serialize(error)
orBaseError.parse(errorObject)
call
BaseError.serialize(error, options)
BaseError.parse(errorObject, options)
Usage
JSON safety
Error plain objects are always safe to serialize with JSON.
const error = new ExampleError('message')
error.cycle = error
// Cycles make `JSON.stringify()` throw, so they are removed
console.log(BaseError.serialize(error).cycle) // undefined
Deep serialization/parsing
The loose
option can be used to deeply serialize/parse objects and
arrays.
const error = new ExampleError('message')
const deepArray = BaseError.serialize([{}, { error }], { loose: true })
const jsonString = JSON.stringify(deepArray)
const newDeepArray = JSON.parse(jsonString)
const newError = BaseError.parse(newDeepArray, { loose: true })[1].error
// ExampleError: message
// at ...
Automatic serialization
error.toJSON()
is defined. It is automatically called by
JSON.stringify()
.
const error = new ExampleError('message')
const deepArray = [{}, { error }]
const jsonString = JSON.stringify(deepArray)
const newDeepArray = JSON.parse(jsonString)
const newError = BaseError.parse(newDeepArray, { loose: true })[1].error
// ExampleError: message
// at ...
Omit additional error properties
const ExampleError = BaseError.subclass('ExampleError', {
serialize: { include: ['name', 'message', 'stack'] },
})
const error = new ExampleError('example')
error.prop = true
const errorObject = ExampleError.serialize(error)
console.log(errorObject.prop) // undefined
console.log(errorObject) // { name: 'Error', message: 'example', stack: '...' }
Omit stack traces
const ExampleError = BaseError.subclass('ExampleError', {
serialize: { exclude: ['stack'] },
})
const error = new ExampleError('example')
const errorObject = ExampleError.serialize(error)
console.log(errorObject.stack) // undefined
console.log(errorObject) // { name: 'Error', message: 'example' }
Transforming
const errors = [new ExampleError('message secret')]
errors[0].date = new Date()
const errorObjects = BaseError.serialize(errors, {
loose: true,
// Serialize `Date` instances as strings
transformObject: (errorObject) => {
errorObject.date = errorObject.date.toString()
},
})
console.log(errorObjects[0].date) // Date string
const newErrors = BaseError.parse(errorObjects, {
loose: true,
// Transform error message
transformArgs: (constructorArgs) => {
constructorArgs[0] = constructorArgs[0].replace('secret', '***')
},
// Parse date strings as `Date` instances
transformInstance: (error) => {
error.date = new Date(error.date)
},
})
console.log(newErrors[0].message) // 'message ***'
console.log(newErrors[0].date) // `Date` instance
Custom serialization/parsing
Errors are converted to/from plain objects, not strings. This allows any serialization/parsing logic to be performed.
import { dump, load } from 'js-yaml'
const error = new ExampleError('message')
const errorObject = BaseError.serialize(error)
const errorYamlString = dump(errorObject)
// name: ExampleError
// message: message
// stack: ExampleError: message ...
const newErrorObject = load(errorYamlString)
const newError = BaseError.parse(newErrorObject) // ExampleError: message
Additional error properties
const error = new ExampleError('message', { props: { prop: true } })
const errorObject = BaseError.serialize(error)
console.log(errorObject.prop) // true
const newError = BaseError.parse(errorObject)
console.log(newError.prop) // true
Aggregate errors
const error = new ExampleError('message', {
errors: [new ExampleError('one'), new ExampleError('two')],
})
const errorObject = BaseError.serialize(error)
// {
// name: 'ExampleError',
// message: 'message',
// stack: '...',
// errors: [{ name: 'ExampleError', message: 'one', stack: '...' }, ...],
// }
const newError = BaseError.parse(errorObject)
// ExampleError: message
// [errors]: [ExampleError: one, ExampleError: two]
Constructors
If an error with a
custom
class is
parsed, its custom constructor is not called. However, any property previously
set by that constructor is still preserved, providing it is serializable and
enumerable.
const ExampleError = BaseError.subclass('ExampleError', {
custom: class extends BaseError {
constructor(message, options, prop) {
super(message, options, prop)
this.prop = prop
}
},
})
const error = new ExampleError('message', {}, true)
const errorObject = BaseError.serialize(error)
// `constructor(message, options, prop)` is not called
const newError = BaseError.parse(errorObject)
// But properties set by that `constructor(...)` are kept
console.log(newError.prop) // true
Related projects
error-serializer
: Convert errors to/from plain objectsmodern-errors
: Handle errors in a simple, stable, consistent waymodern-errors-cli
: Handle errors in CLI modulesmodern-errors-process
: Handle process errorsmodern-errors-bugs
: Print where to report bugsmodern-errors-clean
: Clean stack tracesmodern-errors-http
: Create HTTP error responsesmodern-errors-winston
: Log errors with Winstonmodern-errors-switch
: Execute class-specific logic
Support
For any question, don't hesitate to submit an issue on GitHub.
Everyone is welcome regardless of personal background. We enforce a Code of conduct in order to promote a positive and inclusive environment.
Contributing
This project was made with ❤️. The simplest way to give back is by starring and sharing it online.
If the documentation is unclear or has a typo, please click on the page's Edit
button (pencil icon) and suggest a correction.
If you would like to help us fix a bug or add a new feature, please check our guidelines. Pull requests are welcome!