created: 2023-11-08T01:54:27.008Z
zod を使って csv をパースする
- zod をつかって csv をバリデーションする
- csv のカラム名がスネークケースなのをキャメルケースにする
- eg:
user_id => userId
- eg:
zod-csv
という小さなライブラリを使うとパパッとできた。
コード
import { z } from "zod";
import { zcsv, parseCSVContent } from "zod-csv";
import { camelCase, mapKeys } from "lodash";
export const userParser = z
.record(zcsv.string())
.transform((i) => mapKeys(i, camelCase))
.pipe(
z.object({
userId: zcsv.string(),
userName: zcsv.string(),
})
);
export type CsvUser = z.infer<typeof userParser>;
const csv = await readFile("/tmp/_.csv", "utf-8");
const parsed = await parseCSVContent(csv.toString(), userParser);
なにをやっているか
z.record
データ型がレコード型ですよ。という定義。
レコード型とは { [k: string]: number | string | boolean }
みたいな型で、公式に Utility Types で提供されている。
z.record(keyType, valueType)
のように、第二引数は値側の型でそれは例では省略している。
transform((i) => mapKeys(i, camelCase))
- 一度バリデーションとパースがされたデータを 1 行うけとって、変換処理を記述できる
- ここの時点だと
z.infer
したときの型定義がどうなるのかな
- ここの時点だと
mapKeys(i, camelCase)
で、オブジェクトのキー側だけ camelcase に変更している
pipe
Schemas can be chained into validation "pipelines". It's useful for easily validating the result after a .transform()
とのことで、ドキュメントでも「transform
のあとに使うと便利だよ」と書いてある。
一度チェックしたあとに、別のチェックをしたい。という場合に使われるもので、z.coerce
とペアでつかうことがよくあるようだ。
たとえば、 coerce.date
が new Date(null)
をしてくれてしまうので、null をチェックできない場合などに使う。
z.coerce.date().safeParse(null).success; // => true が返ってしまう
.pipe(z.coerce.date())
とすれば、チェックが通ったものだけ new Date
にかけることができる。
const datelike = z.union([z.number(), z.string(), z.date()]);
const datelikeToDate = datelike.pipe(z.coerce.date());