modern-errors
plugin to serialize/parse
errors.
This adds BaseError.serialize()
and
BaseError.parse()
to serialize/parse errors
to/from plain objects.
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.
- 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
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: '...'
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.
Type: Plugin
Plugin object to pass to the
plugins
option of
ErrorClass.subclass()
.
error
: ErrorInstance
Return value: ErrorObject
Converts error
to an error plain object. All
error properties
are kept.
Plugin options are
also preserved.
errorObject
: ErrorObject
Return value: ErrorInstance
Converts errorObject
to an error instance. The original error classes are
preserved providing they are
subclasses of
BaseError
.
Type: object
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: '...', ... }
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'
Type: string[]
During serialization, only pick specific properties.
BaseError.serialize(error, { include: ['message'] }) // { message: 'example' }
Type: string[]
During serialization, omit specific properties.
BaseError.serialize(error, { exclude: ['stack'] }) // { name: 'Error', message: 'example' }
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.
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.
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.
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)
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
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 ...
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 ...
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: '...' }
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' }
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
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
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
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]
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
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
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.
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!
ehmicky π» π¨ π€ π |
Benjamin Kroeger π€ |