1
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?

JavaScriptで不正なサロゲートペアを検出して安全に扱う

1
Posted at

JavaScriptの文字列はUTF-16コード単位で保持されます。そのため、BMP外の絵文字や一部の漢字は2つのコード単位からなるサロゲートペアとして表現されます。

たとえば😀はJSON内で \\uD83D\\uDE00 と書けます。正しいペアなら JSON.parse() が1つのコードポイントへ戻しますが、上位サロゲートまたは下位サロゲートだけが残った入力は不正です。

まずJSONパーサーを使う

入力が完全なJSONなら、独自の置換処理より先にJSONパーサーへ渡します。

const value = JSON.parse('"\\uD83D\\uDE00"');

console.log(value);             // 😀
console.log(value.length);      // 2: UTF-16 code units
console.log([...value].length); // 1: Unicode code point

String.length はコードポイント数ではありません。文字数を数える目的では、要件に応じてスプレッド構文や Intl.Segmenter を使います。

孤立サロゲートを検出する

外部システムから受け取った文字列や、部分的に切り出したログでは、不正なコード単位が残る場合があります。

function findUnpairedSurrogates(text) {
  const errors = [];

  for (let i = 0; i < text.length; i += 1) {
    const unit = text.charCodeAt(i);

    if (unit >= 0xD800 && unit <= 0xDBFF) {
      const next = text.charCodeAt(i + 1);
      if (!(next >= 0xDC00 && next <= 0xDFFF)) {
        errors.push({ index: i, type: 'unpaired-high-surrogate' });
      } else {
        i += 1;
      }
      continue;
    }

    if (unit >= 0xDC00 && unit <= 0xDFFF) {
      errors.push({ index: i, type: 'unpaired-low-surrogate' });
    }
  }

  return errors;
}

確認例です。

console.log(findUnpairedSurrogates('A😀B'));      // []
console.log(findUnpairedSurrogates('A\uD83DB')); // high surrogate error
console.log(findUnpairedSurrogates('A\uDE00B')); // low surrogate error

自動修復よりエラー方針を決める

孤立サロゲートを見つけても、常に推測で補完できるわけではありません。どの文字だったかという情報が失われているためです。

用途ごとに次のいずれかを選びます。

  • API入力: 400エラーとして拒否し、位置と理由を返す。
  • ログ表示: 元データを保持したまま、画面表示だけ置換文字へ変える。
  • データ移行: 行番号とオフセットを監査ログへ記録し、原本から再取得する。
  • 検索インデックス: 正規化前の値も別フィールドへ保存する。

重要なのは、エラーを無視して保存しないことです。別のシステムやエンコーダーへ渡した時点で、原因の追跡がさらに難しくなります。

U+XXXX\\uXXXX を区別する

U+1F600 はコードポイントの表記です。一方、JSONの \\uD83D\\uDE00 はUTF-16コード単位のエスケープです。同じ絵文字を示していても、入力形式と検証方法は異なります。

複数形式のサンプルを目視で比較したい場合は、ブラウザ内で処理する ASCII to Unicode を確認用に使えます。ただし本番処理では、JSONならJSONパーサー、バイト列なら TextDecoder のように、元の形式を所有する処理を使うべきです。

まとめ

サロゲートペアの問題は「文字化け」ではなく、UTF-16コード単位の整合性の問題です。完全なJSONは標準パーサーで処理し、その後に孤立サロゲートを検査します。不正な入力を推測で直すのではなく、拒否・表示用置換・原本再取得など、用途に合うエラー方針を明示すると再発を防げます。

1
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
1
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?