カンマ区切りの文字列を、扱いやすいオブジェクトの配列に変える処理(パース)は、TypeScriptやJavaScriptでよく書く処理の1つです。split、filter、map を . でつなげると短く書けますが、慣れないうちは途中でデータがどうなっているのかが見えにくいところです。
私はこの書き方をTypeScriptの入門書(紹介記事)で知りました。組み込みのC言語なら、strtok で区切って atoi で数値にし、構造体の配列に詰めていくところです。その流れと比べられるように、この記事では短いコードを一度 for文の書き方に戻し、途中のデータの変化を追います。
コードは、ブラウザでTypeScriptを動かせるTypeScript Playgroundに貼れば、そのまま試せます。
今回のお題:商品の一覧を配列にする
「商品名,価格,在庫(1ならあり、0ならなし)」の形で、1行に1商品ずつ書いた文字列があるとします。これを Product 型の配列に変えます。
type Product = {
name: string;
price: number;
inStock: boolean;
};
const data = `
りんご,120,1
みかん,80,0
ぶどう,300,1
`;バッククォートで囲んだ文字列(テンプレートリテラル)は、改行をそのまま含められます。最初のバッククォートの直後と、最後の行の後ろにも改行があるのが、後で効いてきます。テンプレートリテラルについては「文字列のクォートの違いとテンプレートリテラル」で紹介しています。
短く書いたコード
まず、今回読み解く短いコードです。
const products: Product[] = data
.split("\n")
.filter(line => line !== "")
.map(line => {
const [name, priceText, stockText] = line.split(",");
return {
name,
price: Number(priceText),
inStock: stockText === "1",
};
});これで products には、商品3つ分のオブジェクトが入ります。このコードが何をしているかを、for文に戻して確かめます。
for文に戻したコード
同じ処理を、for...of で1行ずつ処理する形に書き直します。手順に番号を付けました。
// 【準備】結果を入れる空の配列を用意する
const products: Product[] = [];
// 【手順1】改行で区切って、行の配列にする
const lines = data.split("\n");
// 【手順2】行を1つずつ取り出す
for (const line of lines) {
// 【手順3】空の行は飛ばす
if (line === "") {
continue;
}
// 【手順4】カンマで区切って、項目ごとに取り出す
const [name, priceText, stockText] = line.split(",");
// 【手順5】型を変換してオブジェクトを作り、配列に追加する
products.push({
name,
price: Number(priceText),
inStock: stockText === "1",
});
}C言語に近い、「空の配列を用意して、1行ずつ調べて、条件に合うものを追加していく」書き方です。const [name, priceText, stockText] = ... は分割代入という書き方で、配列の1番目・2番目・3番目をそれぞれの変数に入れています。
短いコードとの対応と、データの変化
for文の各手順が、短いコードのどこに当たるかを、途中のデータと一緒に並べます。
const products: Product[] = data
// 【手順1】改行で区切る(for文の lines = data.split("\n") と同じ)
.split("\n")
// → ["", "りんご,120,1", "みかん,80,0", "ぶどう,300,1", ""]
// 最初と最後の "" は、` の直後と最後の改行からできた空の行
// 【手順3】空の行を取り除く(for文の if (line === "") continue と同じ)
.filter(line => line !== "")
// → ["りんご,120,1", "みかん,80,0", "ぶどう,300,1"]
// 【手順2・5】行を1つずつオブジェクトに変えて、新しい配列を作る
// (for文のループと push に当たる)
.map(line => {
// 【手順4】カンマで区切る
const [name, priceText, stockText] = line.split(",");
// 例:"りんご,120,1" → ["りんご", "120", "1"]
// 【手順5】型を変換してオブジェクトを作る
return {
name,
price: Number(priceText), // "120" → 120(文字列から数値へ)
inStock: stockText === "1", // "1" → true、"0" → false
};
});
// → [
// { name: "りんご", price: 120, inStock: true },
// { name: "みかん", price: 80, inStock: false },
// { name: "ぶどう", price: 300, inStock: true }
// ]対応をまとめると、次のようになります。
| for文の書き方 | 短いコード | やっていること |
|---|---|---|
const lines = data.split("\n") | .split("\n") | 改行で区切って、行の配列にする |
if (line === "") continue; | .filter(line => line !== "") | 空の行を取り除く |
for (const line of lines) と products.push(...) | .map(line => { ... }) | 1行ずつ変換して、新しい配列を作る |
const products: Product[] = [] | (なし) | map が新しい配列を作って返すので、空の配列はいらない |
filter は「条件に合う要素だけ残した配列」、map は「要素をそれぞれ変換した配列」を作ります。filter の詳しい書き方は「filterメソッドの書き方」、line => ... の書き方は「アロー関数(=>)とは?」で紹介しています。
配列にしたデータを使う
配列にしておくと、あとの処理が書きやすくなります。商品ごとに在庫の有無を表示してみます。
for (const product of products) {
const stock = product.inStock ? "在庫あり" : "在庫なし";
console.log(`${product.name}(${product.price}円):${stock}`);
}
// りんご(120円):在庫あり
// みかん(80円):在庫なし
// ぶどう(300円):在庫あり型を付けておくと、書き間違いに気づける
const products: Product[] の : Product[] は「この変数には Product の配列が入る」という宣言です。実行したときの動きは変わりませんが、map の中で作るオブジェクトの形が Product と合わないと、コンパイルのときにエラーで教えてくれます。
const products: Product[] = data
.split("\n")
.filter(line => line !== "")
.map(line => {
const [name, priceText, stockText] = line.split(",");
return {
name,
price: Number(priceText),
instock: stockText === "1", // inStock の S を小文字にした書き間違い → コンパイルエラー
};
});プロパティ名の小さな書き間違いも、実行する前に見つかります。C言語で構造体のメンバー名を間違えるとコンパイルエラーになるのと似た安心感があります。
実際のデータで気をつけたいこと
Windowsで作ったデータは改行が \r\n
Windowsで作ったテキストファイルやCSVは、改行が \r\n になっていることがあります。split("\n") だけだと各行の最後に \r が残り、最後の項目が "1\r" になるので、stockText === "1" が false になってしまいます。エラーにはならず、結果だけが間違うので気づきにくい不具合です。
// \r\n と \n のどちらでも区切れるように、正規表現で指定する
const lines = data.split(/\r?\n/);数値にできないときは NaN になる
Number("abc") のように数値にできない文字列を渡すと、エラーにはならず NaN(数値ではないことを表す値)になります。また、Number("") は 0 になるので、価格が空欄だと 0 円として扱われます。外から読み込んだデータを使うときは、Number.isNaN() などで確かめておくと安心です。
const price = Number(priceText);
if (Number.isNaN(price)) {
console.log(`価格が数値にできません:${priceText}`);
}なお、本格的なCSVファイルは、項目の中にカンマや改行を含められる("で囲む)など、split(",") だけでは正しく分けられないことがあります。そのようなデータを扱うときは、CSV用のライブラリを使うのが確実です。
まとめ
split→filter→mapの短いコードは、「改行で区切る → 空の行を飛ばす → 1行ずつ変換して配列に入れる」というfor文の処理と同じmapが新しい配列を作って返すので、空の配列を用意してpushする必要がない- 分かりにくいときは、途中のデータをコメントに書き出すか、for文に戻してみると流れが追いやすい
- 型(
Product[])を付けると、プロパティ名の書き間違いをコンパイルのときに見つけられる - 実際のデータでは、
\r\nの改行とNaNに気をつける



コメント