CSV to JSON JavaScript ガイド
無料の CSVからJSONへの変換ツール をブラウザで直接使用 — インストール不要。
CSVからJSONへの変換ツール をオンラインで試す →私がよく扱うCSVデータは、ファイルのアップロード、データベースのエクスポート、あるいは1970年代のフォーマットを使い続けているAPIからのフラットな文字列として届きます。JavaScriptで CSVをJSONに変換するには、言語が無償で提供する2つのものが必要です:行をパースするための文字列分割と、結果をシリアライズするための JSON.stringify() です。基本的な処理にはnpmパッケージは不要です — このガイドでは再利用可能な csvToJson() ユーティリティからPapaParseおよびNode.jsのファイルI/Oまで、完全なパイプラインをカバーします。コードなしで素早く変換するには、 オンラインCSV to JSONコンバーター が即座に処理します。すべてのサンプルは Node.js 18+ とモダンブラウザを対象としています。
- ✓CSVを改行で分割し、0行目からヘッダーを取得し、残りの行をオブジェクトにマッピングしてから、JSON.stringify(array, null, 2)できれいな出力を生成します。
- ✓JSON.stringify()は文字列を生成し、JSON.parse()はそれを有効なJavaScript配列に戻します — 操作する前にどちらの状態にあるかを確認してください。
- ✓Mapインスタンスは自動的にJSONにシリアライズされません — まずObject.fromEntries(map)を呼び出してください。
- ✓クォートフィールド、値の中のカンマ、またはセル内の改行があるCSVには、手動の分割ではなくPapaParseまたはcsv-parseを使用してください。
- ✓csvtojson(npm)は型変換、ストリーミング、RFC 4180のエッジケースを1回の呼び出しで処理します。
CSV to JSON 変換とは何ですか?
CSV to JSON 変換は、フラットなカンマ区切りのテキスト形式を、各行が列ヘッダーをキーとするJavaScriptオブジェクトになる構造化されたオブジェクトの配列に変換します。CSVフォーマットにはデータ型がありません — すべては文字列です。JSONは構造、ネスト、および明示的な型(数値、真偽値、null)を追加します。この変換は、スプレッドシートのエクスポート、レガシーシステムのダンプ、またはユーザーがアップロードしたファイルから始まるほぼすべてのデータパイプラインの最初のステップです。基礎となるデータは同じままで、フォーマットが位置ベースの列から名前付きプロパティに変わります。
name,email,role,active 田中太郎,tanaka@nexuslabs.io,Engineering Lead,true 山田花子,yamada@nexuslabs.io,Product Manager,true
[
{
"name": "田中太郎",
"email": "tanaka@nexuslabs.io",
"role": "Engineering Lead",
"active": "true"
},
{
"name": "山田花子",
"email": "yamada@nexuslabs.io",
"role": "Product Manager",
"active": "true"
}
]csvToJson() — 再利用可能な変換関数の構築
JavaScriptにおけるCSV-to-JSONのパイプライン全体は3つのステップに分解できます:CSV文字列を改行で分割して行を取得し、 split(',')で最初の行からヘッダーを抽出し、残りの各行をヘッダーからキーを取り対応する列の値を持つプレーンなJavaScriptオブジェクトにマッピングします。最後の JSON.stringify() の呼び出しがオブジェクトの配列をJSON文字列に変換します。最小限の動作バージョンは次のとおりです:
function csvToJson(csv) {
const lines = csv.trim().split('\n')
const headers = lines[0].split(',').map(h => h.trim())
const rows = lines.slice(1)
.filter(line => line.trim() !== '')
.map(line => {
const values = line.split(',')
return Object.fromEntries(
headers.map((header, i) => [header, values[i]?.trim()])
)
})
return JSON.stringify(rows, null, 2)
}
const csv = `server,port,region,status
api-gateway,8080,us-east-1,healthy
auth-service,8443,eu-west-1,degraded
payments-api,9090,ap-south-1,healthy`
console.log(csvToJson(csv))
// [
// { "server": "api-gateway", "port": "8080", "region": "us-east-1", "status": "healthy" },
// { "server": "auth-service", "port": "8443", "region": "eu-west-1", "status": "degraded" },
// { "server": "payments-api", "port": "9090", "region": "ap-south-1", "status": "healthy" }
// ]この関数は基本的な処理を行います:末尾の改行、空行、値の周りの空白。すべてのCSVフィールドは文字列として渡されます。 port が数値の 8080 ではなく文字列の "8080" になっていることに注意してください。JSON出力に適切な型が必要な場合は、自分で強制変換する必要があります。型検出を追加した拡張バージョンは次のとおりです:
function coerceValue(val) {
if (val === undefined || val === '') return null
if (val === 'true') return true
if (val === 'false') return false
if (val === 'null') return null
const num = Number(val)
if (!isNaN(num) && val.trim() !== '') return num
return val
}
function csvToJson(csv, { coerce = false } = {}) {
const lines = csv.trim().split('\n')
const headers = lines[0].split(',').map(h => h.trim())
const rows = lines.slice(1)
.filter(line => line.trim() !== '')
.map(line => {
const values = line.split(',')
return Object.fromEntries(
headers.map((header, i) => [
header,
coerce ? coerceValue(values[i]?.trim()) : values[i]?.trim()
])
)
})
return JSON.stringify(rows, null, 2)
}
const csv = `endpoint,port,max_connections,debug
/api/v2/orders,8443,500,true
/api/v2/health,8080,100,false`
console.log(csvToJson(csv, { coerce: true }))
// [
// { "endpoint": "/api/v2/orders", "port": 8443, "max_connections": 500, "debug": true },
// { "endpoint": "/api/v2/health", "port": 8080, "max_connections": 100, "debug": false }
// ]coerce フラグはオプトイン式です。自動型検出は裏目に出ることがあります — 郵便番号("07302")のようなフィールドはNumber()で数値に変換すると先頭のゼロが失われます。強制変換はデフォルトをオフにして、スキーマを制御できる場合のみ有効にしてください。補足として、 JSON.stringify() はインデントのための第3引数 space を受け取ります。2スペースには 2、4スペースには 4、タブには "\t" を渡してください。APIリクエストボディとしてJSON文字列を送信する場合など、空白が帯域幅を無駄にするときは省略してコンパクトな1行出力にします。
JSON.parse()を呼び出すと、カンマと改行が有効なJSON構文ではないため SyntaxErrorがスローされます。まず csvToJson()関数でCSVをJavaScriptオブジェクトに変換する必要があります。この関数は内部的に JSON.stringify()を呼び出して実際のJSON文字列を生成します。CSVデータからのMap、Date、カスタムオブジェクトの処理
すべてのCSV変換がプレーンなオブジェクトのフラットな配列で終わるわけではありません。時にはヘッダーと値のペアからMapを構築したり、日付文字列をDateオブジェクトにパースしたり、シリアライズ前に計算されたプロパティを付加したりする必要があります。JavaScriptには人々を悩ませる特性があります:Mapインスタンスは JSON.stringify()でシリアライズされません。空のオブジェクトになってしまいます。修正方法は、文字列化する前にMapをプレーンなオブジェクトに変換する Object.fromEntries() です。
Mapが {} にシリアライズされる理由は、 JSON.stringify() がオブジェクト自身の列挙可能なプロパティを反復処理するためです。Mapはエントリを内部スロットに保持しており、オブジェクト自身の列挙可能なプロパティとしては保持していないため、シリアライザーはキーのないオブジェクトを見ます。Mapのプロトタイプには toJSON() メソッドもありません。これは JSON.stringify() がシリアライズ方法を決定する前に任意の値に対して最初に呼び出すフックです。値に toJSON() があれば、そのメソッドの戻り値がオブジェクト自身ではなくシリアライズされます。これが Date オブジェクトが正しくシリアライズされる理由です:Date.prototype.toJSON はISO 8601文字列を返すため、 JSON.stringify(new Date()) は空のオブジェクトではなくクォートされたタイムスタンプを生成します。このフックを理解することで、独自のクラスに同じ動作を定義できます — 下記の EmployeeRecord の例のように — 最終的なJSON出力にどのCSV派生フィールドを含めるかを正確に制御できます。
MapをJSONに変換する
// CSVのヘッダー→値のペアからMapを構築
const headers = ['server', 'port', 'region']
const values = ['payments-api', '9090', 'ap-south-1']
const rowMap = new Map(headers.map((h, i) => [h, values[i]]))
// Mapは直接シリアライズされない
console.log(JSON.stringify(rowMap))
// "{}" — 空のオブジェクト、データが失われた!
// 先にプレーンなオブジェクトに変換する
const rowObj = Object.fromEntries(rowMap)
console.log(JSON.stringify(rowObj, null, 2))
// {
// "server": "payments-api",
// "port": "9090",
// "region": "ap-south-1"
// }日付文字列とtoJSON()
CSVの日付フィールドは文字列として届きます。処理中にDateオブジェクトにパースすると、DateにはISO 8601文字列を返す組み込みの toJSON() メソッドがあるため、正しくシリアライズされます。独自のクラスにカスタムの toJSON() を定義して、シリアライズされた出力にどのCSV列を含めるかを制御することもできます — 例えば、 _rowIndex のような内部追跡フィールドを除外するなど。
class EmployeeRecord {
constructor(csvRow) {
this._rowIndex = csvRow._rowIndex // 内部用、JSONには含めない
this.employeeId = csvRow.employee_id
this.name = csvRow.name
this.hiredAt = new Date(csvRow.hired_date)
this.salary = Number(csvRow.salary)
}
toJSON() {
// JSON出力に含めたいフィールドのみを公開
return {
employee_id: this.employeeId,
name: this.name,
hired_at: this.hiredAt, // Date.toJSON() → 自動的にISO文字列
salary: this.salary,
}
}
}
const csvRow = {
_rowIndex: 42,
employee_id: 'EMP-2847',
name: '田中太郎',
hired_date: '2024-03-15',
salary: '128000',
}
const record = new EmployeeRecord(csvRow)
console.log(JSON.stringify(record, null, 2))
// {
// "employee_id": "EMP-2847",
// "name": "田中太郎",
// "hired_at": "2024-03-15T00:00:00.000Z",
// "salary": 128000
// }
// Note: _rowIndexは除外され、salaryは数値、dateはISO形式Dateのデシリアライズのためのreviver
CSV派生のJSONをファイルに書き込んだりネットワーク経由で送信した後、 JSON.parse() はプレーンなオブジェクトを返します — Dateオブジェクトは再び文字列になります。パース中にISO 8601文字列をDateオブジェクトに戻すにはreviver関数を使用します:
const jsonString = '{"employee_id":"EMP-2847","name":"田中太郎","hired_at":"2024-03-15T00:00:00.000Z","salary":128000}'
const isoDatePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
const parsed = JSON.parse(jsonString, (key, value) => {
if (typeof value === 'string' && isoDatePattern.test(value)) {
return new Date(value)
}
return value
})
console.log(parsed.hired_at instanceof Date) // true
console.log(parsed.hired_at.getFullYear()) // 2024JSON.stringify()は、関数、Symbol、または undefinedのプロパティを含む値に対して文字列ではなく undefinedを返します。CSV派生のオブジェクトが欠損した列から誤って undefinedの値を拾った場合、そのプロパティはJSON出力から静かに消えます。欠損値は常に nullをデフォルト値にしてください。JSON.stringify() パラメータリファレンス
関数シグネチャは JSON.stringify(value, replacer?, space?)です。 replacer と space 引数はどちらもオプションです — インデントだけが必要な場合はreplacerに null を渡してください。
JSON.parse() パラメータ:
JSON.parse() — JSON出力の消費
csvToJson()からJSON文字列が得られたら、通常の次のステップはフィルタリング、マッピング、またはAPIへの入力のために有効なJavaScript配列に戻してパースすることです。JSON文字列(typeof === "string")とJavaScriptオブジェクトの違いは重要です。文字列に対して .filter() を呼び出したり [0].name にアクセスしたりすることはできません — まず JSON.parse() が必要です。この往復(stringify後にparse)は検証技術としても機能します:CSV変換が有効なJSONではないものを生成した場合、parseがスローします。オプションの reviver引数を使用すると、パース中に各キーと値のペアを変換できます — ISOStringからDateオブジェクトを復元したり、別のパスなしにキーをリネームしたりするのに便利です。
const csv = `endpoint,method,avg_latency_ms,error_rate
/api/v2/orders,POST,342,0.02
/api/v2/health,GET,12,0.00
/api/v2/payments,POST,890,0.15
/api/v2/users,GET,45,0.01`
// ステップ1:CSVをJSON文字列に変換
const jsonString = csvToJson(csv, { coerce: true })
// ステップ2:JavaScript配列に戻してパース
const endpoints = JSON.parse(jsonString)
// 配列であることを確認
console.log(Array.isArray(endpoints)) // true
// レイテンシの高いエンドポイントをフィルタリング
const slow = endpoints.filter(ep => ep.avg_latency_ms > 200)
console.log(slow.map(ep => ep.endpoint))
// ["/api/v2/orders", "/api/v2/payments"]
// 最初の行を分割代入
const [first, ...rest] = endpoints
console.log(first.endpoint) // "/api/v2/orders"
console.log(rest.length) // 3ダウンストリーム処理の前に変換出力を検証する際には、 JSON.parse() のセーフラッパーが便利です。何らかの理由(入力の切り捨て、エンコードエラー)でCSV変換が不正なJSONを生成した場合、クラッシュせずにキャッチします:
function safeParse(jsonString) {
try {
return { data: JSON.parse(jsonString), error: null }
} catch (err) {
return { data: null, error: err.message }
}
}
// 有効な出力
const result = safeParse(csvToJson(csv))
if (result.error) {
console.error('CSV変換が無効なJSONを生成しました:', result.error)
} else {
console.log(`${result.data.length}行をパースしました`)
}
// 生のCSVをJSON.parseに誤って渡した場合 — これは失敗する
const bad = safeParse('name,email\n田中太郎,tanaka@nexuslabs.io')
console.log(bad.error) // "Unexpected token 'a', "name,email"... is not valid JSON"キーリネームと検証のためのreviver
reviver関数は、パース中にすべてのキーと値のペアを最も内側のプロパティから外側に向かって受け取ります。キーに対して undefined を返すと結果からそのキーが完全に削除されます;異なる値を返すとそれを置き換えます。reviverはヘッダーのリネーム(camelCaseからsnake_case)、内部フィールドの除去、または必須列の存在確認に便利です。ルート値は最後に(空文字列キーで)呼び出されます。ここで結果が配列でない場合にスローします。
const jsonString = csvToJson(`employeeId,firstName,hiredDate
EMP-2847,田中太郎,2024-03-15
EMP-3012,山田花子,2023-11-01`, { coerce: false })
const camelToSnake = str => str.replace(/[A-Z]/g, c => '_' + c.toLowerCase())
const employees = JSON.parse(jsonString, function(key, value) {
// ルート値 — 形状を検証
if (key === '') {
if (!Array.isArray(value)) throw new Error('Expected JSON array from CSV')
return value
}
// camelCaseのヘッダーキーをsnake_caseにリネーム
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
return Object.fromEntries(
Object.entries(value).map(([k, v]) => [camelToSnake(k), v])
)
}
return value
})
console.log(employees[0])
// { employee_id: 'EMP-2847', first_name: '田中太郎', hired_date: '2024-03-15' }ファイルとAPIレスポンスからのCSV変換
本番環境でCSVデータが実際に来る2つの場所:ディスク上のファイルとHTTPレスポンス。どちらのシナリオも入力が外部から制御されないため、エラーハンドリングが必要です。
CSVファイルの読み取り、変換、JSONの書き込み
import { readFileSync, writeFileSync } from 'node:fs'
function csvToJsonFromFile(inputPath, outputPath) {
let csvText
try {
csvText = readFileSync(inputPath, 'utf8')
} catch (err) {
throw new Error(`${inputPath}の読み取りに失敗しました: ${err.message}`)
}
const lines = csvText.trim().split('\n')
if (lines.length < 2) {
throw new Error(`${inputPath}にはデータ行がありません(${lines.length}行のみ)`)
}
const headers = lines[0].split(',').map(h => h.trim())
const rows = lines.slice(1)
.filter(line => line.trim() !== '')
.map(line => {
const values = line.split(',')
return Object.fromEntries(headers.map((h, i) => [h, values[i]?.trim()]))
})
const jsonOutput = JSON.stringify(rows, null, 2)
writeFileSync(outputPath, jsonOutput, 'utf8')
console.log(`${rows.length}行を変換しました → ${outputPath}`)
return rows
}
// 使用方法
const data = csvToJsonFromFile('inventory.csv', 'inventory.json')
console.log(data[0])
// { sku: "WDG-2847", warehouse: "us-east-1", quantity: "150", ... }APIエンドポイントからのCSVの取得
async function fetchCsvAsJson(url) {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
const contentType = response.headers.get('content-type') || ''
if (!contentType.includes('text/csv') && !contentType.includes('text/plain')) {
console.warn(`予期しないcontent-type: ${contentType}`)
}
const csvText = await response.text()
const lines = csvText.trim().split('\n')
const headers = lines[0].split(',').map(h => h.trim())
const rows = lines.slice(1)
.filter(line => line.trim() !== '')
.map(line => {
const values = line.split(',')
return Object.fromEntries(headers.map((h, i) => [h, values[i]?.trim()]))
})
return rows
}
// 例: データプロバイダーから為替レートCSVを取得
try {
const rates = await fetchCsvAsJson('https://data.ecb.internal/rates/daily.csv')
console.log(JSON.stringify(rates.slice(0, 3), null, 2))
// ダウンストリームサービスにJSONとして送信
await fetch('https://api.internal/v2/rates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data: rates }),
})
} catch (err) {
console.error('レート同期に失敗しました:', err.message)
}JSON.stringify()のreplacer引数を使うと、CSVから特定の列をホワイトリスト化できます。ヘッダー名の配列を渡してそれらのフィールドのみを含めます: JSON.stringify(rows, ['name', 'email', 'department'])。配列にないプロパティは出力から静かに除外されます。コマンドラインでのCSV to JSON変換
Node.jsはインラインスクリプトを実行でき、スクリプトを書かずにCSV-to-JSON変換を処理する専用のCLIツールもあります。
# CSVをNode.jsのインラインスクリプトにパイプ
cat servers.csv | node -e "
const lines = require('fs').readFileSync('/dev/stdin','utf8').trim().split('\n');
const h = lines[0].split(',');
const rows = lines.slice(1).map(l => Object.fromEntries(h.map((k,i) => [k.trim(), l.split(',')[i]?.trim()])));
console.log(JSON.stringify(rows, null, 2));
"# Millerは構造化データのスイスアーミーナイフ # インストール: brew install miller (macOS) または apt install miller (Debian/Ubuntu) mlr --icsv --ojson cat inventory.csv # 変換中に行をフィルタリング mlr --icsv --ojson filter '$quantity > 100' inventory.csv # 特定の列を選択 mlr --icsv --ojson cut -f sku,warehouse,quantity inventory.csv
# グローバルインストール npm install -g csvtojson # ファイルを変換 csvtojson servers.csv > servers.json # stdinからパイプ cat exports/q1-metrics.csv | csvtojson > q1-metrics.json
大きなファイルには、csvtojsonよりもMillerが通常より良い選択です。MillerはCで実装されており、ファイル全体をメモリに読み込まずにCSVをストリームとして処理します。つまり、数ギガバイトのエクスポートを一定のメモリ使用量で処理できます。また、データがJSONになる前にフィールドレベルの操作 — 列のリネーム、値の型変換、行のフィルタリング — をインプレースでサポートするため、2ステップのparse-then-transformパイプラインを回避できます。一方、csvtojsonはNode.jsで動作し、ツールチェーンの残りがJavaScriptの場合に便利です:その出力をNode.jsストリームに直接パイプしたり、ライブラリとしてインポートしたり、コード内でのカラムごとの型変換に colParser APIを使用したりできます。生のスループットとシェルパイプラインにはMillerを;Node.jsアプリケーションとの緊密な統合が必要な場合にはcsvtojsonを優先してください。
jqはCSVをネイティブにパースしません。パイプラインにjqが必要な場合は、まず csvtojsonまたは mlrでJSONに変換してから、フィルタリングと変換のためにJSON出力をjqにパイプしてください。高性能な代替手段 — PapaParse
手動の split(',') アプローチは実際のCSVファイルでは機能しません。カンマを含むクォートフィールド、埋め込まれた改行、エスケープされたダブルクォート — これらすべてが単純な分割を壊します。PapaParseは、CSVが未知のソースから来る場合に私が使用するライブラリです。すべてのRFC 4180エッジケースを処理し、デリミタを自動検出し、Node.jsとブラウザの両方で動作します。
npm install papaparse
import Papa from 'papaparse'
const csv = `product,description,price,in_stock
"Widget, Large","A premium widget with ""extra"" features",29.99,true
Bolt Assembly,Standard M8 bolt kit,4.50,true
"Gasket Set","Includes gasket, seal, and O-ring",12.75,false`
const { data, errors, meta } = Papa.parse(csv, {
header: true,
dynamicTyping: true, // 数値と真偽値を自動変換
skipEmptyLines: true,
transformHeader: h => h.trim().toLowerCase().replace(/\s+/g, '_'),
})
if (errors.length > 0) {
console.error('パースエラー:', errors)
}
console.log(JSON.stringify(data, null, 2))
// [
// {
// "product": "Widget, Large",
// "description": "A premium widget with \"extra\" features",
// "price": 29.99,
// "in_stock": true
// },
// ...
// ]
console.log(`${data.length}行をパースしました。デリミタ: "${meta.delimiter}"`)PapaParseの dynamicTyping オプションは型変換を自動的に行います — 数値は数値になり、"true"/"false"は真偽値になります。 transformHeader コールバックは列名をsnake_caseに正規化し、異なるCSVエクスポートからの一貫性のないヘッダーを処理する手間を省きます。パースコードを書かずに素早く変換するには、 CSV to JSONコンバーター がブラウザ内でこれをすべて処理します。
ターミナル出力のシンタックスハイライト
大きなJSON配列をターミナルにダンプすると、目がすぐに疲れます。出力にシンタックスハイライトを追加すると、デバッグや開発中に読みやすくなります。 cli-highlight パッケージはNode.jsターミナルでJSON出力に色を付けます。
npm install cli-highlight
import { highlight } from 'cli-highlight'
// CSVをJSON配列に変換した後
const jsonOutput = JSON.stringify(rows, null, 2)
// シンタックスハイライト付きで出力
console.log(highlight(jsonOutput, { language: 'json' }))
// キー、文字列、数値、真偽値がそれぞれ異なる色で表示される色付き出力は、大きな変換結果をインタラクティブに検査する際に真価を発揮します。JSONキー、文字列値、数値、真偽値がそれぞれ異なるANSIカラーで表示されるため、型が間違っているフィールドを見つけやすくなります — 例えば、強制変換がオフだったために文字列としてハイライトされているポート番号など。これは、行によって列の型が一貫していないスプレッドシートツールからエクスポートされたCSVファイルをデバッグする際に特に便利です。色なしでは、50行のJSONで誤った型のフィールドを1つ見つけるために各値を個別に読む必要があります。色があれば、文字列色の数値はすぐに目に入ります。
大きなCSVファイルの処理
500 MBのCSVファイルを readFileSync() で文字列に読み込むと、メモリを大量に消費しプロセスがクラッシュする可能性があります。大きなファイルには、CSVを1行ずつストリーミングしてJSONオブジェクトが届くたびに発行してください。 csv-parse パッケージ(npmのcsvエコシステムの一部)はNode.jsストリームで動作するストリーミングパーサーを提供します。
csv-parseでCSVをNDJSONにストリーミング
NDJSON(Newline-Delimited JSON)は、出力ファイルの各行が独立したJSONオブジェクトである形式です。ファイル全体がメモリ内にある必要がある単一の大きなJSON配列とは異なり、NDJSONファイルは1行ずつ処理できます。これにより、NDJSONはログプロセッサー、ストリームパイプライン、または一括インポートAPIを持つデータベースで消費される大きなデータセットに理想的です。 csv-parse パッケージはオブジェクトモードでCSVの1行につき1つのJavaScriptオブジェクトを発行するため、各 JSON.stringify(row)の後に \n を追加するトランスフォームストリームに直接パイプできます。
import { createReadStream, createWriteStream } from 'node:fs'
import { parse } from 'csv-parse'
import { Transform } from 'node:stream'
import { pipeline } from 'node:stream/promises'
// 各CSVの行オブジェクトをJSONの1行に変換
const toNdjson = new Transform({
objectMode: true,
transform(record, encoding, callback) {
callback(null, JSON.stringify(record) + '\n')
},
})
await pipeline(
createReadStream('telemetry-2026-03.csv'),
parse({
columns: true, // 最初の行をヘッダーとして使用
skip_empty_lines: true,
trim: true,
cast: true, // 数値と真偽値を自動変換
}),
toNdjson,
createWriteStream('telemetry-2026-03.ndjson')
)
console.log('ストリーミング変換が完了しました')
// 出力ファイルの各行が1つのJSONオブジェクト:
// {"timestamp":"2026-03-15T08:22:00Z","service":"gateway","latency_ms":42,"status":200}
// {"timestamp":"2026-03-15T08:22:01Z","service":"auth","latency_ms":156,"status":401}
// ...ブラウザとNode.jsでのPapaParseストリーミング
PapaParseのストリーミングモードは、すべての行をメモリに収集するのではなく、1行ごとに発火する step コールバックを使用します。Node.jsのReadStream(Node.jsの場合)または File オブジェクト(ブラウザの場合)を渡すと、PapaParseは内部でチャンク処理を行います。ストリームパイプラインの配線は不要 — コールバックだけで済みます。csv-parseを導入せずにRFC 4180準拠が必要な場合に使用してください。
import Papa from 'papaparse'
import { createReadStream } from 'node:fs'
let rowCount = 0
const errors = []
const fileStream = createReadStream('warehouse-inventory.csv')
Papa.parse(fileStream, {
header: true,
dynamicTyping: true,
step(result) {
// 一度に1行を処理 — 一定のメモリ使用量
rowCount++
if (result.data.quantity === 0) {
errors.push(`行 ${rowCount}: ${result.data.sku} は在庫切れです`)
}
},
complete() {
console.log(`${rowCount}行を処理しました`)
if (errors.length > 0) {
console.log(`問題が見つかりました: ${errors.length}件`)
errors.forEach(e => console.log(` ${e}`))
}
},
error(err) {
console.error('パースに失敗しました:', err.message)
},
})よくある間違い
問題: CSVはJSONではありません。生のCSV文字列をJSON.parse()に渡すと、カンマと改行が有効なJSON構文ではないためSyntaxErrorがスローされます。
修正: まずsplit()またはライブラリを使ってCSVをJavaScriptオブジェクトにパースし、次にJSON.stringify()を使ってJSONを生成してください。JSON.parse()はすでに有効なJSON文字列にのみ呼び出してください。
const csv = 'name,email\n田中太郎,tanaka@nexuslabs.io' const data = JSON.parse(csv) // SyntaxError: Unexpected token 'a'
const csv = 'name,email\n田中太郎,tanaka@nexuslabs.io'
const lines = csv.trim().split('\n')
const headers = lines[0].split(',')
const rows = lines.slice(1).map(line =>
Object.fromEntries(headers.map((h, i) => [h, line.split(',')[i]]))
)
const json = JSON.stringify(rows, null, 2) // 有効なJSON文字列問題: JavaScriptオブジェクトに対してtoString()を呼び出すと、実際のデータの代わりに役に立たない文字列'[object Object]'が返されます。これにより、CSVからJSONへの出力が静かに破壊されます。
修正: JavaScriptオブジェクトをJSON文字列に変換するには常にJSON.stringify()を使用してください。toString()はプリミティブから文字列への変換のためのものであり、シリアライズのためのものではありません。
const row = { server: 'api-gateway', port: 8080 }
const output = row.toString()
// "[object Object]" — データが消えたconst row = { server: 'api-gateway', port: 8080 }
const output = JSON.stringify(row, null, 2)
// '{"server":"api-gateway","port":8080}'問題: 単純なsplit(",")は、CSV値にクォートフィールド内のカンマが含まれている場合に機能しません:"Widget, Large"は1つの値ではなく2つの別々の値になってしまいます。
修正: 完全に制御できないCSVデータにはPapaParseまたはcsv-parseを使用してください。手動でパースしなければならない場合は、現在の位置がクォートフィールド内にあるかどうかを追跡するステートマシンパーサーを実装してください。
const line = '"Widget, Large","Premium quality",29.99'
const values = line.split(',')
// ["\"Widget", " Large\"", "\"Premium quality\"", "29.99"]
// 4つの値(3つの代わりに)— 最初のフィールドが誤って分割されたimport Papa from 'papaparse'
const { data } = Papa.parse('"Widget, Large","Premium quality",29.99')
// data[0] = ["Widget, Large", "Premium quality", "29.99"]
// 3つの値、正しくパースされた問題: 型変換なしでは、port: "8080"はJSONで数値ではなく文字列のままになります。数値型を期待するダウンストリームシステムがデータを拒否または誤って処理します。
修正: マッピングのステップで明示的な型変換を適用するか、dynamicTyping: trueでPapaParseを使用してください。どのフィールドを数値にすべきかについて常に意図的にしてください。
const row = { port: '8443', debug: 'true', workers: '4' }
JSON.stringify(row)
// {"port":"8443","debug":"true","workers":"4"} — すべて文字列const row = {
port: Number('8443'), // 8443
debug: 'true' === 'true', // true
workers: Number('4'), // 4
}
JSON.stringify(row)
// {"port":8443,"debug":true,"workers":4} — 正しい型手動パース vs ライブラリ — 簡単な比較
CSVフォーマットを制御できてクォートフィールドがないことがわかっている簡単なスクリプトには、組み込みの split() + JSON.stringify() のアプローチが機能し、依存関係ゼロです。ユーザーがアップロードしたCSVファイルを処理する本番システムには、ブラウザではPapaParseを、Node.jsでは csv-parse を使用してください — どちらもRFC 4180を正しく処理し、ストリーミングをサポートします。 csvtojson パッケージは、パースとシリアライズを1回の呼び出しで処理してJSONを直接出力する唯一のパッケージです。貼り付けから結果までの最速のパスが必要な場合は、 CSV to JSONコンバーターがブラウザ内でセットアップなしにすべてを処理します。
よくある質問
ライブラリを使わずにJavaScriptでCSVをJSONに変換するにはどうすればよいですか?
CSV文字列を改行で分割して行を取得し、split(",")で最初の行からヘッダーを抽出します。その後、残りの行をヘッダーをキーとするオブジェクトにマッピングします。最後にJSON.stringify(array, null, 2)でフォーマットされたJSON文字列を生成します。このアプローチは、フォーマットを制御できておりクォートフィールドや埋め込みカンマがないシンプルなCSVファイルに適しています。スプレッドシートのエクスポートやサードパーティシステムからのデータの場合、クォートフィールドや複数行の値によって単純な分割が壊れます — そのような場合はPapaParseやcsv-parseを使用してください。ブラウザで処理する非常に小さいファイルには、この依存関係ゼロのアプローチが最適です。
const csv = `name,email,department
田中太郎,tanaka@nexuslabs.io,Engineering
山田花子,yamada@nexuslabs.io,Product`
const lines = csv.trim().split('\n')
const headers = lines[0].split(',')
const rows = lines.slice(1).map(line => {
const values = line.split(',')
return Object.fromEntries(headers.map((h, i) => [h.trim(), values[i]?.trim()]))
})
console.log(JSON.stringify(rows, null, 2))
// [
// { "name": "田中太郎", "email": "tanaka@nexuslabs.io", "department": "Engineering" },
// { "name": "山田花子", "email": "yamada@nexuslabs.io", "department": "Product" }
// ]オブジェクトに対してJSON.stringify()とtoString()の違いは何ですか?
プレーンなオブジェクトに対してtoString()を呼び出すと、"[object Object]"という役に立たない文字列が返されます — 実際のデータについては何も示されません。JSON.stringify()は、ネストされたオブジェクトや配列を含む、すべてのキーと値が適切にシリアライズされた有効なJSON文字列を生成します。CSVから派生した行のようなJavaScriptオブジェクトをJSON文字列に変換する場合は常にJSON.stringify()を使用してください。逆の操作 — JSON文字列から有効なJavaScriptオブジェクトを再構築する — にはJSON.parse()を使用します。これはJSON.stringify()の正確な逆操作です。この2つの関数が完全な往復変換を形成します:データを保持または転送するためにstringify、消費するためにparseを使います。
const row = { name: '田中太郎', role: 'Engineer' }
console.log(row.toString()) // "[object Object]"
console.log(JSON.stringify(row)) // '{"name":"田中太郎","role":"Engineer"}'カンマやクォートを含むCSVフィールドはどのように扱えばよいですか?
RFC 4180では、カンマ、改行、またはダブルクォートを含むフィールドはダブルクォートで囲み、埋め込まれたクォートは二重化してエスケープする(クォートフィールド内の""は単一のリテラルクォートを表す)と規定しています。手動のsplit(",")はこれらのファイルで機能しません — フィールドの境界がもはや単純なカンマではないからです。本番データにはPapaParseまたはcsv-parseを使用するか、クォートフィールド内にいるかどうかを追跡するステートマシンパーサーを作成してください。正しいステートマシンをゼロから書くのは意外と難しいです:フィールドの先頭にあるクォート、フィールド中のエスケープされたクォート、クォートフィールド内の改行、異なる行末規則(CRLFとLF)を処理する必要があります。単純なCSVデータ以外では、十分にテストされたライブラリを使用してください。
// PapaParseはRFC 4180を正しく処理します
import Papa from 'papaparse'
const csv = `product,description,price
"Widget, Large","A big ""widget""",29.99
Bolt,Standard bolt,1.50`
const { data } = Papa.parse(csv, { header: true })
console.log(JSON.stringify(data, null, 2))
// descriptionフィールドは正しく: A big "widget" を含むブラウザで直接CSVをJSONに変換できますか?
はい。JSON.stringify()とJSON.parse()はすべてのブラウザエンジンに組み込まれています。CSVパースのステップでは、シンプルなファイルには改行とカンマで分割するか、CDN経由でPapaParseを含めることができます(Node.jsの依存関係はありません)。変換全体がクライアントサイドで行われ、サーバーへの往復は不要です。これはファイルのプライバシーに有用です — 生のCSVデータはユーザーのマシンから外に出ることはありません。ユーザーが<input type="file">要素でCSVファイルをアップロードすると、File APIのfile.text()メソッドがファイルの内容を文字列として解決するPromiseを返し、それを変換関数に渡すことができます。これにより、バックエンドなしで完全にブラウザ内でのCSV-to-JSON変換が実用的になります。
// ブラウザ: ユーザーがCSVファイルをアップロード
const fileInput = document.querySelector('input[type="file"]')
fileInput.addEventListener('change', async (event) => {
const file = event.target.files[0]
const text = await file.text()
const lines = text.trim().split('\n')
const headers = lines[0].split(',')
const rows = lines.slice(1).map(line =>
Object.fromEntries(headers.map((h, i) => [h.trim(), line.split(',')[i]?.trim()]))
)
console.log(JSON.stringify(rows, null, 2))
})CSVからすべてを文字列のまま保持するのではなく、数値や真偽値としてパースするにはどうすればよいですか?
CSVには型システムがありません — すべてのフィールドは文字列です。マッピングのステップで値を強制変換する必要があります。Number()やparseFloat()で数値パターンを確認し、"true"/"false"を真偽値に変換し、空文字列はnullとして処理してください。数値に見えるが文字列のままにしておく必要があるフィールドには注意してください:"07302"のような郵便番号はNumber()で強制変換すると先頭のゼロが失われますし、数字を含む電話番号や製品コードも同様に壊れやすいです。csvtojsonライブラリはcolParserオプションによる自動型変換をサポートしており、列ごとの変換関数を指定して問題のある列の自動検出をオーバーライドできます。PapaParseのdynamicTypingオプションは同じ変換をすべての列に対してグローバルに適用します。
function coerceValue(val) {
if (val === '') return null
if (val === 'true') return true
if (val === 'false') return false
const num = Number(val)
if (!isNaN(num) && val.trim() !== '') return num
return val
}
// CSVからオブジェクトへのマッピング時に適用
const row = { port: coerceValue('8443'), debug: coerceValue('true'), host: coerceValue('api.internal') }
// { port: 8443, debug: true, host: "api.internal" }Node.jsでCSV-to-JSONの出力をファイルに書き込むにはどうすればよいですか?
JSON.stringify()の出力とともにfs.writeFileSync()を使用します。JSON.stringifyの第3引数でインデントを制御します — 2スペースのインデントには2を、タブには"\t"を渡します。ファイルを読み戻すには、JSON.parse(fs.readFileSync(path, "utf8"))を使用します。これにより有効なJavaScriptオブジェクトの配列が再構築されます。非同期コンテキスト(async関数またはESモジュールのトップレベル)でファイルを書き込む場合は、ファイル書き込みの完了中にイベントループをブロックしないようfs.promises.writeFile()を優先してください。数MBを超える大きなJSON出力の場合は、書き込み前にメモリ上で文字列全体を構築するのではなく、JSONストリームをWriteStreamにパイプすることを検討してください。
import { writeFileSync, readFileSync } from 'node:fs'
// 書き込み
const jsonOutput = JSON.stringify(rows, null, 2)
writeFileSync('employees.json', jsonOutput, 'utf8')
// 読み戻し
const parsed = JSON.parse(readFileSync('employees.json', 'utf8'))
console.log(Array.isArray(parsed)) // true
console.log(parsed[0].name) // "田中太郎"関連ツール
Alex is a front-end and Node.js developer with extensive experience building web applications and developer tooling. He is passionate about web standards, browser APIs, and the JavaScript ecosystem. In his spare time he contributes to open-source projects and writes about modern JavaScript patterns, performance optimisation, and everything related to the web platform.
Sophie is a full-stack developer focused on TypeScript across the entire stack — from React frontends to Express and Fastify backends. She has a particular interest in type-safe API design, runtime validation, and the patterns that make large JavaScript codebases stay manageable. She writes about TypeScript idioms, Node.js internals, and the ever-evolving JavaScript module ecosystem.