ダブルクォートの有無や文字コードを問わず使用できる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;
ダブルクォーテーションで覆われている場合は、まず先頭と末尾を削除し, データ間のダブルクォーテーションはカンマと合わせて削除するようにしています。