Stringify JavaScript Errors
tl;dr You can stringify an Error
in JavaScript with JSON.stringify(err, Object.getOwnPropertyNames(err))
.
You’re writing some error handling in JavaScript and you decide to dump the whole error to a log, stringified using JSON.stringify
. Smart move, having the full error logged can prove to be invaluable. Time goes on, you experience an outage one evening. Not to worry, you think, I’ll just check out the logs and diagnose the issue. You open the logs are are greeted with a series of {}
where you expect to see stringified Errors
. Wat.
In your confusion, you open a node terminal and type
var err = new Error('testing error because lol it is not logging right')
console.log(JSON.stringify(err))
Much to your dismay, the output is in fact {}
.
- Q: Why aren’t the Error properties such as
message
andstack
showing up in theJSON.stringify
output? - A: Those property types are not marked as
enumerable
.
Wat?
- Q: How do you know the properties
message
andstack
are not marked asenumerable
? - A: By observing the output of
Object.getOwnPropertyDescriptor(err, 'message')
andObject.getOwnPropertyDescriptor(err, 'stack')
.
Wat?
Let’s unpack this.
Object.getOwnPropertyDescriptor
Object.getOwnPropertyDescriptor is a function in that gives us some information about a given property in JavaScript. Among other things, this function will tell us if a given property is enumerable
. If a given property is enumerable
, it will show up when you enumerate an object’s properties. For example, you can enumerate, or list, an object’s properties by using the Object.keys function.
If you do this
var test = { foo: 'bar' }
followed by this
Object.getOwnPropertyDescriptor(test, 'foo')
you will get this
{ value: 'bar',
writable: true,
enumerable: true,
configurable: true }
You’ll notice that the output of Object.getOwnPropertyDescriptor(test, 'foo')
contains enumerable: true
. Becuase that is true, our property foo
will show up when we enumerate our object’s properties with Object.keys(test)
.
If, for some reason, we did not want our property foo
to show up when we enumerate our object’s properties, we can use Object.defineProperty.
Object.defineProperty
Object.defineProperty
allows us to create a property on a given object. It also allows us to control the descriptor
for that property, or the metadata about that property. One of the pieces of metadata that we can control is whether or not a given property is enumerable
.
If you do this
var test = {}
followed by this
Object.defineProperty(test, 'foo', {enumerable: false, value: 'bar'})
you will get this
{}
also this
Object.keys(test)
will give you this
{}
By setting enumerable
to false for our foo
property, we prevent it from showing up when our object’s properties are enumerated.
Stringify Errors
To be honest, I’ve never had to use the functions Object.defineProperty
or Object.getOwnPropertyDescriptor
until I tried to stringify an Error
. Frankly, I think it is silly that the important bits on Error
are not marked as enumerable
.
If you do this
var err = new Error('lol, error testing')
followed by this
Object.getOwnPropertyDescriptor(err, 'message')
you will get this
{ value: 'lol, error testing',
writable: true,
enumerable: false,
configurable: true }
Of particular interest is the fact that enumerable
is marked as false. I’m sure there are good justifications for this, I’m just not aware of them.
In order to successfully stringify an Error
in JavaScript you need to tell the JSON.stringify
function which properties you’d like, explicitly.
If you do this
var err = new Error('more error testing')
followed by this
JSON.stringify(err, Object.getOwnPropertyNames(err))
you will get something like this
'{"stack":"Error: more error testing\\n at repl:1:11\\n at sigintHandlersWrap (vm.js:22:35)\\n at sigintHandlersWrap (vm.js:73:12)\\n at ContextifyScript.Script.runInThisContext (vm.js:21:12)\\n at REPLServer.defaultEval (repl.js:346:29)\\n at bound (domain.js:280:14)\\n at REPLServer.runBound [as eval] (domain.js:293:12)\\n at REPLServer.<anonymous> (repl.js:545:10)\\n at emitOne (events.js:101:20)\\n at REPLServer.emit (events.js:188:7)","message":"more error testing"}'
Now that looks like the data that we set out to log.