Javascript is required
ConceptsPublished 2026-07-28Updated 2026-09-307 min read

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.

By: Tiny
JSONUnicodeUTF-16UTF-8代理对

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); // true

2. \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)); // 1f680

4. 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); // 6

7. 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

Keep exploring

Use the related browser tool to test the workflow with your own sample data.

Open related tool