Stringifying JavaScript Objects to JSON

1

Stringifying JavaScript Objects to JSON

▶

When JavaScript applications need to send data over a network, save it to storage, or share it with an external system, the data must be converted from its native in-memory form into a portable text format. JSON (JavaScript Object Notation) is the standard for this, and JSON.stringify() is the built-in method that performs this conversion. Understanding it thoroughly — including its edge cases, its optional parameters, and the contexts where it is required — is essential for any web developer working with APIs, local storage, or data serialization.

What Is JSON.stringify()?

JSON.stringify() takes a JavaScript value and converts it into a JSON-formatted string. That output is always a plain string of text, not a JavaScript object, not a special data structure — just characters. This makes it universally transferable: it can be sent over HTTP, written to a file, stored in a database, or saved in the browser's local storage, all without any special handling on the receiving end beyond reading text.

The method is available globally in all modern browsers and in Node.js without any import or require statement. You can call it anywhere in your JavaScript code immediately:

const result = JSON.stringify({ name: "Alice", age: 30 });
console.log(result);       // '{"name":"Alice","age":30}'
console.log(typeof result); // "string"

It is important to understand that JSON.stringify() is the inverse of JSON.parse(). Where JSON.parse() takes a JSON string and reconstructs a JavaScript object from it, JSON.stringify() does the opposite — it takes a JavaScript value and produces a JSON string. The two methods are complementary and are routinely used together in a round-trip pattern: stringify before storing or sending, parse after retrieving or receiving.

const original = { city: "Paris", population: 2161000 };
const jsonString = JSON.stringify(original);   // Converts to string
const restored = JSON.parse(jsonString);       // Converts back to object

console.log(jsonString);           // '{"city":"Paris","population":2161000}'
console.log(restored.city);        // "Paris"
console.log(restored === original); // false — it is a new object

Basic Syntax and Parameters

The full signature of JSON.stringify() accepts three arguments:

JSON.stringify(value, replacer, space)

Only the first argument is required. The second and third are optional but powerful when you need fine-grained control over the output.

  • value — The JavaScript value to serialize. This can be an object, array, string, number, boolean, or null. This is the only required argument.
  • replacer — An optional filter or transformer. It can be either an array of strings (an allowlist of property names to include) or a function that is called for each key-value pair and controls what appears in the output. Passing null or omitting this argument applies no filtering.
  • space — An optional formatting hint. A number specifies how many spaces to use per indentation level; a string specifies the literal characters to use. When omitted, the output is compact with no extra whitespace.

Here are examples showing each parameter in isolation:

const user = { id: 1, name: "Bob", role: "admin" };

// First argument only — compact output
JSON.stringify(user);
// '{"id":1,"name":"Bob","role":"admin"}'

// With replacer array — allowlist of keys
JSON.stringify(user, ["id", "name"]);
// '{"id":1,"name":"Bob"}'

// With space number — indented output
JSON.stringify(user, null, 2);
// '{
//   "id": 1,
//   "name": "Bob",
//   "role": "admin"
// }'

When Stringification Is Needed in Web Development

There are three recurring scenarios where JSON.stringify() is not just convenient but strictly necessary.

The first and most common is sending data with the Fetch API. The HTTP protocol transfers bytes of text. When you use fetch() to make a POST or PUT request, the body option must be a string (or a Blob, FormData, etc. — but for JSON payloads, always a string). Passing a raw JavaScript object directly to body will cause it to be coerced using .toString(), producing the useless string "[object Object]". You must call JSON.stringify() explicitly:

const payload = { username: "alice", score: 42 };

fetch("https://api.example.com/scores", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(payload)  // Required — body must be a string
});

The Content-Type: application/json header tells the server what format the body is in; JSON.stringify() ensures the body actually is that format.

The second scenario is working with browser storage. Both localStorage and sessionStorage are key-value stores that only accept strings. Attempting to store an object directly results in the string "[object Object]", losing all structure. The correct approach is to stringify before saving and parse after retrieving:

const settings = { theme: "dark", fontSize: 16, notifications: true };

// Saving — must stringify
localStorage.setItem("userSettings", JSON.stringify(settings));

// Retrieving — must parse
const saved = JSON.parse(localStorage.getItem("userSettings"));
console.log(saved.theme); // "dark"

The third scenario is communicating with any JSON-based API or service. REST APIs, GraphQL endpoints, WebSocket servers, and many message queues all expect JSON payloads: properly formatted text where property names are double-quoted, values follow JSON syntax rules, and the whole thing is a single string. A raw JavaScript object satisfies none of those constraints until it passes through JSON.stringify().

How JSON.stringify() Handles Different Data Types

Not every JavaScript value can be represented in JSON, because JSON is a subset of JavaScript syntax with specific rules. Understanding how JSON.stringify() handles each type prevents hard-to-diagnose bugs where data silently disappears or changes shape.

The following table summarizes behavior by type:

JavaScript Value JSON Output Notes
String Quoted string Serialized as-is with double quotes
Number (finite) Number literal Serialized as-is
Boolean true / false Serialized as-is
null null Serialized as-is
Plain Object JSON object {} Own enumerable properties are included
Array JSON array [] All elements included; unsupported element types become null
undefined Omitted (in objects) / null (in arrays) Object properties with undefined values are dropped entirely
Function Omitted (in objects) / null (in arrays) Functions cannot be represented in JSON
Symbol Omitted Symbol-keyed and Symbol-valued properties are silently dropped
NaN null Not a valid JSON number
Infinity / -Infinity null Not a valid JSON number
Date ISO 8601 string Calls .toISOString() automatically

Observe the silent omission of undefined and functions in an object context:

const data = {
  name: "Carol",
  age: undefined,        // omitted
  greet: function() {},  // omitted
  score: NaN,            // becomes null
  total: Infinity        // becomes null
};

JSON.stringify(data);
// '{"name":"Carol","score":null,"total":null}'

This silent omission is a common source of bugs. If you expect a property to be present in the output and it has an undefined value or holds a function, it will simply not appear in the JSON string with no error or warning. Always verify what the stringified output actually looks like during development.

In arrays, the behavior is slightly different — unsupported values are replaced with null rather than omitted, so array indices are preserved:

const arr = [1, undefined, function() {}, "hello", NaN];
JSON.stringify(arr);
// '[1,null,null,"hello",null]'

Using the Replacer to Control Output

The replacer argument gives you programmatic control over what ends up in the JSON string. There are two forms: an array allowlist and a function transformer.

Replacer as an array — When you pass an array of strings, only properties whose names appear in that array are included in the output. This is the simplest way to whittle down a large object to only the fields you want to expose:

const product = {
  id: 101,
  name: "Laptop",
  price: 999.99,
  internalCost: 450.00,  // should not leave the system
  supplierId: "S-42"     // should not leave the system
};

JSON.stringify(product, ["id", "name", "price"]);
// '{"id":101,"name":"Laptop","price":999.99}'

The array replacer does not apply recursively in a configurable way — it filters all objects at all nesting levels by the same list of property names.

Replacer as a function — The function form is more powerful. It is called once for each key-value pair (and once initially with an empty-string key and the entire value as the root). It receives the key and value, and whatever it returns becomes the serialized value. Returning undefined excludes the property entirely:

const config = {
  host: "db.example.com",
  port: 5432,
  username: "admin",
  password: "s3cr3t",   // must not be serialized
  token: "abc123xyz"    // must not be serialized
};

const safeConfig = JSON.stringify(config, (key, value) => {
  if (key === "password" || key === "token") {
    return undefined; // exclude this property
  }
  return value; // include everything else unchanged
});

console.log(safeConfig);
// '{"host":"db.example.com","port":5432,"username":"admin"}'

You can also use the function to transform values rather than merely exclude them:

const order = {
  item: "Widget",
  quantity: 5,
  unitPrice: 4.5,
  discount: 0.1
};

JSON.stringify(order, (key, value) => {
  if (key === "discount") {
    return `${(value * 100).toFixed(0)}%`; // transform number to percentage string
  }
  return value;
});
// '{"item":"Widget","quantity":5,"unitPrice":4.5,"discount":"10%"}'

This sanitization use case is particularly important when logging objects for debugging or sending telemetry data — sensitive credentials should never be serialized into a log line or a network request body without being explicitly removed or masked.

Pretty-Printing JSON with the Space Argument

By default, JSON.stringify() produces a compact string with no extra whitespace. This is ideal for network transmission because it minimizes payload size. However, compact JSON is difficult to read, making it unsuitable for debugging or logging.

The third argument, space, controls indentation. Passing a number inserts that many space characters per level of nesting:

const profile = {
  user: { name: "Dana", age: 28 },
  hobbies: ["reading", "cycling"],
  active: true
};

console.log(JSON.stringify(profile, null, 2));
{
  "user": {
    "name": "Dana",
    "age": 28
  },
  "hobbies": [
    "reading",
    "cycling"
  ],
  "active": true
}

Passing a string uses that string as the indent unit. A common choice is "\t" for a tab character, which some developers prefer for readability in log files:

JSON.stringify(profile, null, "\t");
// Produces output indented with tab characters instead of spaces

If the string is longer than 10 characters, only the first 10 are used — this is an enforced limit in the specification.

You can also use a custom string like "--" for unconventional visual formatting, though this is rarely practical:

JSON.stringify({ a: 1, b: 2 }, null, "--");
// '{
// --"a": 1,
// --"b": 2
// }'

A useful rule of thumb: use a space value of 2 during development and debugging, and omit it (or pass 0 or null) in production code that transmits data over a network. Even for small payloads, unnecessary whitespace adds bytes; at scale, this matters. For logging to the console during development, pretty-printing makes JSON immediately scannable rather than requiring you to mentally parse a dense one-liner.

// Development — easy to read in the console
console.log(JSON.stringify(responseData, null, 2));

// Production — minimal size in a network request
fetch(endpoint, { method: "POST", body: JSON.stringify(payload) });
NotesHow to convert JavaScript objects into JSON strings using JSON.stringify(), and when this operation is needed in web development.