JavaScript Tips | 配列ユーティリティ:バリデーション

JavaScript JavaScript
スポンサーリンク

テーマの整理:「配列バリデーション」とは何か

ここでの「バリデーション」は、配列の中身が「業務で期待している形・値になっているか」をチェックすることです。 特に「オブジェクト配列」(レコード一覧)に対して、

  • 必須項目が入っているか
  • 型が正しいか(数値・文字列・真偽値など)
  • 値の範囲やルールを守っているか

をまとめて確認するためのユーティリティが「配列バリデーション」です。

ポイントは、「1 件ずつ if 文でチェックする」のではなく、“配列全体を、ルールに従って一括で検査する”ことです。

基本コンセプト:「ルールを決めて、配列全体に適用する」

どんなデータをバリデーションするのか

よくある形は、こんなオブジェクト配列です。

const users = [
  { id: 1, name: "Alice", age: 20, email: "alice@example.com" },
  { id: 2, name: "",      age: -5, email: "not-an-email" },
];
JavaScript

ここから、例えば次のようなルールでチェックしたくなります。

  • id は必須で、数値で、1 以上。
  • name は必須で、空文字は禁止。
  • age は 0 以上 120 以下。
  • email は「ざっくりメールっぽい形」かどうか。

この「どうチェックするか」を「バリデーションルール」として定義し、 それを配列全体に適用するのが配列バリデーションです。

ユーティリティ 1:1 レコード分のバリデーション関数を作る

まず「1 件」をどうチェックするかを決める

配列全体をチェックする前に、「1 レコードをどう検査するか」を関数にします。

function validateUser(user) {
  const errors = [];

  if (user.id == null) {
    errors.push("id は必須です");
  } else if (typeof user.id !== "number" || user.id <= 0) {
    errors.push("id は 1 以上の数値である必要があります");
  }

  if (!user.name || user.name.trim() === "") {
    errors.push("name は空文字ではいけません");
  }

  if (typeof user.age !== "number" || user.age < 0 || user.age > 120) {
    errors.push("age は 0〜120 の数値である必要があります");
  }

  if (typeof user.email !== "string" || !user.email.includes("@")) {
    errors.push("email は @ を含む文字列である必要があります");
  }

  return errors;
}
JavaScript

重要ポイントをかみ砕いて説明

errors は「そのレコードに対するエラーメッセージの配列」です。 id は「null / undefined なら必須エラー」「数値でない・1 以下なら範囲エラー」を出しています。 name は「空文字・空白だけ」を禁止しています。 age は「型が数値か」「範囲が 0〜120 か」をチェックしています。 email は「ざっくり @ を含むかどうか」で簡易チェックしています。

この関数は、「1 ユーザーが正しいかどうか」を判定し、 問題があればその理由を文字列で返します。

ユーティリティ 2:配列全体をバリデーションする

実装:validateUsersArray

1 件分のバリデーション関数を、配列全体に適用します。

function validateUsersArray(users) {
  if (!Array.isArray(users)) {
    return { valid: false, errors: ["配列ではありません"] };
  }

  const allErrors = [];

  users.forEach((user, index) => {
    const errors = validateUser(user);
    if (errors.length > 0) {
      allErrors.push({
        index,
        errors,
      });
    }
  });

  const valid = allErrors.length === 0;

  return { valid, errors: allErrors };
}
JavaScript

重要ポイントをかみ砕いて説明

まず「配列かどうか」をチェックし、配列でなければ即エラーにします。 各ユーザーについて validateUser を呼び、エラーがあれば index(何番目の要素か)と一緒に記録します。 最後に「エラーが 0 件なら valid = true」「1 件以上あれば valid = false」と判定します。

戻り値は、

{
  valid: true/false,
  errors: [
    { index: 1, errors: ["name は空文字ではいけません", "age は 0〜120 の数値である必要があります"] },
    ...
  ]
}
JavaScript

のような形になります。 「どの行に」「どんな問題があるか」が分かるので、画面やログに出しやすいです。

例題 1:ユーザー配列をバリデーションして結果を見る

const users = [
  { id: 1, name: "Alice", age: 20, email: "alice@example.com" },
  { id: 2, name: "",      age: -5, email: "not-an-email" },
];

const result = validateUsersArray(users);

console.log("valid:", result.valid);
console.log("errors:", JSON.stringify(result.errors, null, 2));
JavaScript

出力イメージはこうなります。

valid: false
errors: [
  {
    "index": 1,
    "errors": [
      "name は空文字ではいけません",
      "age は 0〜120 の数値である必要があります",
      "email は @ を含む文字列である必要があります"
    ]
  }
]

ここでのポイントは、

1 件目は問題なし。 2 件目(index 1)に、name・age・email の 3 つの問題がある。

ということが、はっきり分かることです。

ユーティリティ 3:バリデーションルールを「定義オブジェクト」として持つ

シナリオ

ユーザーだけでなく、売上データや商品データなど、複数の種類のレコードがある。 それぞれに「バリデーションルール」を持たせたい。

ルール定義の形を決める

例えば、こんな形で「フィールドごとのルール」を持てます。

const UserValidationRules = {
  id: [
    (v) => (v == null ? "id は必須です" : null),
    (v) =>
      typeof v !== "number" || v <= 0
        ? "id は 1 以上の数値である必要があります"
        : null,
  ],
  name: [
    (v) =>
      !v || v.trim() === ""
        ? "name は空文字ではいけません"
        : null,
  ],
  age: [
    (v) =>
      typeof v !== "number" || v < 0 || v > 120
        ? "age は 0〜120 の数値である必要があります"
        : null,
  ],
  email: [
    (v) =>
      typeof v !== "string" || !v.includes("@")
        ? "email は @ を含む文字列である必要があります"
        : null,
  ],
};
JavaScript

ここでのポイントは、

各フィールドに「チェック関数の配列」を割り当てていることです。 チェック関数は「問題があればエラーメッセージを返し、問題なければ null を返す」スタイルにしています。

汎用バリデーション関数:validateRecordWithRules

function validateRecordWithRules(record, rules) {
  const errors = [];

  for (const key in rules) {
    const validators = rules[key];
    const value = record[key];

    validators.forEach((fn) => {
      const message = fn(value, record);
      if (message) {
        errors.push(`${key}: ${message}`);
      }
    });
  }

  return errors;
}
JavaScript

使い方はこうです。

function validateUsersArrayWithRules(users) {
  if (!Array.isArray(users)) {
    return { valid: false, errors: ["配列ではありません"] };
  }

  const allErrors = [];

  users.forEach((user, index) => {
    const errors = validateRecordWithRules(user, UserValidationRules);
    if (errors.length > 0) {
      allErrors.push({ index, errors });
    }
  });

  return { valid: allErrors.length === 0, errors: allErrors };
}
JavaScript

この形にしておくと、「ルール定義」と「バリデーション処理」がきれいに分離されます。 仕様変更があったときは、UserValidationRules だけを直せばよくなります。

ユーティリティ 4:バリデーション結果を「使いやすく」扱う

シナリオ

画面で「どの行にエラーがあるか」を表示したい。 ログで「エラー行だけ」を出したい。

エラー行だけを抽出する

バリデーション結果から、「エラーのあるレコードだけ」を取り出すこともできます。

function extractInvalidRecords(records, validationResult) {
  return validationResult.errors.map((e) => ({
    index: e.index,
    record: records[e.index],
    errors: e.errors,
  }));
}
JavaScript

使い方はこうです。

const result = validateUsersArray(users);
const invalids = extractInvalidRecords(users, result);

console.log(JSON.stringify(invalids, null, 2));
JavaScript

これで、

  • どの行がダメなのか
  • その行の中身は何なのか
  • どんなエラーがあるのか

をまとめて確認できます。

配列バリデーションユーティリティで意識してほしい重要ポイント

「バリデーションは“入口でまとめてやる”」という発想

バリデーションは、できるだけ「データが入ってきた入口」でまとめてやるのが基本です。

API 受信直後。 CSV/Excel 読み込み直後。 フォーム送信直後。

ここで配列バリデーションを通しておけば、 後続の処理は「正しいデータだけを前提に書ける」ようになります。

初心者のうちは、

「この処理の前に、バリデーションを一回挟めないか?」

と考える癖をつけると、 コード全体の安定感がかなり変わります。

「何がダメなのか」を人間に伝わる形で返す

バリデーションは、「通るかどうか」だけでなく、 「なぜ通らないのか」を人間に伝えることが大事です。

  • どの行か(index)。
  • どの項目か(key)。
  • 何が問題か(メッセージ)。

これをセットで返すようにしておくと、 画面でもログでも、原因がすぐ分かります。

手を動かして「配列バリデーション」の感覚をつかむ

次のコードを実行して、

  • 1 レコードのバリデーション
  • 配列全体のバリデーション
  • エラー行の抽出

を体感してみてください。

function validateUser(user) {
  const errors = [];

  if (user.id == null) {
    errors.push("id は必須です");
  } else if (typeof user.id !== "number" || user.id <= 0) {
    errors.push("id は 1 以上の数値である必要があります");
  }

  if (!user.name || user.name.trim() === "") {
    errors.push("name は空文字ではいけません");
  }

  if (typeof user.age !== "number" || user.age < 0 || user.age > 120) {
    errors.push("age は 0〜120 の数値である必要があります");
  }

  if (typeof user.email !== "string" || !user.email.includes("@")) {
    errors.push("email は @ を含む文字列である必要があります");
  }

  return errors;
}

function validateUsersArray(users) {
  if (!Array.isArray(users)) {
    return { valid: false, errors: ["配列ではありません"] };
  }

  const allErrors = [];

  users.forEach((user, index) => {
    const errors = validateUser(user);
    if (errors.length > 0) {
      allErrors.push({ index, errors });
    }
  });

  return { valid: allErrors.length === 0, errors: allErrors };
}

function extractInvalidRecords(records, validationResult) {
  return validationResult.errors.map((e) => ({
    index: e.index,
    record: records[e.index],
    errors: e.errors,
  }));
}

function demo() {
  const users = [
    { id: 1, name: "Alice", age: 20, email: "alice@example.com" },
    { id: 2, name: "",      age: -5, email: "not-an-email" },
  ];

  const result = validateUsersArray(users);

  console.log("valid:", result.valid);
  console.log("errors:", JSON.stringify(result.errors, null, 2));

  console.log("invalid records:", JSON.stringify(extractInvalidRecords(users, result), null, 2));
}

demo();
JavaScript

まとめ:配列バリデーションユーティリティで「壊れたデータを、入口で止める」

配列バリデーションは、 「壊れた・おかしなデータを、業務ロジックに入る前にきちんと止めるためのユーティリティ」です。

プロジェクトに例えば次のような形で置いておくイメージです。

export function validateRecordWithRules(record, rules) { ... }
export function validateUsersArray(users) { ... }
export const UserValidationRules = { ... };
JavaScript

そして、

API 受信直後、 CSV/Excel 読み込み直後、 フォーム送信直後

など、「データが入ってくる入口」で必ずこのバリデーションを通す。

こう決めておくと、 後ろ側の処理は「信頼できる配列」を前提に書けるようになり、 バグも事故もぐっと減ります。

データを信じる前に、一度疑ってみる。 その“疑い方”をコードにしたものが、配列バリデーションユーティリティです。

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