はじめに
以前 Vue.jsとVue RouterとVuexとVuexFireとElectronとFirebaseとFoundation Sitesでアプリを作った時に、CSVの読み書きをする機能を実装しました。Shift_JISで出力するのに苦労したのは、今となっては良い思い出です。
必要なパッケージ
- node-csv
- iconv-lite
前者はCSVをパースしたり生成したりするのに使います。csv-parse
や csv-stringify
などの複数のパッケージに分かれていて、今回はそれらパッケージを個別に呼び出しました。
後者は文字コードのエンコード・デコードをするのに使います。iconvというパッケージもありますが、ネイティヴモジュールを内部で使用しているパッケージで、Electronでアプリのパッケージする時に面倒だなあと思って使いませんでした。
Shift_JISのCSVを読み込む
例えばこんな感じのCSVデータがあるとします。
名前 | 年齢 | 身長 | 体重 |
---|---|---|---|
山田太郎 | 25 | 170 | 60 |
山田二郎 | 24 | 171 | 55 |
山田三郎 | 22 | 172 | 60 |
山田四郎 | 21 | 168 | 58 |
これを以下のようなJavaScriptのオブジェクトに変換します。
数字が文字列になっているところは、必要に応じて変換してください。
const data = [
{ fullname: '山田太郎', age: '25', height: '170', weight: '60' },
{ fullname: '山田二郎', age: '24', height: '171', weight: '55' },
{ fullname: '山田三郎', age: '22', height: '172', weight: '60' },
{ fullname: '山田四郎', age: '21', height: '168', weight: '58' }
]
このように変換するコードは以下の通りです。
ストリームAPIを使って比較的きれいに記述できていると思います。
rxjsとか使うともっと良く書けるのかなと思います。
import parse = require('csv-parse')
import fs = require('fs')
import iconv = require('iconv-lite')
const headers = {
'名前': 'fullname',
'年齢': 'age',
'身長': 'height',
'体重': 'weight'
}
type Dictionary<T> = { [key: string]: T }
function parseColumns(line: Dictionary<keyof typeof headers>): string[] {
const table: string[] = []
for (let key in line) {
table.push(columnTable[line[key]])
}
return table
}
interface Person {
fullname: string
age: string
height: string
weight: string
}
/**
* @param path CSVファイルが保存されているパス
*/
function parseCSV(path: string): Promise<Person[]> {
return new Promise(resolve => {
const parser = parse({ columns: parseColumns as any })
const rs: fs.ReadStream = fs.createReadStream(path)
rs.pipe(iconv.decodeStream('SJIS'))
.pipe(iconv.encodeStream('UTF-8'))
.pipe(parser)
const people: Person[] = []
parser.on('readable', () => {
let data: any
while (data = parser.read()) {
people.push(data)
}
})
parser.on('end', () => {
resolve(people)
})
})
}
Shift_JISのCSVを出力する
今度は、読み込みの逆をやりたいと思います。
あんまり綺麗に書けてないですけど十分間に合ってるコードだと思います。
CSV読み込みの時のように、headerの対応表を辞書型で定義して、
WriteStream
を使うコードを書きたかったなあと思いました。
import stringify = require('csv-stringify')
import fs = require('fs')
import iconv = require('iconv-lite')
interface Person {
fullname: string
age: string
height: string
weight: string
}
function saveCSV(path: string, people: Person[]) {
const header: string[] = ['名前', '年齢', '身長', '体重']
const rows: string[][] = people.map(person => {
return [
person.fullname,
person.age,
person.height,
person.weight
]
})
stringily([header, ...rows], (_err, output) => {
fs.writeFile(path, iconv.encode(output, 'shift_jis'))
})
}
参考にしたサイト
https://solutionware.jp/blog/2016/09/29/node-jsでcsvファイル操作/
このサイトを参考にさせていただきました。