Turn any text into a JSON string body you can
paste between quotes, or take an escaped string from a log line
and get the readable text back. Quotes, backslashes, line breaks
and emoji are handled the way JSON.stringify does, and if
your input was escaped twice, the tool says so.
Invoice #1042 for "Zoë Müller"
Path: C:\invoices\2026\
Note: tab-separated, total €49.90 😀 "Invoice #1042 for \"Zoë Müller\"\nPath: C:\\invoices\\2026\\\nNote:\ttab-separated, total €49.90 😀" With Non-ASCII as \uXXXX turned on, the same input comes out as pure ASCII. The emoji becomes a surrogate pair, two escapes for one character, because JSON escapes are UTF-16 code units:
"Invoice #1042 for \"Zo\u00eb M\u00fcller\"\nPath: C:\\invoices\\2026\\\nNote:\ttab-separated, total \u20ac49.90 \ud83d\ude00" The double quote, the backslash, and every control character below U+0020. That is the whole list in RFC 8259. Newline and tab are control characters, which is why a raw line break inside a JSON string is a syntax error.
Anything else, as \uXXXX. Use it when the JSON has to pass through a system that mangles non-ASCII bytes. It makes the payload bigger (six bytes for é instead of two) and changes nothing once parsed.
\/ is legal but optional. PHP's json_encode() adds it by default so that a string containing </script> cannot close a script tag early. JSON.stringify leaves it unescaped.
Single quotes around the string, \' , \x41 and octal escapes. Those are JavaScript string syntax, not JSON, and a strict parser rejects them. For those, use the backslash escape tool.
If a log shows {\"id\":1}, a value that was already a JSON string was encoded again. It happens
when a JSON body is stored in a JSON column, or when a
message queue wraps the payload and the producer serialized
it first. Unescape it once and you get another escaped
string. When that happens here, an Unescape again
button appears and peels off the next layer.
Fix double encoding where it starts. Decode the right number of times in code, or better, stop encoding twice. When the result of the last layer is a JSON document rather than a string, the note under the tool says so, and the JSON formatter is the next stop.
const text = 'He said "hi"\nC:\\temp 😀';
// Escape: JSON.stringify adds the surrounding quotes
const quoted = JSON.stringify(text);
console.log(quoted); // "He said \"hi\"\nC:\\temp 😀"
const body = quoted.slice(1, -1); // just the part between the quotes
// ASCII-only: escape everything above 0x7E as \uXXXX (UTF-16 units)
const ascii = quoted.replace(/[\u007f-\uffff]/g,
(c) => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0"));
console.log(ascii); // "He said \"hi\"\nC:\\temp \ud83d\ude00"
// Unescape: JSON.parse needs the quotes back
console.log(JSON.parse('"' + body + '"') === text); // true JSON.stringify never escapes non-ASCII, so the regex step is only needed when the output must be pure ASCII.
<?php
$text = "He said \"hi\"\nC:\\temp 😀";
// Default: escapes / and every non-ASCII character
echo json_encode($text), PHP_EOL;
// "He said \"hi\"\nC:\\temp \ud83d\ude00"
// Readable output, same as JavaScript
echo json_encode($text, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;
// Unescape. JSON_THROW_ON_ERROR turns a silent null into an exception.
$back = json_decode('"He said \"hi\""', false, 512, JSON_THROW_ON_ERROR);
echo $back, PHP_EOL; // He said "hi" Without JSON_THROW_ON_ERROR, json_decode() returns null on bad input, which is indistinguishable from a valid null.
import json
text = 'He said "hi"\nC:\\temp 😀'
print(json.dumps(text)) # ensure_ascii=True: \ud83d\ude00
print(json.dumps(text, ensure_ascii=False)) # keeps the emoji
body = json.dumps(text)[1:-1]
print(json.loads('"' + body + '"') == text) # True
# Double-encoded: keep decoding while the result is still a string
value = json.loads(json.dumps(json.dumps({"id": 1})))
while isinstance(value, str):
value = json.loads(value)
print(value) # {'id': 1} RFC 8259 keeps this short: a string is zero or more characters between double quotes, where the quote, the backslash and U+0000 to U+001F must be escaped, and anything may be written as \u followed by four hex digits. There is no \x, no octal, no \', and no single-quoted strings.
JSON.stringify has escaped lone surrogates since ES2019 (well-formed JSON.stringify), so a broken half of an emoji comes out as a visible \uD83D rather than invalid UTF-8. This tool does the same.
This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.
Three kinds: the double quote, the backslash, and control characters below U+0020 such as newline and tab. Everything else, including emoji, may appear as-is in UTF-8 JSON.
JSON escapes are UTF-16 code units, and characters above U+FFFF need a surrogate pair. JSON.parse and every other conforming parser joins the pair back into one character.
No. \/ is allowed but optional. PHP's json_encode() adds it by default so that </script> inside a string cannot end an inline script block. Tick Escape / to match PHP's output.
Yes. Unescape mode accepts the string with or without the outer double quotes, and it reports the line and column of the first invalid escape or stray quote.
The value was encoded twice, for example a JSON body stored inside another JSON field. The tool detects this and shows an Unescape again button that peels off the next layer.
For string literals in other languages, the backslash escape tool covers JavaScript, Python, PHP, Bash and SQL. To validate or pretty-print the whole document use the JSON formatter, and for text headed into HTML or a URL there is the HTML encoder and the URL encoder.