Skip to content

Pretty print JSON in JavaScript

Short answer

To pretty print JSON in JavaScript, pass an indent as the third argument: JSON.stringify(data, null, 2). The second argument is the replacer; null keeps every property.

Updated · 4 min read

Pretty print JSON with JSON.stringify

const cart = { id: 'c-311', currency: 'EUR', items: [{ sku: 'TEA-02', qty: 3 }, { sku: 'MUG-11', qty: 1 }], coupon: null };
console.log(JSON.stringify(cart, null, 2));
{
  "id": "c-311",
  "currency": "EUR",
  "items": [
    {
      "sku": "TEA-02",
      "qty": 3
    },
    {
      "sku": "MUG-11",
      "qty": 1
    }
  ],
  "coupon": null
}

The third argument is a number of spaces, capped at 10, or a string such as '\t' for tabs. Leave it out for one line.

If you have a JSON string rather than an object, parse it first: JSON.stringify(JSON.parse(text), null, 2). Stringifying the string itself only wraps it in quotes and escapes its quotes. For a fetch response, res.json() does the parsing:

const res = await fetch('https://api.example.com/cart');
console.log(JSON.stringify(await res.json(), null, 2));

Parsing has one cost: integers above 253 lose precision, so {"orderId": 9007199254740993} prints as 9007199254740992. JSON Prettier formats the text without converting numbers, so every digit stays.

Filter or change values with a replacer

An array keeps only the keys it names, at every level:

JSON.stringify(cart, ['id', 'items', 'sku'], 2);
{
  "id": "c-311",
  "items": [
    {
      "sku": "TEA-02"
    },
    {
      "sku": "MUG-11"
    }
  ]
}

A function receives each key and value and returns what to write. Returning undefined drops the key. Use it to hide secrets before logging:

const session = { user: 'mia', token: 'eyJhbGciOi...', expires: new Date(Date.UTC(2026, 10, 5, 12, 0)) };
console.log(JSON.stringify(session, (key, value) => (key === 'token' ? '[hidden]' : value), 2));
{
  "user": "mia",
  "token": "[hidden]",
  "expires": "2026-11-05T12:00:00.000Z"
}

Dates reach the replacer as ISO strings already, because toJSON runs first, so value instanceof Date is never true there.

Sort keys

JSON.stringify keeps insertion order. To sort, rebuild each plain object in a replacer:

const sortKeys = (key, value) =>
  value && typeof value === 'object' && !Array.isArray(value)
    ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))
    : value;

console.log(JSON.stringify({ zone: 'eu', active: true, limits: { max: 5, burst: 9 } }, sortKeys, 2));
{
  "active": true,
  "limits": {
    "burst": 9,
    "max": 5
  },
  "zone": "eu"
}

Integer-like keys ignore this: JavaScript objects always list them first, in numeric order, so { b: 1, 10: 'x', 2: 'y', a: 2 } comes out as {"2":"y","10":"x","a":2,"b":1}. JSON Prettier’s Sort keys sorts them as plain text instead ("10" before "2"). In Python, sort_keys=True does this in one argument: see pretty print JSON in Python.

Node.js one-liners

node -p "JSON.stringify(require('./cart.json'), null, 2)"
curl -s https://api.example.com/cart | node -p "JSON.stringify(JSON.parse(require('fs').readFileSync(0, 'utf8')), null, 2)"

node -p prints the result of the expression, and readFileSync(0) reads standard input. The JavaScript uses only single quotes, so both lines work in bash, zsh, cmd and PowerShell (type curl.exe in Windows PowerShell 5.1, where curl means Invoke-WebRequest). jq and other tools are in pretty print JSON on the command line.

To save pretty JSON from a script, add a final newline: writeFileSync('cart.json', JSON.stringify(cart, null, 2) + '\n').

In Node, console.log stops at depth 2:

{ order: { customer: { address: [Object] } } }

console.dir(obj, { depth: null }) prints every level.

In the browser console

console.log(cart) shows an expandable object, but the browser reads its contents when you expand it, not when it was logged, so later changes appear in old log lines. Log a string to capture the moment:

console.log(JSON.stringify(cart, null, 2));
copy(JSON.stringify(cart, null, 2));
console.table(cart.items);

copy() is a DevTools helper in Chrome, Edge and Firefox that puts its argument on the clipboard. console.table shows an array of objects as rows.

In a web page

<pre id="out"></pre>
<script>
  const cart = { id: 'c-311', items: [{ sku: 'TEA-02', qty: 3 }] };
  document.getElementById('out').textContent = JSON.stringify(cart, null, 2);
</script>

<pre> keeps the spaces and line breaks. Use textContent, not innerHTML, or a value such as <img src=x onerror=alert(1)> runs as HTML.

[object Object] and other surprises

String concatenation calls toString(), not JSON.stringify:

console.log('Cart: ' + cart); // Cart: [object Object]
console.log(`Cart: ${cart}`); // Cart: [object Object]
console.log('Cart:', cart);   // logs the object itself

JSON.parse(cart) fails for the same reason, with SyntaxError: "[object Object]" is not valid JSON; see [object Object] is not valid JSON.

Some values do not survive JSON.stringify:

Value Result
undefined, functions, symbols Key left out (null inside arrays)
Map, Set {}
NaN, Infinity null
Date ISO string
BigInt TypeError: Do not know how to serialize a BigInt
Circular reference TypeError: Converting circular structure to JSON

Convert a Map with Object.fromEntries(map) and a Set with [...set] before stringifying. More guides are in JSON guides.

Questions

Why is my output still on one line?

Check the argument order. JSON.stringify(data, 2) treats 2 as a replacer, ignores it and returns one line; the indent is the third argument.

How do I pretty print JSON in React?

Render it inside <pre>: <pre>{JSON.stringify(data, null, 2)}</pre>. React escapes text children, so this is safe without dangerouslySetInnerHTML.

Tools for this

Esc