JSON Escaping and Special Characters
Which characters need a backslash, which do not, and the escaping mistakes that produce unparseable documents or security holes.
The Complete Escape Table
Escaping applies only inside strings. Outside a string there is nothing to escape, because the only things that can appear there are structural characters and numbers.
| Sequence | Character | Required? |
|---|---|---|
| \" | Double quote | Yes |
| \\ | Backslash | Yes |
| \n | Line feed | Yes |
| \r | Carriage return | Yes |
| \t | Tab | Yes |
| \b | Backspace | Yes |
| \f | Form feed | Yes |
| \/ | Forward slash | Optional |
| \uXXXX | Any code point, by hex | Optional |
That is the entire list. A backslash followed by anything else — \x41, \0, \' — is invalid JSON, even though several of those are legal in JavaScript, Python or C string literals.
The Three Mandatory Cases
Double quotes terminate a string, so one appearing inside must be escaped:
{ "quote": "She said "hello"" } // invalid
{ "quote": "She said \"hello\"" } // validSingle quotes need no escaping when they appear inside a string — they are ordinary characters. They are only forbidden as delimiters.
Backslashes are the escape character, so a literal one must be doubled. Windows paths and regular expressions hit this constantly:
{ "path": "C:\Users\Alice" } // invalid: \U and \A are not escapes
{ "path": "C:\\Users\\Alice" } // valid → C:\Users\Alice
{ "regex": "\\d{4}-\\d{2}" } // valid → \d{4}-\d{2}Control characters below U+0020 cannot appear literally. A real line break inside a string is a syntax error:
{ "note": "line one
line two" } // invalid
{ "note": "line one\nline two" } // validUnicode
JSON strings are Unicode. Non-ASCII characters may be written literally provided the document is UTF-8, which it should be:
{ "name": "José", "city": "Bengaluru", "reaction": "🎉" } // valid
{ "name": "Jos\u00e9" } // identical valueBoth forms parse to the same string. Which you get depends on your serialiser — Python escapes by default unless you pass ensure_ascii=False, PHP escapes unless you pass JSON_UNESCAPED_UNICODE, while JavaScript's JSON.stringify leaves characters literal. This is purely a readability choice.
One real subtlety: \uXXXX is limited to four hex digits, so it can only address code points up to U+FFFF. Characters beyond that — most emoji, many historic scripts — must be written as a surrogate pair:
"🎉" escaped as "\ud83c\udf89" // two \u escapes, one characterThis is why naive string-truncation code corrupts emoji: cutting between the two halves of a surrogate pair leaves an unpaired surrogate, which is not valid Unicode and which some parsers will reject outright.
Nested JSON: The Escaping Explosion
When a JSON document is stored as a string inside another JSON document, every quote in the inner document needs escaping. This happens more than it should — in message queues, in audit log columns, in webhook payloads that forward a provider's raw body.
{
"event": "payment.created",
"payload": "{\"amount\":1999,\"currency\":\"INR\"}"
}It parses correctly, but you must now parse twice, and each additional nesting level doubles the backslashes. Two levels deep is already close to unreadable:
"{\\\"amount\\\":1999}" // three levels — genuinely unmaintainableWhere you control the format, nest the object directly instead of stringifying it:
{
"event": "payment.created",
"payload": { "amount": 1999, "currency": "INR" }
}The usual justification for the string form is that the inner payload's shape is unknown or must be preserved byte-for-byte — for signature verification, say. That is a legitimate reason. "It was easier at the time" is not.
Embedding JSON in HTML
Putting JSON inside a <script> tag introduces a hazard that has nothing to do with JSON's own rules, and it is a genuine cross-site scripting vector.
<script>
var data = {"comment": "</script><script>alert(1)</script>"};
</script>The string </script> ends the tag as far as the HTML parser is concerned, no matter that it sits inside JSON quotes. The browser never reaches your JSON — it just runs the injected script. Escaping the slash defuses it, because <\/script> is not a closing tag to HTML but is still a forward slash to JSON:
JSON.stringify(data).replace(/</g, "\\u003c")This is the historical reason PHP escapes forward slashes by default — a blunt instrument aimed at exactly this problem. The safer modern approach is to avoid inline data entirely: put it in <script type="application/json">, which browsers do not execute, and read it with JSON.parse(element.textContent).
Let a Serialiser Do It
Nearly every escaping bug comes from building JSON by string concatenation. Do not:
// Broken the moment name contains a quote, backslash or newline
const json = '{"name": "' + name + '"}';
// Correct — the serialiser handles every case
const json = JSON.stringify({ name });The same applies in every language: json.dumps(), json_encode(), json.Marshal(). Hand-built JSON is to escaping what hand-built SQL is to injection — the failure mode is identical, and so is the fix.
Frequently Asked Questions
Which characters must be escaped in JSON?
Three things are mandatory: the double quote, the backslash, and any control character below U+0020 — which includes literal newlines and tabs. Everything else, including forward slashes and non-ASCII characters, may be written literally.
Why do I see \/ instead of / in JSON output?
Escaping the forward slash is optional and legal. PHP does it by default, a legacy behaviour intended to make embedding JSON inside HTML script tags safer. It parses identically either way. Pass JSON_UNESCAPED_SLASHES in PHP to turn it off.
How do I put a Windows file path in JSON?
Double every backslash: "C:\\Users\\Alice" represents the path C:\Users\Alice. The backslash is JSON's escape character, so a literal one must itself be escaped. Alternatively use forward slashes, which Windows accepts in most contexts.
Do I need to escape emoji and accented characters?
No. JSON strings are Unicode, so é and 🎉 can be written literally as long as the document is UTF-8. Some serialisers escape them anyway — Python's ensure_ascii=True is on by default — but this is a readability choice, not a correctness requirement.
How should I escape JSON that goes inside an HTML script tag?
The dangerous sequence is </script>, which terminates the tag regardless of JSON quoting. Escape the forward slash as <\/script> or replace < with \u003c. This is an HTML problem rather than a JSON one, and it is a real XSS vector.