「CSV 行生成」でやりたいことをはっきりさせる
ここでのゴールは、「配列やオブジェクトの値から、正しい形式の1行分の CSV 文字列を生成するユーティリティ」を作ることです。
見た目はただのカンマ区切りですが、実務でちゃんとやろうとすると、必ずぶつかるポイントがあります。
- 値の中にカンマが入っている
- 値の中にダブルクォート
"が入っている - 値の中に改行が入っている
- null や undefined をどう扱うか
ここを雑にやると、「Excel で開いたら列がズレる」「途中で行が分割される」といった事故になります。
なので、「CSV の最低限のルール」を押さえたうえで、ユーティリティに落としていきます。
CSV の最低限のルールをざっくり理解する
1セルの値にカンマ・改行・ダブルクォートがある場合
CSV では、「問題を起こしそうな値」はダブルクォートで囲むというルールがあります。
特に重要なのはこの3つです。
- 値にカンマ
,が含まれている - 値に改行(
\nや\r\n)が含まれている - 値にダブルクォート
"が含まれている
このどれかが含まれている場合、その値は "..." で囲みます。
さらに、値の中の " は ""(2つ連続)にエスケープします。
例をいくつか見てみます。
元の値:Hello → Hello(そのまま)
元の値:Hello,World → "Hello,World"(カンマがあるので囲む)
元の値:He said "Hi" → "He said ""Hi"""(ダブルクォートを2つに)
このルールさえ守れば、Excel でも他のツールでも、かなり安定して読み書きできます。
1セル分の値を CSV 用にエスケープする関数
基本の escapeCsvValue
まずは、「1つの値を“CSV セル用の文字列”に変換する」小さな関数を作ります。
function escapeCsvValue(value) {
if (value == null) {
return "";
}
const s = String(value);
const needsQuote =
s.includes(",") ||
s.includes("\n") ||
s.includes("\r") ||
s.includes('"');
if (!needsQuote) {
return s;
}
const escaped = s.replace(/"/g, '""');
return `"${escaped}"`;
}
JavaScriptここが重要ポイントです。
- null / undefined は空文字として扱う(CSV では「空セル」になるイメージ)。
- 文字列化してから判定するので、数値や boolean も扱える。
- カンマ・改行・ダブルクォートのどれかが含まれていたら、ダブルクォートで囲む。
- 中の
"は""に置き換える(これが CSV のエスケープルール)。
動きのイメージはこうです。
escapeCsvValue("Hello"); // "Hello"
escapeCsvValue("Hello,World"); // "\"Hello,World\""
escapeCsvValue('He said "Hi"'); // "\"He said \"\"Hi\"\"\""
escapeCsvValue(123); // "123"
escapeCsvValue(null); // ""
JavaScriptこの関数が CSV 行生成の“心臓部”になります。
配列から1行分の CSV を生成する
values → CSV 行文字列
次に、「配列の各要素をセルとみなして、1行分の CSV 文字列を作る」関数です。
function toCsvRow(values) {
if (!Array.isArray(values)) {
throw new Error("values must be an array");
}
const escapedValues = values.map((v) => escapeCsvValue(v));
return escapedValues.join(",");
}
JavaScriptやっていることはシンプルです。
- 配列であることを前提にする(そうでなければエラー)。
- 各要素を
escapeCsvValueに通して、「CSV セルとして安全な文字列」に変換。 - それらをカンマで連結して、1行分の CSV にする。
実際の動きはこうなります。
toCsvRow(["id", "name", "note"]);
// "id,name,note"
toCsvRow([1, "山田太郎", "メモなし"]);
// "1,山田太郎,メモなし"
toCsvRow([2, "田中,花子", "カンマを含む"]);
// "2,\"田中,花子\",カンマを含む"
toCsvRow([3, 'He said "Hi"', "メモ"]);
// "3,\"He said \"\"Hi\"\"\",メモ"
JavaScriptこれで、「1行分の CSV 行生成ユーティリティ」が完成します。
オブジェクトから CSV 行を生成する
カラム順を指定してオブジェクト → 行にする
業務では、「オブジェクトの配列を CSV にしたい」ことが多いです。
const row = {
id: 1,
name: "山田太郎",
note: "メモ",
};
JavaScriptこのとき、「カラムの順番」を決めておいて、その順に値を取り出して CSV 行を作るのが定番です。
function toCsvRowFromObject(obj, columns) {
const values = columns.map((key) => obj[key]);
return toCsvRow(values);
}
JavaScript使い方はこうです。
const columns = ["id", "name", "note"];
const row = { id: 1, name: "山田太郎", note: "メモ" };
toCsvRowFromObject(row, columns);
// "1,山田太郎,メモ"
JavaScriptこの形にしておくと、
- カラム順を
columnsで一元管理できる - ヘッダー行も
columnsから簡単に作れる
というメリットがあります。
実務での使いどころと設計のポイント
「エスケープのルール」をユーティリティに閉じ込める
CSV の怖いところは、「一見動いているように見えて、特定の値でだけ壊れる」ことです。
- たまたまカンマを含む名前が来たときだけ列がズレる
- たまたま改行を含むメモが来たときだけ行が増える
これを防ぐには、「CSV のエスケープルール」を一箇所に閉じ込めて、
「CSV に出したいときは必ず escapeCsvValue / toCsvRow を通す」というルールにしておくのが一番強いです。
画面ごとに value.replace(",", "") みたいな場当たり対応をし始めると、
必ずどこかで漏れます。
改行コードは「行の区切り」として扱う
ここで作っているのは「1行分の CSV 文字列」です。
実際にファイルとして出すときは、行ごとに \r\n などで区切っていきます。
const rows = [
toCsvRow(["id", "name", "note"]),
toCsvRow([1, "山田太郎", "メモ"]),
toCsvRow([2, "田中,花子", "カンマを含む"]),
];
const csv = rows.join("\r\n");
JavaScript「セルの中の改行」は escapeCsvValue が面倒を見てくれるので、
行の区切りは素直に \r\n で統一しておくと、Excel などでも扱いやすくなります。
ちょっとだけ手を動かしてみる
コンソールで、次のあたりを試してみてください。
escapeCsvValue("Hello,World");
escapeCsvValue('He said "Hi"');
escapeCsvValue("複数行\nのメモ");
toCsvRow([1, "田中,花子", 'He said "Hi"']);
toCsvRowFromObject(
{ id: 1, name: "山田太郎", note: "メモ" },
["id", "name", "note"]
);
JavaScript「どの値がダブルクォートで囲まれ、" がどうエスケープされるか」を目で確認してみてください。
そのうえで、自分のプロジェクトに
export function escapeCsvValue(...) { ... }
export function toCsvRow(...) { ... }
export function toCsvRowFromObject(...) { ... }
JavaScriptの3本を置いて、
- 生の値 →
escapeCsvValue - 配列 →
toCsvRow - オブジェクト →
toCsvRowFromObject
という流れをルール化してみてください。
それだけで、あなたの「CSV 行生成」は
場当たり的な join(",") から、
Excel や他システムにも耐えられる“業務レベルの CSV ユーティリティ”に一段レベルアップします。

