JavaScript Tips | 文字列ユーティリティ:生成 - 伏字変換

JavaScript JavaScript
スポンサーリンク

「伏字変換」でやりたいことを言葉にしてみる

ここでの「伏字変換」は、特定の単語やNGワードを、画面に出すときだけ隠す(置き換える)処理のことです。

ログにはそのまま残したいけど、ユーザーには見せたくない。
チャットやコメントで、禁止ワードは伏字にしたい。
名前や社名の一部だけ伏せて表示したい。

つまり、「元の文字列を壊さずに、表示用だけ“伏せた版”を生成するユーティリティ」を作る、という話です。


一番基本の「NGワードを全部伏字にする」パターン

単純な単語置き換えから始める

まずは、NGワードのリストを持っておいて、
それに一致した単語を全部 *** のような伏字に変えるパターンです。

function censorWords(text, ngWords, mask = "***") {
  if (text == null) return "";

  let result = String(text);

  for (const word of ngWords) {
    if (!word) continue;

    const escaped = word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    const re = new RegExp(escaped, "g");
    result = result.replace(re, mask);
  }

  return result;
}
JavaScript

ポイントを噛み砕きます。

NGワードは配列で渡す(["NGワード1", "NGワード2"] のように)。
正規表現で使うために、NGワードの中の記号をエスケープしている。
new RegExp(escaped, "g") で「その単語に一致するところを全部」探す。
result.replace(re, mask) で、見つかった部分を伏字に置き換える。

実際の動きはこんな感じです。

const ng = ["バカ", "死ね"];

censorWords("お前バカだな", ng);          // "お前***だな"
censorWords("そんなこと言うなよ死ねとか", ng); // "そんなこと言うなよ***とか"
JavaScript

まずはこの「単語を丸ごと伏字にする」形が基本になります。


伏字の形を「文字数に合わせる」パターン

「全部 *** だと長さが分からない」問題

さっきの実装だと、"バカ""死ね"*** になってしまい、
元の文字数が分からなくなります。

「文字数だけは残したい」という場合は、
元の単語の長さに合わせて伏字を作るのが自然です。

function censorWordsByLength(text, ngWords, maskChar = "*") {
  if (text == null) return "";

  let result = String(text);

  for (const word of ngWords) {
    if (!word) continue;

    const escaped = word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    const re = new RegExp(escaped, "g");

    result = result.replace(re, (matched) => {
      return maskChar.repeat(matched.length);
    });
  }

  return result;
}
JavaScript

ここが重要ポイントです。

replace に関数を渡すと、「見つかった文字列ごとにどう置き換えるか」を動的に決められる。
matched.length を使って、元の単語の長さ分だけ伏字文字を並べている。

動きはこうなります。

const ng = ["バカ", "死ね"];

censorWordsByLength("お前バカだな", ng);          // "お前**だな"
censorWordsByLength("そんなこと言うなよ死ねとか", ng); // "そんなこと言うなよ***とか"
JavaScript

「どのくらいの長さの単語だったか」は残しつつ、中身は見えない、というバランスになります。


部分伏字:「単語の一部だけ隠す」パターン

名前や社名の一部だけ伏せたいとき

例えば、名前や社名をログや画面に出すときに、

「最初の1文字だけ見せて、残りは伏字」
「最後の1文字だけ見せて、間を伏字」

のようにしたいことがあります。

これは、NGワードというより「特定の文字列を部分マスクする」話なので、
マスクユーティリティと組み合わせるときれいに書けます。

function censorName(name, maskChar = "*") {
  if (name == null) return "";

  const s = String(name);
  if (s.length <= 1) return maskChar.repeat(s.length);

  const first = s[0];
  const masked = maskChar.repeat(s.length - 1);

  return first + masked;
}
JavaScript

動きはこうです。

censorName("山田");     // "山*"
censorName("山田太郎"); // "山***"
censorName("田中");     // "田*"
JavaScript

「伏字変換」というより「部分マスク」ですが、
実務ではこのパターンも“伏字”としてよく使われます。


実務寄り:NGワードリストをどう扱うか

コードにベタ書きしないほうがいいケース

NGワードは、ビジネス側の判断で増えたり減ったりします。

最初は3個だったのが、運用しているうちに10個、20個と増えていく。
国や言語によってNGワードが違う。

こういう場合、コードにベタ書きするより、設定ファイルやDBから読み込むほうが現実的です。

ユーティリティ側は「NGワードの配列を受け取る」だけにしておき、
「その配列をどこから持ってくるか」は別レイヤーに任せる、という分担がきれいです。

// ユーティリティ側
export function censorWordsByLength(text, ngWords, maskChar = "*") { ... }

// 呼び出し側
const ngWords = loadNgWordsFromConfig(); // どこかから読み込む想定
const safeText = censorWordsByLength(comment, ngWords);
JavaScript

設計として意識してほしいこと

「伏字にするだけ」と「保存値を変える」は分ける

伏字変換は、基本的に「表示用の加工」です。

DB に保存する値はそのまま
画面に出すときだけ伏字にする

という構造にしておくと、後から「誰が何を書いたか」を正しく追えるし、
監査ログにも本当の値を残せます。

逆に、「保存するときに伏字にしてしまう」と、
後から何が書かれていたか分からなくなり、
トラブル調査や不正検知が難しくなります。

ユーティリティの責務は「表示用の伏字文字列を生成すること」。
保存値をどうするかは、別の設計の話だと切り分けておくと安全です。

「どこまで伏せるか」はビジネス側と決める

伏字は、単なる見た目ではなく、情報の見せ方のポリシーです。

NGワードは全部 *** にするのか。
文字数に合わせて伏字にするのか。
名前やメールアドレスをどこまで伏せるのか。

これはエンジニアだけで決めるのではなく、
ビジネス・法務・セキュリティと一緒に決めるべき内容です。

ユーティリティは、その決まったルールを正確に実装する場所、として設計しておくと、
役割分担がとてもクリアになります。


ちょっとだけ手を動かしてみる

コンソールで、次のあたりを試してみてください。

const ng = ["バカ", "死ね"];

censorWords("お前バカだな", ng);
censorWordsByLength("そんなこと言うなよ死ねとか", ng);

censorName("山田太郎");
censorName("田中");
JavaScript

「どこがどう伏字になるか」を目で確認してみてください。

そのうえで、自分のプロジェクトに

export function censorWords(...) { ... }
export function censorWordsByLength(...) { ... }
export function censorName(...) { ... }
JavaScript

のようなユーティリティを置いて、
「伏字にしたいときは必ずここを通す」というルールにしてみてください。

その瞬間、あなたの伏字処理は
場当たり的な *** 置き換えから、
意図とポリシーを持った「伏字変換ユーティリティ」に一段レベルアップします。

タイトルとURLをコピーしました