From a7acdd13c670d8aa160da804ee81817ddd76356b Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Thu, 14 Mar 2024 15:22:24 -0700 Subject: [PATCH] Update docs --- README.md | 81 +++++++++++++++------------------------------- deno/lib/README.md | 81 +++++++++++++++------------------------------- playground.ts | 2 +- 3 files changed, 53 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index d346e3d5d5..662dfde043 100644 --- a/README.md +++ b/README.md @@ -2694,94 +2694,65 @@ type inferred = z.infer; // number ### Writing generic functions -By leveraging TypeScript's generic types, you can write reusable functions that accept Zod schemas as parameters. This enables you to create custom validation logic, schema transformations, and more, while maintaining type safety and inference. +With TypeScript generics, you can write reusable functions that accept Zod schemas as parameters. This enables you to create custom validation logic, schema transformations, and more, while maintaining type safety and inference. -#### Limitations of `z.ZodType`, and why you should use `z.ZodTypeAny` instead - -When attempting to write a function that accepts a Zod schema as an input, it's tempting to use `z.ZodType` or `z.ZodSchema` as the type of the input schema so that `T` automatically represents the inferred type of the schema. However, this approach fails to narrow down the type subclass of the schema, which is especially important in cases where the function needs to return a schema that reflects the subclass of the input schema. - -Consider the following: +When attempting to write a function that accepts a Zod schema as an input, it's tempting to try something like this: ```ts -function makeSchemaOptional(schema: z.ZodType) { - return schema.optional(); +function inferSchema(schema: z.ZodType) { + return schema; } ``` -In this function, even if the `schema` variable is defined as a more narrow subclass of `z.ZodType` (e.g. `z.ZodString`), `schema` is forcefully typed as an instance of `ZodType`, which is the broad abstract class that all Zod schemas inherit from, and `T` cannot be inferred as the more specific subclass. +This approach is incorrect, and limits TypeScript's ability to properly infer the argument. No matter what you pass in, the type of `schema` will be an instance of `ZodType`. ```ts -const arg = makeSchemaOptional(z.string()); -arg.unwrap(); // z.ZodType +inferSchema(z.string()); +// => ZodType ``` -This approach loses type information, namely _which subclass_ the input actually is. `arg.unwrap()` is typed as `z.ZodType` instead of the narrower and more specific subclass `z.ZodOptional`. +This approach loses type information, namely _which subclass_ the input actually is (in this case, `ZodString`). That means you can't call any string-specific methods like `.min()` on the result of `inferSchema`. -A better approach is for the generic parameter to refer to _the schema as a whole_ using `z.ZodTypeAny`. +A better approach is to infer _the schema as a whole_ instead of merely it's inferred type. You can do this with a utility type called `z.ZodTypeAny`. ```ts -function makeSchemaOptional(schema: T) { - return schema.optional(); +function inferSchema(schema: T) { + return schema; } + +inferSchema(z.string()); +// => ZodString ``` > `ZodTypeAny` is just a shorthand for `ZodType`, a type that is broad enough to match any Zod schema. -Although at first glance, this seems less specific than the `z.ZodType`, it actually allows the type system to properly infer the narrowest possible type of `T`. +The Result is now fully and properly typed, and the type system can infer the specific subclass of the schema. -```ts -const arg = makeSchemaOptional(z.string()); -arg.unwrap(); // z.ZodOptional -``` - -By using `T extends z.ZodTypeAny`, the schema variable is now fully and properly typed, and the type system can infer the specific subclass of the schema. - -#### Using `z.infer` with `z.ZodTypeAny` to properly infer the parsed type +#### Inferring the inferred type -If you follow the best practice of using `z.ZodTypeAny` as the generic parameter for your schema, you may encounter issues with the parsed data being typed as `any` instead of the inferred type of the schema. This can be especially frustrating if you used the `z.ZodType` approach before, as the data parsed by the schema were correctly inferred as `T`. +If you follow the best practice of using `z.ZodTypeAny` as the generic parameter for your schema, you may encounter issues with the parsed data being typed as `any` instead of the inferred type of the schema. ```ts function parseData(data: unknown, schema: T) { - const result = schema.safeParse(data); - if (!result.success) { - throw new Error('Validation failed'); - } - return result.data; + return schema.parse(data); } -const userSchema = z.object({ - name: z.string(), - age: z.number(), -}); - -const userData = parseData({ name: 'Alice', age: 25 }, userSchema); -// userData is incorrectly inferred as any +parseData("sup", z.string()); +// => any ``` -To fix this, add a type assertion on the parsed value (currently typed as `any`) using `z.infer`. In our case with `parseData`, since `result.data` is typed as `any`, we can set the function's return type to `z.infer` to reflect the inferred type of the passed-in schema. +Due to how TypeScript inference works, it is treating `schema` like a `ZodTypeAny` instead of the inferred type. You can fix this with a type cast using `z.infer`. ```ts -function parseData(data: unknown, schema: T): z.infer { - const result = schema.safeParse(data); - if (!result.success) { - throw new Error('Validation failed'); - } - return result.data; +function parseData(data: unknown, schema: T) { + return schema.parse(data) as z.infer; + // ^^^^^^^^^^^^^^ <- add this } -const userSchema = z.object({ - name: z.string(), - age: z.number(), -}); - -const userData = parseData({ name: 'Alice', age: 25 }, userSchema); -// userData is correctly inferred as { name: string, age: number } +parseData("sup", z.string()); +// => string ``` -> If you do not use `z.infer`, TypeScript will infer the return type as `z.ZodTypeAny`, which is why the parsed data is typed as `any`. This is why it's important to use `z.infer` to properly infer the return type based on the passed-in schema. - -By following these best practices and leveraging z.infer, you can write generic functions that work seamlessly with Zod schemas while maintaining accurate type information throughout your codebase. - #### Constraining allowable inputs The `ZodType` class has three generic parameters. diff --git a/deno/lib/README.md b/deno/lib/README.md index d346e3d5d5..662dfde043 100644 --- a/deno/lib/README.md +++ b/deno/lib/README.md @@ -2694,94 +2694,65 @@ type inferred = z.infer; // number ### Writing generic functions -By leveraging TypeScript's generic types, you can write reusable functions that accept Zod schemas as parameters. This enables you to create custom validation logic, schema transformations, and more, while maintaining type safety and inference. +With TypeScript generics, you can write reusable functions that accept Zod schemas as parameters. This enables you to create custom validation logic, schema transformations, and more, while maintaining type safety and inference. -#### Limitations of `z.ZodType`, and why you should use `z.ZodTypeAny` instead - -When attempting to write a function that accepts a Zod schema as an input, it's tempting to use `z.ZodType` or `z.ZodSchema` as the type of the input schema so that `T` automatically represents the inferred type of the schema. However, this approach fails to narrow down the type subclass of the schema, which is especially important in cases where the function needs to return a schema that reflects the subclass of the input schema. - -Consider the following: +When attempting to write a function that accepts a Zod schema as an input, it's tempting to try something like this: ```ts -function makeSchemaOptional(schema: z.ZodType) { - return schema.optional(); +function inferSchema(schema: z.ZodType) { + return schema; } ``` -In this function, even if the `schema` variable is defined as a more narrow subclass of `z.ZodType` (e.g. `z.ZodString`), `schema` is forcefully typed as an instance of `ZodType`, which is the broad abstract class that all Zod schemas inherit from, and `T` cannot be inferred as the more specific subclass. +This approach is incorrect, and limits TypeScript's ability to properly infer the argument. No matter what you pass in, the type of `schema` will be an instance of `ZodType`. ```ts -const arg = makeSchemaOptional(z.string()); -arg.unwrap(); // z.ZodType +inferSchema(z.string()); +// => ZodType ``` -This approach loses type information, namely _which subclass_ the input actually is. `arg.unwrap()` is typed as `z.ZodType` instead of the narrower and more specific subclass `z.ZodOptional`. +This approach loses type information, namely _which subclass_ the input actually is (in this case, `ZodString`). That means you can't call any string-specific methods like `.min()` on the result of `inferSchema`. -A better approach is for the generic parameter to refer to _the schema as a whole_ using `z.ZodTypeAny`. +A better approach is to infer _the schema as a whole_ instead of merely it's inferred type. You can do this with a utility type called `z.ZodTypeAny`. ```ts -function makeSchemaOptional(schema: T) { - return schema.optional(); +function inferSchema(schema: T) { + return schema; } + +inferSchema(z.string()); +// => ZodString ``` > `ZodTypeAny` is just a shorthand for `ZodType`, a type that is broad enough to match any Zod schema. -Although at first glance, this seems less specific than the `z.ZodType`, it actually allows the type system to properly infer the narrowest possible type of `T`. +The Result is now fully and properly typed, and the type system can infer the specific subclass of the schema. -```ts -const arg = makeSchemaOptional(z.string()); -arg.unwrap(); // z.ZodOptional -``` - -By using `T extends z.ZodTypeAny`, the schema variable is now fully and properly typed, and the type system can infer the specific subclass of the schema. - -#### Using `z.infer` with `z.ZodTypeAny` to properly infer the parsed type +#### Inferring the inferred type -If you follow the best practice of using `z.ZodTypeAny` as the generic parameter for your schema, you may encounter issues with the parsed data being typed as `any` instead of the inferred type of the schema. This can be especially frustrating if you used the `z.ZodType` approach before, as the data parsed by the schema were correctly inferred as `T`. +If you follow the best practice of using `z.ZodTypeAny` as the generic parameter for your schema, you may encounter issues with the parsed data being typed as `any` instead of the inferred type of the schema. ```ts function parseData(data: unknown, schema: T) { - const result = schema.safeParse(data); - if (!result.success) { - throw new Error('Validation failed'); - } - return result.data; + return schema.parse(data); } -const userSchema = z.object({ - name: z.string(), - age: z.number(), -}); - -const userData = parseData({ name: 'Alice', age: 25 }, userSchema); -// userData is incorrectly inferred as any +parseData("sup", z.string()); +// => any ``` -To fix this, add a type assertion on the parsed value (currently typed as `any`) using `z.infer`. In our case with `parseData`, since `result.data` is typed as `any`, we can set the function's return type to `z.infer` to reflect the inferred type of the passed-in schema. +Due to how TypeScript inference works, it is treating `schema` like a `ZodTypeAny` instead of the inferred type. You can fix this with a type cast using `z.infer`. ```ts -function parseData(data: unknown, schema: T): z.infer { - const result = schema.safeParse(data); - if (!result.success) { - throw new Error('Validation failed'); - } - return result.data; +function parseData(data: unknown, schema: T) { + return schema.parse(data) as z.infer; + // ^^^^^^^^^^^^^^ <- add this } -const userSchema = z.object({ - name: z.string(), - age: z.number(), -}); - -const userData = parseData({ name: 'Alice', age: 25 }, userSchema); -// userData is correctly inferred as { name: string, age: number } +parseData("sup", z.string()); +// => string ``` -> If you do not use `z.infer`, TypeScript will infer the return type as `z.ZodTypeAny`, which is why the parsed data is typed as `any`. This is why it's important to use `z.infer` to properly infer the return type based on the passed-in schema. - -By following these best practices and leveraging z.infer, you can write generic functions that work seamlessly with Zod schemas while maintaining accurate type information throughout your codebase. - #### Constraining allowable inputs The `ZodType` class has three generic parameters. diff --git a/playground.ts b/playground.ts index da58d74d08..4e01473b6d 100644 --- a/playground.ts +++ b/playground.ts @@ -1,3 +1,3 @@ import { z } from "./src"; -z; \ No newline at end of file +z;