Logo

Is it not possible to stringify an Error using JSON.stringify?

By default, JSON.stringify(new Error("Something went wrong")) returns an empty object ({}) rather than the expected error details. This might feel counterintuitive but happens because Error objects in JavaScript don’t contain their key properties (such as message, stack) as enumerable fields—which is what JSON.stringify() relies on to serialize objects.

Below, we’ll explain why this happens and how to work around it.

Why Does JSON.stringify(Error) Return {}?

  1. Non-Enumerable Properties
    JavaScript’s Error objects store properties like message, name, and stack as non-enumerable. Consequently, JSON.stringify() can’t see them—meaning it serializes the object with no enumerable properties, resulting in {}.

  2. Specification & Design
    According to the ECMAScript specification, Error objects are meant to capture an exception’s state primarily for debugging. They’re not structured data like typical objects, so features like enumerability often aren’t a priority.

Workarounds for Stringifying Errors

1. Custom Replacer Function

If you want more control, you can provide a custom replacer function to JSON.stringify() to manually pull out relevant error properties. For example:

function errorReplacer(key, value) { // The 'this' context refers to the current object being serialized if (value instanceof Error) { return { name: value.name, message: value.message, stack: value.stack, // Include any other fields, e.g., custom properties }; } return value; } // Example usage try { throw new Error("Something went wrong!"); } catch (err) { const serialized = JSON.stringify(err, errorReplacer, 2); console.log(serialized); /* { "name": "Error", "message": "Something went wrong!", "stack": "Error: Something went wrong!\n at ..." } */ }

Key Points

  • Explicitly Extract Properties: We target Error objects and convert them to a more serializable structure.
  • Works for Nested Errors: If there’s an Error object nested inside another structure, this replacer will pick it up, too.

2. Copy Properties into a Plain Object

Another approach is to create a plain object that copies the essential fields of the error, then serialize that object directly:

function errorToPlainObject(err) { return { name: err.name, message: err.message, stack: err.stack, // Add custom properties if needed }; } try { throw new Error("Oops!"); } catch (err) { const plainError = errorToPlainObject(err); console.log(JSON.stringify(plainError, null, 2)); }

Pros

  • Simplicity: Straightforward function that can be reused.
  • Portability: The resulting object can be passed across processes, stored in logs, or sent to a server.

Cons

  • Manual Maintenance: If you need extra error fields (like custom properties), you must remember to include them in this function.

3. Libraries / Built-In Logging

Some logging libraries handle error serialization for you. For example, Winston, Bunyan, or other Node.js logging frameworks often have built-in logic to capture error messages, names, and stacks for you.

If you’re already using a logging framework, check whether it has an “error serializer” or “formatter” plugin. This can simplify the entire process of turning errors into JSON logs.

Final Thoughts

  • Default Behavior: JSON.stringify() on an Error returns {} because error properties aren’t enumerable.
  • Use a Custom Replacer: A custom replacer function or a helper method is often the simplest solution to capture name, message, stack, and any custom fields.
  • Logging Frameworks: Consider using a robust logging solution that automatically serializes errors for you, reducing boilerplate.

Remember: Errors are primarily for debugging and stack traces; if you need structured data, explicitly define which parts of the error to serialize. By taking control of the serialization process, you’ll produce more helpful logs, more transparent debugging, and better error handling across distributed systems.

CONTRIBUTOR
TechGrind