zodでtransformした後,ジェネリクスで型エラーが出る問題の解決法

2024/10/10に公開

zodにはtransform機能がある

zodでは,スキーマにtransform()メソッドを使用することで,パースを行った後に別の型に変換することができます。
しかし,ジェネリクスと組み合わせるときには注意が必要です。

結論だけ書くと,スキーマの型として以下のように指定すると良いです。

z.ZodType<I, z.ZodTypeDef, O>

エラーの例

import axios from "axios";
import { z } from "zod";

const outputSchema = z
  .object({ name: z.string(), age: z.number() })
  .transform((data) => ({
    ...data,
    isAdult: data.age >= 18,
  }));

export const hogeApi = async <O>({
  endpoint,
  outputSchema,
}: {
  endpoint: string;
  outputSchema: z.ZodType<O>;
}) => {
  const response = await axios.get(endpoint);

  const validatedData = outputSchema.parse(response.data);
  return validatedData;
};

const res = await hogeApi({
  endpoint: "https://example.com",
  outputSchema,
});

// エラー発生
// プロパティ 'isAdult' は型 '{ name: string; age: number; }' に存在しません。ts(2339)
console.log(res.isAdult);

上記のように,transformによりisAdultプロパティが追加されているはずなのに,存在しないというエラーが出ます。

解決法

解決策としては単純です。

zodType<O>

zodType<O, z.ZodTypeDef, T>

に書き換えるだけです。

解決例

import axios from "axios";
import { z } from "zod";

const outputSchema = z
  .object({ name: z.string(), age: z.number() })
  .transform((data) => ({
    ...data,
    isAdult: data.age >= 18,
  }));

export const hogeApi = async <O, T>({
  endpoint,
  outputSchema,
}: {
  endpoint: string;
  outputSchema: zodType<O, z.ZodTypeDef, T>;
}) => {
  const response = await axios.get(endpoint);

  const validatedData = outputSchema.parse(response.data);
  return validatedData;
};

const res = await hogeApi({
  endpoint: "https://example.com",
  outputSchema,
});

// エラーが出ない!!
console.log(res.isAdult);

解説

Zodの型システムについて

ZodのZodTypeは3つの型パラメータを持っています:

ZodType<Input, Def, Output>
  • Input: スキーマが受け入れる入力の型
  • Def: Zodの内部で使用される型定義(通常はz.ZodTypeDefを使用)
  • Output: スキーマが生成する出力の型(transform()メソッドで変更された場合に重要)

なぜエラーが発生したのか

元のコードでz.ZodType<O>と指定した場合、TypeScriptはInputOutputが同じ型Oであると解釈します。つまり、transform()メソッドによる型の変更が反映されていませんでした。

解決策の仕組み

z.ZodType<O, z.ZodTypeDef, T>と指定することで:

  1. Oは入力の型(変換前)を表します。
  2. z.ZodTypeDefはZodの内部型定義を指定します。
  3. Tは出力の型(変換後)を表します。

この指定により、TypeScriptがtransform()メソッドによる型の変更を正確に追跡できるようになります。

Discussion