はじめに
ある開発で、検索欄にキーワードを入力すると
本来ヒットするはずのデータが一部表示されない という問題に遭遇しました。
調査を進めてみると、
濁点・半濁点を含む文字列だけが検索結果に表示されていない
ことが分かりました。
何が起こっていたのか
原因は、Unicode正規化形式の不一致でした。
- 保存されているデータ:NFD
- 検索キーワード:NFC
この状態で文字列検索(includes や LIKE など)を行っていたため、
見た目は同じでもバイト列が異なり、マッチしなかった というわけです。
NFC・NFDとは何か
NFC・NFDは Unicode正規化形式 と呼ばれるもので、
「見た目が同じ文字を、どのようなバイト列で表現するか」のルールです。
例:「ぱ」のUnicode表現
NFC
- 「ぱ」を 1文字(合成文字) として扱う
NFD
- 「は」 + 「゜」に 分解 して表現する
なぜ検索にヒットしなかったのか
見た目は同じ「ぱ」でも、
- NFC:
ぱ(1文字) - NFD:
は + ゜(2文字)
という 異なるUnicode表現 になっていました。
そのため、文字列としては一致せず、
検索条件にヒットしていなかった、という結果になります。
今回この問題が起きた理由
原因をさらに追っていくと、
macOSで作成したファイル名がNFDで保存されていた ことが分かりました。
macOSでは仕様として、
ファイル名が自動的にNFDへ正規化される ようになっています。
(なぜそんなことを....)
このNFDの文字列をそのままDBなどに保存し、
検索時にはNFCの文字列を使っていたため、
正規化形式の不一致が発生していました。
対策
保存時・検索時の両方で正規化形式を統一する ことで解決しました。
const s = "ぱ";
// NFCに正規化
const normalized = s.normalize("NFC");