Escape JSON as a String Literal (and Decode Double-Encoded JSON)

Stringifying JSON wraps it in quotes and escapes the inner quotes and special characters, producing a JSON string literal you can safely embed elsewhere.

What Stringifying to a Literal Means

Passing a JSON string to JSON.stringify wraps it in double quotes and escapes the inner quotes, backslashes, and control characters — producing a single string value that is safe to embed inside another JSON document. The JSON Stringify tool handles arbitrary depths in one click.

const inner   = '{"name":"Ada","note":"She said \"hi\""}';
const wrapped = JSON.stringify(inner);
// wrapped is:  "{\"name\":\"Ada\",\"note\":\"She said \\\"hi\\\"\"}"

The outer document now contains one string value, not a nested object — the receiver has to JSON.parse it once to recover the inner JSON text.

Why You Need It

Three common scenarios force this round trip:

  • Embedding JSON inside another JSON field — a webhook payload field that wraps the real body, a job-queue envelope around the task data.
  • Storing JSON in a string database column (TEXT / VARCHAR) — older schemas before Postgres jsonb and MySQL JSON types existed.
  • Passing JSON as an environment variable or URL parameter — neither can hold raw newlines or unescaped quotes safely.

Escaping Rules

Per RFC 8259 §7, six characters and the general Unicode escape need backslash treatment inside a JSON string:

Character JSON escape
" (double quote) \"
\ (backslash) \\
/ (solidus) \/ (optional — / is also legal unescaped)
\b (backspace, U+0008) \b
\f (form feed, U+000C) \f
\n (newline, U+000A) \n
\r (carriage return, U+000D) \r
\t (tab, U+0009) \t
Any other control char (U+0000–U+001F) \uXXXX

The result contains no raw line breaks — a stringified JSON value is always a single line. That property is what makes it safe in log lines, env vars, and URL parameters.

Decoding Double-Encoded JSON

If you receive a string that starts with {\" or [\" and contains escaped-quote sequences, it was stringified once too many times. Parse it once to recover the inner JSON text, then parse again to get the actual value:

const raw = '"{\\"name\\":\\"Ada\\"}"';   // double-encoded
const innerText = JSON.parse(raw);          // "{\"name\":\"Ada\"}" — still a string!
const value     = JSON.parse(innerText);    // { name: 'Ada' } — finally the object

If the first parse returns a string instead of an object/array, you're not done — repeat until the result type is what you expected.

Where Double-Encoding Shows Up

The recurring offenders, in rough order of frequency:

  • Logging frameworks that serialize context objects with JSON.stringify before joining the line — and a downstream log pipeline that does it again.
  • Webhook payloads that wrap a JSON body inside a body field (Stripe, GitHub, Slack signatures all do this in different ways).
  • Message queues that send JSON-as-string envelopes (SQS messages, Kafka headers).
  • Database columns typed as TEXT / VARCHAR where someone stored JSON.stringify(obj) instead of using a JSON / jsonb column.
  • Form fields carrying JSON that got URL-encoded, decoded into a string, then handed to another JSON parser without unwrapping.

The detection rule: look for a value that starts with "{ or "[ (with the leading quote escaped) and contains an even number of backslashes in front of the inner quotes. That's a stringified JSON string.

Working with Embedded JSON in Logs

Structured loggers often stringify context objects to keep each log line a single value — that's the right thing to do for log infrastructure. To inspect a captured line:

  1. Pull the line out of the log viewer.
  2. Unescape with JSON.parse(line) to recover the inner JSON text.
  3. Format with JSON.stringify(value, null, 2) (or paste into the JSON Stringify tool) to pretty-print.

Avoid grep-ing a multi-line JSON object — JSON stringified to one line is dramatically easier to search.

Tools for the Round-Trip

Three quick options, no install needed:

// Browser DevTools / Node REPL
const value = JSON.parse(line);
console.log(JSON.stringify(value, null, 2));
# Shell — Node CLI
node -e 'console.log(JSON.stringify(JSON.parse(process.argv[1]), null, 2))' "$LINE"

# Shell — jq (if line is on stdin)
echo "$LINE" | jq -r 'fromjson | tojson | fromjson'

The JSON Stringify tool handles arbitrarily deep nesting (triple-encoded, quadruple-encoded) in one click — useful when you can't tell up-front how many JSON.parse calls you need.

See also

This guide pairs with the LLM JSON repair guide (LLM responses sometimes arrive double-encoded when the model wraps its answer in a string literal) and the JSON Stringify tool for the round-trip work.

Sources

  • RFC 8259 §7 — the JSON string grammar, including the full escape table
  • MDN — JSON.stringify (the JS reference for the escape behavior)
  • MDN — JSON.parse (the reverse direction)

Last reviewed June 2026.