To escape JSON, you encode the characters that would otherwise break a string, so the value stays valid JSON. This gets tricky when you embed one JSON document in another, paste JSON into source code, pass it through a shell or store it in a database column. That is when quotes and backslashes start to collide.
This guide covers which characters JSON requires you to escape, how \uXXXX works, and how to turn a whole JSON document into a string literal and unescape it again. It also covers the double-escaping bugs that produce unreadable \\\" sequences. Every example runs as written, and you can test any input in the JSON Escape / Unescape tool as you read.
What escaping a JSON string means
A JSON string is a sequence of characters 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 them with a safe two-character sequence, \" for a quote and \\ for a backslash, so the parser reads them as data and not as structure.
Escaping takes raw characters and produces the text that goes *between the quotes* of a JSON string. Unescaping does the reverse. It takes an escaped JSON string literal and gives back the original characters. The two are exact inverses, so escaping and then unescaping a value always returns what you started with.
Which characters must be escaped in JSON
The JSON specification (RFC 8259) is strict here. Inside a string you must escape the double quote, the backslash and every control character from U+0000 to U+001F. Common control characters have short escapes. The rest use the generic \uXXXX form.
| Character | Escape | Notes |
|---|---|---|
| " (quote) | \" | Required |
| \ (backslash) | \\ | Required |
| / (forward slash) | \/ | Optional. Valid 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 to U+001F must be escaped |
Everything else can appear literally in a UTF-8 JSON document, including letters, digits, punctuation, emoji and accented characters. The forward slash is the odd one out. It is valid escaped or unescaped, and encoders escape it only to avoid a </script> sequence when JSON is embedded in HTML.
When you need to escape JSON
You need to escape JSON when it passes through another context that also treats quotes or backslashes specially. The common cases:
- JSON inside JSON. An API envelope has a
payloadfield whose value is a serialized JSON document. The inner document must be escaped to fit inside the outer string. - JSON in source code. To paste a JSON fixture into a JavaScript, Python or Go string literal, you escape 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 dealing with shell quoting and JSON escaping at the same time. - JSON in a database column. Storing a raw JSON blob in a text column (not a native JSON column), or inside a larger document in a JSONB field, requires the inner value to be escaped.
The operation is the same each time. You take the JSON text and encode it as one string literal. Only the outer layer you escape *for* changes.
How to escape a whole JSON document into a string
The most common task is turning a whole JSON document into one string so you can embed it somewhere else. In JavaScript, JSON.stringify does this correctly for any string. It returns 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\"]}"}The inner quotes became \", and the outer JSON now sees the inner document as one opaque string. To read it back, parse the outer document, then parse the payload string a second time to get the object.
The tool checks that your input is valid JSON, then encodes it as a string literal. To unescape a JSON string, switch direction and paste the quoted literal. You get readable, indented JSON back. If your input was not valid JSON, the tool tells you.
Escape and unescape JSON in code: stringify and parse
For any plain string, JSON or not, JSON.stringify is the right escaper, because JSON string literals follow the same rules everywhere. Here is the round trip with 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 most common escaping bug is escaping twice. Each extra JSON.stringify on an already-stringified value doubles the backslashes, and you get 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 happens when a value is already a JSON string by the time your code gets it. A framework or ORM may serialize the field for you, and your own stringify call escapes it again. The symptom is a stored or sent 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 give \\\", and three give \\\\\\\". Each parse removes one layer. If one unescape does not give valid JSON, try another. If you need three or more, fix the code that produces the value instead of adding more parses.
How to escape quotes, slashes, apostrophes and \u0000 in JSON
Most escaping questions are about one character. The forward slash and the apostrophe never need escaping in JSON. The double quote always does. Control characters such as NUL must use a \u escape.
| Character | Inside a JSON string | What encoders do |
|---|---|---|
Forward slash / | / or \/. Both are valid and both parse to / | JSON.stringify and Python json.dumps leave it alone. PHP json_encode writes \/ unless you pass JSON_UNESCAPED_SLASHES |
Apostrophe ' | Always literal. \' is not a valid JSON escape and makes the document invalid | Never escaped. Escape it for the surrounding context (SQL, shell) instead |
Double quote " | \" (required) | Always escaped |
Backslash \ | \\ (required) | Always escaped |
NUL U+0000 | \u0000 (required, and valid JSON) | Escaped as \u0000. Some stores, such as PostgreSQL jsonb, reject it |
| Other control characters | \n, \r, \t, \b, \f, or \u00XX | JSON.stringify uses the short forms where they exist and \u00XX (for example \u001b) otherwise |
Quotes inside an array of stringified JSON
When each element of an array is a JSON document stored as a string, every inner double quote is escaped once. Build it by stringifying each item and then the array. Don't concatenate strings.
const events = [{ id: 1, type: "click" }, { id: 2, type: "view" }];
// Each element becomes a JSON string, then the array is serialized.
const out = JSON.stringify(events.map((e) => JSON.stringify(e)));
console.log(out);
// ["{\"id\":1,\"type\":\"click\"}","{\"id\":2,\"type\":\"view\"}"]
// Reverse it: parse the array, then parse each element.
const back = JSON.parse(out).map((s) => JSON.parse(s));If you control the format, use an array of objects ([{"id":1},{"id":2}]) instead of an array of strings. It is smaller, needs no escaping, and consumers can read it with one parse.
Python json.dumps and ensure_ascii
Like every JSON encoder, Python's json.dumps escapes quotes, backslashes and control characters. It also defaults to ensure_ascii=True, which writes every non-ASCII character as \uXXXX (emoji become a surrogate pair). Pass ensure_ascii=False to keep UTF-8 characters readable. The result is still valid JSON, but write it to a file or socket opened with UTF-8 encoding.
import json
print(json.dumps({"city": "Zürich", "path": "a/b"}))
# {"city": "Z\u00fcrich", "path": "a/b"}
print(json.dumps({"city": "Zürich"}, ensure_ascii=False))
# {"city": "Zürich"}Unicode and \uXXXX escapes
The \uXXXX escape writes a character as its UTF-16 code unit in four hex digits. JSON only requires it for control characters. Many encoders use it for every non-ASCII character to keep the output pure ASCII, which is safer for older transports and terminals. Both forms parse to the same value, so "café" and "caf\u00e9" are equal strings.
Characters outside the Basic Multilingual Plane, such as most emoji, need a surrogate pair of two \uXXXX escapes. The encoder writes these for you, but it helps to recognize 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é"'A \uXXXX sequence must have exactly four hex digits. \u1 or non-hex characters are a syntax error. A lone surrogate (one half of a pair) is invalid JSON, though many parsers accept it. If you are unsure, run the value through the escape/unescape tool or a validator to check that it round-trips cleanly.