How to Escape JSON Strings: Characters, Stringify & Pitfalls
To escape a JSON string means to turn arbitrary text into a string that can sit safely inside a JSON document as a string literal. A handful of characters carry structural meaning or are simply illegal inside a JSON string: the double quote, the backslash, and control characters like newline and tab. Each one gets replaced with a safe escape sequence such as \", \\, or \n. Do this wrong and your payload stops parsing.
You hit this constantly: nesting one JSON object inside another as a string field, pasting a multi-line code snippet into a config value, or hand-building a REST request body for curl. This guide covers exactly which characters need escaping, clears up the confusion between escaping and JSON.stringify, walks through JSON-in-JSON nesting and Unicode escapes, and lists the pitfalls that quietly break payloads. If you just want to escape something right now, our JSON Escape tool does it in the browser, but read on for why it works the way it does.
What Is JSON String Escaping?
JSON string escaping is the process of converting a raw string into a form that is safe to embed inside a JSON document. JSON reserves a small set of characters that carry structural meaning: the double quote " delimits a string, and the backslash \ starts an escape sequence. On top of that, control characters below U+0020 (newlines, tabs, carriage returns) are not allowed to appear literally inside a JSON string at all. Escaping replaces each of these with a safe sequence so the resulting string parses cleanly anywhere.
When do you actually need it? A few situations come up over and over:
- JSON-in-JSON: a webhook envelope, a Kafka message, or an audit log stores a request body as a string field, so the inner JSON has to be escaped before it can be assigned.
- Hand-authored config: dropping a multi-line shell script, SQL query, or code snippet into a single JSON value means turning every newline into
\n. - REST request bodies: building a JSON body by hand for
curlor an HTTP client, where quotes and newlines must survive the shell and the wire. - Log-safe encoding: writing user-supplied content into a structured log line without letting an injected quote or newline corrupt the format.
A quick word on order of operations. If you are starting from messy or untrusted JSON, validate it first so you are escaping something well-formed. Paste it into the JSON Formatter to pretty-print and check it, then escape the clean result. Escaping garbage just gives you escaped garbage.
Which Characters Must Be Escaped in JSON
The JSON specification defines a precise, short list. Seven characters have a dedicated two-character escape, and everything else below U+0020 falls back to a \uXXXX Unicode escape. Here is the complete set of JSON escape characters:
| Character | Escapes to | Notes |
|---|---|---|
" (U+0022) | \" | String delimiter |
\ (U+005C) | \\ | Escape lead-in (the json escape backslash case) |
| newline (U+000A) | \n | |
| carriage return (U+000D) | \r | |
| tab (U+0009) | \t | |
| backspace (U+0008) | \b | |
| form feed (U+000C) | \f | |
| other controls < U+0020 | \uXXXX | e.g. U+0000 → \u0000 |
What does not need escaping is just as important. The forward slash / is a perfectly normal character (escaping it is optional, and only useful in one narrow case covered below). Single quotes never need escaping because JSON does not use them as delimiters. And every printable character at or above U+0020 is valid as-is, including all multi-byte UTF-8 characters like é, 日, or 😀.
Here is the difference made concrete. The raw input on the left, the escaped JSON string literal on the right:
Input:
She said "hello" then left.
Escaped:
"She said \"hello\"\tthen left."
The double quotes became \" and the tab became \t. Now the string is safe to drop into any JSON parser, log line, or request body.
JSON Escape vs JSON Stringify: What’s the Difference?
This is the point most tutorials skip, and it confuses a lot of people. Escaping and JSON.stringify are not two different operations. They are two views of the same one.
JSON.stringify(value) serializes any JavaScript value into its JSON text representation. When that value happens to be a string, serializing it means wrapping it in double quotes and escaping the special characters inside. That is exactly JSON escaping. So JSON.stringify("a\tb") returns the seven-character string "a\tb", quotes included.
The practical question is whether you want those outer quotes. That maps directly onto the Wrap in double quotes option in the JSON Escape tool:
| Mode | Output for input a"b | When to use it |
|---|---|---|
| Wrap on | "a\"b" | A complete JSON string literal, identical to JSON.stringify. Assign it to a variable or paste it after a colon. |
| Wrap off | a\"b | Just the escaped body, no surrounding quotes. Use it when you are hand-typing the quotes yourself in a JSON document. |
So if you searched for “json stringify” and landed here, the mental model is simple: stringify a string = wrap-on escape. The unquoted form is the same thing with the outer quotes peeled off.
How to Escape a String for JSON in Code
The golden rule: never hand-roll a chain of replace() calls. Every mainstream language ships a JSON serializer that handles quotes, backslashes, control characters, and Unicode correctly. Reach for it.
JavaScript
const text = 'She said "hi"\nthen left.';
const escaped = JSON.stringify(text);
console.log(escaped);
// "She said \"hi\"\nthen left."
JSON.stringify on a string gives you the complete, quoted literal. Want just the body? Slice off the first and last character: JSON.stringify(text).slice(1, -1).
Python
import json
text = 'She said "hi"\nthen left.'
print(json.dumps(text))
# "She said \"hi\"\nthen left."
print(json.dumps(text, ensure_ascii=False))
# "She said \"hi\"\nthen left." (non-ASCII kept as UTF-8)
json.dumps defaults to ensure_ascii=True, which escapes every non-ASCII character to \uXXXX, the same behavior as the tool’s ASCII-safe mode. Pass ensure_ascii=False to keep raw UTF-8.
PHP
<?php
$text = "café \"quoted\"\nline";
echo json_encode($text);
// "caf\u00e9 \"quoted\"\nline" (default escapes non-ASCII to \uXXXX)
echo json_encode($text, JSON_UNESCAPED_UNICODE);
// "café \"quoted\"\nline"
json_encode escapes both non-ASCII characters and forward slashes by default. Add JSON_UNESCAPED_UNICODE to keep accents readable, and JSON_UNESCAPED_SLASHES to leave / alone.
Go and Java
In Go, json.Marshal(text) returns the escaped, quoted bytes:
b, _ := json.Marshal(`a "quoted" line`)
// b == `"a \"quoted\" line"`
In Java, Jackson’s objectMapper.writeValueAsString(text) or org.json’s JSONObject.quote(text) produce the same quoted literal. Whatever the language, lean on the library. It already knows every edge case you would forget.
Embedding JSON Inside JSON (JSON-in-JSON)
This is the single most common reason people escape JSON by hand. A webhook envelope, a message-queue record, or an audit log often stores an entire request body as a string field. To do that, the inner JSON has to be escaped first.
Watch a small object travel through two layers of encoding:
1. Inner object: {"a":1}
2. Escaped as a string: "{\"a\":1}"
3. Placed in envelope: {"payload": "{\"a\":1}"}
Every " in the inner object became \", and the whole thing got wrapped in one outer pair of quotes. The result is a single valid string value you can assign to payload.
The catch with deeper nesting is that backslashes multiply. Escaping an already-escaped string escapes its backslashes too, so each layer roughly doubles them: an inner quote that was \" becomes \\\" one level out, and \\\\\" another level out. Three-deep JSON-in-JSON is genuinely hard to read, which is why a tool helps. To go the other direction and pull the inner object back out of the string, run it through our JSON Unescape tool.
Unicode and \uXXXX Escaping
By default, JSON is happy with raw UTF-8. An é stays an é, a 日 stays a 日, and the document is more readable for it. You do not need to escape any printable Unicode character.
So when would you reach for ASCII-safe \uXXXX output? Only when a downstream system cannot be trusted with UTF-8: old SOAP or XML gateways, certain logging pipelines, email headers, or source files that must stay pure ASCII. In ASCII-safe mode, every character above U+007F becomes a \uXXXX escape, so café turns into caf\u00e9. It is noisier but byte-for-byte ASCII, and it decodes back to the original in any compliant parser.
There is one subtlety. \uXXXX encodes a single 16-bit UTF-16 code unit, but characters outside the Basic Multilingual Plane (emoji, rare scripts) need 21 bits. JSON handles them with a surrogate pair: two \uXXXX escapes back to back. A grinning face 😀 (U+1F600) becomes \ud83d\ude00. Most serializers do this for you; the danger is a hand-written escaper that emits a lone, unpaired surrogate.
If surrogate pairs and code points are new territory, the UTF-8 vs UTF-16 vs Unicode guide breaks down exactly how a single character maps to bytes and code units. It is the missing context behind why one emoji needs two escapes.
Unescaping: Reading Escaped JSON Back
Escaping has an inverse. To turn "a\tb" back into the real two-line-or-tabbed text, you parse it: JSON.parse(str) in JavaScript, json.loads(str) in Python. The parser walks each escape sequence and rebuilds the original characters, surrogate pairs included.
When unescaping fails, the error is almost always “invalid escape sequence,” and it has a few usual causes:
- A lone backslash before a character JSON does not recognize as an escape, like
\q. - A made-up escape such as
\x41. JSON has no\xhex escape; it only uses\u. - A truncated
\uescape with fewer than four hex digits, like\u00. - A stray or unbalanced double quote that breaks the string boundary.
Check that every backslash starts one of the valid escapes (\n \r \t \b \f \" \\ \/ \uXXXX) and that quotes are paired. For escaped strings copied out of the middle of a log line, where the outer quotes got left behind, our JSON Unescape tool accepts the body with or without surrounding quotes and decodes it either way.
Common JSON Escaping Pitfalls
Most broken payloads trace back to one of these six mistakes.
1. Double-escaping. Escaping text that was already escaped turns \n into \\n and \" into \\\", so the consumer reads a literal backslash-n instead of a newline. This usually happens when an upstream service already JSON-escaped the value and you escape it again. Unescape first to check the current state, then escape exactly once.
2. Forgetting the outer quotes. With wrap off you get only the escaped body, not a complete string. Pasting hello \"world\" straight where a JSON value is expected is invalid because the surrounding quotes are missing. Either keep wrap on or type the quotes yourself.
3. Over-escaping non-ASCII. Turning on ASCII-safe mode when the consumer handles UTF-8 fine just bloats the output. café becomes caf\u00e9 for no reason: harder to read, larger on the wire, zero benefit. Leave it off unless a specific legacy system demands pure ASCII.
4. Escaping the forward slash by reflex. The / escape matters in exactly one place: JSON inlined inside an HTML <script> tag, where the substring </script> would close the tag early regardless of JSON context. Escaping / to \/ neutralizes it. Outside that one case, escaping slashes is pure clutter, so leave it off for REST bodies, config files, and message payloads.
5. Hand-rolled replace chains. A manual replace('"', '\\"') pipeline almost always forgets something: a control character, a backspace, a surrogate pair. Use the language’s serializer, which covers the whole specification.
6. Escaping but never unescaping (or unescaping twice). A round-trip has to balance. Escape once on the way in, unescape once on the way out. Unescape twice and you mangle real backslashes that were part of the data.
One more distinction worth nailing down: JSON escaping is not URL or percent-encoding. They solve different problems for different transports, and mixing them produces a mess that neither parser can read cleanly: percent-encoding a value and then JSON-escaping the result, or vice versa. The URL Encoding & Decoding guide covers when percent-encoding is the right tool and how it differs from what JSON does.
Frequently Asked Questions
What does it mean to escape a string in JSON?
It means replacing the characters that carry structural meaning to JSON (the double quote, the backslash, and control characters like newline and tab) with safe escape sequences such as \", \\, and \n. The result can be embedded as a string literal inside a JSON document without breaking the parse.
What characters need to be escaped in JSON?
The double quote, backslash, newline, carriage return, tab, backspace, and form feed each get a dedicated escape, and every other control character below U+0020 becomes \uXXXX. Printable characters and multi-byte UTF-8 need no escaping; the forward slash is optional and only matters inside HTML <script> tags.
Is JSON escape the same as JSON.stringify?
Mostly two views of one operation. JSON.stringify applied to a string wraps it in double quotes and escapes the special characters inside, and that is JSON escaping. Wrap on equals the quoted form (identical to JSON.stringify); wrap off gives you just the escaped body without the surrounding quotes.
How do I escape a string for JSON in JavaScript or Python?
In JavaScript use JSON.stringify(str); in Python use json.dumps(str). Always rely on the built-in function rather than a hand-written replace chain; the built-ins correctly handle Unicode, control characters, and every edge case you would otherwise miss.
Why does my JSON break with extra backslashes?
The usual cause is double-escaping: escaping text that was already escaped, so \n becomes \\n and the consumer reads a literal backslash-n instead of a newline. Unescape the value first to check its real state, then escape it exactly once.
Do I need to escape forward slashes or Unicode in JSON?
Neither is required. The / is a normal character and only needs escaping when you inline JSON into an HTML <script> tag, to stop the </script> sequence from closing it early. Unicode stays as raw UTF-8 by default; use \uXXXX only when a downstream system cannot handle UTF-8.