APIのレスポンスやログで \u65E5\u672C\u8A9E のような文字列を見ると、すぐに「文字化け」と判断しがちです。しかし、これは多くの場合JSONのUnicodeエスケープであり、文字化けとは原因も直し方も異なります。
この記事では、似て見える5種類の表記を切り分け、安全に元の文字へ戻す方法を整理します。例はブラウザの開発者ツールとNode.jsで確認できます。
1. JSONの \uXXXX はJSONパーサーに任せる
完全なJSON文字列を受け取っているなら、正規表現で置換せず JSON.parse() を使うのが基本です。
const payload = '"\\u65E5\\u672C\\u8A9E"';
const decoded = JSON.parse(payload);
console.log(decoded); // 日本語
\uXXXX はUTF-16の16進コード単位です。手作業で \u を % に置き換えて decodeURIComponent() に渡す方法は、ASCII以外や複数バイト文字で意味が変わるため避けます。
なお、ログに表示された文字列がJSON全体ではなく一部分だけなら、まず元データの境界を確認してください。引用符やバックスラッシュを推測して補うと、二重デコードの原因になります。
2. 絵文字はサロゲートペアになる
BMP外の文字は2つのUTF-16コード単位で表されます。たとえば😀は \uD83D\uDE00 です。
const emoji = JSON.parse('"\\uD83D\\uDE00"');
console.log(emoji); // 😀
console.log(emoji.length); // 2 (UTF-16 code units)
console.log([...emoji].length); // 1 (Unicode code point)
console.log(emoji.codePointAt(0).toString(16)); // 1f600
上位サロゲートだけ、または下位サロゲートだけが残っている入力は不完全です。表示できたように見えても、保存や別システムへの転送で壊れる可能性があります。
3. HTMLエンティティはJSONとは別物
日 や 日 はHTMLの数値文字参照です。JSONパーサーではなく、HTMLとして扱う場所でだけデコードします。
const doc = new DOMParser().parseFromString(
'日本語',
'text/html'
);
console.log(doc.documentElement.textContent); // 日本語
ユーザー入力を画面に戻すときは、デコード後の文字列を innerHTML に直接代入せず textContent を使います。文字参照の変換とHTMLの実行を同時にしないことが重要です。
4. U+65E5 は表記でありエスケープではない
U+65E5 はコードポイントを説明するための表記です。JavaScriptの文字列リテラルではないため、範囲を検証してから String.fromCodePoint() を使います。
function fromUPlus(value) {
const match = /^U\+([0-9A-F]{1,6})$/i.exec(value);
if (!match) throw new Error('Invalid U+ notation');
const codePoint = Number.parseInt(match[1], 16);
const isSurrogate = codePoint >= 0xD800 && codePoint <= 0xDFFF;
if (codePoint > 0x10FFFF || isSurrogate) {
throw new Error('Invalid Unicode scalar value');
}
return String.fromCodePoint(codePoint);
}
console.log(fromUPlus('U+65E5')); // 日
5. 文字化けはバイト列の解釈ミス
日本語 が別の記号列に変わっている場合は、UTF-8のバイト列を別の文字コードとして読んだ可能性があります。これは \uXXXX のデコードでは直りません。
復旧には、元のバイト列と「どの文字コードで誤読したか」が必要です。情報が失われた後の文字列だけから、常に一意に戻せるとは限りません。HTTPの Content-Type、ファイルの実際のエンコーディング、データベース接続設定を先に確認します。
やってはいけない方法
-
eval()やFunction()で入力を評価する - すべてのバックスラッシュを無条件に削除する
- 変換結果を検証せず、もう一度デコードする
- JSON、URLエンコード、HTMLエンティティを同じルールで処理する
- 壊れたサロゲートを正常なUnicode文字として保存する
入力をコードとして評価すると、文字変換の問題がコード実行の問題に変わります。データ形式ごとの標準パーサーを使う方が安全です。
切り分けのチェックリスト
- 元データはJSON、HTML、URL、ログ、バイト列のどれか。
-
\uXXXX、&#xXXXX;、%XX、U+XXXXのどの形式か。 - 変換は一度だけ行われているか。
- 絵文字などBMP外の文字を含むか。
- 変換後をコードポイント単位で確認したか。
数件の入力を目視で比較したい場合は、ブラウザ上で変換前後を確認できる ASCII to Unicode converter も補助に使えます。ただし、機密データは貼り付けず、大量処理や本番処理では必ず対象形式の標準パーサーを使ってください。
まとめ
同じ「読めない文字列」でも、JSONエスケープ、サロゲートペア、HTMLエンティティ、U+表記、文字化けでは処理が異なります。最初にデータ形式を特定し、その形式のパーサーを一度だけ使うことが、最も再現性の高い解決策です。