Unicode Escape/Unescape_
Type text on the left to escape it, or paste escapes on the right to turn them back. It reads \uXXXX, \u{...}, \U0001F600, HTML references and percent-encoding all at once, without being told which it is looking at.
Pick the notation for where the text is going, because they disagree. \uXXXX holds four hex digits and stops at U+FFFF, so an emoji needs two escapes in JSON and one in modern JavaScript — and the table underneath shows exactly why.
| Char | Code point | UTF-16 | UTF-8 | This notation |
|---|
- Input
- Text to escape, or escapes to decode. Either box drives the other.
- Output
- The same text in the notation you chose, plus a per-character breakdown with code points and both encodings.
- Processing
- Walked by code point rather than by array index, so a character above U+FFFF is one item and not two broken halves.
- Limits
- A lone surrogate is not a character. It is reported rather than silently turned into a replacement character or dropped.
- Why the notations differ
- Four hex digits reach U+FFFF. Everything above needs a surrogate pair, a braced escape or a capital-U form, and no language supports all three.
Four digits only reach U+FFFF
Which is why an emoji takes two escapes in JSON
\uXXXX carries exactly four hex digits, so it can name any character up to U+FFFF and nothing beyond. Emoji, historic scripts and tens of thousands of CJK ideographs all live above that line. JSON has no other escape, so it writes them as a surrogate pair: 😀 is two escapes standing in for the one character U+1F600. Modern JavaScript added \u{1F600} and Python has \U0001F600. Neither is legal JSON, so a document containing one is rejected outright rather than read leniently — and since the two languages sit next to each other in most codebases, that is how this usually goes wrong. The JSON escaper covers the closed list JSON does allow.
Where the pair comes from
Subtract 0x10000 from the code point, which leaves twenty bits. The top ten become 0xD800 + n and the bottom ten 0xDC00 + n. Those two ranges — D800 to DBFF and DC00 to DFFF — are permanently unassigned in Unicode, deliberately, so that a decoder can always tell a surrogate from a real character. That is also why a lone surrogate is not a character at all: it cannot be encoded as UTF-8, and any tool that claims to have done so has produced something invalid.
One character is not one code point
A family emoji looks like a single character and is five code points joined by zero-width joiners. An accented letter may be one code point or two depending on where the text came from. So "escape this one character" is often several escapes, and a length check on the escaped form tells you nothing about how many characters a reader sees. The breakdown under the tool lists every code point separately for exactly this reason — it is usually the answer to why a string escaped to more than expected.
CSS escapes have no closing delimiter
Every other notation here has an end: a fixed digit count, a closing brace, a semicolon. CSS has neither — \1F600 is a backslash followed by up to six hex digits, terminated by a single space that the parser then eats. Leave the space out before a character that happens to be a hex digit and the escape silently swallows it, producing a different character with no error. That is why this page always emits the trailing space, and why decoding a bare \A is treated cautiously: in CSS it is a newline, and in every other language it is a backslash followed by the letter A.
Either direction, no mode switch
- 01Type or paste plain text on the left. The escaped form appears on the right in whichever notation is selected.
- 02Or paste escapes on the right — any mixture of notations — and the text appears on the left. Nothing has to be told which format it is.
- 03Choose the notation for the destination. The line under the controls says which languages take it and what to watch for.
- 04Read the breakdown at the bottom when the output is longer than expected. It shows every code point, both encodings, and which characters need a surrogate pair.
Four times the notation matters
An emoji into a JSON file
\\u{1F600} is a parse error there.
\uXXXX with surrogate pairs
\uD83D\uDE00
A log full of escapes
Mixed notations from several services.
\u00e9 😀 %C3%A9
the readable text
An icon in CSS content
The trailing space is part of the escape.
CSS
content: "\1F600 "
Why a string is longer than it looks
One visible character escaped to five sequences.
the breakdown table
five code points and two joiners
The same emoji in every notation
| Notation | U+1F600 becomes | Where it belongs |
|---|---|---|
\u{...} | \u{1F600} | JavaScript (ES6+), Rust, Swift, PHP 7+ |
\uXXXX with surrogate pairs | \uD83D\uDE00 | JSON, Java, older JavaScript |
\U0001F600 | \U0001F600 | Python, C, C++ |
\U0001F600 / \uXXXX | \U0001F600 | C#, and .NET string literals |
\u{1F600} | \u{1F600} | Rust, Swift |
\1F600 | \1F600 | CSS content and selectors |
😀 | 😀 | HTML, XML |
😀 | 😀 | HTML, XML |
%F0%9F%98%80 | %F0%9F%98%80 | URLs, form encoding |
U+1F600 | U+1F600 | Documentation, bug reports |
Every row is the same character. The differences are not stylistic — a braced escape in a JSON file is a syntax error, and a surrogate pair in an HTML reference is invalid.
What each language accepts
| Language | Up to U+FFFF | Above U+FFFF |
|---|---|---|
| JSON | \uXXXX | Surrogate pair only — there is no other form |
| JavaScript | \uXXXX | \u{...} since ES6, or a pair |
| Java | \uXXXX | Surrogate pair only |
| Python | \uXXXX | \U0001F600, exactly eight digits |
| C and C++ | \uXXXX | \U0001F600, exactly eight digits |
| C# | \uXXXX | \U0001F600, or a pair |
| Rust and Swift | \u{...} | \u{...} — one form for both |
| CSS | \E9 | \1F600 , note the space |
| HTML and XML | é | 😀 — the code point, never a pair |
Java is the one that surprises people: it has no braced escape and no capital-U form, so a surrogate pair is the only way to write an emoji in a Java string literal.
Working with escapes
- Pick the notation by destination, not by preference. A braced escape in a JSON file will not parse, and no linter will warn you before it ships.
- Leave "Keep ASCII readable" on for anything a human will read. Escaping every character is only useful when the transport is genuinely ASCII-only.
- When output is longer than you expected, read the breakdown. It is almost always a joined emoji or a decomposed accent showing up as several code points.
- Escaping is not encoding. These sequences are for putting a character into source code; the URL encoder and JSON escaper handle their formats end to end.
- A pasted string that already contains escapes can go straight into the right-hand box — it decodes mixed notations in one pass, so a log line with three different formats comes back readable.
- Keep the CSS trailing space when you copy. It looks like whitespace and it is part of the escape.
Where escaping goes wrong
A braced escape is not valid JSON
\u{1F600} is JavaScript, not JSON. The two look close enough that it gets copied between them constantly, and a JSON parser rejects it outright. If the destination is a .json file or a JSON string in an API, the surrogate pair is the only correct spelling.
A lone surrogate is not a character
Half a pair — \uD83D on its own — names nothing. It cannot be encoded as UTF-8, most APIs reject it, and some silently replace it with a question-mark character. This page reports it and leaves it as written rather than pretending it decoded to something.
HTML references never use surrogates
A numeric character reference names a code point, so 😀 is right and �� is invalid even though it looks like the same trick. Browsers vary in how they recover from it, which makes it worse rather than better.
The CSS trailing space is load-bearing
A CSS escape ends at a space, which the parser then consumes. Drop it and a following hex digit joins the escape: \E9f is one character U+E9F, not é followed by f. Nothing reports an error — you simply get a different character.
Escaped length is not visible length
One thing a reader sees can be several code points, and each of those can be several bytes. Counting escapes, UTF-16 units or bytes gives three different numbers and none of them is the number of characters on screen. For that, the character counter counts what a reader actually perceives.
Notations, detection and limits
- Encoding
- Ten notations: braced, four-digit with surrogate pairs, capital-U, Rust braces, CSS, HTML hex and decimal, percent-encoded UTF-8, and the U+ documentation form
- Iteration
- By code point, never by array index. Walking a string by index splits an astral character into two broken halves, which is the defect this whole page is about
- Surrogate arithmetic
- Subtract 0x10000, high is 0xD800 plus the top ten bits, low is 0xDC00 plus the bottom ten. Both ranges are permanently unassigned so the encoding stays unambiguous
- Decoding
- Every notation at once, longest first so
\Uwith eight digits is not read as\uwith four, and surrogate pairs are rejoined before lone escapes are considered - Escaped backslashes
- Taken out of play before anything else. Leaving them in let
\\u0041decode its inner escape and then read the remaining\Aas a CSS newline — a control character produced from a string containing neither - CSS decoding
- Deliberately conservative. A single hex digit only counts as an escape when the terminating space is present, because
\Ais a newline in CSS and a backslash-then-letter everywhere else - Invalid input
- Lone surrogates, code points above U+10FFFF, character references naming a surrogate, and malformed percent sequences are all reported and left as written rather than replaced
- Scope
- Unicode escapes for source code. Full-format encoding lives with JSON escape and URL encode; internationalised domains with Punycode
- Network
- None from tool code. A test sweep calls every function this page uses with
fetchandXMLHttpRequestreplaced by stubs that throw, so a stray request fails the build instead of shipping. Disconnect from the network and the page still works.
Questions about Unicode escapes
Why does one emoji become two \u escapes?
Because \uXXXX holds four hex digits and reaches only U+FFFF. An emoji sits above that, so it is written as a surrogate pair — two escapes standing in for one code point. JSON and Java have no alternative; JavaScript can use \u{1F600} and Python \U0001F600 instead.
What is a surrogate pair?
A way of writing one code point above U+FFFF as two UTF-16 units. Subtract 0x10000, put the top ten bits into 0xD800 and the bottom ten into 0xDC00. Those ranges are permanently unassigned in Unicode so a decoder can always tell them from real characters — which is also why half a pair means nothing on its own.
Can I use \u{1F600} in JSON?
No. That form is JavaScript, and a JSON parser rejects it. JSON only has \uXXXX, so anything above U+FFFF has to be a surrogate pair. The two languages look similar enough that this gets copied between them constantly.
How do I escape a character in Python versus Java?
Python uses \uXXXX for four digits and \U0001F600 — capital U, exactly eight digits — for anything higher. Java has neither the capital-U form nor braces, so a surrogate pair is the only way to put an emoji in a Java string literal. Pick the notation above and the output matches.
Why did my string escape to more sequences than it has characters?
Because what looks like one character often is not. A family emoji is five code points joined by zero-width joiners; an accented letter can be a letter plus a combining mark. The breakdown table lists each code point separately, which is usually the explanation.
What is the space after a CSS escape for?
It terminates the escape and is then consumed. A CSS escape has no closing delimiter, so without the space a following hex digit joins it — \E9f is one character U+E9F rather than é followed by f, with no error reported. Keep the space when you copy.
Is anything I paste here sent anywhere?
The work is JavaScript running in this tab. Every function it calls is covered by a test that stubs fetch and XMLHttpRequest to throw, so a request that slipped in would break the build rather than reach a server — and you can confirm it for yourself by disconnecting and carrying on.