何をしたいユーティリティか:「CSV エスケープ」
ここで作りたいのは、「任意の値を“CSV の 1 セルとして安全な文字列”に変換する関数」です。
CSV はただのカンマ区切りではなく、
「カンマ」「改行」「ダブルクォート」が入っているときに、正しくエスケープしないと壊れます。
だから、業務では
csvEscape("山田太郎"); // そのまま "山田太郎"
csvEscape("1,234"); // "\"1,234\""
csvEscape("A\nB"); // "\"A\nB\""
csvEscape("He said \"Hi\""); // "\"He said \"\"Hi\"\"\""
JavaScriptのように、「CSV として安全な形」に変換するユーティリティが必須になります。
CSV のルールをざっくり理解する
どんなときにダブルクォートで囲む必要があるか
CSV の基本ルール(RFC4180 ベースでざっくり)を、必要なところだけ押さえます。
次のどれかが含まれているフィールドは、ダブルクォートで囲む必要があります。
- カンマ
, - 改行(
\nや\r\n) - ダブルクォート
"
逆に言うと、何も特殊文字がなければ、そのまま出してよいです。
ダブルクォートはどうエスケープするか
フィールド内にダブルクォート " がある場合、
CSV では ""(2 つ連続)に置き換える ルールになっています。
例:
元の文字列:He said "Hi"
CSV フィールド内:"He said ""Hi"""
つまり、
- 文字列中の
"を""に置き換える - フィールド全体を
"で囲む
という二段構えです。
シンプルな csvEscape 関数を作る
実装コード
function csvEscape(value) {
if (value == null) {
return "";
}
const str = String(value);
const needsQuote =
str.includes(",") ||
str.includes("\n") ||
str.includes("\r") ||
str.includes('"');
if (!needsQuote) {
return str;
}
const escaped = str.replace(/"/g, '""');
return `"${escaped}"`;
}
JavaScript重要なポイントをかみ砕いて説明する
null / undefined の扱い
if (value == null) {
return "";
}
JavaScriptCSV では、空セルを表現したいことがよくあります。
ここでは null や undefined を「空文字」として扱い、a,,c のように「何もないセル」として出力できるようにしています。
まずは文字列に変換する
const str = String(value);
JavaScript数値でも日付でも、CSV に出すときは最終的に文字列です。
ここで一度文字列にしてしまえば、あとは「文字列としてのルール」だけを考えればよくなります。
「囲む必要があるか」を判定する
const needsQuote =
str.includes(",") ||
str.includes("\n") ||
str.includes("\r") ||
str.includes('"');
JavaScriptカンマ・改行・ダブルクォートのどれかが含まれていれば、
そのフィールドはダブルクォートで囲む必要があります。
ここで大事なのは、「何でもかんでも囲う必要はない」ということです。
シンプルな値("123", "山田太郎" など)は、そのまま出した方が読みやすいことも多いです。
ダブルクォートを "" に置き換える
const escaped = str.replace(/"/g, '""');
JavaScriptここが CSV エスケープの核心です。
" → "" に置き換えることで、
フィールド内のダブルクォートを「文字としての "」として扱えるようにします。
最後に全体を " で囲む
return `"${escaped}"`;
JavaScriptこれで、CSV として安全な 1 フィールドが完成します。
実際の動きを例で確認する
特殊文字なし
csvEscape("山田太郎"); // "山田太郎"
csvEscape(123); // "123"
csvEscape("ABC123"); // "ABC123"
JavaScriptカンマ・改行・ダブルクォートがないので、そのまま返っています。
カンマを含む場合
csvEscape("1,234"); // "\"1,234\""
csvEscape("A,B,C"); // "\"A,B,C\""
JavaScriptカンマがあるので、ダブルクォートで囲まれます。
実際の CSV 行としては、例えば
ID,Name,Amount
1,山田太郎,"1,234"
のような形になります。
改行を含む場合
csvEscape("A\nB"); // "\"A\nB\""
csvEscape("行1\r\n行2"); // "\"行1\r\n行2\""
JavaScript改行があるので、ダブルクォートで囲まれます。
Excel などで開くと、1 セルの中に改行として表示されます。
ダブルクォートを含む場合
csvEscape('He said "Hi"');
// "\"He said \"\"Hi\"\"\""
JavaScript中身だけ見ると、
元の文字列:He said "Hi"
エスケープ後:"He said ""Hi"""
となっています。
行全体を CSV にするユーティリティ
配列を 1 行の CSV にする
csvEscape が 1 セル分を担当するので、
行全体は「配列をエスケープしてカンマで join」すれば作れます。
function toCsvRow(values) {
return values.map(csvEscape).join(",");
}
JavaScript使い方はこんな感じです。
const row = toCsvRow([
1,
"山田太郎",
"1,234",
'He said "Hi"',
"A\nB",
]);
// 結果(見やすく改行)
// 1,山田太郎,"1,234","He said ""Hi""","A
// B"
JavaScriptこれをそのままファイルに書き出せば、
Excel などで正しく 1 行として読み込めます。
実務で意識してほしい設計のポイント
一つ目は、「CSV エスケープは絶対に自前で雑に書かない」ということです。value.replace(",", "\\,") のような「なんちゃってエスケープ」は、
改行やダブルクォートを扱えず、すぐに壊れます。
「CSV に出すときは必ず csvEscape を通す」
「行を作るときは必ず toCsvRow を通す」
というルールにしておくと、事故が激減します。
二つ目は、「null や undefined の扱いをチームで決める」ことです。
空セルにするのか、"0" にするのか、"N/A" にするのか——ここは業務仕様です。
ユーティリティの中で一貫したルールにしておけば、画面ごとに挙動が変わることを防げます。
三つ目は、「入力側(CSV を読む側)のルールもセットで考える」ことです。
今回は「書き出し」の話をしましたが、読み込み側も
「ダブルクォートで囲まれたフィールド」「"" の扱い」などを正しく実装する必要があります。
書き出しと読み込みのルールが揃っていると、往復しても壊れない CSV になります。
少し手を動かして感覚をつかむ
コンソールで、次のようなコードを実際に打ってみてください。
csvEscape("山田太郎");
csvEscape("1,234");
csvEscape("A\nB");
csvEscape('He said "Hi"');
toCsvRow([1, "山田太郎", "1,234", 'He said "Hi"', "A\nB"]);
JavaScript返ってきた文字列をコピーして、
実際に .csv ファイルに貼り付けて保存し、Excel で開いてみてください。
「セルが崩れない」「改行やダブルクォートがちゃんと見える」
という体験ができたら、それが “正しい CSV エスケープができている” という証拠です。
そのうえで、自分のプロジェクトに
export function csvEscape(...) { ... }
export function toCsvRow(...) { ... }
JavaScriptを置き、
「CSV に出すときは必ずここを通す」
というルールを作ってみてください。
それだけで、あなたの CSV 出力は、場当たり的な文字列連結から、
業務で安心して使える「堅牢な CSV エスケープユーティリティ」に変わっていきます。
