Excelから出力したCSVをJavaScriptで読みたいという要望をよく受けており、その度に10年ほど前に自作したパーサーを使い回して対応してきた。
ふと「今ならもっと良いものが作れるのではないか」と思い立って書いてみたところ、昔の自分が作ったものより短く可読性が高い物が出来た。
Excel産CSVは「"」によるエスケープの処理が面倒なのだが、それについては「"の出現数が偶数の状態で,\nが来たら列/行確定とする」と言うシンプルなルールで簡単に対応出来てしまった。
(書き上げてから気付いたのだが、Excel前提なのに\nベースで組んでしまった。まあ入力時にreplaceAllすれば良いので問題ないだろう。)
2026-02-21追記: GitHubにCRLFやBOM対応、パフォーマンス改善を施した最新コードがあります。
https://github.com/crabspider/CSVParser.js
class CSVParser {
static parse(csvString, config = {}) {
const result = [];
let row = config.useHeaderAsKey ? {} : [];
let cellStart = 0;
let qmCount = 0;
let firstRow = [];
let isFirstRow = true;
let currentCol = 0;
csvString = csvString.trimEnd() + '\n';
for (let i = 0; i < csvString.length; i++) {
if (csvString[i] === '"') {
qmCount++;
} else if ((csvString[i] === ',' || csvString[i] === '\n') && qmCount % 2 === 0) {
let cellString;
if (csvString.charAt(cellStart) === '"') {
cellString = csvString.slice(cellStart + 1, i - 1);
} else {
cellString = csvString.slice(cellStart, i);
}
cellString = cellString.replaceAll('""', '"');
if (config.useHeaderAsKey) {
if (isFirstRow) {
firstRow.push(cellString);
} else {
row[firstRow[currentCol]] = cellString;
}
} else {
row.push(cellString);
}
cellStart = i + 1;
qmCount = 0;
currentCol++;
if (csvString[i] === '\n') {
if (!(isFirstRow && config.useHeaderAsKey)) {
result.push(row);
}
row = config.useHeaderAsKey ? {} : [];
currentCol = 0;
isFirstRow = false;
}
}
}
return result;
}
}