JavaScript | JavaScriptで使える小さなCSVユーティリティ

JavaScript
スポンサーリンク

短いコードで「CSVの読み込み(文字列→配列/オブジェクト)」と「書き込み(配列/オブジェクト→文字列)」を扱えるユーティリティです。標準ライブラリだけで動き、カンマ・ダブルクオート・改行に対応します。


コード

// CSVを配列の配列へ変換(1行=1配列)
export function parseCSV(csv, { delimiter = ',', trim = true } = {}) {
  const rows = [];
  let row = [];
  let field = '';
  let i = 0;
  let inQuotes = false;

  while (i < csv.length) {
    const ch = csv[i];

    if (inQuotes) {
      if (ch === '"') {
        // 連続する "" はエスケープされた " を意味する
        if (csv[i + 1] === '"') {
          field += '"';
          i += 2;
          continue;
        }
        inQuotes = false;
        i++;
        continue;
      }
      field += ch;
      i++;
      continue;
    }

    if (ch === '"') {
      inQuotes = true;
      i++;
      continue;
    }

    if (ch === delimiter) {
      row.push(trim ? field.trim() : field);
      field = '';
      i++;
      continue;
    }

    if (ch === '\n') {
      row.push(trim ? field.trim() : field);
      rows.push(row);
      row = [];
      field = '';
      i++;
      continue;
    }

    if (ch === '\r') {
      // 先に \r が来て次が \n の場合(Windows改行)をスキップ
      if (csv[i + 1] === '\n') {
        i += 2;
      } else {
        i++;
      }
      row.push(trim ? field.trim() : field);
      rows.push(row);
      row = [];
      field = '';
      continue;
    }

    field += ch;
    i++;
  }

  // 最終フィールド・最終行を反映
  if (field !== '' || inQuotes || row.length > 0) {
    row.push(trim ? field.trim() : field);
    rows.push(row);
  }

  return rows;
}

// ヘッダー行をキーに、配列の配列→配列のオブジェクトへ
export function toObjects(rows) {
  if (!rows.length) return [];
  const [header, ...body] = rows;
  return body.map(r => {
    const obj = {};
    for (let i = 0; i < header.length; i++) {
      obj[header[i]] = r[i] ?? '';
    }
    return obj;
  });
}

// 配列の配列からCSV文字列へ(各フィールドを適切にエスケープ)
export function stringifyRows(rows, { delimiter = ',' } = {}) {
  const escapeField = (val) => {
    const s = val == null ? '' : String(val);
    const needsQuote =
      s.includes('"') || s.includes('\n') || s.includes('\r') || s.includes(delimiter);
    const escaped = s.replace(/"/g, '""');
    return needsQuote ? `"${escaped}"` : escaped;
  };

  return rows
    .map(row => row.map(escapeField).join(delimiter))
    .join('\n');
}

// オブジェクト配列をCSVへ(キー順でヘッダーを生成)
export function stringifyObjects(items, { delimiter = ',' } = {}) {
  if (!items.length) return '';
  const keys = Array.from(
    items.reduce((set, obj) => {
      Object.keys(obj).forEach(k => set.add(k));
      return set;
    }, new Set())
  );

  const rows = [
    keys,
    ...items.map(obj => keys.map(k => obj[k] ?? '')),
  ];
  return stringifyRows(rows, { delimiter });
}
JavaScript

使い方

読み込み(CSV文字列 → 配列/オブジェクト)

const csv = `name,age,note
"田中,太郎",28,"改行
を含むメモ"
佐藤,31,"ダブルクオート ""あり"""`;

const rows = parseCSV(csv);      // 2次元配列
const list = toObjects(rows);    // [{ name: '田中,太郎', age: '28', note: '改行\nを含むメモ' }, ...]
console.log(list);
JavaScript

書き込み(配列/オブジェクト → CSV文字列)

const items = [
  { name: '田中,太郎', age: 28, note: '改行\nOK' },
  { name: '佐藤', age: 31, note: 'ダブルクオート " あり' },
];

const csvOut = stringifyObjects(items);
console.log(csvOut);
/*
name,age,note
"田中,太郎",28,"改行
OK"
佐藤,31,"ダブルクオート "" あり"
*/
JavaScript

特徴と注意点

  • 対応: カンマ、改行、CRLF、ダブルクオートのエスケープ(””)をサポート。
  • オプション: delimiter で区切り変更(例: ';''\t')、trim でフィールドの前後空白を除去。
  • 未対応の範囲: コメント行、数値/日時の型推論、大規模ファイルのストリーミング処理は対象外。大きなCSVはストリームや既存ライブラリ(例: csv-parse, Papaparse)の利用を検討してください。
  • ヘッダー生成: stringifyObjects は複数オブジェクトのキーを集約してヘッダーを作成。不足キーは空文字で埋めます。
タイトルとURLをコピーしました