Escaping a JSON string means encoding the characters that would otherwise break the string, so the whole value stays valid JSON. It sounds trivial until you embed one JSON document inside another, paste JSON into source code, pass it through a shell, or store it in a database column — and suddenly your quotes and backslashes are fighting each other.
This guide covers exactly which characters JSON requires you to escape, how \uXXXX works, how to turn an entire JSON document into a string literal and back, and the double-escaping traps that produce those unreadable \\\" messes. Every example is runnable, and you can try any input in the JSON Escape / Unescape tool as you read.
What escaping a JSON string actually means
In JSON, a string is a sequence of characters wrapped in double quotes. Some characters have special meaning inside those quotes: a literal double quote would end the string early, and a backslash starts an escape sequence. Escaping replaces those characters with a safe two-character sequence — \" for a quote, \\ for a backslash — so the parser reads them as data rather than as structure.
Concretely, escaping takes a raw character sequence and produces the text that belongs *between the quotes* of a JSON string. Unescaping does the reverse: it takes an escaped JSON string literal and recovers the original characters. The two operations are exact inverses, so round-tripping a value through escape then unescape always returns what you started with.
The characters JSON requires you to escape
The JSON specification (RFC 8259) is strict here. Inside a string you must escape the double quote, the backslash, and every control character in the range U+0000 to U+001F. Common control characters have shorthand escapes; everything else uses the generic \uXXXX form.
| Character | Escape | Notes |
|---|---|---|
| " (quote) | \" | Required |
| \ (backslash) | \\ | Required |
| / (forward slash) | \/ | Optional — legal either way |
| Backspace | \b | U+0008 |
| Form feed | \f | U+000C |
| Newline | \n | U+000A |
| Carriage return | \r | U+000D |
| Tab | \t | U+0009 |
| Other control chars | \uXXXX | U+0000–U+001F must be escaped |
Everything else — letters, digits, punctuation, emoji, accented characters — can appear literally in a UTF-8 encoded JSON document without escaping. The forward slash is the odd one out: it is legal both escaped and unescaped, and encoders escape it only to avoid the </script> sequence when JSON is embedded in HTML.
When you actually need to escape JSON
You rarely escape JSON by hand for its own sake. It comes up when JSON must survive a trip through a context that also treats quotes or backslashes specially. The common cases:
- JSON inside JSON. An API envelope carries a
payloadfield whose value is itself a serialized JSON document. The inner document must be escaped to live inside the outer string. - JSON in source code. Pasting a JSON fixture into a JavaScript, Python, or Go string literal means escaping quotes and backslashes so the compiler reads it as one string.
- JSON in a shell or environment variable. Passing JSON as a
curlbody or an env var means handling both shell quoting and JSON escaping at once. - JSON in a database column. Storing a raw JSON blob in a text (not native JSON) column, or inside a larger document in a JSONB field, requires the inner value to be escaped.
In every case the mechanical operation is the same: take the JSON text and encode it as a single string literal. Only the outer layer you are escaping *for* changes.
Escaping a whole JSON document into a string literal
The most common real task is turning an entire JSON document into one string so it can be embedded elsewhere. In JavaScript, JSON.stringify does this correctly for any string input — it emits a valid JSON string literal with every special character escaped.
const inner = { name: "Ada", tags: ["a", "b"] };
// 1. Serialize the inner document to text.
const innerText = JSON.stringify(inner);
// innerText === '{"name":"Ada","tags":["a","b"]}'
// 2. Escape that text by placing it in a field of the outer document.
const envelope = { payload: innerText };
const out = JSON.stringify(envelope);
console.log(out);
// {"payload":"{\"name\":\"Ada\",\"tags\":[\"a\",\"b\"]}"}Notice what happened: the inner quotes became \", and the inner document is now a single opaque string as far as the outer JSON is concerned. To read it back, parse the outer document, then parse the payload string a second time to recover the object.
The tool above validates that your input is JSON, then encodes it as a string literal — so you get a guaranteed-valid escaped result plus a one-click unescape to reverse it. It is faster than eyeballing backslashes, and it catches the case where your input was not valid JSON to begin with.
Doing it in code: stringify to escape, parse to unescape
For a plain string (not necessarily JSON), JSON.stringify is still the right escaper, because a JSON string literal follows the same rules everywhere. Here is the round trip, including a Windows path where the backslashes matter.
const path = "C:\\Users\\Ana"; // the 12-char string C:\Users\Ana
const escaped = JSON.stringify(path);
console.log(escaped);
// "C:\\Users\\Ana" (a valid JSON string literal, quotes included)
const back = JSON.parse(escaped);
console.log(back === path); // truePython's json module works the same way: json.dumps escapes and json.loads unescapes. By default dumps also escapes non-ASCII characters to \uXXXX — pass ensure_ascii=False to keep UTF-8 characters literal.
import json
data = {"name": "Ada", "note": "say \"hi\""}
# Escape: serialize, then embed as a string field.
inner = json.dumps(data)
envelope = json.dumps({"payload": inner})
print(envelope)
# {"payload": "{\"name\": \"Ada\", \"note\": \"say \\\"hi\\\"\"}"}
# Unescape: parse twice.
outer = json.loads(envelope)
restored = json.loads(outer["payload"])
print(restored == data) # TrueThe double-escaping trap
The single most common escaping bug is applying the escape step twice. Each extra JSON.stringify on an already-stringified value doubles the backslashes, producing values like "{\\\"ok\\\":true}" that no consumer expects.
const obj = { ok: true };
// Correct: escape once.
const once = JSON.stringify(obj);
// '{"ok":true}'
// Wrong: escape the already-serialized string again.
const twice = JSON.stringify(once);
// '"{\\"ok\\":true}"'
// Reversing a double-escaped value takes two parses.
JSON.parse(JSON.parse(twice)); // { ok: true }This usually creeps in when a value is already a JSON string by the time your code receives it. A framework or ORM may serialize the field for you, and then your own stringify call escapes it again. The symptom is a stored or transmitted value with runs of \\ where you expected single backslashes.
How to tell how many times a value was escaped
Count the backslashes in front of an inner quote. One escape gives \", two gives \\\", three gives \\\\\\\". Each parse peels off one layer. If a single unescape does not yield valid JSON, try one more — but if you need three or more, fix the producer instead of piling on parses.
Unicode and \uXXXX escapes
The \uXXXX escape represents a character by its four-hex-digit UTF-16 code unit. JSON requires it only for control characters, but encoders often use it for any non-ASCII character to keep output in pure ASCII — safer for older transports and terminals. Both forms parse to the same value: "café" and "café" are equal strings.
Characters outside the Basic Multilingual Plane, such as most emoji, need a surrogate pair — two \uXXXX escapes together. You almost never write these by hand; the encoder produces them, but it is worth recognizing the pattern.
JSON.parse('"caf\\u00e9"') === "café"; // true
// An emoji as a surrogate pair.
JSON.parse('"\\ud83d\\ude80"'); // "🚀"
// JSON.stringify has no ASCII-only option; it keeps BMP
// characters literal by default:
JSON.stringify("café"); // '"café"'One practical note: a \uXXXX sequence must always have exactly four hex digits. Truncating it to \u1 or using non-hex characters is a syntax error, and a lone surrogate (one half of a pair) is technically invalid JSON, even though many parsers tolerate it. When in doubt, run the value through the escape/unescape tool or a validator to confirm it round-trips cleanly.