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());
