Node.js解析含转义Unicode的JSON时Emoji显示乱码问题
Instagram导出JSON解析后Emoji乱码修复方案
问题背景
- 从Instagram平台下载的JSON格式导出数据,在Node.js环境解析后存储至MongoDB时,出现转义Unicode字符无法在客户端正确渲染为对应Emoji的异常。
复现示例
待解析JSON文件中的title字段原始内容:
"title": "@mujenspirits is in the house!NEW York City \u00f0\u009f\u0097\u00bd\u00f0\u009f\u008d\u008e \nImperial Vintner Liquor Store"
- 预期渲染效果:
@mujenspirits is in the house!NEW York City 🗽🍎
Imperial Vintner Liquor Store
- 实际渲染乱码:
@mujenspirits is in the house!NEW York City 🗽ðŸŽ
Imperial Vintner Liquor Store
已尝试的无效方案
方案1:Uint8Array配合TextDecoder转码
该方案处理普通字符串时控制台验证有效,但配合JSON.parse使用仍存在显示异常,实现代码如下:
export default function parseJsonFile(filepath: string) { const value = fs.readFileSync(filepath) const converted = new Uint8Array( new Uint8Array(Array.prototype.map.call(value, (c) => c.charCodeAt(0))) ) return JSON.parse(new TextDecoder().decode(converted)) }
方案2:Latin1转UTF8转码
根据社区建议,该类JSON生成逻辑存在缺陷:转义Unicode码点本质是按Latin1编码解码的UTF-8数据,尝试将加载内容先转码为latin1再解码为utf8,问题仍未解决,实现代码如下:
import buffer from 'buffer' const value = fs.readFileSync(filepath) const buffered = buffer.transcode(value, 'latin1', 'utf8') return JSON.parse(buffered.toString())
问题根因
此前所有转码操作均作用于JSON原始文本层面,未命中问题发生的核心环节:
- 示例中
\u00f0\u009f\u0097\u00bd这类转义序列,实际对应Emoji🗽的UTF-8字节流(0xF0 0x9F 0x97 0xBD)。但JSON标准规定\uXXXX转义符代表UTF-16码元,JSON.parse执行时会直接将这类转义序列解析为U+00F0、U+009F等独立的Latin1区间字符,也就是最终看到的乱码。该错误发生在JSON解析执行阶段,对原始文件字节提前转码无法修正解析阶段生成的错误字符。
可行修复方案
方案1:解析后递归修复字符串(推荐,无侵入)
在JSON.parse完成后,遍历所有字符串类型字段,将错误解析的字符按Latin1编码取回字节,再用UTF-8解码即可得到正确内容,代码实现如下:
import fs from 'fs'; // 修复错误解析的乱码字符串 function fixMojibake(str: string): string { const buf = Buffer.from(str.split('').map(c => c.charCodeAt(0) & 0xff)); return buf.toString('utf8'); } // 递归遍历对象修复所有字符串字段 function deepFix(obj: any): any { if (typeof obj === 'string') { return fixMojibake(obj); } if (Array.isArray(obj)) { return obj.map(item => deepFix(item)); } if (obj && typeof obj === 'object') { return Object.fromEntries( Object.entries(obj).map(([k, v]) => [k, deepFix(v)]) ); } return obj; } export default function parseJsonFile(filepath: string) { const rawContent = fs.readFileSync(filepath, 'utf8'); const parsed = JSON.parse(rawContent); return deepFix(parsed); }
验证说明:针对示例title字段,修复后可正确输出带🗽🍎Emoji的完整文本,存入MongoDB后客户端可正常渲染。
方案2:解析前预处理JSON文本(适合大文件场景)
如果JSON文件体积过大,递归遍历全量对象性能开销较高,可以在JSON.parse之前,将所有字节范围在0x80-0xff的\u00xx格式转义序列提前替换为原始字节,再统一按UTF-8解析,示例实现:
export default function parseJsonFile(filepath: string) { let rawContent = fs.readFileSync(filepath, 'latin1'); // 替换所有UTF-8字节范围的\u转义序列为原始字节 rawContent = rawContent.replace(/\\u00([89a-f][0-9a-f])/gi, (_, hex) => { return String.fromCharCode(parseInt(hex, 16)); }); return JSON.parse(Buffer.from(rawContent, 'latin1').toString('utf8')); }
注意事项
- 修复后存入MongoDB前无需额外转码,MongoDB默认使用UTF-8编码存储字符串,只要Node.js侧拿到正确的JS字符串,存储和客户端读取环节不会再出现乱码。
- 读取文件阶段不要随意指定非
latin1/binary的编码做前置转码,否则会提前破坏转义序列的原始值,导致后续修复失效。
内容的提问来源于stack exchange,提问作者bflemi3
相关产品推荐
相关产品推荐

