0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JSONやログの \uXXXX を安全にUnicodeへ戻す方法

0
Posted at

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文字として保存する

入力をコードとして評価すると、文字変換の問題がコード実行の問題に変わります。データ形式ごとの標準パーサーを使う方が安全です。

切り分けのチェックリスト

  1. 元データはJSON、HTML、URL、ログ、バイト列のどれか。
  2. \uXXXX&#xXXXX;%XXU+XXXX のどの形式か。
  3. 変換は一度だけ行われているか。
  4. 絵文字などBMP外の文字を含むか。
  5. 変換後をコードポイント単位で確認したか。

数件の入力を目視で比較したい場合は、ブラウザ上で変換前後を確認できる ASCII to Unicode converter も補助に使えます。ただし、機密データは貼り付けず、大量処理や本番処理では必ず対象形式の標準パーサーを使ってください。

まとめ

同じ「読めない文字列」でも、JSONエスケープ、サロゲートペア、HTMLエンティティ、U+表記、文字化けでは処理が異なります。最初にデータ形式を特定し、その形式のパーサーを一度だけ使うことが、最も再現性の高い解決策です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?