[{"data":1,"prerenderedAt":174},["ShallowReactive",2],{"article-unicode-json-escapes":3},{"article":4,"relatedArticles":135},{"slug":5,"category":6,"date":7,"readingMinutes":8,"tags":9,"relatedTool":15,"cn":16,"en":71,"author":123,"reviewedBy":126,"reviewedDate":128,"updatedDate":128,"sources":129,"noIndex":134},"unicode-json-escapes","原理解析","2026-07-28",7,[10,11,12,13,14],"JSON","Unicode","UTF-16","UTF-8","代理对","/tools/dev/unicode",{"title":17,"description":18,"intro":19,"sections":20},"JSON Unicode 转义：\\uXXXX、代理对与 UTF-8","解释 JSON 的 \\uXXXX 为什么表示 UTF-16 码元、Emoji 为何需要代理对，以及原生 Unicode 文本与转义文本的等价关系、转换代码和异常边界。","接口中看到 `\\u4e2d\\u6587` 并不代表内容被加密，它只是 JSON 字符串的一种 Unicode 转义写法。每个 `\\uXXXX` 恰好描述一个 16 位 UTF-16 码元；BMP 字符通常用一个码元，Emoji 和部分生僻字需要一对代理项。本文区分码点、码元和 UTF-8 字节，并给出不拆坏代理对的转换与验证方法。",[21,27,33,39,45,51,57,63],{"heading":22,"paragraphs":23,"code":26},"一、最小示例：转义形式与原生字符解析后相同",[24,25],"JSON 字符串可以直接包含 Unicode 字符，也可以用 `\\u` 加四位十六进制数字表示码元。标准解析器会在 JSON.parse 时把转义序列还原到 JavaScript 字符串。","注意下面代码中的双反斜杠属于 JavaScript 源码字符串层；传给 JSON.parse 的实际文本包含单个反斜杠。这个层级区别可参考 /articles/nested-json-escaping。","const escapedJson = '\"\\\\u4e2d\\\\u6587\"';\nconst nativeJson = '\"中文\"';\n\nconst a = JSON.parse(escapedJson);\nconst b = JSON.parse(nativeJson);\n\nconsole.log(a); // 中文\nconsole.log(a === b); // true",{"heading":28,"paragraphs":29,"code":32},"二、\\uXXXX 表示 UTF-16 码元，不是 UTF-8 字节",[30,31],"JSON 的 `\\uXXXX` 固定为四个十六进制数字，对应一个 16 位码元。JavaScript 字符串也以 UTF-16 码元序列为基础，因此 `charCodeAt` 返回的是码元值，而 `codePointAt` 可以在代理对起始位置返回完整码点。","UTF-8 是网络和文件常用的字节编码。`\\u4e2d` 六个 ASCII 字符与汉字“中”的 UTF-8 三个字节是两种不同表示，不能把十六进制码元直接当作 UTF-8 字节。","const text = '中';\n\nconsole.log(text.charCodeAt(0).toString(16)); // 4e2d\nconsole.log(text.codePointAt(0)?.toString(16)); // 4e2d\nconsole.log(new TextEncoder().encode(text));\n// Uint8Array(3) [228, 184, 173]",{"heading":34,"paragraphs":35,"code":38},"三、辅助平面字符需要两个 \\uXXXX 组成代理对",[36,37],"码点 U+0000 到 U+FFFF 位于基本多文种平面，但 U+D800 到 U+DFFF 保留为代理项。超过 U+FFFF 的字符会在 UTF-16 中使用高代理项和低代理项两个码元表示。","例如 🚀 的码点是 U+1F680，在 JSON 中可写成 `\\ud83d\\ude80`。JavaScript 的 length 统计码元，因此结果为 2；`Array.from` 或字符串迭代按码点处理，结果长度为 1。","const rocket = JSON.parse('\"\\\\ud83d\\\\ude80\"');\n\nconsole.log(rocket); // 🚀\nconsole.log(rocket.length); // 2 个 UTF-16 码元\nconsole.log(Array.from(rocket).length); // 1 个码点\nconsole.log(rocket.codePointAt(0)?.toString(16)); // 1f680",{"heading":40,"paragraphs":41,"code":44},"四、优先使用原生 Unicode，按需生成全转义 JSON",[42,43],"现代系统之间交换 JSON 时应使用 UTF-8。原生中文更便于阅读和调试；`\\uXXXX` 通常只在 ASCII-only 输出、旧系统兼容或需要显式展示码元时使用。","JSON.stringify 会自动转义引号、反斜杠和控制字符，但不会默认把所有非 ASCII 字符都转成 `\\uXXXX`。若协议确实要求 ASCII-only，可以在 stringify 结果上逐个处理 UTF-16 码元；这样 Emoji 会自然变成两个代理转义。","function stringifyAsciiOnly(value: unknown): string {\n  return JSON.stringify(value).replace(/[\\u007f-\\uffff]/g, (unit) =>\n    `\\\\u${unit.charCodeAt(0).toString(16).padStart(4, '0')}`,\n  );\n}\n\nconsole.log(stringifyAsciiOnly({ text: '中文 🚀' }));\n// {\"text\":\"\\u4e2d\\u6587 \\ud83d\\ude80\"}",{"heading":46,"paragraphs":47,"code":50},"五、解码时使用 JSON.parse，不要手写 \\u 替换器",[48,49],"手写正则常忽略代理对、反斜杠层级、转义引号和非法输入。若输入是完整 JSON，直接 JSON.parse；若输入声称是一个 JSON 字符串字面量，应先验证解析结果确实为 string。","不要使用 eval 或 Function 解析不可信输入。它们接受的语法范围与 JSON 不同，并会引入代码执行风险。","function parseJsonStringLiteral(literal: string): string {\n  const value: unknown = JSON.parse(literal);\n  if (typeof value !== 'string') {\n    throw new TypeError('Expected a JSON string literal');\n  }\n  return value;\n}\n\nconsole.log(parseJsonStringLiteral('\"\\\\u4e2d\\\\u6587\"'));\n// 中文",{"heading":52,"paragraphs":53,"code":56},"六、原生字符与转义形式的体积不能只看字符数",[54,55],"以“中”为例，原生 UTF-8 是 3 个字节，而 ASCII 文本 `\\u4e2d` 是 6 个字节；在未压缩情况下，转义形式更大。Emoji 的 UTF-8 通常为 4 个字节，而代理对转义包含 12 个 ASCII 字节。","实际 HTTP 传输还可能经过 Gzip 或 Brotli，压缩后差异取决于整份数据，不能从单个字符推导固定百分比。需要用 TextEncoder 测原始字节，并在相同压缩设置下比较完整响应。","const encoder = new TextEncoder();\nconst native = '中';\nconst escaped = '\\\\u4e2d';\n\nconsole.log(encoder.encode(native).byteLength); // 3\nconsole.log(encoder.encode(escaped).byteLength); // 6",{"heading":58,"paragraphs":59,"code":62},"七、孤立代理项是需要单独防范的异常边界",[60,61],"合法 JSON 语法可能包含单独的 `\\ud800`，但它并不组成完整 Unicode 标量值。不同系统在编码、显示和长度处理上可能表现不同。字符串截断如果恰好切在代理对中间，也会制造孤立代理项。","现代 JavaScript 可用 isWellFormed 检查字符串是否包含孤立代理项，并用 toWellFormed 把它们替换为 U+FFFD。若目标环境较旧，应在进入 UTF-8 编码、URI 编码或跨系统传输前自行验证。","const broken = JSON.parse('\"\\\\ud800\"') as string;\n\nif ('isWellFormed' in String.prototype) {\n  console.log(broken.isWellFormed()); // false\n  console.log(broken.toWellFormed()); // �\n}",{"heading":64,"paragraphs":65},"八、验证、FAQ 与结论",[66,67,68,69,70],"验证应覆盖 BMP 中文、Emoji、生僻字、组合字符、引号、反斜杠和孤立代理项。对每个样本执行 stringify → parse，断言字符串保持一致；若协议要求 ASCII-only，再断言输出只包含 ASCII，并确认重新 parse 后值不变。可使用 /tools/dev/unicode 查看码点与码元。","问：`\\uXXXX` 是加密或压缩吗？答：都不是，它是 JSON 字符串的转义表示。","问：为什么一个 Emoji 会出现两个 `\\u`？答：它的码点超过 U+FFFF，在 UTF-16 中由两个代理码元表示。","问：接口必须把中文全部转义吗？答：通常不需要；跨系统 JSON 应使用 UTF-8，是否转义不改变解析后的字符串。","结论：理解 `\\uXXXX` 时必须区分码点、UTF-16 码元和 UTF-8 字节。解析用 JSON.parse，生成用 JSON.stringify；只有协议明确要求时才做全量 ASCII 转义。",{"title":72,"description":73,"intro":74,"sections":75},"JSON Unicode Escapes: \\uXXXX, Surrogate Pairs, and UTF-8","Understand why JSON \\uXXXX represents UTF-16 code units, why emoji require surrogate pairs, how native Unicode and escaped text compare, and how to convert and validate safely.","`\\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.",[76,82,88,94,100,105,110,116],{"heading":77,"paragraphs":78,"code":81},"1. Escaped and native forms parse to the same string",[79,80],"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\"';\nconst nativeJson = '\"中文\"';\n\nconst a = JSON.parse(escapedJson);\nconst b = JSON.parse(nativeJson);\nconsole.log(a === b); // true",{"heading":83,"paragraphs":84,"code":87},"2. \\uXXXX is a UTF-16 code unit, not a UTF-8 byte sequence",[85,86],"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 = '中';\nconsole.log(text.charCodeAt(0).toString(16)); // 4e2d\nconsole.log(text.codePointAt(0)?.toString(16)); // 4e2d\nconsole.log(new TextEncoder().encode(text));",{"heading":89,"paragraphs":90,"code":93},"3. Supplementary characters use surrogate pairs",[91,92],"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\"');\nconsole.log(rocket.length); // 2 code units\nconsole.log(Array.from(rocket).length); // 1 code point\nconsole.log(rocket.codePointAt(0)?.toString(16)); // 1f680",{"heading":95,"paragraphs":96,"code":99},"4. Prefer native Unicode; generate ASCII-only JSON only when required",[97,98],"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 {\n  return JSON.stringify(value).replace(/[\\u007f-\\uffff]/g, (unit) =>\n    `\\\\u${unit.charCodeAt(0).toString(16).padStart(4, '0')}`,\n  );\n}\n\nconsole.log(stringifyAsciiOnly({ text: '中文 🚀' }));",{"heading":101,"paragraphs":102,"code":104},"5. Decode with JSON.parse, not a handwritten regex",[103],"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 {\n  const value: unknown = JSON.parse(literal);\n  if (typeof value !== 'string') {\n    throw new TypeError('Expected a JSON string literal');\n  }\n  return value;\n}\n\nconsole.log(parseJsonStringLiteral('\"\\\\u4e2d\\\\u6587\"'));",{"heading":106,"paragraphs":107,"code":109},"6. Measure bytes, not visible character count",[108],"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();\nconsole.log(encoder.encode('中').byteLength); // 3\nconsole.log(encoder.encode('\\\\u4e2d').byteLength); // 6",{"heading":111,"paragraphs":112,"code":115},"7. Lone surrogates are a separate boundary case",[113,114],"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;\nif ('isWellFormed' in String.prototype) {\n  console.log(broken.isWellFormed()); // false\n  console.log(broken.toWellFormed()); // �\n}",{"heading":117,"paragraphs":118},"8. Verification, FAQ, and conclusion",[119,120,121,122],"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.",{"name":124,"url":125},"Tiny's Tool Editorial Team","https://tinystool.org/about",{"name":127,"url":125},"Tiny's Tool Technical Review","2026-08-08",[130],{"title":131,"url":132,"publisher":133},"RFC 8259 — The JavaScript Object Notation (JSON) Data Interchange Format","https://www.rfc-editor.org/rfc/rfc8259","RFC Editor",false,[136,149,161],{"slug":137,"noIndex":138,"category":139,"date":7,"updatedDate":128,"reviewedDate":128,"readingMinutes":140,"tags":141,"relatedTool":15,"cn":143,"en":146},"unicode-utf8-utf16-surrogate-pairs-deep-dive",true,"实现原理",6,[11,13,12,142],"Surrogate Pairs",{"title":144,"description":145},"深入浅出 Unicode、UTF-8、UTF-16 与 Surrogate Pairs 代理对编码原理","全面对比 Unicode 字符集与 UTF-8 / UTF-16 传输存储编码，详解高低代理项 (High/Low Surrogates) 的二进制计算公式与位运算推导。",{"title":147,"description":148},"Unicode, UTF-8, UTF-16, and Surrogate Pairs: Encoding Mechanics and Calculation","Compare Unicode character sets with UTF-8 / UTF-16 encodings, detailing High/Low Surrogate pair bitwise formulas and binary conversions.",{"slug":150,"noIndex":138,"category":151,"date":7,"updatedDate":128,"reviewedDate":128,"readingMinutes":140,"tags":152,"relatedTool":15,"cn":155,"en":158},"unicode-emoji-surrogate-pairs-length-truncation","踩坑避坑",[11,153,142,154],"Emoji","JavaScript Length",{"title":156,"description":157},"Emoji 表情与辅助平面字符在 JavaScript length 计算及截断乱码坑","讲解 JavaScript `'🚀'.length === 2` 的根因，对比 Code Unit、Code Point 与 Grapheme Cluster 区别，提供基于 Intl.Segmenter 的安全截断算法。",{"title":159,"description":160},"Emoji, Surrogate Pairs, and JavaScript String Length: Fixing Truncation Bugs","Explain why `'🚀'.length === 2` in JavaScript, compare Code Units, Code Points, and Grapheme Clusters, and provide safe Intl.Segmenter truncation.",{"slug":162,"noIndex":138,"category":163,"date":7,"updatedDate":128,"reviewedDate":128,"readingMinutes":140,"tags":164,"relatedTool":15,"cn":168,"en":171},"unicode-garbled-text-repair-troubleshooting-handbook","错误排查",[165,11,166,167],"Garbled Text","Charset","Troubleshooting",{"title":169,"description":170},"全网乱码终极排查手册：从字符集匹配到数据库乱码诊断全流程","系统梳理前端展示乱码 (如 `\\u4e2d` 未转义、`Ã¤Â½Â` 错解码)、HTTP Header 缺失与数据库 `latin1` 乱码的排查方向，揭示不可逆替换字符损毁原理。",{"title":172,"description":173},"Garbled Text Troubleshooting Guide: Diagnosing Charset Mismatches and Recovery Bounds","Systematically diagnose UI garbled text (un-decoded `\\u4e2d`, Latin1 mismatches), HTTP header omissions, and MySQL `latin1` corruptions.",1786725508302]