colinhacks / zod

TypeScript-first schema validation with static type inference
https://zod.dev
MIT License
33.13k stars 1.15k forks source link

Implement TypeScript generic functions. #2086

Open muametgrooby opened 1 year ago

muametgrooby commented 1 year ago

Since zod tries to resemble TypeScripts API and its types, I think it would be awesome if some built in utility typescript functions were available in zod too

Here are a few examples how these generic functions would work

const eventSchema = z.union([
  z.object({
    type: "success",
    data: z.any(),
  }),
  z.object({
    type: "error",
    message: z.string(),
  }),
]);

z.extract(eventSchema, z.object({ type: z.literal("error") })); // result: z.object({ type: "error", message: z.string() });
z.required(
  z.object({
    foo: z.string().optional(),
    bar: z.string().optional(),
  })
);

/**
 * result:
 *
 * z.object({
 *   foo: z.string(),
 *   bar: z.string()
 * })
 */
colinhacks commented 1 year ago

Most typescript built-ins are implemented as methods, which lets us provide cleaner autocompletion.

I don't think we'll implement extract. It's very difficult to determine at runtime if one Zod schema represents a type that assignable to another Zod schema. It would require re-implementing a non-trivial chunk of the TypeScript compiler.

muametgrooby commented 1 year ago

I really needed a way to extract a type, and I was happy to find that the following suits my needs

const eventSchema = z.union([
  z.object({
    type: z.literal('success'),
    data: z.any(),
  }),
  z.object({
    type: z.literal('error'),
    message: z.string(),
  }),
]);

eventSchema.and(z.object({ type: z.literal('error') })); // result: z.object({ type: z.literal('error'), message: z.string() });

@colinhacks Maybe .extract can be an alias to the example above?

colinhacks commented 1 year ago

Ah smart! That would indeed be logically equivalent here. In the general case Extract<A, B> isn't equivalent to A & B though. Zod could potentially implement a ZodExtract subclass that would use the same parsing logic as ZodIntersection internally, but it would be a performance footgun. In your example, Zod will still naively parse the input using the entire union schema, plus the schema you passed into .and(), then merge together the outputs.