550

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.

Community
  • 1
  • 1
Jay
  • 18,959
  • 11
  • 53
  • 72
  • 6
    Have you examined the property descriptors for the properties in the error object? – Ray Toal Aug 22 '13 at 21:37
  • 4
    The question for me was 'why', and I found the answer was at the bottom of the question. There's nothing wrong with posting an answer for your own question, and you'll probably get more cred that way. :-) – Michael Scheper Dec 01 '14 at 05:04
  • 1
    The `serialize-error` package handles this for you: https://www.npmjs.com/package/serialize-error – tim-phillips Aug 24 '20 at 20:06

14 Answers14

473
JSON.stringify(err, Object.getOwnPropertyNames(err))

seems to work

[from a comment by /u/ub3rgeek on /r/javascript] and felixfbecker's comment below

Toolkit
  • 10,779
  • 8
  • 59
  • 68
laggingreflex
  • 32,948
  • 35
  • 141
  • 196
  • 74
    Combing the answers, `JSON.stringify(err, Object.getOwnPropertyNames(err))` – felixfbecker Jan 06 '16 at 13:47
  • 10
    This works fine for a native ExpressJS Error object, but it will not work with a Mongoose error. Mongoose errors have nested objects for `ValidationError` types. This will not stringify the nested `errors` object in a Mongoose error object of type `ValidationError`. – steampowered Mar 14 '16 at 22:49
  • 5
    this should be the answer, because it's the simplest way to do this. – Huan Oct 06 '16 at 10:23
  • 12
    @felixfbecker That only looks for property names *one level deep*. If you have `var spam = { a: 1, b: { b: 2, b2: 3} };` and run `Object.getOwnPropertyNames(spam)`, you'll get `["a", "b"]` -- deceptive here, because the `b` object has it's own `b`. You'd get both in your stringify call, but **you'd miss `spam.b.b2`**. That's bad. – ruffin Jun 02 '17 at 15:07
  • 3
    @ruffin that's true, but it might even be desirable. I think what OP wanted was just to make sure `message` and `stack` are included in the JSON. – felixfbecker Jul 23 '17 at 15:37
  • Why not skip the serialization and use the `message` and `stack` properties directly? – Steven Vachon Nov 19 '18 at 16:13
  • The problem with this answer is that it doesn't include the `response`, if you want to console the full object on the browser check this https://stackoverflow.com/questions/4482950/how-to-show-full-object-in-chrome-console – Alfrex92 Jun 02 '20 at 09:18
  • 1
    This is useful, but it's not a full answer to "JSON stringify Error". With this you only get the property names wrapped in an array. See @Bryan's answer below for a complete solution. – Maciej Krawczyk Sep 23 '21 at 04:27
248

You can define a Error.prototype.toJSON to retrieve a plain Object representing the Error:

if (!('toJSON' in Error.prototype))
Object.defineProperty(Error.prototype, 'toJSON', {
    value: function () {
        var alt = {};

        Object.getOwnPropertyNames(this).forEach(function (key) {
            alt[key] = this[key];
        }, this);

        return alt;
    },
    configurable: true,
    writable: true
});
var error = new Error('testing');
error.detail = 'foo bar';

console.log(JSON.stringify(error));
// {"message":"testing","detail":"foo bar"}

Using Object.defineProperty() adds toJSON without it being an enumerable property itself.


Regarding modifying Error.prototype, while toJSON() may not be defined for Errors 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()'s replacer parameter can be used instead:

function replaceErrors(key, value) {
    if (value instanceof Error) {
        var error = {};

        Object.getOwnPropertyNames(value).forEach(function (propName) {
            error[propName] = value[propName];
        });

        return error;
    }

    return value;
}

var error = new Error('testing');
error.detail = 'foo bar';

console.log(JSON.stringify(error, replaceErrors));
Jonathan Lonowski
  • 121,453
  • 34
  • 200
  • 199
  • 4
    If you use `.getOwnPropertyNames()` instead of `.keys()`, you'll get the non-enumerable properties without having to manually define them. –  Aug 22 '13 at 21:56
  • 1
    @CrazyTrain Yeah. Forgot `.keys()` is for "*own*" properties as well. Thought it would include inherited. – Jonathan Lonowski Aug 22 '13 at 22:07
  • Thanks. Based on your answer I've made a wrapper for errors that (among other features) can be stringified: https://www.npmjs.org/package/error2 . You can wrap native errors in it like that `var error2 = new Error2(error)` and then `JSON.stringify(error2)`. – Tad Lispy Jun 03 '14 at 15:05
  • 9
    Better not addit to the Error.prototype, can give issues when in a future version of JavaScrip the Error.prototype actually has a toJSON function. – Jos de Jong Dec 02 '14 at 11:34
  • 5
    Careful! This solution breaks error handling in native node mongodb driver: https://jira.mongodb.org/browse/NODE-554 – Sebastian Nowak Feb 25 '16 at 15:34
  • @SebastianNowak The error complains about the property being read-only. Defining it also with `writable: true` should resolve that. – Jonathan Lonowski Feb 25 '16 at 15:42
  • Why is it that I get the call stack when I use the `Error.prototype.toJSON` technique? It's a stringified call stack, but a call stack for sure. – Chris Prince Jun 18 '16 at 17:25
  • 5
    In case anyone pays attention to their linker errors and naming conflicts: if using the replacer option, you should choose a different parameter name for `key` in `function replaceErrors(key, value)` to avoid naming conflict with `.forEach(function (key) { .. })`; the `replaceErrors` `key` parameter is unused in this answer. – 404 Not Found Apr 20 '17 at 22:58
  • @404NotFound Why? This is called shadowing and totally normal. – xehpuk Sep 03 '19 at 15:54
  • 2
    The shadowing of `key` in this example, while allowed, is potentially confusing as it leaves doubt as to whether the author intended to refer to the outer variable or not. `propName` would be a more expressive choice for the inner loop. (BTW, I think @404NotFound meant ["linter"](https://en.wikipedia.org/wiki/Linter_(software)) (static analysis tool) not ["linker"](https://en.wikipedia.org/wiki/Linker_%28computing%29)) In any case, using a custom `replacer` function is an excellent solution for this as it solves the problem in one, appropriate place and does not alter native/global behavior. – jacobq Sep 20 '19 at 13:15
219

As no one is talking about the why part, I'm gonna answer it.

Why this JSON.stringify returns an empty object?

> JSON.stringify(error);
'{}'

Answer

From the document of JSON.stringify(),

For all the other Object instances (including Map, Set, WeakMap and WeakSet), only their enumerable properties will be serialized.

and Error object doesn't have its enumerable properties, that's why it prints an empty object.

Sanghyun Lee
  • 21,644
  • 19
  • 100
  • 126
  • 23
    Strange no one even bothered. As long as fix works I assume :) – Ilya Chernomordik Aug 06 '18 at 12:02
  • 2
    The first part of this answer is not correct. There is a way to use `JSON.stringify` using its `replacer` parameter. – Todd Chaffee Jun 01 '19 at 21:37
  • 2
    @ToddChaffee that's a good point. I've fixed my answer. Please check it and feel free to improve it. Thanks. – Sanghyun Lee Jun 02 '19 at 09:20
  • 8
    This doesn't really answer the question though. Why was it decided then to make the Error objects properties not enumerable? What is the rationale behind that? It's inconsistent, confusing and yet another JS pothole to watch out for as if there weren't enough already. – user3700562 May 20 '21 at 20:02
90

There is a great Node.js package for that: serialize-error.

npm install serialize-error

It handles well even nested Error objects.

import {serializeError} from 'serialize-error';

const stringifiedError = serializeError(error);

Docs: https://www.npmjs.com/package/serialize-error

slaesh
  • 16,659
  • 6
  • 50
  • 52
Lukasz Czerwinski
  • 13,499
  • 10
  • 55
  • 65
  • No, but it can be transpiled to do so. See [this comment](https://github.com/sindresorhus/serialize-error/issues/13#issuecomment-452984024). – jacobq Sep 20 '19 at 13:59
  • 19
    **This** is the correct answer. Serializing errors is not a trivial problem, and the author of the library (an excellent dev with many highly popular packages) went to significant lengths to handle edge cases, as can be seen in the README: *"Custom properties are preserved. Non-enumerable properties are kept non-enumerable (name, message, stack). Enumerable properties are kept enumerable (all properties besides the non-enumerable ones). Circular references are handled."* – Dan Dascalescu May 19 '20 at 05:04
  • 1
    @DanDascalescu thanks, that's a great package! It works on nested properties that are Errors, replaces buffers, and removes circular reference exceptions! This should be the answer. – ps2goat Jul 12 '21 at 20:41
  • 2
    as of the `serialize-error` package docs - I don't think the `JSON.stringify()` part is needed. – Richard Trembecký Mar 07 '22 at 21:34
69

Modifying Jonathan's great answer to avoid monkey patching:

var stringifyError = function(err, filter, space) {
  var plainObject = {};
  Object.getOwnPropertyNames(err).forEach(function(key) {
    plainObject[key] = err[key];
  });
  return JSON.stringify(plainObject, filter, space);
};

var error = new Error('testing');
error.detail = 'foo bar';

console.log(stringifyError(error, null, '\t'));
Bryan Larsen
  • 9,468
  • 8
  • 56
  • 46
  • 10
    First time I've heard `monkey patching` :) – Chris Prince Jun 18 '16 at 17:27
  • 2
    @ChrisPrince But it won't be the last time, esp in JavaScript! Here's Wikipedia on [Monkey Patching](https://en.wikipedia.org/wiki/Monkey_patch), just for future folks' info. (In [Jonathan's answer](https://stackoverflow.com/a/18391400/1028230), as Chris understands, you're adding a new function, `toJSON`, *directly to `Error`'s prototype*, which is often not a great idea. Maybe someone else already has, which this checks, but then you don't know what that other version does. Or if someone unexpectedly gets yours, or assumes Error's prototype has specific properties, things could bork.) – ruffin Jun 06 '17 at 15:22
  • 1
    this is nice, but omits the stack of the error (which is shown in the console). not sure of the details, if this is Vue-related or what, just wanted to mention this. – phil294 Mar 13 '19 at 23:54
13

We needed to serialise an arbitrary object hierarchy, where the root or any of the nested properties in the hierarchy could be instances of Error.

Our solution was to use the replacer param of JSON.stringify(), e.g.:

function jsonFriendlyErrorReplacer(key, value) {
  if (value instanceof Error) {
    return {
      // Pull all enumerable properties, supporting properties on custom Errors
      ...value,
      // Explicitly pull Error's non-enumerable properties
      name: value.name,
      message: value.message,
      stack: value.stack,
    }
  }

  return value
}

let obj = {
    error: new Error('nested error message')
}

console.log('Result WITHOUT custom replacer:', JSON.stringify(obj))
console.log('Result WITH custom replacer:', JSON.stringify(obj, jsonFriendlyErrorReplacer))
Joel Malone
  • 1,214
  • 1
  • 12
  • 20
13

I was working on a JSON format for log appenders and ended up here trying to solve a similar problem. After a while, I realized I could just make Node do the work:

const util = require("util");
...
return JSON.stringify(obj, (name, value) => {
    if (value instanceof Error) {
        return util.format(value);
    } else {
        return value;
    }
}
Jason
  • 3,021
  • 1
  • 23
  • 25
  • 1
    It should be `instanceof` and not `instanceOf`. – lakshman.pasala Apr 22 '20 at 09:59
  • it looks like that only formats the message, not other properties. I was missing the `stack` property from my error when using this. – ps2goat Jul 09 '21 at 21:56
  • I've tried this on node 6 through 16. I get stack traces in all of them, @ps2goat what version are you using? – Jason Jul 12 '21 at 22:44
  • I'm using 16.0.4, but I found my issue. I missed the `name` parameter to the formatter function. After fixing it, this solution still strings all properties together. `name: message: stacktrace`. The serialize-error package seems to be a better fit if you want to retain the structure of the error object and prevent a few other issues (logging buffers, circular references, etc.) – ps2goat Jul 13 '21 at 15:02
9

You can also just redefine those non-enumerable properties to be enumerable.

Object.defineProperty(Error.prototype, 'message', {
    configurable: true,
    enumerable: true
});

and maybe stack property too.

cheolgook
  • 107
  • 2
  • 2
  • 21
    **Don't change objects you don't own,** it can break other parts of your application and good luck finding why. – fregante Mar 17 '19 at 04:58
9

If using nodejs there is better reliable way by using native nodejs inspect. As well you can specify to print objects to unlimited depth.

Typescript example:

import { inspect }  from "util";

const myObject = new Error("This is error");
console.log(JSON.stringify(myObject)); // Will print {}
console.log(myObject); // Will print full error object
console.log(inspect(myObject, {depth: null})); // Same output as console.log plus it works as well for objects with many nested properties.

Link to documentation, link to example usage.

And as well discussed in the topic How can I get the full object in Node.js's console.log(), rather than '[Object]'? here in stack overflow.

David Navrkal
  • 464
  • 5
  • 8
7

String constructor should be able to stringify error

try { 
  throw new Error("MY ERROR MSG")
} catch (e) {
  String(e) // returns 'Error: MY ERROR MSG'
}
Deep Panchal
  • 129
  • 1
  • 4
6

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.

 function recursivePropertyFinder(obj){
    if( obj === Object.prototype){
        return {};
    }else{
        return _.reduce(Object.getOwnPropertyNames(obj), 
            function copy(result, value, key) {
                if( !_.isFunction(obj[value])){
                    if( _.isObject(obj[value])){
                        result[value] = recursivePropertyFinder(obj[value]);
                    }else{
                        result[value] = obj[value];
                    }
                }
                return result;
            }, recursivePropertyFinder(Object.getPrototypeOf(obj)));
    }
}


Error.prototype.toJSON = function(){
    return recursivePropertyFinder(this);
}

Here's the test I did in Chrome:

var myError = Error('hello');
myError.causedBy = Error('error2');
myError.causedBy.causedBy = Error('error3');
myError.causedBy.causedBy.displayed = true;
JSON.stringify(myError);

{"name":"Error","message":"hello","stack":"Error: hello\n    at <anonymous>:66:15","causedBy":{"name":"Error","message":"error2","stack":"Error: error2\n    at <anonymous>:67:20","causedBy":{"name":"Error","message":"error3","stack":"Error: error3\n    at <anonymous>:68:29","displayed":true}}}  
4

Just convert to a regular object

// example error
let err = new Error('I errored')

// one liner converting Error into regular object that can be stringified
err = Object.getOwnPropertyNames(err).reduce((acc, key) => { acc[key] = err[key]; return acc; }, {})

If you want to send this object from child process, worker or though the network there's no need to stringify. It will be automatically stringified and parsed like any other normal object

Pawel
  • 16,093
  • 5
  • 70
  • 73
3

I've extended this answer: Is it not possible to stringify an Error using JSON.stringify?

serializeError.ts

export function serializeError(err: unknown) {
    return JSON.parse(JSON.stringify(err, Object.getOwnPropertyNames(err)))
}

And I can use it like this:

import { serializeError } from '../helpers/serializeError'; // Change to your path

try {
    const res = await create(data);
    return { status: 201 };
} catch (err) {
    return { status: 400, error: serializeError(err) };
}
Elron
  • 1,235
  • 1
  • 13
  • 26
0

You can solve this with a one-liner( errStringified ) in plain javascript:

var error = new Error('simple error message');
var errStringified = (err => JSON.stringify(Object.getOwnPropertyNames(Object.getPrototypeOf(err)).reduce(function(accumulator, currentValue) { return accumulator[currentValue] = err[currentValue], accumulator}, {})))(error);
console.log(errStringified);

It works with DOMExceptions as well.

savram
  • 500
  • 4
  • 18