ダブルクォートの有無や文字コードを問わず使用できるCSV取込

記事のトップ画像

こんにちはリビットです。

いきなりですが、CSV取込って難しくありませんか?

取り込む際はFileオブジェクトを取り扱わないといけませんし、stringとして取得できた後も、データの中にカンマが含まれていたり、ダブルクォートで囲まれてたり囲まれていなかったり…

考慮することが多いです。

JavaScriptを使ったCSV取込について検索して調べてみたんですが、古い情報が載っていたり、場合によっては正常に取り込めなかったり。

ということで今回は各文字コードに対応し、かつダブルクォートの有無にかかわらずCSVを取り込むことのできるコードを紹介します。 ちなみにですが取り込むCSVデータは、

<input type="file" />

上記のような入力から取得できるFileオブジェクトを使うことを前提としています。

CSVファイルからテキストデータを取得する

CSVファイルからString型のデータを取得する部分です。

まずコード全文をご覧ください。

/**
 @param {File} file
 @param {string} encoding
 @return {Promise<string | undefined>}
*/
const getTextFromCSV = (file, encoding = "Shift_JIS") => {
  if (encoding === "UTF-8") {
    return file.text();
  }

  return new Promise((resolve, reject) => {
    try {
      const reader = new FileReader();

      reader.readAsText(file, encoding);

      reader.onload = (event) => resolve(event.target?.result);
      reader.onerror = (event) => reject(event);
    } catch (error) {
      reject(error);
    }
  });
};

CSVファイルの文字コードがUTF-8であれば、Blob.text()を使用できます。 FileはBlobのラッパークラスなので、そのままFile.text()として使用できます。

文字コードがUTF-8以外の場合は、イベントベースのFileReaderを使うしかありません。

FileReaderであればUTF-8も処理できてしまうのでBlob.text()を使う必要はなさそうですが、UTF-8エンコードのファイルからデータを取得する際はこちらが推奨されているので、このようなコードになっています。

扱い方を統一させるため、戻り値はいずれのエンコードの場合もPromise<string | undefined>です。

データを配列に変換する

続いて取得したstring型の変数を二次元配列に変換します。

/**
 @param {File} file
 @param {string} encoding
 @return {string[][]}
*/
const getArrayFromCSV = async (file, encoding = "Shift_JIS") => {
  const text = await getTextFromCSV(file, encoding);

  if (!text || typeof text !== "string") {
    throw new Error("ファイルを配列に変換できませんでした");
  }

  const arr = text.split(/\r?\n/).map((row) => {
    const wrapsQuat =
      row.indexOf('"') === 0 && row.lastIndexOf('"') === row.length - 1;

    if (wrapsQuat) {
      return row.substring(1, row.length - 1).split('","');
    }
    return row.split(",");
  });

  return arr;
};

CSVファイルの改行コードもCRLFとLFのパターンがあるので、正規表現を使って

str.split(/\r?\n/)

のように設定しています。

時々、特定の行はダブルクォーテーションで覆い、そのほかの行はカンマのみ。といった曲者もいるので、ダブルクォーテーションの判定は行ごとにおこなっています。

row.indexOf('"') === 0 && row.lastIndexOf('"') === row.length - 1;

ダブルクォーテーションで覆われている場合は、まず先頭と末尾を削除し, データ間のダブルクォーテーションはカンマと合わせて削除するようにしています。