The JSON.stringify() function is the canonical way to convert a JavaScript object to JSON. Many JavaScript frameworks use JSON.stringify() under the hood: Express' res.json(), Axios' post(), and Webpack stats all call JSON.stringify(). In this article, I'll provide a practical overview of JSON.stringify(), including error cases.

Getting Started

All modern JavaScript runtimes support JSON.stringify(). Even Internet Explorer has JSON.stringify() support since IE8. Here's an example of converting a simple object to JSON:

const obj = { answer: 42 };

const str = JSON.stringify(obj);
str; // '{"answer":42}'
typeof str; // 'string'

You may see JSON.stringify() used with JSON.parse() as shown below. This pattern is one way of deep cloning a JavaScript object.

const obj = { answer: 42 };
const clone = JSON.parse(JSON.stringify(obj));

clone.answer; // 42
clone === obj; // false

Errors and Edge Cases

JSON.stringify() throws an error when it detects a cyclical object. In other words, if an object obj has a property whose value is obj, JSON.stringify() will throw an error.

const obj = {};
// Cyclical object that references itself
obj.prop = obj;

// Throws "TypeError: TypeError: Converting circular structure to JSON"
JSON.stringify(obj);

That is the only case where JSON.stringify() throws an exception, unless you're using custom toJSON() functions or replacer functions. However, you should still wrap JSON.stringify() calls in try/catch, because circular objects do pop up in practice.

There are several edge cases where JSON.stringify() doesn't throw an error, but you might expect it does. For example, JSON.stringify() converts NaN and Infinity to null:

const obj = { nan: parseInt('not a number'), inf: Number.POSITIVE_INFINITY };

JSON.stringify(obj); // '{"nan":null,"inf":null}'

JSON.stringify() also strips out properties whose values are functions or undefined:

const obj = { fn: function() {}, undef: undefined };

// Empty object. `JSON.stringify()` removes functions and `undefined`.
JSON.stringify(obj); // '{}'

Pretty Printing

The first parameter to JSON.stringify() is the object to serialize to JSON. JSON.stringify() actually takes 3 parameters, and the 3rd parameter is called spaces. The spaces parameter is used for formatting JSON output in a human readable way.

You can set the spaces parameter to either be a string or a number. If spaces is not undefined, JSON.stringify() will put each key in the JSON output on its own line, and prefix each key with spaces.

const obj = { a: 1, b: 2, c: 3, d: { e: 4 } };

// '{"a":1,"b":2,"c":3,"d":{"e":4}}'
JSON.stringify(obj);

// {
//   "a": 1,
//   "b": 2,
//   "c": 3,
//   "d": {
//     "e": 4
//   }
// }
JSON.stringify(obj, null, '  ');

// Use 2 spaces when formatting JSON output. Equivalent to the above.
JSON.stringify(obj, null, 2);

The spaces string doesn't have to be all whitespace, although in practice it usually will be. For example:

// {
// __"a": 1,
// __"b": 2,
// __"c": 3,
// __"d": {
// ____"e": 4
// __}
// }
JSON.stringify(obj, null, '__');

Replacers

The 2nd parameter to JSON.stringify() is the replacer function. In the above examples, replacer was null. JavaScript calls the replacer function with every key/value pair in the object, and uses the value the replacer function returns. For example:

const obj = { a: 1, b: 2, c: 3, d: { e: 4 } };

// `replacer` increments every number value by 1. The output will be:
// '{"a":2,"b":3,"c":4,"d":{"e":5}}'
JSON.stringify(obj, function replacer(key, value) {
  if (typeof value === 'number') {
    return value + 1;
  }
  return value;
});

The replacer function is useful for omitting sensitive data. For example, suppose you wanted to omit all keys that contain the substring 'password':

const obj = {
  name: 'Jean-Luc Picard',
  password: 'stargazer',
  nested: {
    hashedPassword: 'c3RhcmdhemVy'
  }
};

// '{"name":"Jean-Luc Picard","nested":{}}'
JSON.stringify(obj, function replacer(key, value) {
  // This function gets called 5 times. `key` will be equal to:
  // '', 'name', 'password', 'nested', 'hashedPassword'
  if (key.match(/password/i)) {
    return undefined;
  }
  return value;
});

The toJSON() Function

The JSON.stringify() function also traverses the object looking for properties that have a toJSON() function. If it finds a toJSON() function, JSON.stringify() calls the toJSON() function and uses the return value as a replacement. For example:

const obj = {
  name: 'Jean-Luc Picard',
  nested: {
    test: 'not in output',
    toJSON: () => 'test'
  }
};

// '{"name":"Jean-Luc Picard","nested":"test"}'
JSON.stringify(obj);

The toJSON() function can return any value, including objects, primitives, or undefined. If toJSON() returns undefined, JSON.stringify() will ignore that property.

Many JavaScript modules use toJSON() to ensure sophisticated objects get serialized correctly, like Mongoose documents and Moment objects.

Moving On

The JSON.stringify() function is a core JavaScript fundamental. Many libraries and frameworks use it under the hood, so a solid understanding of JSON.stringify() lets you do more with your favorite npm modules. For example, you can define alternate date formatting for your Express REST API using a custom toJSON() function on the native Date class, or ensure that a circular client-side object gets converted to JSON correctly when sending an HTTP request with Axios.

Found a typo or error? Open up a pull request! This post is available as markdown on Github
comments powered by Disqus