JavaScript Tips | 文字列ユーティリティ:生成 - CSV 行生成

JavaScript JavaScript
スポンサーリンク

「CSV 行生成」でやりたいことをはっきりさせる

ここでのゴールは、「配列やオブジェクトの値から、正しい形式の1行分の CSV 文字列を生成するユーティリティ」を作ることです。

見た目はただのカンマ区切りですが、実務でちゃんとやろうとすると、必ずぶつかるポイントがあります。

  • 値の中にカンマが入っている
  • 値の中にダブルクォート " が入っている
  • 値の中に改行が入っている
  • null や undefined をどう扱うか

ここを雑にやると、「Excel で開いたら列がズレる」「途中で行が分割される」といった事故になります。
なので、「CSV の最低限のルール」を押さえたうえで、ユーティリティに落としていきます。


CSV の最低限のルールをざっくり理解する

1セルの値にカンマ・改行・ダブルクォートがある場合

CSV では、「問題を起こしそうな値」はダブルクォートで囲むというルールがあります。

特に重要なのはこの3つです。

  • 値にカンマ , が含まれている
  • 値に改行(\n\r\n)が含まれている
  • 値にダブルクォート " が含まれている

このどれかが含まれている場合、その値は "..." で囲みます。
さらに、値の中の """(2つ連続)にエスケープします。

例をいくつか見てみます。

元の値:HelloHello(そのまま)
元の値: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 ユーティリティ”に一段レベルアップします。

タイトルとURLをコピーしました