created: 2023-11-08T01:54:27.008Z

zod を使って csv をパースする

  • zod をつかって csv をバリデーションする
  • csv のカラム名がスネークケースなのをキャメルケースにする
    • eg: user_id => userId

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

参考

CAREER SKILLS ソフトウェア開発者の完全キャリアガイド
[ad] CAREER SKILLS ソフトウェア開発者の完全キャリアガイド
ジョン・ソンメズ, 長尾 高弘 (Kindle版)