dodaのようにやらかさないためのJavaScriptの一致判定処理

dodaスカウトサービスに不具合発生

dodaスカウトサービスの「直近の勤務先のブロック」機能に不具合があったとパーソルキャリアからお知らせがありました。

dodaスカウトサービスにおける
「直近の勤務先のブロック」機能の不具合に関するお詫びとお知らせ

2018年8月7日から2023年10月31日までの間に96,338人のユーザー情報が最近の勤務先に表示されてしまう可能性があったとのことです。

氏名や連絡先などの重要な個人情報は開示されていないとしています。

この問題はデータベースとプロフィールの照合プログラムの設計ミスによるもので、現在「doda Request」サービスは停止されています。

事象概要

以下の条件に該当する場合、正式な社名表記に関係なく、ブロック機能が作動しないという不具合が発生していました。(以下引用)

【条件】
直近の勤務先会社名にアルファベット、数字、カタカナのいずれかが含まれるケースにおいて、当社のシステムに登録されている企業名の表記*1と、個人のお客さまが入力した「直近の勤務先会社名」が一致しない。

*1 アルファベット、数字、カタカナ部分における、全角・半角、大文字・小文字の表記

例えば「PERSOL株式会社」で会社側で登録されていても、ユーザー側が「PERSOL株式会社」と入力した場合は一致しないため、ブロック機能が作動しなかったということです。

JavaScriptで例えて書くと以下のようになっていたようです。

function blockTest() {
  const company = 'PERSOL株式会社'
  const str = prompt('会社名を入力して下さい')
  if (company === str) {
    console.log('一致したのでブロックします。')
  } else {
    console.log('一致しないのでブロックしません!')
  }
}

blockTest()

もしJavaScriptで同様の問題が発生した場合、解決するにはいくつかの処理の追加が必要です。

全角英字を半角英字に変換する

まず、比較する会社名を全角から半角にする処理を追加します。

この処理があれば「PERSOL株式会社」で登録されていて、入力が「PERSOL株式会社」でも一致します。

function zenToHan(str) {
  return str.replace(/[A-Za-z]/g, function(s) {
    return String.fromCharCode(s.charCodeAt(0) - 0xFEE0)
  })
}

function blockTest() {
  const company = zenToHan('PERSOL株式会社')
  const str = zenToHan(prompt('会社名を入力して下さい'))
  if (company === str) {
    console.log('一致したのでブロックします。')
  } else {
    console.log('一致しないのでブロックしません!')
  }
}

blockTest()

大文字と小文字を区別しない

前述のコードだと大文字と小文字を区別するため「Persol株式会社」の場合は一致しません。

toUpperCase()を使えば両方とも大文字で揃えることができるので、大文字と小文字を区別せずに比較できます。

function zenToHan(str) {
  return str.replace(/[A-Za-z]/g, function(s) {
    return String.fromCharCode(s.charCodeAt(0) - 0xFEE0)
  })
}

function normalizeCompany(str) {
  return zenToHan(str).toUpperCase()
}

function blockTest() {
  const company = normalizeCompany('PERSOL株式会社')
  const str = normalizeCompany(prompt('会社名を入力して下さい'))
  if (company === str) {
    console.log('一致したのでブロックします。')
  } else {
    console.log('一致しないのでブロックしません!')
  }
}

blockTest()

半角カナを全角カナにする

日本語のカタカナには半角カナと全角カナが存在するため、これが原因で一致しないことも考えられます。

そんなときは半角カナを全角カナに変換する処理を加えて以下のようにします。

function zenToHan(str) {
  return str.replace(/[A-Za-z]/g, function(s) {
    return String.fromCharCode(s.charCodeAt(0) - 0xFEE0)
  })
}

function hanToKana(str) {
  const kanaMap = {
    'ガ': 'ガ', 'ギ': 'ギ', 'グ': 'グ', 'ゲ': 'ゲ', 'ゴ': 'ゴ',
    'ザ': 'ザ', 'ジ': 'ジ', 'ズ': 'ズ', 'ゼ': 'ゼ', 'ゾ': 'ゾ',
    'ダ': 'ダ', 'ヂ': 'ヂ', 'ヅ': 'ヅ', 'デ': 'デ', 'ド': 'ド',
    'バ': 'バ', 'ビ': 'ビ', 'ブ': 'ブ', 'ベ': 'ベ', 'ボ': 'ボ',
    'パ': 'パ', 'ピ': 'ピ', 'プ': 'プ', 'ペ': 'ペ', 'ポ': 'ポ',
    'ヴ': 'ヴ', 'ヷ': 'ヷ', 'ヺ': 'ヺ',
    'ア': 'ア', 'イ': 'イ', 'ウ': 'ウ', 'エ': 'エ', 'オ': 'オ',
    'カ': 'カ', 'キ': 'キ', 'ク': 'ク', 'ケ': 'ケ', 'コ': 'コ',
    'サ': 'サ', 'シ': 'シ', 'ス': 'ス', 'セ': 'セ', 'ソ': 'ソ',
    'タ': 'タ', 'チ': 'チ', 'ツ': 'ツ', 'テ': 'テ', 'ト': 'ト',
    'ナ': 'ナ', 'ニ': 'ニ', 'ヌ': 'ヌ', 'ネ': 'ネ', 'ノ': 'ノ',
    'ハ': 'ハ', 'ヒ': 'ヒ', 'フ': 'フ', 'ヘ': 'ヘ', 'ホ': 'ホ',
    'マ': 'マ', 'ミ': 'ミ', 'ム': 'ム', 'メ': 'メ', 'モ': 'モ',
    'ヤ': 'ヤ', 'ユ': 'ユ', 'ヨ': 'ヨ',
    'ラ': 'ラ', 'リ': 'リ', 'ル': 'ル', 'レ': 'レ', 'ロ': 'ロ',
    'ワ': 'ワ', 'ヲ': 'ヲ', 'ン': 'ン',
    'ァ': 'ァ', 'ィ': 'ィ', 'ゥ': 'ゥ', 'ェ': 'ェ', 'ォ': 'ォ',
    'ッ': 'ッ', 'ャ': 'ャ', 'ュ': 'ュ', 'ョ': 'ョ',
    '。': '。', '、': '、', 'ー': 'ー', '「': '「', '」': '」', '・': '・'
  }

  const reg = new RegExp('(' + Object.keys(kanaMap).join('|') + ')', 'g')
  return str.replace(reg, (match) => {
    return kanaMap[match]
  })
}

function normalizeCompany(str) {
  return hanToKana(zenToHan(str)).toUpperCase()
}

function blockTest() {
  const company = normalizeCompany('パーソル株式会社')
  const str = normalizeCompany(prompt('会社名を入力して下さい'))
  if (company === str) {
    console.log('一致したのでブロックします。')
  } else {
    console.log('一致しないのでブロックしません!')
  }
}

blockTest()

ひらがなとカタカナを区別しない

もし、システム的に「ひらがな」と「カタカナ」を区別しないで比較したい場合は、Unicode値に基づいて「ひらがな」から「カタカナ」に変換します。

こうすることで「ヤマダ電機」で登録されていて、入力側が「やまだ電機」でも一致します。

参考: Unicodeの平仮名

function zenToHan(str) {
  return str.replace(/[A-Za-z]/g, function(s) {
    return String.fromCharCode(s.charCodeAt(0) - 0xFEE0)
  })
}

function hanToKana(str) {
  const kanaMap = {
    'ガ': 'ガ', 'ギ': 'ギ', 'グ': 'グ', 'ゲ': 'ゲ', 'ゴ': 'ゴ',
    'ザ': 'ザ', 'ジ': 'ジ', 'ズ': 'ズ', 'ゼ': 'ゼ', 'ゾ': 'ゾ',
    'ダ': 'ダ', 'ヂ': 'ヂ', 'ヅ': 'ヅ', 'デ': 'デ', 'ド': 'ド',
    'バ': 'バ', 'ビ': 'ビ', 'ブ': 'ブ', 'ベ': 'ベ', 'ボ': 'ボ',
    'パ': 'パ', 'ピ': 'ピ', 'プ': 'プ', 'ペ': 'ペ', 'ポ': 'ポ',
    'ヴ': 'ヴ', 'ヷ': 'ヷ', 'ヺ': 'ヺ',
    'ア': 'ア', 'イ': 'イ', 'ウ': 'ウ', 'エ': 'エ', 'オ': 'オ',
    'カ': 'カ', 'キ': 'キ', 'ク': 'ク', 'ケ': 'ケ', 'コ': 'コ',
    'サ': 'サ', 'シ': 'シ', 'ス': 'ス', 'セ': 'セ', 'ソ': 'ソ',
    'タ': 'タ', 'チ': 'チ', 'ツ': 'ツ', 'テ': 'テ', 'ト': 'ト',
    'ナ': 'ナ', 'ニ': 'ニ', 'ヌ': 'ヌ', 'ネ': 'ネ', 'ノ': 'ノ',
    'ハ': 'ハ', 'ヒ': 'ヒ', 'フ': 'フ', 'ヘ': 'ヘ', 'ホ': 'ホ',
    'マ': 'マ', 'ミ': 'ミ', 'ム': 'ム', 'メ': 'メ', 'モ': 'モ',
    'ヤ': 'ヤ', 'ユ': 'ユ', 'ヨ': 'ヨ',
    'ラ': 'ラ', 'リ': 'リ', 'ル': 'ル', 'レ': 'レ', 'ロ': 'ロ',
    'ワ': 'ワ', 'ヲ': 'ヲ', 'ン': 'ン',
    'ァ': 'ァ', 'ィ': 'ィ', 'ゥ': 'ゥ', 'ェ': 'ェ', 'ォ': 'ォ',
    'ッ': 'ッ', 'ャ': 'ャ', 'ュ': 'ュ', 'ョ': 'ョ',
    '。': '。', '、': '、', 'ー': 'ー', '「': '「', '」': '」', '・': '・'
  }

  const reg = new RegExp('(' + Object.keys(kanaMap).join('|') + ')', 'g')
  return str.replace(reg, (match) => {
    return kanaMap[match]
  })
}

function hiraToKana(str) {
  return str.replace(/[\u3041-\u3096]/g, function(match) {
    const chr = match.charCodeAt(0) + 0x60
    return String.fromCharCode(chr)
  });
}

function normalizeCompany(str) {
  return hiraToKana(hanToKana(zenToHan(str))).toUpperCase()
}

function blockTest() {
  const company = normalizeCompany('ヤマダ電機')
  const str = normalizeCompany(prompt('会社名を入力して下さい'))
  if (company === str) {
    console.log('一致したのでブロックします。')
  } else {
    console.log('一致しないのでブロックしません!')
  }
}

blockTest()

ちなみに「カタカナ」から「ひらがな」に変換したい場合はUnicode値は以下のように「0x60」を加算ではなく減算して算出します。

参考: Unicodeの片仮名

function kanaToHira(str) {
  return str.replace(/[\u30A1-\u30F6]/g, (match) => {
    const chr = match.charCodeAt(0) - 0x60
    return String.fromCharCode(chr)
  })
}

まとめ

JavaScriptで一致判定処理を使用することは結構多いので、dodaのようにやらかさないために以下の各変換処理方法は必ず覚えておいたほうが良いでしょう。

  • 全角英字を半角英字に変換
  • 英字を大文字(小文字)に変換
  • 半角カナを全角カナに変換
  • ひらがなをカタカナに変換