前提
TSV ファイルを DB に insert するとき、 csv-parse というモジュール1を使ってパースを行いました。
ただその際、デフォルトでは数値や真偽値もキャストされず String として読み込まれてしまいます。
方法
cast
オプションにキャストを行うコールバック関数を書いて渡します。
コールバック関数は value と context を引数として受け取ります。
context が持っている様々なプロパティを使って列を指定し、value をキャストして値を return します。
なお、今回は TSV ファイルを扱っていますが、 CSV ファイルについても同様です。
環境
Bun 1.1.3
ファイル構成
|--index.ts
|--joyokanji.tsv
id grapheme_orth grapheme_alt grapheme_olds radicals stroke_count grade is_new onyomis kunyomis
1 亜 亞 二 7 S false ア
2 哀 口 9 S false アイ あわ-れ,あわ-れむ
3 挨 手 10 S true アイ
4 愛 心 13 4 false アイ
5 曖 日 17 S true アイ
6 悪 惡 心 11 3 false アク,オ わる-い
https://ja.wikipedia.org/wiki/常用漢字一覧 から取ってきて適当に加工したものです。
bun add csv-parse
npm install
とか yarn add
とか。
忘れがちですがやっておきましょう。
コード例
例0: オプションに cast を指定しない場合
import fs from 'fs';
import { parse } from 'csv-parse/sync';
const file = fs.readFileSync('joyokanji.tsv', 'utf8');
const options = {
columns: true,
delimiter: '\t'
};
const records = parse(file, options);
console.log(records);
実行結果
[
{
id: "1",
grapheme_orth: "亜",
grapheme_alt: "",
grapheme_olds: "亞",
radicals: "二",
stroke_count: "7",
grade: "S",
is_new: "false",
onyomis: "ア",
kunyomis: "",
}, {
id: "2",
grapheme_orth: "哀",
grapheme_alt: "",
grapheme_olds: "",
radicals: "口",
stroke_count: "9",
grade: "S",
is_new: "false",
onyomis: "アイ",
kunyomis: "あわ-れ,あわ-れむ",
}, {
...
}, {
id: "6",
grapheme_orth: "悪",
grapheme_alt: "",
grapheme_olds: "惡",
radicals: "心",
stroke_count: "11",
grade: "3",
is_new: "false",
onyomis: "アク,オ",
kunyomis: "わる-い",
}
]
value がすべて String になっています。
例1: 列名によって型変換を行う
今回対象としたファイルにはヘッダー列があります。
こういった場合、そもそもオプションで columns: true
を指定する必要がありますが、さらに cast
オプション内の context.column
で列名を取得・検査することで、任意の列を任意の型にキャストできます。
今回は id
と stroke_count
列をInteger型に、 is_new
列を Boolean型にしたいので、
switch
文を使って context.column
を検査し、それぞれの value
をキャストして return
しています。
import fs from 'fs';
import { parse } from 'csv-parse/sync';
const file = fs.readFileSync('joyokanji.tsv', 'utf8');
const options = {
columns: true,
delimiter: '\t',
cast: function(value, context) {
switch(context.column) {
case 'id':
case 'stroke_count':
return parseInt(value, 10);
case 'is_new':
return value.toLowerCase() === 'true';
default:
return value;
}
}
};
const records = parse(file, options);
console.log(records);
実行結果
[
{
id: 1,
grapheme_orth: "亜",
grapheme_alt: "",
grapheme_olds: "亞",
radicals: "二",
stroke_count: 7,
grade: "S",
is_new: false,
onyomis: "ア",
kunyomis: "",
}, {
...
}, {
id: 5,
grapheme_orth: "曖",
grapheme_alt: "",
grapheme_olds: "",
radicals: "日",
stroke_count: 17,
grade: "S",
is_new: true,
onyomis: "アイ",
kunyomis: "",
}, {
id: 6,
grapheme_orth: "悪",
grapheme_alt: "",
grapheme_olds: "惡",
radicals: "心",
stroke_count: 11,
grade: "3",
is_new: false,
onyomis: "アク,オ",
kunyomis: "わる-い",
}
]
キャストしてパースできました。
ちなみにヘッダー列がない場合は、例えば context.index
プロパティで列インデックスを指定してキャストするといいと思います。
余談
みんな大好き Papa Parse には dynamicTyping
というオプションが用意されていて、これを true
にしておくだけで、よしなに型変換を行ってくれるそうです。
モジュールの選定は大事ですね。