让 zod 对 .parse() 输入进行顶级类型检查

dav*_*wil 5 typescript zod

我知道 zod 的目的是解析不受信任的输入数据并断言它的类型与您的架构匹配。

但通常这些数据是通过 Web API 传入的,至少保证其顶级形状,例如stringobject

看来 zod 对 进行顶级类型检查是有意义的parse(),即使只是为了防止拼写错误等愚蠢错误。但看起来,事实并非如此。

作为一个简化的例子来说明

const schema = z.string().email();
schema.parse(1); // no type error here - why?
Run Code Online (Sandbox Code Playgroud)

看起来parse(1)应该有一个编译时类型错误,因为我们知道文字number1 不可能在运行时正确验证。我们无法通过一些随机string输入来做到这一点 - 需要运行时解析以确保它是有效的电子邮件 - 但这里的数字似乎是明显的程序员错误,甚至不应该编译。

一个更实际的例子,这让我提出了这个问题

async function validateRequest(request: Request) {
  const someSchema = z.object({ ... })
  return someSchema.parse(request.json()) // didn't await request.json() so won't work
}
Run Code Online (Sandbox Code Playgroud)

像省略上述内容这样的愚蠢错误await,似乎应该很容易通过someSchema.parse()检查我是否将其传递给 anobject而不是 a 来发现Promise<object>

那么,有没有办法用 zod 启用这种顶级类型检查呢?

或者这种行为是出于某种我不理解 zod 设计的原因而故意的吗?

Sou*_*man 0

我试图在 Zod 中寻找一种方法来做到这一点,但找不到任何令人信服的东西。我怀疑用例可能不受开箱即用的支持。如果您想继续使用 Zod 进行解析,一个建议是创建一个具有更严格类型的包装函数,在内部调用您的 Zod 架构。

// I went for a record type because Promise<any> is assignable to object
function parseResponse(input: Record<string, unknown>) {
  const someSchema = z.object({ ... });
  return someSchema.parse(input)
}
async function validateRequest(request: Request) {
  return parseResponse(request.json()) // This will throw because now it sees the promise
}

Run Code Online (Sandbox Code Playgroud)

编辑:

事实上,你可以像这样编写这种类型的助手:

import { z } from "zod";
const schema = z.object({
  field: z.string(),
})

function restrict<T, Output, Def extends z.ZodTypeDef, Input = Output>(schema: z.ZodType<Output, Def, Input>) {
  return (t: T) => schema.parse(t);
}
type InferTypes<Z> = Z extends z.ZodType<infer Output, infer Defs, infer Input> ? [Output, Defs, Input] : [never, never, never];
type InferOutput<Z> = InferTypes<Z>[0];
type InferDefs<Z> = InferTypes<Z>[1];
type InferInput<Z> = InferTypes<Z>[2];

const validate = restrict<
  Record<string, unknown>,
  InferOutput<typeof schema>,
  InferDefs<typeof schema>,
  InferInput<typeof schema>
>(schema);

validate({}); // This typechecks because it's possible
async function foo(req: Request) {
  return validate(req.json()); // This has a type error.
}
Run Code Online (Sandbox Code Playgroud)

唯一真正的缺点是您需要进行类型推断才能使泛型正常工作。

另类图书馆

如果您对替代库持开放态度,这在io-ts中以相对一流的方式得到支持。您可以明确声明模式的输入和输出类型,它们默认开始unknown。权衡是开发人员经验。io-ts使用需要大量时间来学习的函数范式。