Reproducing the problem
I'm running into an issue when trying to pass error messages around using web sockets. I can replicate the issue I am facing using JSON.stringify
to cater to a wider audience:
// node v0.10.15
> var error = new Error('simple error message');
undefined
> error
[Error: simple error message]
> Object.getOwnPropertyNames(error);
[ 'stack', 'arguments', 'type', 'message' ]
> JSON.stringify(error);
'{}'
The problem is that I end up with an empty object.
What I've tried
Browsers
I first tried leaving node.js and running it in various browsers. Chrome version 28 gives me the same result, and interestingly enough, Firefox at least makes an attempt but left out the message:
>>> JSON.stringify(error); // Firebug, Firefox 23
{"fileName":"debug eval code","lineNumber":1,"stack":"@debug eval code:1\n"}
Replacer function
I then looked at the Error.prototype. It shows that the prototype contains methods such as toString and toSource. Knowing that functions can't be stringified, I included a replacer function when calling JSON.stringify to remove all functions, but then realized that it too had some weird behavior:
var error = new Error('simple error message');
JSON.stringify(error, function(key, value) {
console.log(key === ''); // true (?)
console.log(value === error); // true (?)
});
It doesn't seem to loop over the object as it normally would, and therefore I can't check if the key is a function and ignore it.
The Question
Is there any way to stringify native Error messages with JSON.stringify
? If not, why does this behavior occur?
Methods of getting around this
- Stick with simple string-based error messages, or create personal error objects and don't rely on the native Error object.
- Pull properties:
JSON.stringify({ message: error.message, stack: error.stack })
Updates
@Ray Toal Suggested in a comment that I take a look at the property descriptors. It is clear now why it does not work:
var error = new Error('simple error message');
var propertyNames = Object.getOwnPropertyNames(error);
var descriptor;
for (var property, i = 0, len = propertyNames.length; i < len; ++i) {
property = propertyNames[i];
descriptor = Object.getOwnPropertyDescriptor(error, property);
console.log(property, descriptor);
}
Output:
stack { get: [Function],
set: [Function],
enumerable: false,
configurable: true }
arguments { value: undefined,
writable: true,
enumerable: false,
configurable: true }
type { value: undefined,
writable: true,
enumerable: false,
configurable: true }
message { value: 'simple error message',
writable: true,
enumerable: false,
configurable: true }
Key: enumerable: false
.
Accepted answer provides a workaround for this problem.
You can define a
Error.prototype.toJSON
to retrieve a plainObject
representing theError
:Using
Object.defineProperty()
addstoJSON
without it being anenumerable
property itself.Regarding modifying
Error.prototype
, whiletoJSON()
may not be defined forError
s specifically, the method is still standardized for objects in general (ref: step 3). So, the risk of collisions or conflicts is minimal.Though, to still avoid it completely,
JSON.stringify()
'sreplacer
parameter can be used instead:None of the answers above seemed to properly serialize properties which are on the prototype of Error (because
getOwnPropertyNames()
does not include inherited properties). I was also not able to redefine the properties like one of the answers suggested.This is the solution I came up with - it uses lodash but you could replace lodash with generic versions of those functions.
Here's the test I did in Chrome:
seems to work
[from a comment by /u/ub3rgeek on /r/javascript] and felixfbecker's comment below
Modifying Jonathan's great answer to avoid monkey patching:
You can also just redefine those non-enumerable properties to be enumerable.
and maybe
stack
property too.As no one is talking about the why part, I'm gonna answer these
Q: Is there any way to stringify native Error messages with JSON.stringify?
No.
Q: If not, why does this behavior occur?
From the document of JSON.stringify(),
and
Error
object doesn't have its enumerable properties, that's why it prints an empty object.