Papa Parse v5 で CSV をブラウザでパース・生成する
Papa Parse v5 系で CSV を header 自動検出 + dynamicTyping + ストリーム処理で扱うパターン。Excel の Shift_JIS / BOM、worker:true で main thread を逃がす設計、step ストリーミング時に results.data が空になる罠まで実装メモ。
検証日: 2026-05-26
使用バージョン:
papaparse@5.5.3(2026-05 時点でmain)対象: ブラウザでユーザがアップロードする CSV を扱う、Node スクリプトで CSV ↔ JSON 変換したい人
Papa Parse v5 で CSV をブラウザでパース / 生成 するパターン。header 自動検出 + dynamicTyping + Web Worker によるストリーム処理(数 GB 級でも UI freeze なし)、エスケープ周りの落とし穴を動く demo で確認します。
触って試す
| name | age | city |
|---|---|---|
| Alice | 30 | Tokyo |
| Bob | 25 | Osaka |
| Charlie | 35 | Kyoto |
| Dana, the Great | 28 | New York |
CSV を編集すると右側で header 自動検出 + クォート処理が即時表示される。
この実装を体系立てて学ぶ本(ブラウザでファイルを作る処理は、Blob / File API と非同期処理の理解で安定する)
改訂3版JavaScript本格入門
ES2022対応の文法基礎からオブジェクト指向構文まで一冊で体系的に押さえる
実践Node.js入門ー基礎・開発・運用
Node.jsの基礎文法から非同期処理・CLI・Express実装まで一気通貫で学ぶ。フロントエンド開発の経験があり、Node.jsでのサーバーサイド開発を体系的に学び直したい人向け。
セットアップ(npm 1 行 / ESM + UMD / Node どちらも)
npm i papaparse
# TypeScript の型を別途
npm i -D @types/papaparse
| 項目 | 値 |
|---|---|
| 公開バージョン | 5.5.3 |
| Node 必要バージョン | 6+(現実には 18 LTS で動かす) |
| TypeScript 型 | @types/papaparse(DefinitelyTyped) |
| ブラウザ対応 | ESM / UMD 同梱、Worker / FileReader を使う(ブラウザ環境のみ) |
| ライセンス | MIT |
ブラウザだけで使うなら import Papa from "papaparse"、Node でも同じ import で OK。Worker を使う場合(後述)は bundle 配布が必要(papaparse.min.js を public に置く)。
最小サンプル(PapaParse 5.x、3 行で CSV パース)
Papa.parse(csv, options) で文字列を 1 回でパース。header: true で 1 行目をキーとして解釈:
import Papa from "papaparse";
const csv = `name,age
Alice,30
Bob,25`;
const result = Papa.parse(csv, {
header: true, // 1 行目をキーとして使う
skipEmptyLines: true,
dynamicTyping: true, // "30" → 30(数値変換)
});
console.log(result.data);
// [{ name: "Alice", age: 30 }, { name: "Bob", age: 25 }]
ファイルから読む(ブラウザ)
<input type="file"> の File オブジェクトを Papa.parse に直接渡す。worker: true で別 thread、step で 1 行ずつ stream 処理:
const file = inputElement.files![0];
Papa.parse<Record<string, string>>(file, {
header: true,
worker: true, // 大きいファイルは worker で処理
step: (row) => {
// 1 行ずつストリーム処理(数 GB の CSV でも OK)
console.log(row.data);
},
complete: () => console.log("done"),
});
worker: true で別 thread で処理。UI を block しない。
ポイント:
- worker:true にすると
stepcallback は main thread で実行される(worker→main の postMessage 経由)。DOM 操作 / state 更新が安全 stepを入れたらdataは空配列:全部メモリに溜め込まないストリーム動作になる(complete(results)のresults.dataも空)- chunk size はブラウザ内部で自動調整、明示制御は基本不要
JSON → CSV(unparse)
逆方向(オブジェクト配列 → CSV 文字列)は Papa.unparse。quotes / delimiter 等を渡して書式を制御:
const csv = Papa.unparse([
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
], {
delimiter: ",",
quotes: true, // すべてのフィールドを quote
header: true,
});
文字コード判定 — Excel が吐く Shift_JIS / CP932 への対処
日本語 Windows の Excel から「CSV (カンマ区切り)」 で保存すると Shift_JIS / CP932 で出力される。UTF-8 前提で Papa.parse(file) に投げると、日本語が全部文字化けする(� の連鎖)。
実用的な対処は 3 通り:
import Papa from "papaparse";
// (1) Papa の encoding オプションを使う(brower の TextDecoder 経由で読み込み)
Papa.parse(file, { header: true, encoding: "Shift-JIS" });
// (2) 自前で TextDecoder を通す(BOM の有無で UTF-8 / SJIS を切り分けたい時)
const buf = await file.arrayBuffer();
const head = new Uint8Array(buf, 0, 3);
const hasBom = head[0] === 0xef && head[1] === 0xbb && head[2] === 0xbf;
const encoding = hasBom ? "utf-8" : "shift-jis";
const text = new TextDecoder(encoding).decode(buf);
const result = Papa.parse(text, { header: true });
// (3) ユーザに「UTF-8 で保存して」と注意する(技術者向け社内ツールはこれが楽)
TextDecoder の shift-jis ラベルは Web 標準で Windows-31J / CP932 を実装するので、実質 Excel の CSV を読める。Node 単体だと shift-jis decoder は標準では入っていないので、iconv-lite か @kayahr/text-encoding を別途入れる必要がある。
5 つの落とし穴と直し方
| 症状 | 原因 | 直し方 |
|---|---|---|
step 設定したのに complete(results) の results.data が空配列 | step を渡すと メモリに溜め込まない動作に切り替わる | step の callback 内で自前に配列に push、もしくは IndexedDB 等にストリーム書き出し |
"01"(郵便番号)が 1 になる | dynamicTyping: true で 数値変換される | dynamicTyping: { age: true, code: false } のように列単位で制御、または全部 false |
| 改行を含むセルが分裂する | 改行入りセルが quote されていない | 出力側で quotes: true を Papa.unparse に渡すか、CSV 仕様に従い "line1\nline2" で囲む |
| 巨大 CSV で UI が固まる | main thread で一括パースしている | File を渡して chunk + chunkSize: 1MB(下の実測で最長停止 15 ms)。文字列に worker: true を付けるだけでは 63MB のコピーで 290 ms 止まる |
TS で Papa.parse<T>(...) の戻り値型が緩い | header の型推論まではしてくれない | Papa.parse<{ name: string; age: number }>(csv, { header: true }) のように 明示で generic を当てる |
ストリーム + worker の組み合わせは「数百 MB の CSV を即座に行ごとに描画する」ような UX に向いている(Excel 互換の見た目を web で実装する時の定番)。
1,000,000 行で実測: どの渡し方なら UI が止まらないか
「worker: true を付ければ UI は止まらない」と書きかけて、本当かどうかを測ることにしました。6 列 × 1,000,000 行(63MB、quote と日本語入り)を Chromium で読み、処理全体の時間と、メインスレッドが止まった最長時間(requestAnimationFrame の間隔で計測)を分けて測った(papaparse 5.5.3、2026-09-06)。
測ってみて驚いたのは、worker を付けた方が遅く、しかも止まる時間が長かったことです。要点:
- 止まらないのは「File + chunk」。文字列に
worker: trueを付けても、文字列と結果を worker と往復させるコピーで 290 ms 止まる(全体も 2 倍遅い) chunkSizeは既定 10MB(File)。1MB にすると最長停止が 107 → 15 ms になり、全体時間は変わらないdynamicTypingは 27% 遅く、しかも"01"を1にする。必要な列だけ後段で変換する方が速くて安全- バンドルは 19KB(min)/ 7KB(gzip)。CSV 処理のために入れて重いということはない
自分の CSV で試すなら、公式 docs の chunkSize の項と、この記事の demo で File を渡す方を選んでみてください。止まらないことを目で確かめてから採用する方が安全です。
評価
| 観点 | 評価 | コメント |
|---|---|---|
| 学習コスト | ◎ | API は parse / unparse の 2 つ |
| 性能 | ◎ | 1,000,000 行 / 63MB を 0.6 秒。File + chunk なら UI を止めずに処理できる(上の実測) |
| RFC 4180 互換 | ◎ | quote / 改行 / カンマすべて対応 |
| 型 | ○ | 結果型は generic で絞れる |
| バンドルサイズ | ◎ | 19KB(min)/ 7KB(gzip)、実測 |
向く / 向かないケース
- 向く: ブラウザでユーザアップロード CSV 処理、Node スクリプト、ETL 前処理
- 向かない: TSV や独自区切りの軽量パース(
split('\n').map(s => s.split(','))で十分なケース) - 向かない: 厳密な型バリデーションが必要 → papaparse でパース後 zod で検証 が現実解
関連 Topic / 関連書籍
この記事と関係する tech-book.net の Topic と、それぞれの Topic に紐づく書籍:
改訂3版JavaScript本格入門
ES2022対応の文法基礎からオブジェクト指向構文まで一冊で体系的に押さえる
実践Node.js入門ー基礎・開発・運用
Node.jsの基礎文法から非同期処理・CLI・Express実装まで一気通貫で学ぶ。フロントエンド開発の経験があり、Node.jsでのサーバーサイド開発を体系的に学び直したい人向け。
プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで
TypeScriptの型システムを基礎から高度な表現力まで体系的に習得する
はじめてのWebデザイン&プログラミング : HTML、CSS、JavaScript、PHPの基本
HTML・CSS・JavaScript・PHP を横断してWebの全体像を最短で把握する
フロントエンドの知識地図ーー 一冊でHTML/CSS/JavaScriptの開発技術が…
フロントエンド技術の全体像を俯瞰し、学習の優先順位を自分で判断できるようにする