JSON Unicode Escapes: UTF-16 and UTF-8
\u4e2d\u6587 is not encrypted data. It is one JSON string representation of Unicode text. Each \uXXXX describes one 16-bit UTF-16 code unit; BMP characters commonly use one unit, while emoji and some rare characters require a surrogate pair. This guide separates code points, code units, and UTF-8 bytes.
1. Escaped and native forms parse to the same string
A JSON string may contain Unicode directly or represent a code unit with a backslash-u escape followed by four hexadecimal digits. JSON.parse restores both forms to JavaScript strings.
The doubled backslash in JavaScript source belongs to the source-literal layer. See /articles/nested-json-escaping for representation layers.
const escapedJson = '"\\u4e2d\\u6587"';
const nativeJson = '"中文"';
const a = JSON.parse(escapedJson);
const b = JSON.parse(nativeJson);
console.log(a === b); // true2. \uXXXX is a UTF-16 code unit, not a UTF-8 byte sequence
The escape contains exactly four hexadecimal digits and represents one 16-bit code unit. JavaScript strings are based on UTF-16 code units, so charCodeAt reports a unit while codePointAt can report a complete code point at the start of a surrogate pair.
UTF-8 is a byte encoding used for files and network data. The six ASCII characters in \u4e2d are not the same representation as the three UTF-8 bytes for “中”.
const text = '中';
console.log(text.charCodeAt(0).toString(16)); // 4e2d
console.log(text.codePointAt(0)?.toString(16)); // 4e2d
console.log(new TextEncoder().encode(text));3. Supplementary characters use surrogate pairs
Code points above U+FFFF are represented in UTF-16 by a high surrogate and a low surrogate. The rocket emoji U+1F680 can therefore appear in JSON as \ud83d\ude80.
String.length counts UTF-16 code units, while string iteration and Array.from operate by code point.
const rocket = JSON.parse('"\\ud83d\\ude80"');
console.log(rocket.length); // 2 code units
console.log(Array.from(rocket).length); // 1 code point
console.log(rocket.codePointAt(0)?.toString(16)); // 1f6804. Prefer native Unicode; generate ASCII-only JSON only when required
Interoperable JSON exchanged between systems uses UTF-8. Native text is easier to inspect. JSON.stringify escapes required syntax but does not normally escape every non-ASCII character.
When a legacy contract explicitly requires ASCII-only output, transform the stringify result by UTF-16 code unit. A supplementary character then becomes two surrogate escapes.
function stringifyAsciiOnly(value: unknown): string {
return JSON.stringify(value).replace(/[\u007f-\uffff]/g, (unit) =>
`\\u${unit.charCodeAt(0).toString(16).padStart(4, '0')}`,
);
}
console.log(stringifyAsciiOnly({ text: '中文 🚀' }));5. Decode with JSON.parse, not a handwritten regex
Manual replacements often mishandle surrogate pairs, nested backslashes, escaped quotes, and invalid input. Parse complete JSON directly. If input is claimed to be a JSON string literal, validate that the result is actually a string. Never use eval or Function for untrusted data.
function parseJsonStringLiteral(literal: string): string {
const value: unknown = JSON.parse(literal);
if (typeof value !== 'string') {
throw new TypeError('Expected a JSON string literal');
}
return value;
}
console.log(parseJsonStringLiteral('"\\u4e2d\\u6587"'));6. Measure bytes, not visible character count
The native character “中” uses three UTF-8 bytes, while the ASCII text \u4e2d uses six. An emoji is commonly four UTF-8 bytes but twelve ASCII bytes as a surrogate-pair escape. Compression can change the final difference, so compare complete payloads under identical settings.
const encoder = new TextEncoder();
console.log(encoder.encode('中').byteLength); // 3
console.log(encoder.encode('\\u4e2d').byteLength); // 67. Lone surrogates are a separate boundary case
JSON grammar can contain an isolated surrogate escape such as \ud800, but it does not form a Unicode scalar value. Truncating a UTF-16 string between a pair can create the same condition.
Modern JavaScript provides isWellFormed and toWellFormed for detection and replacement before APIs that require well-formed strings.
const broken = JSON.parse('"\\ud800"') as string;
if ('isWellFormed' in String.prototype) {
console.log(broken.isWellFormed()); // false
console.log(broken.toWellFormed()); // �
}8. Verification, FAQ, and conclusion
Test BMP text, emoji, rare characters, combining marks, quotes, backslashes, and lone surrogates. Run stringify → parse and assert equality. For ASCII-only output, assert that parsing the escaped result restores the same value. Use /tools/dev/unicode to inspect code points and code units.
Is \uXXXX encryption or compression? Neither; it is a JSON escape representation.
Why does one emoji use two escapes? Its code point is above U+FFFF and UTF-16 represents it with two surrogate code units.
Must APIs escape all Chinese text? Usually no. Use UTF-8 and escape all non-ASCII text only when a contract explicitly requires it.
Sources and further reading
Specifications and references for checking the article’s technical details.
Related articles
Unicode: UTF-8, UTF-16 and Surrogate Pairs
Compare Unicode character sets with UTF-8 / UTF-16 encodings, detailing High/Low Surrogate pair bitwise formulas and binary conversions.
PitfallsJavaScript Emoji: String Length and Safe Truncation
Explain why '🚀'.length === 2 in JavaScript, compare Code Units, Code Points, and Grapheme Clusters, and provide safe Intl.Segmenter truncation.
TroubleshootingGarbled Text: Charset Diagnosis and Recovery Limits
Systematically diagnose UI garbled text (un-decoded \u4e2d, Latin1 mismatches), HTTP header omissions, and MySQL latin1 corruptions.
Keep exploring
Use the related browser tool to test the workflow with your own sample data.
Open related tool