Four Backslashes and Counting
"{\\\"user\\\":{\\\"name\\\":\\\"Anna\\\"}}"
Somewhere under that is {"user":{"name":"Anna"}}. Getting there means unescaping, and the question is how many times.
Guess wrong in one direction and you're left with a string that still contains backslashes. Guess wrong in the other and your parser throws, or worse, succeeds and gives you something subtly mangled.
The reliable approach isn't guessing. It's counting.
Counting Escape Layers by the Backslashes
Each round of JSON encoding doubles the backslashes protecting a quote character. That gives you a direct read on how many layers you're dealing with.
| What you see before a quote | Encoding layers | Parses needed |
|---|---|---|
" |
0 | 1 |
\" |
1 | 2 |
\\" |
2 | 3 |
\\\" |
3 | 4 |
\\\\" |
4 | 5 |
Count the backslashes immediately before a quote character that's part of the inner data — not the outer quotes wrapping the whole string.
{"payload": "{\"id\":42}"}
↑ one backslash → one layer of nesting inside payload
{"payload": "{\\\"id\\\":42}"}
↑ three backslashes → the payload is itself double-encoded
The same doubling applies to newlines: \n → \\n → \\\\n, and so on.
A quick sanity check: the backslash count before an inner quote should always be an odd number in valid nested JSON — 1, 3, 7, 15. It follows the pattern 2ⁿ − 1. If you see an even number, something has been escaped inconsistently and you have a bug upstream rather than a straightforward nesting.
Unescaping in Steps
The JSON Unescape tool handles the mechanical part:
- Paste the escaped string.
- Unescape.
- Look at the result — is it valid JSON, or is it still a string with backslashes?
- If backslashes remain, unescape again.
- Repeat until you have parseable JSON.
Do it one layer at a time and inspect between each. Unescaping blindly until "it looks right" risks stripping a backslash that was genuinely part of the data — a Windows path, a regex pattern, a LaTeX string.
Working through the example:
Start: "{\\\"user\\\":{\\\"name\\\":\\\"Anna\\\"}}"
Layer 1: "{\"user\":{\"name\":\"Anna\"}}"
Layer 2: {"user":{"name":"Anna"}}
Layer 3: parses to an object
Three operations: two unescapes and a parse. Which matches the three-backslash count from the table.
Where the Layers Come From
Understanding the source usually tells you how many layers to expect.
Logging a serialised payload. A service logs a structured event, and the event includes an already-serialised request body as a string field. One extra layer.
Message queues. Many queue systems take a string payload. Producers serialise their object to a string; the queue message itself is then serialised as JSON for transport. One layer.
Database TEXT columns. JSON stored as text, then that row serialised into a JSON API response. One layer.
Webhook forwarding. A webhook receiver wraps the original payload in an envelope and forwards it. Each hop can add a layer.
Environment variables. Config JSON stored in an env var, read as a string, embedded in another config document. One layer.
Double JSON.stringify. The classic bug — a value that's already a string gets stringified again, usually because a function stringifies defensively without checking.
// The bug
const payload = JSON.stringify(data);
send(JSON.stringify({ body: payload })); // payload was already a string
// The fix
send(JSON.stringify({ body: data }));
The general rule: each hop that treats structured data as an opaque string adds a layer.
Detecting Programmatically
If you're handling data with variable nesting depth, iterate rather than hard-coding a count:
function fullyParse(input, maxDepth = 5) {
let current = input;
for (let i = 0; i < maxDepth; i++) {
if (typeof current !== 'string') return current;
const trimmed = current.trim();
// Only attempt a parse if it looks like JSON
if (!/^[[{"]/.test(trimmed)) return current;
try {
const next = JSON.parse(trimmed);
if (next === current) return current; // no progress
current = next;
} catch {
return current;
}
}
return current;
}
Three safeguards matter here:
A depth limit. Without one, malformed input can loop. Five is generous; real data rarely exceeds three.
A shape check before parsing. Otherwise a legitimate string value that happens to be "42" or "true" gets parsed into a number or boolean. This is a real bug — a product code of "007" becoming 7, or a version string "1.5" becoming a float.
A no-progress check. If parsing returns the same value, stop.
Don't apply this indiscriminately across an entire payload. Parse specific fields you know are nested. Recursively auto-parsing every string in a document will eventually mangle something.
What Not to Do
Don't strip backslashes with find-and-replace.
// Wrong
const fixed = escaped.replace(/\\/g, '');
This destroys legitimate backslashes in the data. A Windows path "C:\\Users\\data" becomes "C:Usersdata". A regex "\\d+" becomes "d+". Use a proper parser, which understands escape sequences.
Don't guess the layer count. Count the backslashes.
Don't unescape without inspecting. Look at the intermediate result each time.
Don't fix it only at the consumer. Double encoding is a producer bug. Unescaping twice at the receiving end works and leaves the bug in place, where it will surface again in a different consumer.
Escape Sequences Worth Knowing
Valid JSON string escapes are a short list:
\" quote
\\ backslash
\/ forward slash (optional, rarely needed)
\b backspace
\f form feed
\n newline
\r carriage return
\t tab
\uXXXX four-hex-digit Unicode code point
Anything else after a backslash is invalid.
The \uXXXX form covers the Basic Multilingual Plane. Characters above U+FFFF — emoji, some CJK extensions, historic scripts — are encoded as a surrogate pair: two consecutive \u escapes.
{"emoji": "\ud83d\ude00"}
That's one character encoded as two escapes. Splitting or truncating a string between the two halves produces an unpaired surrogate, which is invalid Unicode and causes errors downstream. Anything that truncates strings — a database column limit, a log line cap — needs to be surrogate-aware.
Fixing the Source
Once you've unescaped enough times to see the data, fix the producer.
Check whether a value is already a string before serialising it. The defensive JSON.stringify on a possibly-stringified value is the usual culprit.
Use a nested object, not a string field, when the schema allows it. {"payload": {...}} beats {"payload": "{...}"}.
If a string field is unavoidable, document it. A field name like payload_json signals that its contents need parsing; payload doesn't.
Consider Base64 for opaque blobs. If a field genuinely carries arbitrary serialised data that shouldn't be inspected, Base64 avoids escape-layer confusion entirely at the cost of a size increase.
Add a test. A test asserting that response.payload is an object rather than a string catches a regression the moment someone adds a stray stringify.
FAQ
How many times should I unescape? Count the backslashes before an inner quote: one backslash means one extra layer, three means two, seven means three. Then unescape that many times before the final parse.
Why does my JSON parse into a string? It's double-encoded. The parse succeeded and returned the string that the outer JSON contained.
Can I just remove all the backslashes? No. That destroys legitimate escaped characters in the data, including Windows paths, regex patterns and newlines.
What causes double encoding? A value being serialised twice, usually because something stringified data that was already a string. Common at logging, queueing and storage boundaries.
What are \ud83d\ude00?
A surrogate pair encoding a single character above U+FFFF. Both halves are required; splitting them produces invalid Unicode.
Should I fix this at the producer or consumer? The producer. Handling it at the consumer works but leaves the bug in place for every other consumer.
The Takeaway
Escape layers are countable, not guessable — the backslash count before an inner quote tells you exactly how many parses you need. Unescape one layer at a time, inspect between each, never strip backslashes with a regex, and fix the producer once you can see what it's actually sending.
Unescape and clean nested JSON free with the JSON Unescape tool at sadiqbd.com — no sign-up, instant results.