diff --git a/packages/docs-v3/.editorconfig b/packages/docs-v3/.editorconfig new file mode 100644 index 0000000000..a9452c74a7 --- /dev/null +++ b/packages/docs-v3/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +charset = utf-8 diff --git a/packages/docs-v3/CHANGELOG.md b/packages/docs-v3/CHANGELOG.md new file mode 100644 index 0000000000..a18ea6f143 --- /dev/null +++ b/packages/docs-v3/CHANGELOG.md @@ -0,0 +1,255 @@ +# Changelog + +## Release notes are now stored in Github Releases: https://github.com/colinhacks/zod/releases + +## Previous Releases + +### 3.10 + +- New parser that allows parsing to continue after non-fatal errors have occurred. This allows Zod to surface more errors to the user at once. + +### 3.9 + +- Custom error messages in schemas + +```ts +const name = z.string({ + invalid_type_error: "Name must be string", + required_error: "Name is required", +}); +``` + +Under the hood, this creates a custom error map that's bound to the schema. You can also pass a custom error map explicitly. + +```ts +const name = z.string({ errorMap: myErrorMap }); +``` + +- Rest parameters for tuples + +```ts +const myTuple = z.tuple([z.string(), z.number()]).rest(z.boolean()); +type t1 = z.output; // [string, number, ...boolean[]] +``` + +- Selective `.partial` + +You can specify certain fields to make optional with the `ZodObject.partial` method. + +```ts +const user = z.object({ + name: z.string(), + age: z.number(), +}); + +const optionalNameUser = user.partial({ name: true }); +// { name?: string; age: number; } +``` + +- Specify key schema in ZodRecord + +Previously, `z.record` only accepted a single schema: + +```ts +z.record(z.boolean()); // Record; +``` + +Now `z.record` has been overloaded to support two schemas. The first validates the _keys_ of the record, and the second validates the _values_. + +```ts +const schema = z.record(z.number(), z.boolean()); +type schema = z.infer; // Record + +const schema = z.record(z.enum(["Tuna", "Trout"]), z.boolean()); +type schema = z.infer; // Record<"Tuna" | "Trout", boolean> +``` + +### 3.8 + +- Add `z.preprocess` +- Implement CUID validation on ZodString (`z.string().cuid()`) +- Improved `.deepPartial()`: now recursively operates on arrays, tuples, optionals, and nullables (in addition to objects) + +### 3.7 + +- Eliminate `ZodNonEmptyArray`, add `Cardinality` to `ZodArray` +- Add optional error message to `ZodArray.nonempty` +- Add `.gt/.gte/.lt/.lte` to `ZodNumber` + +### 3.6 + +- Add IE11 support +- `ZodError.flatten` now optionally accepts a map function for customizing the output +- `.void()` now only accepts undefined, not null. +- `z.enum` now supports `Readonly` string tuples + +### 3.5 + +- Add discriminator to all first-party schema defs + +### 3.4 + +- `unknown` and `any` schemas are always interpreted as optional. Reverts change from 3.3. + +### 3.3 + +- HUGE speed improvements +- Added benchmarking: `yarn benchmark` +- Type signature of `ZodType#_parse` has changed. This will affects users who have implemented custom subclasses of `ZodType`. +- [reverted] Object fields of type `unknown` are no longer inferred as optional. + +### 3.2 + +- Certain methods (`.or`, `.transform`) now return a new instance that wrap the current instance, instead of trying to avoid additional nesting. For example: + +```ts +z.union([z.string(), z.number()]).or(z.boolean()); +// previously +// => ZodUnion<[ZodString, ZodNumber, ZodBoolean]> + +// now +// => ZodUnion<[ZodUnion<[ZodString, ZodNumber]>, ZodBoolean]> +``` + +This change was made due to recursion limitations in TypeScript 4.3 that made it impossible to properly type these methods. + +### 3.0.0-beta.1 + +- Moved default value logic into ZodDefault. Implemented `.nullish()` method. + +### 3.0.0-alpha.33 + +- Added `.returnType` and `.parameters` methods to ZodFunction + +### 3.0.0-alpha.32 + +- Added `.required()` method to ZodObject + +### 3.0.0-alpha.30 + +- Added Rollup for bundling ESM module + +### zod@3.0.0-alpha.24 + +- Added back ZodIntersection +- Added .and() method to base class + +### zod@3.0.0-alpha.9 + +- Added `z.strictCreate` + +### zod@3.0.0-alpha.8 + +- Allowing optional default values on ZodOptional + +### zod@3.0.0-alpha.5 + +March 17, 2021 + +- Refactored parsing logic into individual subclass methods +- Eliminated ZodTypes to enable custom ZodType subclasses +- Removed ZodIntersection +- Added ZodEffects as a container for refinement and transform logic +- Added `or` method to `ZodType` +- Added `format` method to `ZodError` +- Added `unwrap` method to `ZodOptional` and `ZodNullable` +- Added new `default` method and moved default functionality into ZodOptional +- Implemented `z.setErrorMap` +- Exporting `z` variable from `index.ts` to enable `import { z } from 'zod';` + +### zod@3.0.0-alpha.4 + +Jan 25, 2021 + +- New implementation of transformers +- Removed type guards + +### zod@2 + +- Added ZodTransformer +- Async refinements + +### zod@1.11 + +- Introduced `.safeParse` option +- Introduced .regex method on string schemas +- Implemented `.primitives()` and `.nonprimitives()` on object schemas +- Implemented `z.nativeEnum()` for creating schemas from TypeScript `enum`s +- Switched to `new URL()` constructor to check valid URLs + +### zod@1.10 + +- Dropping support for TypeScript 3.2 + +### zod@1.9 + +- Added z.instanceof() and z.custom() +- Implemented ZodSchema.array() method + +### zod@1.8 + +- Introduced z.void() +- Major overhaul to error handling system, including the introduction of custom error maps +- Wrote new [error handling guide](./ERROR_HANDLING.md) + +### zod@1.7 + +- Added several built-in validators to string, number, and array schemas +- Calls to `.refine` now return new instance + +### zod@1.5 + +- Introduces ZodAny and ZodUnknown + +### zod@1.4 + +- Refinement types (`.refine`) +- Parsing no longer returns deep clone + +### zod@1.3 + +- Promise schemas + +### zod@1.2.6 + +- `.parse` accepts `unknown` +- `bigint` schemas + +### zod@1.2.5 + +- `.partial` and `.deepPartial` on object schemas + +### zod@1.2.3 + +- Added ZodDate + +### zod@1.2.0 + +- Added `.pick`, `.omit`, and `.extend` on object schemas + +### zod@1.1.0 + +- Added ZodRecord + +### zod@1.0.11 + +- Added `.nonstrict` + +### zod@1.0.10 + +- Added type assertions with `.check` + +### zod@1.0.4 + +- Support for empty tuples + +### zod@1.0.2 + +- Added type assertions +- Added ZodLiteral +- Added ZodEnum +- Improved error reporting + +### zod@1.0.0 + +- Initial release diff --git a/packages/docs-v3/CODE_OF_CONDUCT.md b/packages/docs-v3/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..2daa233fc0 --- /dev/null +++ b/packages/docs-v3/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +colinmcd94@gmail.com or scott@scotttrinh.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][mozilla coc]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][faq]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[mozilla coc]: https://github.com/mozilla/diversity +[faq]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/packages/docs-v3/CONTRIBUTING.md b/packages/docs-v3/CONTRIBUTING.md new file mode 100644 index 0000000000..23afcd926d --- /dev/null +++ b/packages/docs-v3/CONTRIBUTING.md @@ -0,0 +1,85 @@ +# Contributing + +> ⚠ The default branch has just been switched to `main` from `master` (as of May 15th, 2024). Follow the following instructions to update your local fork. +> +> ```sh +> git branch -m master main # rename local branch +> git fetch origin # fetch from remote +> git branch -u origin/main main # set upstream +> git remote set-head origin -a # update remote +> ``` + +When it comes to open source, there are different ways you can contribute, all +of which are valuable. Here's few guidelines that should help you as you prepare +your contribution. + +## Initial steps + +Before you start working on a contribution, create an issue describing what you want to build. It's possible someone else is already working on something similar, or perhaps there is a reason that feature isn't implemented. The maintainers will point you in the right direction. + + + +## Development + +The following steps will get you setup to contribute changes to this repo: + +1. Fork this repo. + +2. Clone your forked repo: `git clone git@github.com:{your_username}/zod.git` + +3. Run `yarn` to install dependencies. + +4. Start playing with the code! You can do some simple experimentation in [`playground.ts`](playground.ts) (see `yarn play` below) or start implementing a feature right away. + +## Alternative: VSCode Dev Container setup + +For an officially supported isolated dev environment that automatically installs dependencies for you: + +1. `F1` in VSCode and start typing `Dev Containers: Clone Repository in Named Container Volume` to run the command. +2. For the repo, paste `git@github.com:{your_username}/zod.git` if you're using ssh. +3. Click `Create a new volume...` and name it `zod` and the folder name as `zod`. + +Note: if you can't see `Dev Containers` in the `F1` menu, follow [this guide](https://code.visualstudio.com/docs/devcontainers/tutorial) to install the needed extension. +In the OSS version of VSCode the extension may be not available. + +### Commands + +**`yarn build`** + +- deletes `lib` and re-compiles `src` to `lib` + +**`yarn test`** + +- runs all Jest tests and generates coverage badge + +**`yarn test enum`** + +- runs a single test file (e.g. `enum.test.ts`) + +**`yarn play`** + +- executes [`playground.ts`](playground.ts), watches for changes. useful for experimentation + +### Tests + +Zod uses Jest for testing. After implementing your contribution, write tests for it. Just create a new file under `src/__tests__` or add additional tests to the appropriate existing file. + +Before submitting your PR, run `yarn test` to make sure there are no (unintended) breaking changes. + +### Documentation + +The Zod documentation lives in the README.md. Be sure to document any API changes you implement. + +## License + +By contributing your code to the zod GitHub repository, you agree to +license your contribution under the MIT license. diff --git a/packages/docs-v3/ERROR_HANDLING.md b/packages/docs-v3/ERROR_HANDLING.md new file mode 100644 index 0000000000..e41601b0e6 --- /dev/null +++ b/packages/docs-v3/ERROR_HANDLING.md @@ -0,0 +1,492 @@ +# Error Handling in Zod + +This guide explains Zod's internal error handling system, and the various ways you can customize it for your purposes. + +## ZodError + +All validation errors thrown by Zod are instances of `ZodError`. + +```ts +class ZodError extends Error { + issues: ZodIssue[]; +} +``` + +ZodError is a subclass of `Error`; you can create your own instance easily: + +```ts +import * as z from "zod"; + +const myError = new z.ZodError([]); +``` + +Each ZodError has an `issues` property that is an array of `ZodIssues`. Each issue documents a problem that occurred during validation. + +## ZodIssue + +`ZodIssue` is _not_ a class. It is a [discriminated union](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions). + +The link above is the best way to learn about the concept. Discriminated unions are an ideal way to represent a data structures that may be one of many possible variants. You can see all the possible variants defined [here](https://github.com/colinhacks/zod/blob/main/src/ZodError.ts). They are also described in the table below if you prefer. + +_Every_ ZodIssue has these fields: + +| field | type | details | +| --------- | ---------------------- | ------------------------------------------------------------------------------------------------- | +| `code` | `z.ZodIssueCode` | You can access this enum with `z.ZodIssueCode`. A full breakdown of the possible values is below. | +| `path` | `(string \| number)[]` | e.g, `['addresses', 0, 'line1']` | +| `message` | `string` | e.g. `Invalid type. Expected string, received number.` | + +**However** depending on the error code, there may be additional properties as well. Here is a full breakdown of the additional fields by error code: + +## ZodIssueCode + +| code | additional fields | +| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| ZodIssueCode.invalid_type | `expected: ZodParsedType`
`received: ZodParsedType`

Jump to [this section](#zodparsedtype) for a breakdown of the possible values of ZodParsedType. | +| ZodIssueCode.unrecognized_keys | `keys: string[]`
The list of unrecognized keys
| +| ZodIssueCode.invalid_union | `unionErrors: ZodError[]`
The errors thrown by each element of the union. | +| ZodIssueCode.invalid_enum_value | `options: string[]`
The set of acceptable string values for this enum. | +| ZodIssueCode.invalid_arguments | `argumentsError: ZodError`
This is a special error code only thrown by a wrapped function returned by `ZodFunction.implement()`. The `argumentsError` property is another ZodError containing the validation error details. | +| ZodIssueCode.invalid_return_type | `returnTypeError: ZodError`
This is a special error code only thrown by a wrapped function returned by `ZodFunction.implement()`. The `returnTypeError` property is another ZodError containing the validation error details. | +| ZodIssueCode.invalid_date | _no additional properties_ | +| ZodIssueCode.invalid_string | `validation: "url" \| "email" \| "uuid"`
Which built-in string validator failed | +| ZodIssueCode.too_small | `type: "string" \| "number" \| "array" \| "set" \| "date"`
The type of the data failing validation

`minimum: number`
The expected length/value.

`inclusive: boolean`
Whether the minimum is included in the range of acceptable values.

`exact: boolean`
Whether the size/length is constrained to be an exact value (used to produce more readable error messages).
| +| ZodIssueCode.too_big | `type: "string" \| "number" \| "array" \| "set" \| "date"`
The type of the data failing validation

`maximum: number`
The expected length/value.

`inclusive: boolean`
Whether the maximum is included in the range of acceptable values.

`exact: boolean`
Whether the size/length is constrained to be an exact value (used to produce more readable error messages).
| +| ZodIssueCode.not_multiple_of | `multipleOf: number`
The value the number should be a multiple of.
| +| ZodIssueCode.custom | `params: { [k: string]: any }`
This is the error code throw by refinements (unless you are using `superRefine` in which case it's possible to throw issues of any `ZodIssueCode`). You are able to pass in a `params` object here that is available in your custom error maps (see [ZodErrorMap](#Customizing-errors-with-ZodErrorMap) below for details on error maps) | + + + +## ZodParsedType + +This is an enum used by Zod internally to represent the type of a parsed value. The possible values are: + +- `string` +- `nan` +- `number` +- `integer` +- `float` +- `boolean` +- `date` +- `bigint` +- `symbol` +- `function` +- `undefined` +- `null` +- `array` +- `object` +- `unknown` +- `promise` +- `void` +- `never` +- `map` +- `set` + +## A demonstrative example + +Here's a sample Person schema. + +```ts +const person = z.object({ + names: z.array(z.string()).nonempty(), // at least 1 name + address: z + .object({ + line1: z.string(), + zipCode: z.number().min(10000), // American 5-digit code + }) + .strict(), // do not allow unrecognized keys +}); +``` + +Let's pass in some improperly formatted data. + +```ts +try { + person.parse({ + names: ["Dave", 12], // 12 is not a string + address: { + line1: "123 Maple Ave", + zipCode: 123, // zip code isn't 5 digits + extra: "other stuff", // unrecognized key + }, + }); +} catch (err) { + if (err instanceof z.ZodError) { + console.log(err.issues); + } +} +``` + +Here are the errors that will be printed: + +```ts +[ + { + code: "invalid_type", + expected: "string", + received: "number", + path: ["names", 1], + message: "Invalid input: expected string, received number", + }, + { + code: "unrecognized_keys", + keys: ["extra"], + path: ["address"], + message: "Unrecognized key(s) in object: 'extra'", + }, + { + code: "too_small", + minimum: 10000, + type: "number", + inclusive: true, + path: ["address", "zipCode"], + message: "Value should be greater than or equal to 10000", + }, +]; +``` + +As you can see three different issues were identified. Every ZodIssue has a `code` property and additional metadata about the validation failure. For instance the `unrecognized_keys` error provides a list of the unrecognized keys detected in the input. + +## Customizing errors with ZodErrorMap + +You can customize **all** error messages produced by Zod by providing a custom "error map" to Zod, like so: + +```ts +import { z } from "zod"; + +const customErrorMap: z.ZodErrorMap = (issue, ctx) => { + if (issue.code === z.ZodIssueCode.invalid_type) { + if (issue.expected === "string") { + return { message: "bad type!" }; + } + } + if (issue.code === z.ZodIssueCode.custom) { + return { message: `less-than-${(issue.params || {}).minimum}` }; + } + return { message: ctx.defaultError }; +}; + +z.setErrorMap(customErrorMap); +``` + +`ZodErrorMap` is a special function. It accepts two arguments: `issue` and `ctx`. The return type is `{ message: string }`. Essentially the error map accepts some information about the validation that is failing and returns an appropriate error message. + +- `issue: Omit` + + As mentioned above, ZodIssue is a discriminated union. + +- `ctx: { defaultError: string; data: any }` + + - `ctx.defaultError` is the error message generated by the default error map. If you only want to override the message for a single type of error, you can do that. Just return `{ message: ctx.defaultError }` for everything else. + + - `ctx.data` contains the data that was passed into `.parse`. You can use this to customize the error message. + +As in the example, you can modify certain error messages and simply fall back to `ctx.defaultError` otherwise. + +## Error map priority + +A custom error maps doesn't need to produce an error message for every kind of issue in Zod. Instead, your error map can override certain errors and return `ctx.defaultError` for everything else. + +But how is the value of `ctx.defaultError` determined? + +Error messages in Zod are generated by passing metadata about a validation issue through a chain of error maps. Error maps with higher priority override messages generated by maps with lower priority. + +The lowest priority map is the `defaultErrorMap`, which defined in [`src/errors.ts`](https://github.com/colinhacks/zod/blob/main/src/errors.ts). This produces the default error message for all issues in Zod. + +### Global error map + +This message is then passed as `ctx.defaultError` into `overrideErrorMap`. This is a global error map you can set with `z.setErrorMap`: + +```ts +const myErrorMap: z.ZodErrorMap = /* ... */; +z.setErrorMap(myErrorMap); +``` + +### Schema-bound error map + +The `overrideErrorMap` message is then passed as `ctx.defaultError` into any schema-bound error maps. Every schema can be associated with an error map. + +```ts +z.string({ errorMap: myErrorMap }); + +// this creates an error map under the hood +z.string({ + invalid_type_error: "Invalid name", + required_error: "Name is required", +}); +``` + +### Contextual error map + +Finally, you can pass an error map as a parameter to any `parse` method. This error map, if provided, has highest priority. + +```ts +z.string().parse("adsf", { errorMap: myErrorMap }); +``` + +## A working example + +Let's look at a practical example of of customized error map: + +```ts +import * as z from "zod"; + +const customErrorMap: z.ZodErrorMap = (error, ctx) => { + /* + This is where you override the various error codes + */ + switch (error.code) { + case z.ZodIssueCode.invalid_type: + if (error.expected === "string") { + return { message: `This ain't a string!` }; + } + break; + case z.ZodIssueCode.custom: + // produce a custom message using error.params + // error.params won't be set unless you passed + // a `params` arguments into a custom validator + const params = error.params || {}; + if (params.myField) { + return { message: `Bad input: ${params.myField}` }; + } + break; + } + + // fall back to default message! + return { message: ctx.defaultError }; +}; + +z.string().parse(12, { errorMap: customErrorMap }); + +/* throws: + ZodError { + errors: [{ + code: "invalid_type", + path: [], + message: "This ain't a string!", + expected: "string", + received: "number", + }] + } +*/ +``` + +## Error handling for forms + +If you're using Zod to validate the inputs from a web form, there is a convenient way to "flatten" a ZodError to a rich, structured format that can be easily rendered in your interface. + +Consider this example of a simple signup form: + +```ts +const FormData = z.object({ + name: z.string(), + contactInfo: z.object({ + email: z.string().email(), + phone: z.string().optional(), + }), +}); +``` + +Now lets pass in some invalid data: + +```ts +const result = FormData.safeParse({ + name: null, + contactInfo: { + email: "not an email", + phone: "867-5309", + }, +}); +``` + +This will throw a ZodError with two issues: + +```ts +if (!result.success) { + console.log(result.error.issues); +} +/* + [ + { + "code": "invalid_type", + "expected": "string", + "received": "null", + "path": ["name"], + "message": "Expected string, received null" + }, + { + "validation": "email", + "code": "invalid_string", + "message": "Invalid email", + "path": ["contactInfo","email"] + } + ] +*/ +``` + +### Formatting errors + +Using the `.format()` method on `ZodError`, we can make this error easier to work with. + +```ts +if (!result.success) { + console.log(result.error.format()); + /* + { + name: { + _errors: ['Expected string, received null'] + }, + contactInfo: { + email: { + _errors: ['Invalid email'] + } + } + } + */ +} +``` + +As you can see, the result is an object that denormalizes the issues array into a nested object. This makes it easier to display error messages in your form interface. + +```tsx +const FormData = z.object({ ... }); + +function Errors(props: {errors?: string[]}){ + if(!props.errors?.length) return null; + return
{props.errors.map(err =>

{err}

)}
+} + +function MyForm(){ + const {register, data} = useForm({ ... }); + + const result = FormData.safeParse(data); + const errors = result.success ? {} : result.error.format(); + + return
+
+} +``` + +### Flattening errors + +Because `.format` returns a deeply nested object, the errors are contained within the `_errors` property to avoid key collisions. However this isn't necessary if your object schema is only one level deep. + +In this scenario, `.flatten()` may be more convenient. + +```ts +if (!result.success) { + console.log(result.error.flatten()); +} +/* + { + formErrors: [], + fieldErrors: { + name: ['Expected string, received null'], + contactInfo: ['Invalid email'] + }, + } +*/ +``` + +The `fieldErrors` key points to an object that groups all issues by key. + +The `formErrors` element is a list of issues that occurred on the "root" of the object schema. For instance: if you called `FormData.parse(null)`, `flatten()` would return: + +```ts +const result = FormData.safeParse(null); +if (!result.success) { + result.error.flatten(); + /* + { + formErrors: ["Invalid input: expected object, received null"], + fieldErrors: {} + } + */ +} +``` + +### Post-processing issues + +Both `.flatten()` and `.format()` accept an optional mapping function of `(issue: ZodIssue) => U` to `flatten()`, which can customize how each `ZodIssue` is transformed in the final output. + +This can be particularly useful when integrating Zod with form validation, as it allows you to pass back whatever `ZodIssue` specific context you might need. + +```ts +result.error.flatten((issue: ZodIssue) => ({ + message: issue.message, + errorCode: issue.code, +})); +/* + { + formErrors: [], + fieldErrors: { + name: [ + {message: "Expected string, received null", errorCode: "invalid_type"} + ] + contactInfo: [ + {message: "Invalid email", errorCode: "invalid_string"} + ] + }, + } +*/ +``` + +### Extract type signature + +You can infer the return type signature of `.format()` and `.flatten()` with the following utilities: + +```ts +type FormattedErrors = z.inferFormattedError; +/* + { + name?: {_errors?: string[]}, + contactInfo?: { + _errors?: string[], + email?: { + _errors?: string[], + }, + phone?: { + _errors?: string[], + }, + }, + } +*/ + +type FlattenedErrors = z.inferFlattenedErrors; +/* + { + formErrors: string[], + fieldErrors: { + email?: string[], + password?: string[], + confirm?: string[] + } + } +*/ +``` + +These utilities also accept a second generic argument that corresponds to the result of any `ZodIssue` mapper function. + +```ts +type FormDataErrors = z.inferFlattenedErrors< + typeof FormData, + { message: string; errorCode: string } +>; + +/* + { + formErrors: { message: string, errorCode: string }[], + fieldErrors: { + email?: { message: string, errorCode: string }[], + password?: { message: string, errorCode: string }[], + confirm?: { message: string, errorCode: string }[] + } + } +*/ +``` diff --git a/packages/docs-v3/LICENSE b/packages/docs-v3/LICENSE new file mode 100644 index 0000000000..2c93bb52b9 --- /dev/null +++ b/packages/docs-v3/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Colin McDonnell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/docs-v3/MIGRATION.md b/packages/docs-v3/MIGRATION.md new file mode 100644 index 0000000000..472dea686c --- /dev/null +++ b/packages/docs-v3/MIGRATION.md @@ -0,0 +1,77 @@ +# Migration guide + +This is a migration guide to walk you through the process of upgrading to Zod 3. + +- If you're upgrading directly from Zod 1, you should read through the list of features + changes in both Zod 2 and Zod 3. +- If you're upgrading from Zod 2 -> 3, you can skip to the + +## Upgrading from Zod 1 → Zod 2 + +Zod 2 is being retired and will not leave beta. This is due to some unintuitive ramifications of the transformers API: details [here](https://github.com/colinhacks/zod/issues/264). + +### New features + +- Transformers! These let you provide default values, do casting/coercion, and a lot more. Read more here: [Transformers](https://github.com/colinhacks/zod#transformers) +- Asynchronous refinements and new .parseAsync and .safeParseAsync methods. Read more here: [Refinements](https://github.com/colinhacks/zod#refinements) +- Modify unknown key behavior for object schemas: `.strip()` (the default), `.passthrough()`, and `.strict()` +- New .catchall() method for object schemas: [catchall](https://github.com/colinhacks/zod#catchall) + +### Breaking changes + +- Object schemas now _strip_ unknown keys by default. +- Schema parsing now returns a deep clone of the data you pass in (instead of the exact value you pass in) +- Relatedly, Zod no longer supports cyclical data. Recursive schemas are still supported, but Zod can't properly parse nested objects that contain cycles. +- Optional and nullable schemas are now represented with the dedicated ZodOptional and ZodNullable classes, instead of using ZodUnion. + +## Upgrading from Zod 2 → Zod 3 + +### New features + +- You can now import Zod like `import { z } from 'zod';` instead of using `import * as` syntax. +- **Structured error messages**. Use the `.format()` method to ZodError to convert the error into a strongly-typed, nested object: [format method](#error-formatting) +- **Easier unions**. Use the `or` method to ZodType (the base class for all Zod schemas) to easily create union types like `z.string().or(z.number())` +- **Easier intersections**. Use the `and` method to ZodType (the base class for all Zod schemas) to easily create intersection types +- **Global error customization**. Use `z.setErrorMap(myErrorMap)` to _globally_ customize the error messages produced by Zod: [setErrorMap](ERROR_HANDLING.md#customizing-errors-with-zoderrormap) +- **Maps and sets**. Zod now supports [`Map`](#maps) and [`Set`](#set) schemas. +- **Optional and nullable unwrapping**. ZodOptional and ZodNullable now have a `.unwrap()` method for retrieving the schema they wrap. +- **A new implementation of transformers**. Details below. + +### Breaking changes + +- The **minimum TypeScript version** is now _4.1_ (up from 3.7 for Zod 2). Several features have been rewritten to use [recursive conditional types](https://devblogs.microsoft.com/typescript/announcing-typescript-4-1/#recursive-conditional-types), an incredibly powerful new feature introduced in TS4.1. + +- **Transformers syntax**. Previously, creating a transformer required an input schema, an output schema, and a function to transform between them. You created transformers like `z.transform(A, B, func)`, where `A` and `B` are Zod schemas. This is no longer the case. Accordingly: + + The old syntax is no longer available: + + ```ts + # not available + z.transformer(A, B, func); + A.transform(B, func) + ``` + + Instead, apply transformations by simply using the `.transform()` method that exists on all Zod schemas. + + ```ts + z.string().transform((val) => val.length); + ``` + +- Under the hood, all refinements and transformations are executed inside a dedicated "ZodEffects" class. Post-parsing, ZodEffects passes the data through a chain of refinements and transformations, then returns the final value. As such, you can now _interleave_ transformations and refinements. For instance: + + ```ts + const test = z + .string() + .transform((val) => val.length) + .refine((val) => val > 5, { message: "Input is too short" }) + .transform((val) => val * 2); + + test.parse("12characters"); // => 24 + ``` + +- **Type guards** (the `.check()` method) have been removed. Type guards interact with transformers in unintuitive ways so they were removed. Use `.safeParse` instead. + +- Object merging now behaves differently. If you merge two object schema (`A.merge(B)`), the fields of B will overwrite the fields of A if there are shared keys. This is how the `.extend` method already works. If you're looking to create an intersection of the two types, use `z.intersection(A, B)` or use the new `.and` method (`A.and(B)`). + +- **Default values**: default value logic is now implemented inside a `ZodDefault` class, instead of using transformers. (In a previous alpha version of Zod 3, default values were implemented inside the ZodOptional class.) + +- There have been small internal changes to the ZodIssue subtypes. See the new subtypes in the [Error Handling guide](ERROR_HANDLING.md). This may impact user who have written a custom error maps. Most users will not be affected. diff --git a/packages/docs-v3/README.md b/packages/docs-v3/README.md new file mode 100644 index 0000000000..eebb7ce27a --- /dev/null +++ b/packages/docs-v3/README.md @@ -0,0 +1,3095 @@ +

+ Zod logo +

Zod

+

+ zod.dev +
+ TypeScript-first schema validation with static type inference +

+

+

+Zod CI status +Created by Colin McDonnell +License +npm +stars +

+ +
+ Website +   •   + + Discord +   •   + 𝕏 +   •   + Bluesky +
+
+ +

+ + +
+

Zod 4 is now available! +
+ Read the announcement 👉

+ +
+
+ +

Featured sponsor: Jazz

+ +
+ + + + jazz logo + + +
+

Learn more about featured sponsorships

+
+ +
+
+ +## Table of contents + +> These docs have been translated into [Chinese](./README_ZH.md) and [Korean](./README_KO.md). + +- [Table of contents](#table-of-contents) +- [Introduction](#introduction) +- [Sponsors](#sponsors) + - [Ecosystem](#ecosystem) + - [Resources](#resources) + - [API libraries](#api-libraries) + - [Form integrations](#form-integrations) + - [Zod to X](#zod-to-x) + - [X to Zod](#x-to-zod) + - [Mocking](#mocking) + - [Powered by Zod](#powered-by-zod) + - [Utilities for Zod](#utilities-for-zod) +- [Installation](#installation) + - [Requirements](#requirements) + - [From `npm`](#from-npm) +- [Basic usage](#basic-usage) +- [Primitives](#primitives) +- [Coercion for primitives](#coercion-for-primitives) +- [Literals](#literals) +- [Strings](#strings) + - [Datetimes](#datetimes) + - [Dates](#dates) + - [Times](#times) + - [IP addresses](#ip-addresses) + - [IP ranges (CIDR)](#ip-ranges-cidr) +- [Numbers](#numbers) +- [BigInts](#bigints) +- [NaNs](#nans) +- [Booleans](#booleans) +- [Dates](#dates-1) +- [Zod enums](#zod-enums) +- [Native enums](#native-enums) +- [Optionals](#optionals) +- [Nullables](#nullables) +- [Objects](#objects) + - [`.shape`](#shape) + - [`.keyof`](#keyof) + - [`.extend`](#extend) + - [`.merge`](#merge) + - [`.pick/.omit`](#pickomit) + - [`.partial`](#partial) + - [`.deepPartial`](#deeppartial) + - [`.required`](#required) + - [`.passthrough`](#passthrough) + - [`.strict`](#strict) + - [`.strip`](#strip) + - [`.catchall`](#catchall) +- [Arrays](#arrays) + - [`.element`](#element) + - [`.nonempty`](#nonempty) + - [`.min/.max/.length`](#minmaxlength) +- [Tuples](#tuples) +- [Unions](#unions) +- [Discriminated unions](#discriminated-unions) +- [Records](#records) +- [Maps](#maps) +- [Sets](#sets) +- [Intersections](#intersections) +- [Recursive types](#recursive-types) + - [ZodType with ZodEffects](#zodtype-with-zodeffects) + - [JSON type](#json-type) + - [Cyclical objects](#cyclical-objects) +- [Promises](#promises) +- [Instanceof](#instanceof) +- [Functions](#functions) +- [Preprocess](#preprocess) +- [Custom schemas](#custom-schemas) +- [Schema methods](#schema-methods) + - [`.parse`](#parse) + - [`.parseAsync`](#parseasync) + - [`.safeParse`](#safeparse) + - [`.safeParseAsync`](#safeparseasync) + - [`.refine`](#refine) + - [Arguments](#arguments) + - [Customize error path](#customize-error-path) + - [Asynchronous refinements](#asynchronous-refinements) + - [Relationship to transforms](#relationship-to-transforms) + - [`.superRefine`](#superrefine) + - [Abort early](#abort-early) + - [Type refinements](#type-refinements) + - [`.transform`](#transform) + - [Chaining order](#chaining-order) + - [Validating during transform](#validating-during-transform) + - [Relationship to refinements](#relationship-to-refinements) + - [Async transforms](#async-transforms) + - [`.default`](#default) + - [`.describe`](#describe) + - [`.catch`](#catch) + - [`.optional`](#optional) + - [`.nullable`](#nullable) + - [`.nullish`](#nullish) + - [`.array`](#array) + - [`.promise`](#promise) + - [`.or`](#or) + - [`.and`](#and) + - [`.brand`](#brand) + - [`.readonly`](#readonly) + - [`.pipe`](#pipe) +- [Guides and concepts](#guides-and-concepts) + - [Type inference](#type-inference) + - [Writing generic functions](#writing-generic-functions) + - [Inferring the inferred type](#inferring-the-inferred-type) + - [Constraining allowable inputs](#constraining-allowable-inputs) + - [Error handling](#error-handling) + - [Error formatting](#error-formatting) +- [Comparison](#comparison) + - [Joi](#joi) + - [Yup](#yup) + - [io-ts](#io-ts) + - [Runtypes](#runtypes) + - [Ow](#ow) +- [Changelog](#changelog) + +
+ +## Introduction + +Zod is a TypeScript-first schema declaration and validation library. I'm using the term "schema" to broadly refer to any data type, from a simple `string` to a complex nested object. + +Zod is designed to be as developer-friendly as possible. The goal is to eliminate duplicative type declarations. With Zod, you declare a validator _once_ and Zod will automatically infer the static TypeScript type. It's easy to compose simpler types into complex data structures. + +Some other great aspects: + +- Zero dependencies +- Works in Node.js and all modern browsers +- Tiny: 8kb minified + zipped +- Immutable: methods (e.g. `.optional()`) return a new instance +- Concise, chainable interface +- Functional approach: [parse, don't validate](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/) +- Works with plain JavaScript too! You don't need to use TypeScript. + +
+ +## Sponsors + +Sponsorship at any level is appreciated and encouraged. If you built a paid product using Zod, consider one of the [corporate tiers](https://github.com/sponsors/colinhacks). + +
+ +

Platinum

+ + + + + +
+

+

+ + + + CodeRabbit logo + + +
+ Cut code review time & bugs in half +
+ coderabbit.ai +

+

+
+ +
+ +

Gold

+ + + + + + + + + + + + + + +
+

+

+ + + + Courier logo + + +
+ The API platform for sending notifications +
+ courier.com +

+

+
+

+

+ + + + LibLab + + +
+ Generate better SDKs for your APIs +
+ liblab.com +

+

+
+

+

+ + + + Neon + + +
+ Serverless Postgres — Ship faster +
+ neon.tech +

+

+
+

+

+ + + + Retool + + +
+ Build AI apps and workflows with Retool AI +
+ retool.com +

+

+
+

+

+ + + + stainless + + +
+ Generate best-in-class SDKs +
+ stainless.com +

+

+
+

+

+ + + + speakeasy + + +
+ SDKs & Terraform providers for your API +
+ speakeasy.com +

+

+
+ +
+ +

Silver

+ + + + + + + + + + + + + + + + + + + + + + +
+ Nitric +
+ Nitric +
+ PropelAuth +
+ PropelAuth +
+ Cerbos +
+ Cerbos +
+ Scalar.com logo +
+ Scalar +
+ Trigger.dev logo +
+ Trigger.dev +
+ Transloadit logo +
+ Transloadit +
+ Infisical logo +
+ Infisical +
+ Whop logo +
+ Whop +
+ CryptoJobsList logo +
+ CryptoJobsList +
+ Plain logo +
+ Plain. +
+ Inngest logo +
+ Inngest +
+ Storyblok CMS +
+ Storyblok +
+ Mux logo +
+ Mux +
+ Cybozu logo +
+ Cybozu +
+ Juno logo +
+ Juno +
+ +
+ +

Bronze

+ + + + + + + + + + + + + + + + + + +
+ + + + val town logo + + + + + route4me logo + + + + Encore.dev logo + + + + Replay.io logo + +
+ + Numeric logo + + + + Marcato Partners + + + + + + + + + +
+ + Bamboo Creative logo + + + + Jason Laster + +
+ + + +### Ecosystem + +There are a growing number of tools that are built atop or support Zod natively! If you've built a tool or library on top of Zod, tell me about it [on Twitter](https://twitter.com/colinhacks) or [start a Discussion](https://github.com/colinhacks/zod/discussions). I'll add it below and tweet it out. + +#### Resources + +- [Total TypeScript Zod Tutorial](https://www.totaltypescript.com/tutorials/zod) by [@mattpocockuk](https://twitter.com/mattpocockuk) +- [Fixing TypeScript's Blindspot: Runtime Typechecking](https://www.youtube.com/watch?v=rY_XqfSHock) by [@jherr](https://twitter.com/jherr) + +#### API libraries + +- [`tRPC`](https://github.com/trpc/trpc): Build end-to-end typesafe APIs without GraphQL. +- [`@anatine/zod-nestjs`](https://github.com/anatine/zod-plugins/tree/main/packages/zod-nestjs): Helper methods for using Zod in a NestJS project. +- [`zod-endpoints`](https://github.com/flock-community/zod-endpoints): Contract-first strictly typed endpoints with Zod. OpenAPI compatible. +- [`zhttp`](https://github.com/evertdespiegeleer/zhttp): An OpenAPI compatible, strictly typed http library with Zod input and response validation. +- [`domain-functions`](https://github.com/SeasonedSoftware/domain-functions/): Decouple your business logic from your framework using composable functions. With first-class type inference from end to end powered by Zod schemas. +- [`@zodios/core`](https://github.com/ecyrbe/zodios): A typescript API client with runtime and compile time validation backed by axios and zod. +- [`express-zod-api`](https://github.com/RobinTail/express-zod-api): Build Express-based APIs with I/O schema validation and custom middlewares. +- [`tapiduck`](https://github.com/sumukhbarve/monoduck/blob/main/src/tapiduck/README.md): End-to-end typesafe JSON APIs with Zod and Express; a bit like tRPC, but simpler. +- [`koa-zod-router`](https://github.com/JakeFenley/koa-zod-router): Create typesafe routes in Koa with I/O validation using Zod. +- [`zod-sockets`](https://github.com/RobinTail/zod-sockets): Zod-powered Socket.IO microframework with I/O validation and built-in AsyncAPI specs +- [`oas-tszod-gen`](https://github.com/inkognitro/oas-tszod-gen): Client SDK code generator to convert OpenApi v3 specifications into TS endpoint caller functions with Zod types. +- [`GQLoom`](https://github.com/modevol-com/gqloom): Weave GraphQL schema and resolvers using Zod. +- [`oRPC`](https://github.com/unnoq/orpc): Typesafe APIs Made Simple + +#### Form integrations + +- [`react-hook-form`](https://github.com/react-hook-form/resolvers#zod): A first-party Zod resolver for React Hook Form. +- [`TanStack Form`](https://github.com/TanStack/form): Headless, performant, and type-safe form state management for TS/JS, React, Vue, Angular, Solid, and Lit +- [`zod-validation-error`](https://github.com/causaly/zod-validation-error): Generate user-friendly error messages from `ZodError`s. +- [`zod-formik-adapter`](https://github.com/robertLichtnow/zod-formik-adapter): A community-maintained Formik adapter for Zod. +- [`react-zorm`](https://github.com/esamattis/react-zorm): Standalone `
` generation and validation for React using Zod. +- [`zodix`](https://github.com/rileytomasek/zodix): Zod utilities for FormData and URLSearchParams in Remix loaders and actions. +- [`conform`](https://conform.guide/api/zod/parseWithZod): A typesafe form validation library for progressive enhancement of HTML forms. Works with Remix and Next.js. +- [`remix-params-helper`](https://github.com/kiliman/remix-params-helper): Simplify integration of Zod with standard URLSearchParams and FormData for Remix apps. +- [`formik-validator-zod`](https://github.com/glazy/formik-validator-zod): Formik-compliant validator library that simplifies using Zod with Formik. +- [`zod-i18n-map`](https://github.com/aiji42/zod-i18n): Useful for translating Zod error messages. +- [`@modular-forms/solid`](https://github.com/fabian-hiller/modular-forms): Modular form library for SolidJS that supports Zod for validation. +- [`houseform`](https://github.com/crutchcorn/houseform/): A React form library that uses Zod for validation. +- [`sveltekit-superforms`](https://github.com/ciscoheat/sveltekit-superforms): Supercharged form library for SvelteKit with Zod validation. +- [`mobx-zod-form`](https://github.com/MonoidDev/mobx-zod-form): Data-first form builder based on MobX & Zod. +- [`@vee-validate/zod`](https://github.com/logaretm/vee-validate/tree/main/packages/zod): Form library for Vue.js with Zod schema validation. +- [`zod-form-renderer`](https://github.com/thepeaklab/zod-form-renderer): Auto-infer form fields from zod schema and render them with react-hook-form with E2E type safety. +- [`antd-zod`](https://github.com/MrBr/antd-zod): Zod adapter for Ant Design form fields validation. +- [`frrm`](https://github.com/schalkventer/frrm): Tiny 0.5kb Zod-based, HTML form abstraction that goes brr. + +#### Zod to X + +- [`zod-to-ts`](https://github.com/sachinraja/zod-to-ts): Generate TypeScript definitions from Zod schemas. +- [`zod-to-json-schema`](https://github.com/StefanTerdell/zod-to-json-schema): Convert your Zod schemas into [JSON Schemas](https://json-schema.org/). +- [`@anatine/zod-openapi`](https://github.com/anatine/zod-plugins/tree/main/packages/zod-openapi): Converts a Zod schema to an OpenAPI v3.x `SchemaObject`. +- [`zod-fast-check`](https://github.com/DavidTimms/zod-fast-check): Generate `fast-check` arbitraries from Zod schemas. +- [`zod-dto`](https://github.com/kbkk/abitia/tree/master/packages/zod-dto): Generate Nest.js DTOs from a Zod schema. +- [`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod): Create Fastify type providers from Zod schemas. +- [`zod-to-openapi`](https://github.com/asteasolutions/zod-to-openapi): Generate full OpenAPI (Swagger) docs from Zod, including schemas, endpoints & parameters. +- [`nestjs-graphql-zod`](https://github.com/incetarik/nestjs-graphql-zod): Generates NestJS GraphQL model classes from Zod schemas. Provides GraphQL method decorators working with Zod schemas. +- [`zod-openapi`](https://github.com/samchungy/zod-openapi): Create full OpenAPI v3.x documentation from Zod schemas. +- [`fastify-zod-openapi`](https://github.com/samchungy/fastify-zod-openapi): Fastify type provider, validation, serialization and @fastify/swagger support for Zod schemas. +- [`typeschema`](https://typeschema.com/): Universal adapter for schema validation. +- [`zodex`](https://github.com/commonbaseapp/zodex): (De)serialization for zod schemas + +#### X to Zod + +- [`ts-to-zod`](https://github.com/fabien0102/ts-to-zod): Convert TypeScript definitions into Zod schemas. +- [`@runtyping/zod`](https://github.com/johngeorgewright/runtyping): Generate Zod from static types & JSON schema. +- [`json-schema-to-zod`](https://github.com/StefanTerdell/json-schema-to-zod): Convert your [JSON Schemas](https://json-schema.org/) into Zod schemas. [Live demo](https://StefanTerdell.github.io/json-schema-to-zod-react/). +- [`json-to-zod`](https://github.com/rsinohara/json-to-zod): Convert JSON objects into Zod schemas. [Live demo](https://rsinohara.github.io/json-to-zod-react/). +- [`graphql-codegen-typescript-validation-schema`](https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema): GraphQL Code Generator plugin to generate form validation schema from your GraphQL schema. +- [`zod-prisma`](https://github.com/CarterGrimmeisen/zod-prisma): Generate Zod schemas from your Prisma schema. +- [`Supervillain`](https://github.com/Southclaws/supervillain): Generate Zod schemas from your Go structs. +- [`prisma-zod-generator`](https://github.com/omar-dulaimi/prisma-zod-generator): Emit Zod schemas from your Prisma schema. +- [`drizzle-zod`](https://orm.drizzle.team/docs/zod): Emit Zod schemas from your Drizzle schema. +- [`prisma-trpc-generator`](https://github.com/omar-dulaimi/prisma-trpc-generator): Emit fully implemented tRPC routers and their validation schemas using Zod. +- [`zod-prisma-types`](https://github.com/chrishoermann/zod-prisma-types) Create Zod types from your Prisma models. +- [`quicktype`](https://app.quicktype.io/): Convert JSON objects and JSON schemas into Zod schemas. +- [`@sanity-typed/zod`](https://github.com/saiichihashimoto/sanity-typed/tree/main/packages/zod): Generate Zod Schemas from [Sanity Schemas](https://www.sanity.io/docs/schema-types). +- [`java-to-zod`](https://github.com/ivangreene/java-to-zod): Convert POJOs to Zod schemas +- [`Orval`](https://github.com/anymaniax/orval): Generate Zod schemas from OpenAPI schemas +- [`Kubb`](https://github.com/kubb-labs/kubb): Generate SDKs and Zod schemas from your OpenAPI schemas + +#### Mocking + +- [`@anatine/zod-mock`](https://github.com/anatine/zod-plugins/tree/main/packages/zod-mock): Generate mock data from a Zod schema. Powered by [faker.js](https://github.com/faker-js/faker). +- [`zod-mocking`](https://github.com/dipasqualew/zod-mocking): Generate mock data from your Zod schemas. +- [`zod-fixture`](https://github.com/timdeschryver/zod-fixture): Use your zod schemas to automate the generation of non-relevant test fixtures in a deterministic way. +- [`zocker`](https://zocker.sigrist.dev): Generate plausible mock-data from your schemas. +- [`zodock`](https://github.com/ItMaga/zodock) Generate mock data based on Zod schemas. +- [`zod-schema-faker`](https://github.com/soc221b/zod-schema-faker) Generates mock data from Zod schemas. Powered by [@faker-js/faker](https://github.com/faker-js/faker) and [randexp.js](https://github.com/fent/randexp.js) + +#### Powered by Zod + +- [`freerstore`](https://github.com/JacobWeisenburger/freerstore): Firestore cost optimizer. +- [`slonik`](https://github.com/gajus/slonik/tree/gajus/add-zod-validation-backwards-compatible#runtime-validation-and-static-type-inference): Node.js Postgres client with strong Zod integration. +- [`schemql`](https://github.com/a2lix/schemql): Enhances your SQL workflow by combining raw SQL with targeted type safety and schema validation. +- [`soly`](https://github.com/mdbetancourt/soly): Create CLI applications with zod. +- [`pastel`](https://github.com/vadimdemedes/pastel): Create CLI applications with react, zod, and ink. +- [`zod-xlsx`](https://github.com/sidwebworks/zod-xlsx): A xlsx based resource validator using Zod schemas. +- [`znv`](https://github.com/lostfictions/znv): Type-safe environment parsing and validation for Node.js with Zod schemas. +- [`zod-config`](https://github.com/alexmarqs/zod-config): Load configurations across multiple sources with flexible adapters, ensuring type safety with Zod. +- [`unplugin-environment`](https://github.com/r17x/js/tree/main/packages/unplugin-environment#readme): A plugin for loading enviroment variables safely with schema validation, simple with virtual module, type-safe with intellisense, and better DX 🔥 🚀 👷. Powered by Zod. +- [`zod-struct`](https://codeberg.org/reesericci/zod-struct): Create runtime-checked structs with Zod. +- [`zod-csv`](https://github.com/bartoszgolebiowski/zod-csv): Validation helpers for zod for parsing CSV data. +- [`fullproduct.dev`](https://fullproduct.dev?identity=freelancers&v=z3): Universal Expo + Next.js App Starter that uses Zod schemas as the single source of truth to keep generated MDX docs, GraphQL, database models, forms, and fetcher functions in sync. + +#### Utilities for Zod + +- [`zod_utilz`](https://github.com/JacobWeisenburger/zod_utilz): Framework agnostic utilities for Zod. +- [`zod-playground`](https://github.com/marilari88/zod-playground): A tool for learning and testing Zod schema validation functionalities. [Link](https://zod-playground.vercel.app/). +- [`zod-sandbox`](https://github.com/nereumelo/zod-sandbox): Controlled environment for testing zod schemas. [Live demo](https://zod-sandbox.vercel.app/). +- [`zod-dev`](https://github.com/schalkventer/zod-dev): Conditionally disables Zod runtime parsing in production. +- [`zod-accelerator`](https://github.com/duplojs/duplojs-zod-accelerator): Accelerates Zod's throughput up to ~100x. + +
+ +## Installation + +### Requirements + +- TypeScript 4.5+! +- You must enable `strict` mode in your `tsconfig.json`. This is a best practice for all TypeScript projects. + + ```ts + // tsconfig.json + { + // ... + "compilerOptions": { + // ... + "strict": true + } + } + ``` + +### From `npm` + +```sh +npm install zod # npm +deno add npm:zod # deno +yarn add zod # yarn +bun add zod # bun +pnpm add zod # pnpm +``` + +Zod also publishes a canary version on every commit. To install the canary: + +```sh +npm install zod@canary # npm +deno add npm:zod@canary # deno +yarn add zod@canary # yarn +bun add zod@canary # bun +pnpm add zod@canary # pnpm +``` + +> The rest of this README assumes you are using npm and importing directly from the `"zod"` package. + +
+ +## Basic usage + +Creating a simple string schema + +```ts +import { z } from "zod"; + +// creating a schema for strings +const mySchema = z.string(); + +// parsing +mySchema.parse("tuna"); // => "tuna" +mySchema.parse(12); // => throws ZodError + +// "safe" parsing (doesn't throw error if validation fails) +mySchema.safeParse("tuna"); // => { success: true; data: "tuna" } +mySchema.safeParse(12); // => { success: false; error: ZodError } +``` + +Creating an object schema + +```ts +import { z } from "zod"; + +const User = z.object({ + username: z.string(), +}); + +User.parse({ username: "Ludwig" }); + +// extract the inferred type +type User = z.infer; +// { username: string } +``` + +
+ +## Primitives + +```ts +import { z } from "zod"; + +// primitive values +z.string(); +z.number(); +z.bigint(); +z.boolean(); +z.date(); +z.symbol(); + +// empty types +z.undefined(); +z.null(); +z.void(); // accepts undefined + +// catch-all types +// allows any value +z.any(); +z.unknown(); + +// never type +// allows no values +z.never(); +``` + +
+ +## Coercion for primitives + +Zod now provides a more convenient way to coerce primitive values. + +```ts +const schema = z.coerce.string(); +schema.parse("tuna"); // => "tuna" +schema.parse(12); // => "12" +``` + +During the parsing step, the input is passed through the `String()` function, which is a JavaScript built-in for coercing data into strings. + +```ts +schema.parse(12); // => "12" +schema.parse(true); // => "true" +schema.parse(undefined); // => "undefined" +schema.parse(null); // => "null" +``` + +The returned schema is a normal `ZodString` instance so you can use all string methods. + +```ts +z.coerce.string().email().min(5); +``` + +**How coercion works** + +All primitive types support coercion. Zod coerces all inputs using the built-in constructors: `String(input)`, `Number(input)`, `new Date(input)`, etc. + +```ts +z.coerce.string(); // String(input) +z.coerce.number(); // Number(input) +z.coerce.boolean(); // Boolean(input) +z.coerce.bigint(); // BigInt(input) +z.coerce.date(); // new Date(input) +``` + +**Note** — Boolean coercion with `z.coerce.boolean()` may not work how you expect. Any [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) value is coerced to `true`, and any [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) value is coerced to `false`. + +```ts +const schema = z.coerce.boolean(); // Boolean(input) + +schema.parse("tuna"); // => true +schema.parse("true"); // => true +schema.parse("false"); // => true +schema.parse(1); // => true +schema.parse([]); // => true + +schema.parse(0); // => false +schema.parse(""); // => false +schema.parse(undefined); // => false +schema.parse(null); // => false +``` + +For more control over coercion logic, consider using [`z.preprocess`](#preprocess) or [`z.pipe()`](#pipe). + +
+ +## Literals + +Literal schemas represent a [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types), like `"hello world"` or `5`. + +```ts +const tuna = z.literal("tuna"); +const twelve = z.literal(12); +const twobig = z.literal(2n); // bigint literal +const tru = z.literal(true); + +const terrificSymbol = Symbol("terrific"); +const terrific = z.literal(terrificSymbol); + +// retrieve literal value +tuna.value; // "tuna" +``` + +> Currently there is no support for Date literals in Zod. If you have a use case for this feature, please file an issue. + +
+ +## Strings + +Zod includes a handful of string-specific validations. + +```ts +// validations +z.string().max(5); +z.string().min(5); +z.string().length(5); +z.string().email(); +z.string().url(); +z.string().emoji(); +z.string().uuid(); +z.string().nanoid(); +z.string().cuid(); +z.string().cuid2(); +z.string().ulid(); +z.string().regex(regex); +z.string().includes(string); +z.string().startsWith(string); +z.string().endsWith(string); +z.string().datetime(); // ISO 8601; by default only `Z` timezone allowed +z.string().ip(); // defaults to allow both IPv4 and IPv6 +z.string().cidr(); // defaults to allow both IPv4 and IPv6 + +// transforms +z.string().trim(); // trim whitespace +z.string().toLowerCase(); // toLowerCase +z.string().toUpperCase(); // toUpperCase + +// added in Zod 3.23 +z.string().date(); // ISO date format (YYYY-MM-DD) +z.string().time(); // ISO time format (HH:mm:ss[.SSSSSS] or HH:mm) +z.string().duration(); // ISO 8601 duration +z.string().base64(); +``` + +> Check out [validator.js](https://github.com/validatorjs/validator.js) for a bunch of other useful string validation functions that can be used in conjunction with [Refinements](#refine). + +You can customize some common error messages when creating a string schema. + +```ts +const name = z.string({ + required_error: "Name is required", + invalid_type_error: "Name must be a string", +}); +``` + +When using validation methods, you can pass in an additional argument to provide a custom error message. + +```ts +z.string().min(5, { message: "Must be 5 or more characters long" }); +z.string().max(5, { message: "Must be 5 or fewer characters long" }); +z.string().length(5, { message: "Must be exactly 5 characters long" }); +z.string().email({ message: "Invalid email address" }); +z.string().url({ message: "Invalid url" }); +z.string().emoji({ message: "Contains non-emoji characters" }); +z.string().uuid({ message: "Invalid UUID" }); +z.string().includes("tuna", { message: "Must include tuna" }); +z.string().startsWith("https://", { message: "Must provide secure URL" }); +z.string().endsWith(".com", { message: "Only .com domains allowed" }); +z.string().datetime({ message: "Invalid datetime string! Must be UTC." }); +z.string().date({ message: "Invalid date string!" }); +z.string().time({ message: "Invalid time string!" }); +z.string().ip({ message: "Invalid IP address" }); +z.string().cidr({ message: "Invalid CIDR" }); +``` + +### Datetimes + +As you may have noticed, Zod string includes a few date/time related validations. These validations are regular expression based, so they are not as strict as a full date/time library. However, they are very convenient for validating user input. + +The `z.string().datetime()` method enforces ISO 8601; default is no timezone offsets and arbitrary sub-second decimal precision. Seconds may be omitted if precision is not set. + +```ts +const datetime = z.string().datetime(); + +datetime.parse("2020-01-01T00:00:00Z"); // pass +datetime.parse("2020-01-01T00:00:00.123Z"); // pass +datetime.parse("2020-01-01T00:00:00.123456Z"); // pass (arbitrary precision) +datetime.parse("2020-01-01T00:00Z"); // pass (hours and minutes only) +datetime.parse("2020-01-01T00:00:00+02:00"); // fail (no offsets allowed) +``` + +Timezone offsets can be allowed by setting the `offset` option to `true`. + +```ts +const datetime = z.string().datetime({ offset: true }); + +datetime.parse("2020-01-01T00:00:00+02:00"); // pass +datetime.parse("2020-01-01T00:00+02:00"); // pass +datetime.parse("2020-01-01T00:00:00.123+02:00"); // pass (millis optional) +datetime.parse("2020-01-01T00:00:00.123+0200"); // pass (millis optional) +datetime.parse("2020-01-01T00:00:00.123+02"); // pass (only offset hours) +datetime.parse("2020-01-01T00:00:00Z"); // pass (Z still supported) +``` + +Allow unqualified (timezone-less) datetimes with the `local` flag. + +```ts +const schema = z.string().datetime({ local: true }); +schema.parse("2020-01-01T00:00:00"); // pass +schema.parse("2020-01-01T00:00"); // pass +``` + +You can additionally constrain the allowable `precision`. By default, arbitrary sub-second precision is supported (but optional). + +```ts +const datetime = z.string().datetime({ precision: 3 }); + +datetime.parse("2020-01-01T00:00:00.123Z"); // pass +datetime.parse("2020-01-01T00:00:00Z"); // fail +datetime.parse("2020-01-01T00:00Z"); // fail +datetime.parse("2020-01-01T00:00:00.123456Z"); // fail +``` + +### Dates + +> Added in Zod 3.23 + +The `z.string().date()` method validates strings in the format `YYYY-MM-DD`. + +```ts +const date = z.string().date(); + +date.parse("2020-01-01"); // pass +date.parse("2020-1-1"); // fail +date.parse("2020-01-32"); // fail +``` + +### Times + +> Added in Zod 3.23 + +The `z.string().time()` method validates strings in the format `HH:MM` or `HH:MM:SS[.s+]`. The second can include arbitrary decimal precision. It does not allow timezone offsets of any kind. + +```ts +const time = z.string().time(); + +time.parse("00:00:00"); // pass +time.parse("09:52:31"); // pass +time.parse("09:52"); // pass +time.parse("23:59:59.9999999"); // pass (arbitrary precision) + +time.parse("00:00:00.123Z"); // fail (no `Z` allowed) +time.parse("00:00:00.123+02:00"); // fail (no offsets allowed) +``` + +You can set the `precision` option to constrain the allowable decimal precision. + +```ts +const time = z.string().time({ precision: 3 }); + +time.parse("00:00:00.123"); // pass +time.parse("00:00:00.123456"); // fail +time.parse("00:00:00"); // fail +time.parse("00:00"); // fail +``` + +### IP addresses + +By default `.ip()` allows both IPv4 and IPv6. + +```ts +const ip = z.string().ip(); + +ip.parse("192.168.1.1"); // pass +ip.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // pass +ip.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:192.168.1.1"); // pass + +ip.parse("256.1.1.1"); // fail +ip.parse("84d5:51a0:9114:gggg:4cfa:f2d7:1f12:7003"); // fail +``` + +You can additionally set the IP `version`. + +```ts +const ipv4 = z.string().ip({ version: "v4" }); +ipv4.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // fail + +const ipv6 = z.string().ip({ version: "v6" }); +ipv6.parse("192.168.1.1"); // fail +``` + +### IP ranges (CIDR) + +Validate IP address ranges specified with [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing). By default, `.cidr()` allows both IPv4 and IPv6. + +```ts +const cidr = z.string().cidr(); +cidr.parse("192.168.0.0/24"); // pass +cidr.parse("2001:db8::/32"); // pass +``` + +You can specify a version with the `version` parameter. + +```ts +const ipv4Cidr = z.string().cidr({ version: "v4" }); +ipv4Cidr.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // fail + +const ipv6Cidr = z.string().cidr({ version: "v6" }); +ipv6Cidr.parse("192.168.1.1"); // fail +``` + +
+ +## Numbers + +You can customize certain error messages when creating a number schema. + +```ts +const age = z.number({ + required_error: "Age is required", + invalid_type_error: "Age must be a number", +}); +``` + +Zod includes a handful of number-specific validations. + +```ts +z.number().gt(5); +z.number().gte(5); // alias .min(5) +z.number().lt(5); +z.number().lte(5); // alias .max(5) + +z.number().int(); // value must be an integer + +z.number().positive(); // > 0 +z.number().nonnegative(); // >= 0 +z.number().negative(); // < 0 +z.number().nonpositive(); // <= 0 + +z.number().multipleOf(5); // Evenly divisible by 5. Alias .step(5) + +z.number().finite(); // value must be finite, not Infinity or -Infinity +z.number().safe(); // value must be between Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER +``` + +Optionally, you can pass in a second argument to provide a custom error message. + +```ts +z.number().lte(5, { message: "this👏is👏too👏big" }); +``` + +
+ +## BigInts + +Zod includes a handful of bigint-specific validations. + +```ts +z.bigint().gt(5n); +z.bigint().gte(5n); // alias `.min(5n)` +z.bigint().lt(5n); +z.bigint().lte(5n); // alias `.max(5n)` + +z.bigint().positive(); // > 0n +z.bigint().nonnegative(); // >= 0n +z.bigint().negative(); // < 0n +z.bigint().nonpositive(); // <= 0n + +z.bigint().multipleOf(5n); // Evenly divisible by 5n. +``` + +
+ +## NaNs + +You can customize certain error messages when creating a nan schema. + +```ts +const isNaN = z.nan({ + required_error: "isNaN is required", + invalid_type_error: "isNaN must be 'not a number'", +}); +``` + +
+ +## Booleans + +You can customize certain error messages when creating a boolean schema. + +```ts +const isActive = z.boolean({ + required_error: "isActive is required", + invalid_type_error: "isActive must be a boolean", +}); +``` + +
+ +## Dates + +Use z.date() to validate `Date` instances. + +```ts +z.date().safeParse(new Date()); // success: true +z.date().safeParse("2022-01-12T00:00:00.000Z"); // success: false +``` + +You can customize certain error messages when creating a date schema. + +```ts +const myDateSchema = z.date({ + required_error: "Please select a date and time", + invalid_type_error: "That's not a date!", +}); +``` + +Zod provides a handful of date-specific validations. + +```ts +z.date().min(new Date("1900-01-01"), { message: "Too old" }); +z.date().max(new Date(), { message: "Too young!" }); +``` + +**Coercion to Date** + +Since [zod 3.20](https://github.com/colinhacks/zod/releases/tag/v3.20), use [`z.coerce.date()`](#coercion-for-primitives) to pass the input through `new Date(input)`. + +```ts +const dateSchema = z.coerce.date(); +type DateSchema = z.infer; +// type DateSchema = Date + +/* valid dates */ +console.log(dateSchema.safeParse("2023-01-10T00:00:00.000Z").success); // true +console.log(dateSchema.safeParse("2023-01-10").success); // true +console.log(dateSchema.safeParse("1/10/23").success); // true +console.log(dateSchema.safeParse(new Date("1/10/23")).success); // true + +/* invalid dates */ +console.log(dateSchema.safeParse("2023-13-10").success); // false +console.log(dateSchema.safeParse("0000-00-00").success); // false +``` + +For older zod versions, use [`z.preprocess`](#preprocess) like [described in this thread](https://github.com/colinhacks/zod/discussions/879#discussioncomment-2036276). + +
+ +## Zod enums + +```ts +const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]); +type FishEnum = z.infer; +// 'Salmon' | 'Tuna' | 'Trout' +``` + +`z.enum` is a Zod-native way to declare a schema with a fixed set of allowable _string_ values. Pass the array of values directly into `z.enum()`. Alternatively, use `as const` to define your enum values as a tuple of strings. See the [const assertion docs](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions) for details. + +```ts +const VALUES = ["Salmon", "Tuna", "Trout"] as const; +const FishEnum = z.enum(VALUES); +``` + +This is not allowed, since Zod isn't able to infer the exact values of each element. + +```ts +const fish = ["Salmon", "Tuna", "Trout"]; +const FishEnum = z.enum(fish); +``` + +**`.enum`** + +To get autocompletion with a Zod enum, use the `.enum` property of your schema: + +```ts +FishEnum.enum.Salmon; // => autocompletes + +FishEnum.enum; +/* +=> { + Salmon: "Salmon", + Tuna: "Tuna", + Trout: "Trout", +} +*/ +``` + +You can also retrieve the list of options as a tuple with the `.options` property: + +```ts +FishEnum.options; // ["Salmon", "Tuna", "Trout"]; +``` + +**`.exclude/.extract()`** + +You can create subsets of a Zod enum with the `.exclude` and `.extract` methods. + +```ts +const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]); +const SalmonAndTrout = FishEnum.extract(["Salmon", "Trout"]); +const TunaOnly = FishEnum.exclude(["Salmon", "Trout"]); +``` + +
+ +## Native enums + +Zod enums are the recommended approach to defining and validating enums. But if you need to validate against an enum from a third-party library (or you don't want to rewrite your existing enums) you can use `z.nativeEnum()`. + +**Numeric enums** + +```ts +enum Fruits { + Apple, + Banana, +} + +const FruitEnum = z.nativeEnum(Fruits); +type FruitEnum = z.infer; // Fruits + +FruitEnum.parse(Fruits.Apple); // passes +FruitEnum.parse(Fruits.Banana); // passes +FruitEnum.parse(0); // passes +FruitEnum.parse(1); // passes +FruitEnum.parse(3); // fails +``` + +**String enums** + +```ts +enum Fruits { + Apple = "apple", + Banana = "banana", + Cantaloupe, // you can mix numerical and string enums +} + +const FruitEnum = z.nativeEnum(Fruits); +type FruitEnum = z.infer; // Fruits + +FruitEnum.parse(Fruits.Apple); // passes +FruitEnum.parse(Fruits.Cantaloupe); // passes +FruitEnum.parse("apple"); // passes +FruitEnum.parse("banana"); // passes +FruitEnum.parse(0); // passes +FruitEnum.parse("Cantaloupe"); // fails +``` + +**Const enums** + +The `.nativeEnum()` function works for `as const` objects as well. ⚠️ `as const` requires TypeScript 3.4+! + +```ts +const Fruits = { + Apple: "apple", + Banana: "banana", + Cantaloupe: 3, +} as const; + +const FruitEnum = z.nativeEnum(Fruits); +type FruitEnum = z.infer; // "apple" | "banana" | 3 + +FruitEnum.parse("apple"); // passes +FruitEnum.parse("banana"); // passes +FruitEnum.parse(3); // passes +FruitEnum.parse("Cantaloupe"); // fails +``` + +You can access the underlying object with the `.enum` property: + +```ts +FruitEnum.enum.Apple; // "apple" +``` + +
+ +## Optionals + +You can make any schema optional with `z.optional()`. This wraps the schema in a `ZodOptional` instance and returns the result. + +```ts +const schema = z.optional(z.string()); + +schema.parse(undefined); // => returns undefined +type A = z.infer; // string | undefined +``` + +For convenience, you can also call the `.optional()` method on an existing schema. + +```ts +const user = z.object({ + username: z.string().optional(), +}); +type C = z.infer; // { username?: string | undefined }; +``` + +You can extract the wrapped schema from a `ZodOptional` instance with `.unwrap()`. + +```ts +const stringSchema = z.string(); +const optionalString = stringSchema.optional(); +optionalString.unwrap() === stringSchema; // true +``` + +
+ +## Nullables + +Similarly, you can create nullable types with `z.nullable()`. + +```ts +const nullableString = z.nullable(z.string()); +nullableString.parse("asdf"); // => "asdf" +nullableString.parse(null); // => null +``` + +Or use the `.nullable()` method. + +```ts +const E = z.string().nullable(); // equivalent to nullableString +type E = z.infer; // string | null +``` + +Extract the inner schema with `.unwrap()`. + +```ts +const stringSchema = z.string(); +const nullableString = stringSchema.nullable(); +nullableString.unwrap() === stringSchema; // true +``` + +
+ +## Objects + +```ts +// all properties are required by default +const Dog = z.object({ + name: z.string(), + age: z.number(), +}); + +// extract the inferred type like this +type Dog = z.infer; + +// equivalent to: +type Dog = { + name: string; + age: number; +}; +``` + +### `.shape` + +Use `.shape` to access the schemas for a particular key. + +```ts +Dog.shape.name; // => string schema +Dog.shape.age; // => number schema +``` + +### `.keyof` + +Use `.keyof` to create a `ZodEnum` schema from the keys of an object schema. + +```ts +const keySchema = Dog.keyof(); +keySchema; // ZodEnum<["name", "age"]> +``` + +### `.extend` + +You can add additional fields to an object schema with the `.extend` method. + +```ts +const DogWithBreed = Dog.extend({ + breed: z.string(), +}); +``` + +You can use `.extend` to overwrite fields! Be careful with this power! + +### `.merge` + +Equivalent to `A.extend(B.shape)`. + +```ts +const BaseTeacher = z.object({ students: z.array(z.string()) }); +const HasID = z.object({ id: z.string() }); + +const Teacher = BaseTeacher.merge(HasID); +type Teacher = z.infer; // => { students: string[], id: string } +``` + +> If the two schemas share keys, the properties of B overrides the property of A. The returned schema also inherits the "unknownKeys" policy (strip/strict/passthrough) and the catchall schema of B. + +### `.pick/.omit` + +Inspired by TypeScript's built-in `Pick` and `Omit` utility types, all Zod object schemas have `.pick` and `.omit` methods that return a modified version. Consider this Recipe schema: + +```ts +const Recipe = z.object({ + id: z.string(), + name: z.string(), + ingredients: z.array(z.string()), +}); +``` + +To only keep certain keys, use `.pick` . + +```ts +const JustTheName = Recipe.pick({ name: true }); +type JustTheName = z.infer; +// => { name: string } +``` + +To remove certain keys, use `.omit` . + +```ts +const NoIDRecipe = Recipe.omit({ id: true }); + +type NoIDRecipe = z.infer; +// => { name: string, ingredients: string[] } +``` + +### `.partial` + +Inspired by the built-in TypeScript utility type [Partial](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype), the `.partial` method makes all properties optional. + +Starting from this object: + +```ts +const user = z.object({ + email: z.string(), + username: z.string(), +}); +// { email: string; username: string } +``` + +We can create a partial version: + +```ts +const partialUser = user.partial(); +// { email?: string | undefined; username?: string | undefined } +``` + +You can also specify which properties to make optional: + +```ts +const optionalEmail = user.partial({ + email: true, +}); +/* +{ + email?: string | undefined; + username: string +} +*/ +``` + +### `.deepPartial` + +The `.partial` method is shallow — it only applies one level deep. There is also a "deep" version: + +```ts +const user = z.object({ + username: z.string(), + location: z.object({ + latitude: z.number(), + longitude: z.number(), + }), + strings: z.array(z.object({ value: z.string() })), +}); + +const deepPartialUser = user.deepPartial(); + +/* +{ + username?: string | undefined, + location?: { + latitude?: number | undefined; + longitude?: number | undefined; + } | undefined, + strings?: { value?: string}[] +} +*/ +``` + +> Important limitation: deep partials only work as expected in hierarchies of objects, arrays, and tuples. + +### `.required` + +Contrary to the `.partial` method, the `.required` method makes all properties required. + +Starting from this object: + +```ts +const user = z + .object({ + email: z.string(), + username: z.string(), + }) + .partial(); +// { email?: string | undefined; username?: string | undefined } +``` + +We can create a required version: + +```ts +const requiredUser = user.required(); +// { email: string; username: string } +``` + +You can also specify which properties to make required: + +```ts +const requiredEmail = user.required({ + email: true, +}); +/* +{ + email: string; + username?: string | undefined; +} +*/ +``` + +### `.passthrough` + +By default Zod object schemas strip out unrecognized keys during parsing. + +```ts +const person = z.object({ + name: z.string(), +}); + +person.parse({ + name: "bob dylan", + extraKey: 61, +}); +// => { name: "bob dylan" } +// extraKey has been stripped +``` + +Instead, if you want to pass through unknown keys, use `.passthrough()` . + +```ts +person.passthrough().parse({ + name: "bob dylan", + extraKey: 61, +}); +// => { name: "bob dylan", extraKey: 61 } +``` + +### `.strict` + +By default Zod object schemas strip out unrecognized keys during parsing. You can _disallow_ unknown keys with `.strict()` . If there are any unknown keys in the input, Zod will throw an error. + +```ts +const person = z + .object({ + name: z.string(), + }) + .strict(); + +person.parse({ + name: "bob dylan", + extraKey: 61, +}); +// => throws ZodError +``` + +### `.strip` + +You can use the `.strip` method to reset an object schema to the default behavior (stripping unrecognized keys). + +### `.catchall` + +You can pass a "catchall" schema into an object schema. All unknown keys will be validated against it. + +```ts +const person = z + .object({ + name: z.string(), + }) + .catchall(z.number()); + +person.parse({ + name: "bob dylan", + validExtraKey: 61, // works fine +}); + +person.parse({ + name: "bob dylan", + validExtraKey: false, // fails +}); +// => throws ZodError +``` + +Using `.catchall()` obviates `.passthrough()` , `.strip()` , or `.strict()`. All keys are now considered "known". + +
+ +## Arrays + +```ts +const stringArray = z.array(z.string()); + +// equivalent +const stringArray = z.string().array(); +``` + +Be careful with the `.array()` method. It returns a new `ZodArray` instance. This means the _order_ in which you call methods matters. For instance: + +```ts +z.string().optional().array(); // (string | undefined)[] +z.string().array().optional(); // string[] | undefined +``` + +### `.element` + +Use `.element` to access the schema for an element of the array. + +```ts +stringArray.element; // => string schema +``` + +### `.nonempty` + +If you want to ensure that an array contains at least one element, use `.nonempty()`. + +```ts +const nonEmptyStrings = z.string().array().nonempty(); +// the inferred type is now +// [string, ...string[]] + +nonEmptyStrings.parse([]); // throws: "Array cannot be empty" +nonEmptyStrings.parse(["Ariana Grande"]); // passes +``` + +You can optionally specify a custom error message: + +```ts +// optional custom error message +const nonEmptyStrings = z.string().array().nonempty({ + message: "Can't be empty!", +}); +``` + +### `.min/.max/.length` + +```ts +z.string().array().min(5); // must contain 5 or more items +z.string().array().max(5); // must contain 5 or fewer items +z.string().array().length(5); // must contain 5 items exactly +``` + +Unlike `.nonempty()` these methods do not change the inferred type. + +
+ +## Tuples + +Unlike arrays, tuples have a fixed number of elements and each element can have a different type. + +```ts +const athleteSchema = z.tuple([ + z.string(), // name + z.number(), // jersey number + z.object({ + pointsScored: z.number(), + }), // statistics +]); + +type Athlete = z.infer; +// type Athlete = [string, number, { pointsScored: number }] +``` + +A variadic ("rest") argument can be added with the `.rest` method. + +```ts +const variadicTuple = z.tuple([z.string()]).rest(z.number()); +const result = variadicTuple.parse(["hello", 1, 2, 3]); +// => [string, ...number[]]; +``` + +
+ +## Unions + +Zod includes a built-in `z.union` method for composing "OR" types. + +```ts +const stringOrNumber = z.union([z.string(), z.number()]); + +stringOrNumber.parse("foo"); // passes +stringOrNumber.parse(14); // passes +``` + +Zod will test the input against each of the "options" in order and return the first value that validates successfully. + +For convenience, you can also use the [`.or` method](#or): + +```ts +const stringOrNumber = z.string().or(z.number()); +``` + +**Optional string validation:** + +To validate an optional form input, you can union the desired string validation with an empty string [literal](#literals). + +This example validates an input that is optional but needs to contain a [valid URL](#strings): + +```ts +const optionalUrl = z.union([z.string().url().nullish(), z.literal("")]); + +console.log(optionalUrl.safeParse(undefined).success); // true +console.log(optionalUrl.safeParse(null).success); // true +console.log(optionalUrl.safeParse("").success); // true +console.log(optionalUrl.safeParse("https://zod.dev").success); // true +console.log(optionalUrl.safeParse("not a valid url").success); // false +``` + +
+ +## Discriminated unions + +A discriminated union is a union of object schemas that all share a particular key. + +```ts +type MyUnion = + | { status: "success"; data: string } + | { status: "failed"; error: Error }; +``` + +Such unions can be represented with the `z.discriminatedUnion` method. This enables faster evaluation, because Zod can check the _discriminator key_ (`status` in the example above) to determine which schema should be used to parse the input. This makes parsing more efficient and lets Zod report friendlier errors. + +With the basic union method, the input is tested against each of the provided "options", and in the case of invalidity, issues for all the "options" are shown in the zod error. On the other hand, the discriminated union allows for selecting just one of the "options", testing against it, and showing only the issues related to this "option". + +```ts +const myUnion = z.discriminatedUnion("status", [ + z.object({ status: z.literal("success"), data: z.string() }), + z.object({ status: z.literal("failed"), error: z.instanceof(Error) }), +]); + +myUnion.parse({ status: "success", data: "yippie ki yay" }); +``` + +You can extract a reference to the array of schemas with the `.options` property. + +```ts +myUnion.options; // [ZodObject<...>, ZodObject<...>] +``` + +To merge two or more discriminated unions, use `.options` with destructuring. + +```ts +const A = z.discriminatedUnion("status", [ + /* options */ +]); +const B = z.discriminatedUnion("status", [ + /* options */ +]); + +const AB = z.discriminatedUnion("status", [...A.options, ...B.options]); +``` + +
+ +## Records + +Record schemas are used to validate types such as `Record`. This is particularly useful for storing or caching items by ID. + + + +```ts +const User = z.object({ name: z.string() }); + +const UserStore = z.record(z.string(), User); +type UserStore = z.infer; +// => Record +``` + +The schema and inferred type can be used like so: + +```ts +const userStore: UserStore = {}; + +userStore["77d2586b-9e8e-4ecf-8b21-ea7e0530eadd"] = { + name: "Carlotta", +}; // passes + +userStore["77d2586b-9e8e-4ecf-8b21-ea7e0530eadd"] = { + whatever: "Ice cream sundae", +}; // TypeError +``` + +**A note on numerical keys** + +While `z.record(keyType, valueType)` is able to accept numerical key types and TypeScript's built-in Record type is `Record`, it's hard to represent the TypeScript type `Record` in Zod. + +As it turns out, TypeScript's behavior surrounding `[k: number]` is a little unintuitive: + +```ts +const testMap: { [k: number]: string } = { + 1: "one", +}; + +for (const key in testMap) { + console.log(`${key}: ${typeof key}`); +} +// prints: `1: string` +``` + +As you can see, JavaScript automatically casts all object keys to strings under the hood. Since Zod is trying to bridge the gap between static and runtime types, it doesn't make sense to provide a way of creating a record schema with numerical keys, since there's no such thing as a numerical key in runtime JavaScript. + +
+ +## Maps + +```ts +const stringNumberMap = z.map(z.string(), z.number()); + +type StringNumberMap = z.infer; +// type StringNumberMap = Map +``` + +
+ +## Sets + +```ts +const numberSet = z.set(z.number()); +type NumberSet = z.infer; +// type NumberSet = Set +``` + +Set schemas can be further constrained with the following utility methods. + +```ts +z.set(z.string()).nonempty(); // must contain at least one item +z.set(z.string()).min(5); // must contain 5 or more items +z.set(z.string()).max(5); // must contain 5 or fewer items +z.set(z.string()).size(5); // must contain 5 items exactly +``` + +
+ +## Intersections + +Intersections are useful for creating "logical AND" types. This is useful for intersecting two object types. + +```ts +const Person = z.object({ + name: z.string(), +}); + +const Employee = z.object({ + role: z.string(), +}); + +const EmployedPerson = z.intersection(Person, Employee); + +// equivalent to: +const EmployedPerson = Person.and(Employee); +``` + +Though in many cases, it is recommended to use `A.merge(B)` to merge two objects. The `.merge` method returns a new `ZodObject` instance, whereas `A.and(B)` returns a less useful `ZodIntersection` instance that lacks common object methods like `pick` and `omit`. + +```ts +const a = z.union([z.number(), z.string()]); +const b = z.union([z.number(), z.boolean()]); +const c = z.intersection(a, b); + +type c = z.infer; // => number +``` + + + + + +
+ +## Recursive types + +You can define a recursive schema in Zod, but because of a limitation of TypeScript, their type can't be statically inferred. Instead you'll need to define the type definition manually, and provide it to Zod as a "type hint". + +```ts +const baseCategorySchema = z.object({ + name: z.string(), +}); + +type Category = z.infer & { + subcategories: Category[]; +}; + +const categorySchema: z.ZodType = baseCategorySchema.extend({ + subcategories: z.lazy(() => categorySchema.array()), +}); + +categorySchema.parse({ + name: "People", + subcategories: [ + { + name: "Politicians", + subcategories: [ + { + name: "Presidents", + subcategories: [], + }, + ], + }, + ], +}); // passes +``` + +Thanks to [crasite](https://github.com/crasite) for this example. + +### ZodType with ZodEffects + +When using `z.ZodType` with `z.ZodEffects` ( +[`.refine`](https://github.com/colinhacks/zod#refine), +[`.transform`](https://github.com/colinhacks/zod#transform), +[`preprocess`](https://github.com/colinhacks/zod#preprocess), +etc... +), you will need to define the input and output types of the schema. `z.ZodType` + +```ts +const isValidId = (id: string): id is `${string}/${string}` => + id.split("/").length === 2; + +const baseSchema = z.object({ + id: z.string().refine(isValidId), +}); + +type Input = z.input & { + children: Input[]; +}; + +type Output = z.output & { + children: Output[]; +}; + +const schema: z.ZodType = baseSchema.extend({ + children: z.lazy(() => schema.array()), +}); +``` + +Thanks to [marcus13371337](https://github.com/marcus13371337) and [JoelBeeldi](https://github.com/JoelBeeldi) for this example. + +### JSON type + +If you want to validate any JSON value, you can use the snippet below. + +```ts +const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]); +type Literal = z.infer; +type Json = Literal | { [key: string]: Json } | Json[]; +const jsonSchema: z.ZodType = z.lazy(() => + z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]), +); + +jsonSchema.parse(data); +``` + +Thanks to [ggoodman](https://github.com/ggoodman) for suggesting this. + +### Cyclical objects + +Despite supporting recursive schemas, passing cyclical data into Zod will cause an infinite loop in some cases. + +> To detect cyclical objects before they cause problems, consider [this approach](https://gist.github.com/colinhacks/d35825e505e635df27cc950776c5500b). + +
+ +## Promises + +```ts +const numberPromise = z.promise(z.number()); +``` + +"Parsing" works a little differently with promise schemas. Validation happens in two parts: + +1. Zod synchronously checks that the input is an instance of Promise (i.e. an object with `.then` and `.catch` methods.). +2. Zod uses `.then` to attach an additional validation step onto the existing Promise. You'll have to use `.catch` on the returned Promise to handle validation failures. + +```ts +numberPromise.parse("tuna"); +// ZodError: Non-Promise type: string + +numberPromise.parse(Promise.resolve("tuna")); +// => Promise + +const test = async () => { + await numberPromise.parse(Promise.resolve("tuna")); + // ZodError: Non-number type: string + + await numberPromise.parse(Promise.resolve(3.14)); + // => 3.14 +}; +``` + + + +
+ +## Instanceof + +You can use `z.instanceof` to check that the input is an instance of a class. This is useful to validate inputs against classes that are exported from third-party libraries. + +```ts +class Test { + name: string; +} + +const TestSchema = z.instanceof(Test); + +const blob: any = "whatever"; +TestSchema.parse(new Test()); // passes +TestSchema.parse(blob); // throws +``` + +
+ +## Functions + +Zod also lets you define "function schemas". This makes it easy to validate the inputs and outputs of a function without intermixing your validation code and "business logic". + +You can create a function schema with `z.function(args, returnType)` . + +```ts +const myFunction = z.function(); + +type myFunction = z.infer; +// => ()=>unknown +``` + +Define inputs and outputs. + +```ts +const myFunction = z + .function() + .args(z.string(), z.number()) // accepts an arbitrary number of arguments + .returns(z.boolean()); + +type myFunction = z.infer; +// => (arg0: string, arg1: number)=>boolean +``` + + + +Function schemas have an `.implement()` method which accepts a function and returns a new function that automatically validates its inputs and outputs. + +```ts +const trimmedLength = z + .function() + .args(z.string()) // accepts an arbitrary number of arguments + .returns(z.number()) + .implement((x) => { + // TypeScript knows x is a string! + return x.trim().length; + }); + +trimmedLength("sandwich"); // => 8 +trimmedLength(" asdf "); // => 4 +``` + +If you only care about validating inputs, just don't call the `.returns()` method. The output type will be inferred from the implementation. + +> You can use the special `z.void()` option if your function doesn't return anything. This will let Zod properly infer the type of void-returning functions. (Void-returning functions actually return undefined.) + +```ts +const myFunction = z + .function() + .args(z.string()) + .implement((arg) => { + return [arg.length]; + }); + +myFunction; // (arg: string)=>number[] +``` + +Extract the input and output schemas from a function schema. + +```ts +myFunction.parameters(); +// => ZodTuple<[ZodString, ZodNumber]> + +myFunction.returnType(); +// => ZodBoolean +``` + + + +
+ +## Preprocess + +> Zod now supports primitive coercion without the need for `.preprocess()`. See the [coercion docs](#coercion-for-primitives) for more information. + +Typically Zod operates under a "parse then transform" paradigm. Zod validates the input first, then passes it through a chain of transformation functions. (For more information about transforms, read the [.transform docs](#transform).) + +But sometimes you want to apply some transform to the input _before_ parsing happens. A common use case: type coercion. Zod enables this with the `z.preprocess()`. + +```ts +const castToString = z.preprocess((val) => String(val), z.string()); +``` + +This returns a `ZodEffects` instance. `ZodEffects` is a wrapper class that contains all logic pertaining to preprocessing, refinements, and transforms. + +
+ +## Custom schemas + +You can create a Zod schema for any TypeScript type by using `z.custom()`. This is useful for creating schemas for types that are not supported by Zod out of the box, such as template string literals. + +```ts +const px = z.custom<`${number}px`>((val) => { + return typeof val === "string" ? /^\d+px$/.test(val) : false; +}); + +type px = z.infer; // `${number}px` + +px.parse("42px"); // "42px" +px.parse("42vw"); // throws; +``` + +If you don't provide a validation function, Zod will allow any value. This can be dangerous! + +```ts +z.custom<{ arg: string }>(); // performs no validation +``` + +You can customize the error message and other options by passing a second argument. This parameter works the same way as the params parameter of [`.refine`](#refine). + +```ts +z.custom<...>((val) => ..., "custom error message"); +``` + +
+ +## Schema methods + +All Zod schemas contain certain methods. + +### `.parse` + +`.parse(data: unknown): T` + +Given any Zod schema, you can call its `.parse` method to check `data` is valid. If it is, a value is returned with full type information! Otherwise, an error is thrown. + +> IMPORTANT: The value returned by `.parse` is a _deep clone_ of the variable you passed in. + +```ts +const stringSchema = z.string(); + +stringSchema.parse("fish"); // => returns "fish" +stringSchema.parse(12); // throws error +``` + +### `.parseAsync` + +`.parseAsync(data:unknown): Promise` + +If you use asynchronous [refinements](#refine) or [transforms](#transform) (more on those later), you'll need to use `.parseAsync`. + +```ts +const stringSchema = z.string().refine(async (val) => val.length <= 8); + +await stringSchema.parseAsync("hello"); // => returns "hello" +await stringSchema.parseAsync("hello world"); // => throws error +``` + +### `.safeParse` + +`.safeParse(data:unknown): { success: true; data: T; } | { success: false; error: ZodError; }` + +If you don't want Zod to throw errors when validation fails, use `.safeParse`. This method returns an object containing either the successfully parsed data or a ZodError instance containing detailed information about the validation problems. + +```ts +stringSchema.safeParse(12); +// => { success: false; error: ZodError } + +stringSchema.safeParse("billie"); +// => { success: true; data: 'billie' } +``` + +The result is a _discriminated union_, so you can handle errors very conveniently: + +```ts +const result = stringSchema.safeParse("billie"); +if (!result.success) { + // handle error then return + result.error; +} else { + // do something + result.data; +} +``` + +### `.safeParseAsync` + +> Alias: `.spa` + +An asynchronous version of `safeParse`. + +```ts +await stringSchema.safeParseAsync("billie"); +``` + +For convenience, this has been aliased to `.spa`: + +```ts +await stringSchema.spa("billie"); +``` + +### `.refine` + +`.refine(validator: (data:T)=>any, params?: RefineParams)` + +Zod lets you provide custom validation logic via _refinements_. (For advanced features like creating multiple issues and customizing error codes, see [`.superRefine`](#superrefine).) + +Zod was designed to mirror TypeScript as closely as possible. But there are many so-called "refinement types" you may wish to check for that can't be represented in TypeScript's type system. For instance: checking that a number is an integer or that a string is a valid email address. + +For example, you can define a custom validation check on _any_ Zod schema with `.refine` : + +```ts +const myString = z.string().refine((val) => val.length <= 255, { + message: "String can't be more than 255 characters", +}); +``` + +> ⚠️ Refinement functions should not throw. Instead they should return a falsy value to signal failure. + +#### Arguments + +As you can see, `.refine` takes two arguments. + +1. The first is the validation function. This function takes one input (of type `T` — the inferred type of the schema) and returns `any`. Any truthy value will pass validation. (Prior to zod@1.6.2 the validation function had to return a boolean.) +2. The second argument accepts some options. You can use this to customize certain error-handling behavior: + +```ts +type RefineParams = { + // override error message + message?: string; + + // appended to error path + path?: (string | number)[]; + + // params object you can use to customize message + // in error map + params?: object; +}; +``` + +For advanced cases, the second argument can also be a function that returns `RefineParams`. + +```ts +const longString = z.string().refine( + (val) => val.length > 10, + (val) => ({ message: `${val} is not more than 10 characters` }), +); +``` + +#### Customize error path + +```ts +const passwordForm = z + .object({ + password: z.string(), + confirm: z.string(), + }) + .refine((data) => data.password === data.confirm, { + message: "Passwords don't match", + path: ["confirm"], // path of error + }); + +passwordForm.parse({ password: "asdf", confirm: "qwer" }); +``` + +Because you provided a `path` parameter, the resulting error will be: + +```ts +ZodError { + issues: [{ + "code": "custom", + "path": [ "confirm" ], + "message": "Passwords don't match" + }] +} +``` + +#### Asynchronous refinements + +Refinements can also be async: + +```ts +const userId = z.string().refine(async (id) => { + // verify that ID exists in database + return true; +}); +``` + +> ⚠️ If you use async refinements, you must use the `.parseAsync` method to parse data! Otherwise Zod will throw an error. + +#### Relationship to transforms + +Transforms and refinements can be interleaved: + +```ts +z.string() + .transform((val) => val.length) + .refine((val) => val > 25); +``` + + + +### `.superRefine` + +The `.refine` method is actually syntactic sugar atop a more versatile (and verbose) method called `superRefine`. Here's an example: + +```ts +const Strings = z.array(z.string()).superRefine((val, ctx) => { + if (val.length > 3) { + ctx.addIssue({ + code: z.ZodIssueCode.too_big, + maximum: 3, + type: "array", + inclusive: true, + message: "Too many items 😡", + }); + } + + if (val.length !== new Set(val).size) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `No duplicates allowed.`, + }); + } +}); +``` + +You can add as many issues as you like. If `ctx.addIssue` is _not_ called during the execution of the function, validation passes. + +Normally refinements always create issues with a `ZodIssueCode.custom` error code, but with `superRefine` it's possible to throw issues of any `ZodIssueCode`. Each issue code is described in detail in the Error Handling guide: [ERROR_HANDLING.md](ERROR_HANDLING.md). + +#### Abort early + +By default, parsing will continue even after a refinement check fails. For instance, if you chain together multiple refinements, they will all be executed. However, it may be desirable to _abort early_ to prevent later refinements from being executed. To achieve this, pass the `fatal` flag to `ctx.addIssue` and return `z.NEVER`. + +```ts +const schema = z.number().superRefine((val, ctx) => { + if (val < 10) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "should be >= 10", + fatal: true, + }); + + return z.NEVER; + } + + if (val !== 12) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "should be twelve", + }); + } +}); +``` + +#### Type refinements + +If you provide a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) to `.refine()` or `.superRefine()`, the resulting type will be narrowed down to your predicate's type. This is useful if you are mixing multiple chained refinements and transformations: + +```ts +const schema = z + .object({ + first: z.string(), + second: z.number(), + }) + .nullable() + .superRefine((arg, ctx): arg is { first: string; second: number } => { + if (!arg) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, // customize your issue + message: "object should exist", + }); + } + + return z.NEVER; // The return value is not used, but we need to return something to satisfy the typing + }) + // here, TS knows that arg is not null + .refine((arg) => arg.first === "bob", "`first` is not `bob`!"); +``` + +> ⚠️ You **must** use `ctx.addIssue()` instead of returning a boolean value to indicate whether the validation passes. If `ctx.addIssue` is _not_ called during the execution of the function, validation passes. + +### `.transform` + +To transform data after parsing, use the `transform` method. + +```ts +const stringToNumber = z.string().transform((val) => val.length); + +stringToNumber.parse("string"); // => 6 +``` + +#### Chaining order + +Note that `stringToNumber` above is an instance of the `ZodEffects` subclass. It is NOT an instance of `ZodString`. If you want to use the built-in methods of `ZodString` (e.g. `.email()`) you must apply those methods _before_ any transforms. + +```ts +const emailToDomain = z + .string() + .email() + .transform((val) => val.split("@")[1]); + +emailToDomain.parse("colinhacks@example.com"); // => example.com +``` + +#### Validating during transform + +The `.transform` method can simultaneously validate and transform the value. This is often simpler and less duplicative than chaining `transform` and `refine`. + +As with `.superRefine`, the transform function receives a `ctx` object with an `addIssue` method that can be used to register validation issues. + +```ts +const numberInString = z.string().transform((val, ctx) => { + const parsed = parseInt(val); + if (isNaN(parsed)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Not a number", + }); + + // This is a special symbol you can use to + // return early from the transform function. + // It has type `never` so it does not affect the + // inferred return type. + return z.NEVER; + } + return parsed; +}); +``` + +#### Relationship to refinements + +Transforms and refinements can be interleaved. These will be executed in the order they are declared. + +```ts +const nameToGreeting = z + .string() + .transform((val) => val.toUpperCase()) + .refine((val) => val.length > 15) + .transform((val) => `Hello ${val}`) + .refine((val) => val.indexOf("!") === -1); +``` + +#### Async transforms + +Transforms can also be async. + +```ts +const IdToUser = z + .string() + .uuid() + .transform(async (id) => { + return await getUserById(id); + }); +``` + +> ⚠️ If your schema contains asynchronous transforms, you must use .parseAsync() or .safeParseAsync() to parse data. Otherwise Zod will throw an error. + +### `.default` + +You can use transforms to implement the concept of "default values" in Zod. + +```ts +const stringWithDefault = z.string().default("tuna"); + +stringWithDefault.parse(undefined); // => "tuna" +``` + +Optionally, you can pass a function into `.default` that will be re-executed whenever a default value needs to be generated: + +```ts +const numberWithRandomDefault = z.number().default(Math.random); + +numberWithRandomDefault.parse(undefined); // => 0.4413456736055323 +numberWithRandomDefault.parse(undefined); // => 0.1871840107401901 +numberWithRandomDefault.parse(undefined); // => 0.7223408162401552 +``` + +Conceptually, this is how Zod processes default values: + +1. If the input is `undefined`, the default value is substituted +2. Then the data is parsed using the base schema. Your default value will be parsed by the schema (including any potential transforms). + +### `.describe` + +Use `.describe()` to add a `description` property to the resulting schema. + +```ts +const documentedString = z + .string() + .describe("A useful bit of text, if you know what to do with it."); +documentedString.description; // A useful bit of text… +``` + +This can be useful for documenting a field, for example in a JSON Schema using a library like [`zod-to-json-schema`](https://github.com/StefanTerdell/zod-to-json-schema)). + +### `.catch` + +Use `.catch()` to provide a "catch value" to be returned in the event of a parsing error. + +```ts +const numberWithCatch = z.number().catch(42); + +numberWithCatch.parse(5); // => 5 +numberWithCatch.parse("tuna"); // => 42 +``` + +Optionally, you can pass a function into `.catch` that will be re-executed whenever a default value needs to be generated. A `ctx` object containing the caught error will be passed into this function. + +```ts +const numberWithRandomCatch = z.number().catch((ctx) => { + ctx.error; // the caught ZodError + return Math.random(); +}); + +numberWithRandomCatch.parse("sup"); // => 0.4413456736055323 +numberWithRandomCatch.parse("sup"); // => 0.1871840107401901 +numberWithRandomCatch.parse("sup"); // => 0.7223408162401552 +``` + +Conceptually, this is how Zod processes "catch values": + +1. The data is parsed using the base schema +2. If the parsing fails, the "catch value" is returned + +### `.optional` + +A convenience method that returns an optional version of a schema. + +```ts +const optionalString = z.string().optional(); // string | undefined + +// equivalent to +z.optional(z.string()); +``` + +### `.nullable` + +A convenience method that returns a nullable version of a schema. + +```ts +const nullableString = z.string().nullable(); // string | null + +// equivalent to +z.nullable(z.string()); +``` + +### `.nullish` + +A convenience method that returns a "nullish" version of a schema. Nullish schemas will accept both `undefined` and `null`. Read more about the concept of "nullish" [in the TypeScript 3.7 release notes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#nullish-coalescing). + +```ts +const nullishString = z.string().nullish(); // string | null | undefined + +// equivalent to +z.string().nullable().optional(); +``` + +### `.array` + +A convenience method that returns an array schema for the given type: + +```ts +const stringArray = z.string().array(); // string[] + +// equivalent to +z.array(z.string()); +``` + +### `.promise` + +A convenience method for promise types: + +```ts +const stringPromise = z.string().promise(); // Promise + +// equivalent to +z.promise(z.string()); +``` + +### `.or` + +A convenience method for [union types](#unions). + +```ts +const stringOrNumber = z.string().or(z.number()); // string | number + +// equivalent to +z.union([z.string(), z.number()]); +``` + +### `.and` + +A convenience method for creating intersection types. + +```ts +const nameAndAge = z + .object({ name: z.string() }) + .and(z.object({ age: z.number() })); // { name: string } & { age: number } + +// equivalent to +z.intersection(z.object({ name: z.string() }), z.object({ age: z.number() })); +``` + +### `.brand` + +`.brand() => ZodBranded` + +TypeScript's type system is structural, which means that any two types that are structurally equivalent are considered the same. + +```ts +type Cat = { name: string }; +type Dog = { name: string }; + +const petCat = (cat: Cat) => {}; +const fido: Dog = { name: "fido" }; +petCat(fido); // works fine +``` + +In some cases, its can be desirable to simulate _nominal typing_ inside TypeScript. For instance, you may wish to write a function that only accepts an input that has been validated by Zod. This can be achieved with _branded types_ (AKA _opaque types_). + +```ts +const Cat = z.object({ name: z.string() }).brand<"Cat">(); +type Cat = z.infer; + +const petCat = (cat: Cat) => {}; + +// this works +const simba = Cat.parse({ name: "simba" }); +petCat(simba); + +// this doesn't +petCat({ name: "fido" }); +``` + +Under the hood, this works by attaching a "brand" to the inferred type using an intersection type. This way, plain/unbranded data structures are no longer assignable to the inferred type of the schema. + +```ts +const Cat = z.object({ name: z.string() }).brand<"Cat">(); +type Cat = z.infer; +// {name: string} & {[symbol]: "Cat"} +``` + +Note that branded types do not affect the runtime result of `.parse`. It is a static-only construct. + +### `.readonly` + +`.readonly() => ZodReadonly` + +This method returns a `ZodReadonly` schema instance that parses the input using the base schema, then calls `Object.freeze()` on the result. The inferred type is also marked as `readonly`. + +```ts +const schema = z.object({ name: z.string() }).readonly(); +type schema = z.infer; +// Readonly<{name: string}> + +const result = schema.parse({ name: "fido" }); +result.name = "simba"; // error +``` + +The inferred type uses TypeScript's built-in readonly types when relevant. + +```ts +z.array(z.string()).readonly(); +// readonly string[] + +z.tuple([z.string(), z.number()]).readonly(); +// readonly [string, number] + +z.map(z.string(), z.date()).readonly(); +// ReadonlyMap + +z.set(z.string()).readonly(); +// ReadonlySet +``` + +### `.pipe` + +Schemas can be chained into validation "pipelines". It's useful for easily validating the result after a `.transform()`: + +```ts +z.string() + .transform((val) => val.length) + .pipe(z.number().min(5)); +``` + +The `.pipe()` method returns a `ZodPipeline` instance. + +
+ +## Guides and concepts + +### Type inference + +You can extract the TypeScript type of any schema with `z.infer` . + +```ts +const A = z.string(); +type A = z.infer; // string + +const u: A = 12; // TypeError +const u: A = "asdf"; // compiles +``` + +**What about transforms?** + +In reality each Zod schema internally tracks **two** types: an input and an output. For most schemas (e.g. `z.string()`) these two are the same. But once you add transforms into the mix, these two values can diverge. For instance `z.string().transform(val => val.length)` has an input of `string` and an output of `number`. + +You can separately extract the input and output types like so: + +```ts +const stringToNumber = z.string().transform((val) => val.length); + +// ⚠️ Important: z.infer returns the OUTPUT type! +type input = z.input; // string +type output = z.output; // number + +// equivalent to z.output! +type inferred = z.infer; // number +``` + +### Writing generic functions + +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. + +When attempting to write a function that accepts a Zod schema as an input, it's tempting to try something like this: + +```ts +function inferSchema(schema: z.ZodType) { + return schema; +} +``` + +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 +inferSchema(z.string()); +// => ZodType +``` + +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 to infer _the schema as a whole_ instead of merely its inferred type. You can do this with a utility type called `z.ZodTypeAny`. + +```ts +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. + +The Result is now fully and properly typed, and the type system can infer the specific subclass of the schema. + +#### 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. + +```ts +function parseData(data: unknown, schema: T) { + return schema.parse(data); +} + +parseData("sup", z.string()); +// => any +``` + +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) { + return schema.parse(data) as z.infer; + // ^^^^^^^^^^^^^^ <- add this +} + +parseData("sup", z.string()); +// => string +``` + +#### Constraining allowable inputs + +The `ZodType` class has three generic parameters. + +```ts +class ZodType< + Output = any, + Def extends ZodTypeDef = ZodTypeDef, + Input = Output +> { ... } +``` + +By constraining these in your generic input, you can limit what schemas are allowable as inputs to your function: + +```ts +function makeSchemaOptional>(schema: T) { + return schema.optional(); +} + +makeSchemaOptional(z.string()); +// works fine + +makeSchemaOptional(z.number()); +// Error: 'ZodNumber' is not assignable to parameter of type 'ZodType' +``` + +### Error handling + +Zod provides a subclass of Error called `ZodError`. ZodErrors contain an `issues` array containing detailed information about the validation problems. + +```ts +const result = z + .object({ + name: z.string(), + }) + .safeParse({ name: 12 }); + +if (!result.success) { + result.error.issues; + /* [ + { + "code": "invalid_type", + "expected": "string", + "received": "number", + "path": [ "name" ], + "message": "Expected string, received number" + } + ] */ +} +``` + +> For detailed information about the possible error codes and how to customize error messages, check out the dedicated error handling guide: [ERROR_HANDLING.md](ERROR_HANDLING.md) + +Zod's error reporting emphasizes _completeness_ and _correctness_. If you are looking to present a useful error message to the end user, you should either override Zod's error messages using an error map (described in detail in the Error Handling guide) or use a third-party library like [`zod-validation-error`](https://github.com/causaly/zod-validation-error) + +### Error formatting + +You can use the `.format()` method to convert this error into a nested object. + +```ts +const result = z + .object({ + name: z.string(), + }) + .safeParse({ name: 12 }); + +if (!result.success) { + const formatted = result.error.format(); + /* { + name: { _errors: [ 'Expected string, received number' ] } + } */ + + formatted.name?._errors; + // => ["Expected string, received number"] +} +``` + +
+ +## Comparison + +There are a handful of other widely-used validation libraries, but all of them have certain design limitations that make for a non-ideal developer experience. + + + + + + + +### Joi + +[https://github.com/hapijs/joi](https://github.com/hapijs/joi) + +Doesn't support static type inference 😕 + +### Yup + +[https://github.com/jquense/yup](https://github.com/jquense/yup) + +Yup is a full-featured library that was implemented first in vanilla JS, and later rewritten in TypeScript. + +- Supports casting and transforms +- All object fields are optional by default + +- Missing promise schemas +- Missing function schemas +- Missing union & intersection schemas + + + +### io-ts + +[https://github.com/gcanti/io-ts](https://github.com/gcanti/io-ts) + +io-ts is an excellent library by gcanti. The API of io-ts heavily inspired the design of Zod. + +In our experience, io-ts prioritizes functional programming purity over developer experience in many cases. This is a valid and admirable design goal, but it makes io-ts particularly hard to integrate into an existing codebase with a more procedural or object-oriented bias. For instance, consider how to define an object with optional properties in io-ts: + +```ts +import * as t from "io-ts"; + +const A = t.type({ + foo: t.string, +}); + +const B = t.partial({ + bar: t.number, +}); + +const C = t.intersection([A, B]); + +type C = t.TypeOf; +// returns { foo: string; bar?: number | undefined } +``` + +You must define the required and optional props in separate object validators, pass the optionals through `t.partial` (which marks all properties as optional), then combine them with `t.intersection` . + +Consider the equivalent in Zod: + +```ts +const C = z.object({ + foo: z.string(), + bar: z.number().optional(), +}); + +type C = z.infer; +// returns { foo: string; bar?: number | undefined } +``` + +This more declarative API makes schema definitions vastly more concise. + +`io-ts` also requires the use of gcanti's functional programming library `fp-ts` to parse results and handle errors. This is another fantastic resource for developers looking to keep their codebase strictly functional. But depending on `fp-ts` necessarily comes with a lot of intellectual overhead; a developer has to be familiar with functional programming concepts and the `fp-ts` nomenclature to use the library. + +- Supports codecs with serialization & deserialization transforms +- Supports branded types +- Supports advanced functional programming, higher-kinded types, `fp-ts` compatibility +- Missing object methods: (pick, omit, partial, deepPartial, merge, extend) +- Missing nonempty arrays with proper typing (`[T, ...T[]]`) +- Missing promise schemas +- Missing function schemas + +### Runtypes + +[https://github.com/runtypes/runtypes](https://github.com/runtypes/runtypes) + +Runtypes is focused on ergonomics, with good type inference support. + +- Supports "pattern matching": computed properties that distribute over unions +- Supports branded types +- Supports template literals +- Supports conformance to predefined static types +- Missing object methods: (deepPartial, merge) +- Missing promise schemas +- Missing error customization + +### Ow + +[https://github.com/sindresorhus/ow](https://github.com/sindresorhus/ow) + +Ow is focused on function input validation. It's a library that makes it easy to express complicated assert statements, but it doesn't let you parse untyped data. They support a much wider variety of types; Zod has a nearly one-to-one mapping with TypeScript's type system, whereas ow lets you validate several highly-specific types out of the box (e.g. `int32Array` , see full list in their README). + +If you want to validate function inputs, use function schemas in Zod! It's a much simpler approach that lets you reuse a function type declaration without repeating yourself (namely, copy-pasting a bunch of ow assertions at the beginning of every function). Also Zod lets you validate your return types as well, so you can be sure there won't be any unexpected data passed downstream. + +
+ +## Changelog + +View the changelog at [CHANGELOG.md](CHANGELOG.md) diff --git a/packages/docs-v3/README_KO.md b/packages/docs-v3/README_KO.md new file mode 100644 index 0000000000..7e9a5ca727 --- /dev/null +++ b/packages/docs-v3/README_KO.md @@ -0,0 +1,3032 @@ +

+ Zod 로고 +

Zod

+

+ ✨ https://zod.dev ✨ +
+ 정적 타입 추론을 지원하는 TypeScript 기반 스키마 검증 라이브러리 +

+

+
+

+Zod CI 상태 +Created by Colin McDonnell +라이선스 +npm +스타 +

+ +
+ 문서 +   •   + Discord +   •   + npm +   •   + deno +   •   + 이슈 +   •   + @colinhacks +   •   + tRPC +
+
+ +
+
+ + + +![clerk 공지](https://github.com/colinhacks/zod/assets/3084745/6327cf99-8d82-4b44-a5b1-ba2b5c2ff6ad) + +
+ +## 목차 + +> 이 문서는 [중국어](./README_ZH.md)로도 번역되었습니다. + +- [Zod](#zod) + - [목차](#목차) + - [소개](#소개) + - [스폰서](#스폰서) + - [생태계](#생태계) + - [리소스](#리소스) + - [API 라이브러리](#api-라이브러리) + - [폼 통합 라이브러리](#폼-통합-라이브러리) + - [Zod를 활용한 다양한 도구들](#zod를-활용한-다양한-도구들) + - [X to Zod](#x-to-zod) + - [목킹(Mocking)](#목킹mocking) + - [Zod로 구동되는 프로젝트들](#zod로-구동되는-프로젝트들) + - [Zod 유틸리티 모음](#zod-유틸리티-모음) + - [설치](#설치) + - [요구사항](#요구사항) + - [`npm`에서 설치하기](#npm에서-설치하기) + - [기본 사용법](#기본-사용법) + - [간단한 문자열 스키마 생성하기](#간단한-문자열-스키마-생성하기) + - [객체 스키마 생성하기](#객체-스키마-생성하기) + - [기본 타입(Primitives)](#기본-타입primitives) + - [기본 타입 강제 변환(Coercion)](#기본-타입-강제-변환coercion) + - [리터럴(Literals)](#리터럴literals) + - [문자열](#문자열) + - [날짜와 시간](#날짜와-시간) + - [날짜](#날짜) + - [시간 형식 검증](#시간-형식-검증) + - [IP 주소](#ip-주소) + - [IP 범위 (CIDR)](#ip-범위-cidr) + - [숫자](#숫자) + - [BigInts](#bigints) + - [NaN (Not a Number)](#nan-not-a-number) + - [불리언(Booleans)](#불리언booleans) + - [날짜](#날짜) + - [Zod 열거형(enum)](#zod-열거형enum) + - [네이티브 열거형(Native Enums)](#네이티브-열거형native-enums) + - [옵셔널(Optional)](#옵셔널optional) + - [Nullable 타입](#nullable-타입) + - [객체](#객체) + - [`.shape`](#shape) + - [`.keyof`](#keyof) + - [`.extend`](#extend) + - [`.merge`](#merge) + - [`.pick` / `.omit`](#pick--omit) + - [`.partial`](#partial) + - [`.deepPartial`](#deeppartial) + - [`.required`](#required) + - [`.passthrough`](#passthrough) + - [`.strict`](#strict) + - [`.strip` 메서드](#strip-메서드) + - [`.catchall`](#catchall) + - [배열](#배열) + - [`.element`](#element) + - [`.nonempty`](#nonempty) + - [`.min` / `.max` / `.length`](#min--max--length) + - [튜플(Tuples)](#튜플tuples) + - [유니온 타입](#유니온-타입) + - [구별된 유니온(Discriminated Unions)](#구별된-유니온discriminated-unions) + - [레코드(Records)](#레코드records) + - [맵(Map)](#맵map) + - [Set(집합)](#set집합) + - [교차 타입(Intersections)](#교차-타입intersections) + - [재귀 타입](#재귀-타입) + - [ZodType과 ZodEffects 함께 사용하기](#zodtype과-zodeffects-함께-사용하기) + - [JSON 타입 검증](#json-타입-검증) + - [순환 객체](#순환-객체) + - [Promise](#promise) + - [Instanceof](#instanceof) + - [함수](#함수) + - [전처리(Preprocess)](#전처리preprocess) + - [커스텀 스키마](#커스텀-스키마) + - [스키마 메서드](#스키마-메서드) + - [`.parse`](#parse) + - [`.parseAsync`](#parseasync) + - [`.safeParse`](#safeparse) + - [`.safeParseAsync`](#safeparseasync) + - [`.refine`](#refine) + - [인자](#인자) + - [에러 경로 커스텀하기](#에러-경로-커스텀하기) + - [비동기 정제(Refinements)](#비동기-정제refinements) + - [transform과 refine의 관계](#transform과-refine의-관계) + - [`.superRefine`](#superrefine) + - [조기 중단](#조기-중단) + - [타입 정제](#타입-정제) + - [`.transform`](#transform) + - [체이닝 순서](#체이닝-순서) + - [변환 중에 유효성 검사하기](#변환-중에-유효성-검사하기) + - [변환과 정제의 관계](#변환과-정제의-관계) + - [비동기 변환(Async Transforms)](#비동기-변환async-transforms) + - [`.default`](#default) + - [`.describe`](#describe) + - [`.catch`](#catch) + - [`.optional`](#optional) + - [`.nullable`](#nullable) + - [`.nullish`](#nullish) + - [`.array()`](#array) + - [`.promise`](#promise) + - [`.or`](#or) + - [`.and`](#and) + - [`.brand`](#brand) + - [`.readonly`](#readonly) + - [`.pipe`](#pipe) + - [`.pipe()`를 사용하여 `z.coerce`의 일반적인 문제를 해결할 수 있습니다.](#pipe를-사용하여-zcoerce의-일반적인-문제를-해결할-수-있습니다) + - [Guides and concepts](#guides-and-concepts) + - [타입 추론](#타입-추론) + - [제네릭 함수 작성하기](#제네릭-함수-작성하기) + - [추론된 타입 유추하기](#추론된-타입-유추하기) + - [허용 가능한 입력 제한하기](#허용-가능한-입력-제한하기) + - [에러 처리](#에러-처리) + - [에러 포맷팅](#에러-포맷팅) + - [비교](#비교) + - [Joi](#joi) + - [Yup](#yup) + - [io-ts](#io-ts) + - [Runtypes](#runtypes) + - [Ow](#ow) + - [변경 내역](#변경-내역) + +## 소개 + +Zod는 TypeScript를 우선으로 하는 스키마 선언 및 검증 라이브러리입니다. 여기서 "스키마"라는 용어는 단순한 `string`부터 복잡한 중첩 객체까지 모든 데이터 타입을 포괄적으로 의미합니다. + +Zod는 개발자 친화적으로 설계되었습니다. 목표는 중복된 타입 선언을 없애는 것입니다. Zod를 사용하면 검증기를 **한 번만** 선언하면, Zod가 자동으로 정적 TypeScript 타입을 추론합니다. 간단한 타입을 복잡한 데이터 구조로 합성하기도 쉽습니다. + +Zod의 주요 특징은 다음과 같습니다: + +- **의존성 없음**: 외부 라이브러리에 의존하지 않습니다. +- **범용성**: Node.js와 모든 최신 브라우저에서 동작합니다. +- **경량화**: 압축 후 8KB로 매우 작습니다. +- **불변성**: `.optional()`과 같은 메서드는 새로운 인스턴스를 반환합니다. +- **간결한 체인 인터페이스**: 메서드 체이닝을 통해 코드를 간결하게 작성할 수 있습니다. +- **함수형 접근**: ["검증하지 말고 파싱하라"](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/) 철학을 따릅니다. +- **JavaScript 지원**: TypeScript를 사용하지 않아도 됩니다. 일반 JavaScript에서도 동작합니다. + +## 스폰서 + +어떤 수준의 스폰서십도 환영하며 권장합니다. Zod를 사용해 유료 제품을 개발했다면, [기업용 티어](https://github.com/sponsors/colinhacks) 중 하나를 고려해 보세요. + +
+

다이아몬드

+ +
+ +
+ + + + clerk 로고 + + +
+
+

+ 가장 포괄적인 사용자 관리 플랫폼 +
+ clerk.com +

+
+ +
+
+ +

플래티넘

+ + + + + + + + + + + + + + + + + +
+

+

+ + + + LibLab + + +
+ API를 위한 더 나은 SDK 생성 +
+ liblab.com +

+

+
+

+

+ + + + Neon + + +
+ 서버리스 Postgres — 더 빠르게 배포 +
+ neon.tech +

+

+
+

+

+ + + + stainless + + +
+ Retool AI로 AI 앱과 워크플로우 구축 +
+ retool.com +

+

+
+

+

+ + + + stainless + + +
+ 최고 수준의 SDK 생성 +
+ stainlessapi.com +

+

+
+

+

+ + + + speakeasy + + +
+ API를 위한 SDK 및 Terraform 프로바이더 +
+ speakeasy.com +

+

+
+ +
+ +

골드

+ + + + + + + + + + + + + + + + + + +
+ PropelAuth +
+ PropelAuth +
+ Cerbos +
+ Cerbos +
+ Scalar.com 로고 +
+ Scalar +
+ Trigger.dev 로고 +
+ Trigger.dev +
+ Transloadit 로고 +
+ Transloadit +
+ Infisical 로고 +
+ Infisical +
+ Whop 로고 +
+ Whop +
+ CryptoJobsList 로고 +
+ CryptoJobsList +
+ Plain 로고 +
+ Plain. +
+ Inngest 로고 +
+ Inngest +
+ Storyblok CMS +
+ Storyblok +
+ Mux 로고 +
+ Mux +
+ +
+ +

실버

+ + + + + + + + + + + + + + + + + + +
+ + + + val town 로고 + + + + + route4me 로고 + + + + Encore.dev 로고 + + + + Replay.io 로고 + +
+ + Numeric 로고 + + + + Marcato Partners + + + + + + + + + +
+ + Bamboo Creative 로고 + + + + Jason Laster + +
+ +

브론즈

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Brandon BayerJiří BrabecAlex JohanssonFungible Systems
AdaptableAvana WalletJason LengstorfGlobal Illumination, Inc.
MasterBornRyan PalmerMichael SweeneyNextbase
RemotionConnor SinnottMohammad-Ali A'râbiSupatool
+ +### 생태계 + +Zod를 기반으로 구축되거나 Zod를 기본적으로 지원하는 도구들이 점점 늘어나고 있습니다! 여러분이 Zod를 기반으로 도구나 라이브러리를 만들었다면, [트위터](https://twitter.com/colinhacks)로 알려주거나 [GitHub Discussions](https://github.com/colinhacks/zod/discussions)에 글을 남겨주세요. 아래에 추가하고 트윗으로 알리겠습니다. + +#### 리소스 + +- [Total TypeScript Zod 튜토리얼](https://www.totaltypescript.com/tutorials/zod) by [@mattpocockuk](https://twitter.com/mattpocockuk) +- [TypeScript의 약점 해결: 런타임 타입 체크](https://www.youtube.com/watch?v=rY_XqfSHock) by [@jherr](https://twitter.com/jherr) + +#### API 라이브러리 + +- [`tRPC`](https://github.com/trpc/trpc): GraphQL 없이도 타입 안전한 API를 구축할 수 있는 라이브러리. +- [`@anatine/zod-nestjs`](https://github.com/anatine/zod-plugins/tree/main/packages/zod-nestjs): NestJS 프로젝트에서 Zod를 사용하기 위한 헬퍼 메서드 제공. +- [`zod-endpoints`](https://github.com/flock-community/zod-endpoints): Zod를 사용해 엄격하게 타입이 지정된 엔드포인트를 구축. OpenAPI와 호환. +- [`zhttp`](https://github.com/evertdespiegeleer/zhttp): OpenAPI 호환, 엄격한 타입 지정 HTTP 라이브러리. Zod를 사용한 입력 및 응답 검증 지원. +- [`domain-functions`](https://github.com/SeasonedSoftware/domain-functions/): 비즈니스 로직을 프레임워크와 분리할 수 있는 합성 가능한 함수 제공. Zod 스키마를 통해 타입 추론을 퍼스트클래스로 지원. +- [`@zodios/core`](https://github.com/ecyrbe/zodios): axios와 Zod를 기반으로 한 타입스크립트 API 클라이언트. 런타임 및 컴파일 타임 검증 지원. +- [`express-zod-api`](https://github.com/RobinTail/express-zod-api): Express 기반 API를 구축할 때 I/O 스키마 검증과 커스텀 미들웨어를 제공. +- [`tapiduck`](https://github.com/sumukhbarve/monoduck/blob/main/src/tapiduck/README.md): Zod와 Express를 사용한 엔드투엔드 타입 안전 JSON API. tRPC와 유사하지만 더 간단한 구조. +- [`koa-zod-router`](https://github.com/JakeFenley/koa-zod-router): Koa에서 Zod를 사용해 I/O 검증이 포함된 타입 안전한 라우트 생성. +- [`zod-sockets`](https://github.com/RobinTail/zod-sockets): Zod 기반 Socket.IO 마이크로프레임워크. I/O 검증과 내장 AsyncAPI 스펙 지원. +- [`oas-tszod-gen`](https://github.com/inkognitro/oas-tszod-gen): OpenAPI v3 스펙을 Zod 타입이 포함된 TS 엔드포인트 호출 함수로 변환하는 클라이언트 SDK 코드 생성기. +- [`GQLoom`](https://github.com/modevol-com/gqloom): Zod를 사용하여 GraphQL 스키마와 리졸버를 엮습니다. + +#### 폼 통합 라이브러리 + +- [`react-hook-form`](https://github.com/react-hook-form/resolvers#zod): React Hook Form을 위한 공식 Zod 리졸버입니다. +- [`zod-validation-error`](https://github.com/causaly/zod-validation-error): `ZodError`에서 사용자 친화적인 오류 메시지를 생성합니다. +- [`zod-formik-adapter`](https://github.com/robertLichtnow/zod-formik-adapter): 커뮤니티에서 관리되는 Formik용 Zod 어댑터입니다. +- [`react-zorm`](https://github.com/esamattis/react-zorm): Zod를 사용하여 React에서 독립적인 `` 생성 및 검증을 제공합니다. +- [`zodix`](https://github.com/rileytomasek/zodix): Remix 로더와 액션에서 FormData 및 URLSearchParams를 위한 Zod 유틸리티입니다. +- [`conform`](https://conform.guide/api/zod/parseWithZod): HTML 폼의 점진적 향상을 위한 타입 안전한 폼 검증 라이브러리입니다. Remix와 Next.js에서 사용 가능합니다. +- [`remix-params-helper`](https://github.com/kiliman/remix-params-helper): Remix 앱에서 Zod와 표준 URLSearchParams 및 FormData 통합을 단순화합니다. +- [`formik-validator-zod`](https://github.com/glazy/formik-validator-zod): Formik과 호환되는 검증 라이브러리로, Zod를 Formik과 함께 사용하기 쉽게 해줍니다. +- [`zod-i18n-map`](https://github.com/aiji42/zod-i18n): Zod 오류 메시지를 번역하는 데 유용합니다. +- [`@modular-forms/solid`](https://github.com/fabian-hiller/modular-forms): SolidJS를 위한 모듈식 폼 라이브러리로, Zod를 검증에 사용합니다. +- [`houseform`](https://github.com/crutchcorn/houseform/): Zod를 검증에 사용하는 React 폼 라이브러리입니다. +- [`sveltekit-superforms`](https://github.com/ciscoheat/sveltekit-superforms): SvelteKit을 위한 강력한 폼 라이브러리로, Zod 검증을 지원합니다. +- [`mobx-zod-form`](https://github.com/MonoidDev/mobx-zod-form): MobX와 Zod를 기반으로 한 데이터 중심 폼 빌더입니다. +- [`@vee-validate/zod`](https://github.com/logaretm/vee-validate/tree/main/packages/zod): Vue.js를 위한 폼 라이브러리로, Zod 스키마 검증을 지원합니다. +- [`zod-form-renderer`](https://github.com/thepeaklab/zod-form-renderer): Zod 스키마에서 폼 필드를 자동으로 추론하고, react-hook-form을 사용하여 E2E 타입 안전성을 보장합니다. +- [`antd-zod`](https://github.com/MrBr/antd-zod): Ant Design 폼 필드 검증을 위한 Zod 어댑터입니다. +- [`frrm`](https://github.com/schalkventer/frrm): 0.5kb 크기의 Zod 기반 HTML 폼 추상화 라이브러리입니다. + +#### Zod를 활용한 다양한 도구들 + +- **[`zod-to-ts`](https://github.com/sachinraja/zod-to-ts)**: Zod 스키마를 TypeScript 타입 정의로 변환해주는 도구입니다. +- **[`zod-to-json-schema`](https://github.com/StefanTerdell/zod-to-json-schema)**: Zod 스키마를 [JSON 스키마](https://json-schema.org/)로 변환합니다. +- **[`@anatine/zod-openapi`](https://github.com/anatine/zod-plugins/tree/main/packages/zod-openapi)**: Zod 스키마를 OpenAPI v3.x `SchemaObject`로 변환합니다. +- **[`zod-fast-check`](https://github.com/DavidTimms/zod-fast-check)**: Zod 스키마를 기반으로 `fast-check`의 임의 데이터 생성기를 만듭니다. +- **[`zod-dto`](https://github.com/kbkk/abitia/tree/main/packages/zod-dto)**: Zod 스키마를 Nest.js의 DTO(Data Transfer Object)로 변환합니다. +- **[`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod)**: Zod 스키마를 사용해 Fastify의 타입 프로바이더를 생성합니다. +- **[`zod-to-openapi`](https://github.com/asteasolutions/zod-to-openapi)**: Zod 스키마를 기반으로 OpenAPI(Swagger) 문서를 생성합니다. 스키마, 엔드포인트, 파라미터 등을 포함합니다. +- **[`nestjs-graphql-zod`](https://github.com/incetarik/nestjs-graphql-zod)**: Zod 스키마를 NestJS GraphQL 모델 클래스로 변환합니다. Zod 스키마와 함께 작동하는 GraphQL 메서드 데코레이터를 제공합니다. +- **[`zod-openapi`](https://github.com/samchungy/zod-openapi)**: Zod 스키마를 사용해 완전한 OpenAPI v3.x 문서를 생성합니다. +- **[`fastify-zod-openapi`](https://github.com/samchungy/fastify-zod-openapi)**: Fastify 타입 프로바이더, 유효성 검사, 직렬화 및 `@fastify/swagger` 지원을 Zod 스키마와 함께 제공합니다. +- **[`typeschema`](https://typeschema.com/)**: 스키마 유효성 검사를 위한 범용 어댑터입니다. +- **[`zodex`](https://github.com/commonbaseapp/zodex)**: Zod 스키마의 (역)직렬화를 지원합니다. + +이 도구들은 Zod 스키마를 다양한 형식으로 변환하거나 다른 프레임워크와 통합하는 데 유용하게 사용할 수 있습니다. + +#### X to Zod + +- [`ts-to-zod`](https://github.com/fabien0102/ts-to-zod): TypeScript 정의를 Zod 스키마로 변환합니다. +- [`@runtyping/zod`](https://github.com/johngeorgewright/runtyping/tree/main/packages/zod): 정적 타입과 JSON 스키마에서 Zod를 생성합니다. +- [`json-schema-to-zod`](https://github.com/StefanTerdell/json-schema-to-zod): [JSON 스키마](https://json-schema.org/)를 Zod 스키마로 변환합니다. [라이브 데모](https://StefanTerdell.github.io/json-schema-to-zod-react/). +- [`json-to-zod`](https://github.com/rsinohara/json-to-zod): JSON 객체를 Zod 스키마로 변환합니다. [라이브 데모](https://rsinohara.github.io/json-to-zod-react/). +- [`graphql-codegen-typescript-validation-schema`](https://github.com/Code-Hex/graphql-codegen-typescript-validation-schema): GraphQL 스키마에서 폼 검증 스키마를 생성하는 GraphQL Code Generator 플러그인입니다. +- [`zod-prisma`](https://github.com/CarterGrimmeisen/zod-prisma): Prisma 스키마에서 Zod 스키마를 생성합니다. +- [`Supervillain`](https://github.com/Southclaws/supervillain): Go 구조체에서 Zod 스키마를 생성합니다. +- [`prisma-zod-generator`](https://github.com/omar-dulaimi/prisma-zod-generator): Prisma 스키마에서 Zod 스키마를 생성합니다. +- [`drizzle-zod`](https://orm.drizzle.team/docs/zod): Drizzle 스키마에서 Zod 스키마를 생성합니다. +- [`prisma-trpc-generator`](https://github.com/omar-dulaimi/prisma-trpc-generator): Zod를 사용하여 완전히 구현된 tRPC 라우터와 검증 스키마를 생성합니다. +- [`zod-prisma-types`](https://github.com/chrishoermann/zod-prisma-types): Prisma 모델에서 Zod 타입을 생성합니다. +- [`quicktype`](https://app.quicktype.io/): JSON 객체와 JSON 스키마를 Zod 스키마로 변환합니다. +- [`@sanity-typed/zod`](https://github.com/saiichihashimoto/sanity-typed/tree/main/packages/zod): [Sanity 스키마](https://www.sanity.io/docs/schema-types)에서 Zod 스키마를 생성합니다. +- [`java-to-zod`](https://github.com/ivangreene/java-to-zod): POJO를 Zod 스키마로 변환합니다. +- [`Orval`](https://github.com/anymaniax/orval): OpenAPI 스키마에서 Zod 스키마를 생성합니다. +- [`Kubb`](https://github.com/kubb-labs/kubb): OpenAPI 스키마에서 SDK와 Zod 스키마를 생성합니다. + +#### 목킹(Mocking) + +- [`@anatine/zod-mock`](https://github.com/anatine/zod-plugins/tree/main/packages/zod-mock): Zod 스키마에서 목 데이터를 생성합니다. [faker.js](https://github.com/faker-js/faker)를 기반으로 동작합니다. +- [`zod-mocking`](https://github.com/dipasqualew/zod-mocking): Zod 스키마를 사용해 목 데이터를 생성합니다. +- [`zod-fixture`](https://github.com/timdeschryver/zod-fixture): Zod 스키마를 활용해 테스트용 픽스처를 결정론적인 방식으로 자동 생성합니다. +- [`zocker`](https://zocker.sigrist.dev): 스키마를 기반으로 실제와 유사한 목 데이터를 생성합니다. +- [`zodock`](https://github.com/ItMaga/zodock): Zod 스키마를 기반으로 목 데이터를 생성합니다. +- [`zod-schema-faker`](https://github.com/soc221b/zod-schema-faker): Zod 스키마에서 목 데이터를 생성합니다. [@faker-js/faker](https://github.com/faker-js/faker)와 [randexp.js](https://github.com/fent/randexp.js)를 사용합니다. + +#### Zod로 구동되는 프로젝트들 + +- [`freerstore`](https://github.com/JacobWeisenburger/freerstore): Firestore 비용 최적화 도구. +- [`slonik`](https://github.com/gajus/slonik/tree/gajus/add-zod-validation-backwards-compatible#runtime-validation-and-static-type-inference): 강력한 Zod 통합을 지원하는 Node.js Postgres 클라이언트. +- [`schemql`](https://github.com/a2lix/schemql): 원시 SQL에 타입 안전성과 스키마 검증을 결합하여 SQL 워크플로우를 강화. +- [`soly`](https://github.com/mdbetancourt/soly): Zod를 사용하여 CLI 애플리케이션 생성. +- [`pastel`](https://github.com/vadimdemedes/pastel): React, Zod, Ink를 사용하여 CLI 애플리케이션 생성. +- [`zod-xlsx`](https://github.com/sidwebworks/zod-xlsx): Zod 스키마를 사용한 XLSX 기반 리소스 검증 도구. +- [`znv`](https://github.com/lostfictions/znv): Zod 스키마를 사용한 Node.js 환경 변수의 타입 안전한 파싱 및 검증. +- [`zod-config`](https://github.com/alexmarqs/zod-config): 유연한 어댑터를 통해 여러 소스에서 설정을 로드하고 Zod를 사용해 타입 안전성 보장. +- [`unplugin-environment`](https://github.com/r17x/js/tree/main/packages/unplugin-environment#readme): 스키마 검증을 통해 안전하게 환경 변수를 로드하는 플러그인. 가상 모듈을 사용해 간단하고, 인텔리센스를 통해 타입 안전성을 제공하며, 더 나은 개발자 경험(DX)을 제공. Zod로 구동. + +#### Zod 유틸리티 모음 + +- [`zod_utilz`](https://github.com/JacobWeisenburger/zod_utilz): 프레임워크에 구애받지 않는 Zod 유틸리티 모음. +- [`zod-playground`](https://github.com/marilari88/zod-playground): Zod 스키마 검증 기능을 학습하고 테스트할 수 있는 도구. [링크](https://zod-playground.vercel.app/). +- [`zod-sandbox`](https://github.com/nereumelo/zod-sandbox): Zod 스키마를 테스트하기 위한 제어된 환경. [라이브 데모](https://zod-sandbox.vercel.app/). +- [`zod-dev`](https://github.com/schalkventer/zod-dev): 프로덕션 환경에서 Zod 런타임 파싱을 조건부로 비활성화. +- [`zod-accelerator`](https://github.com/duplojs/duplojs-zod-accelerator): Zod의 처리 속도를 최대 ~100배까지 가속. + +## 설치 + +### 요구사항 + +- **TypeScript 4.5 이상**이 필요합니다! +- `tsconfig.json`에서 `strict` 모드를 활성화해야 합니다. 이는 모든 TypeScript 프로젝트에서 권장되는 모범 사례입니다. + + ```ts + // tsconfig.json + { + // ... + "compilerOptions": { + // ... + "strict": true + } + } + ``` + +### `npm`에서 설치하기 + +```sh +npm install zod # npm +deno add npm:zod # deno +yarn add zod # yarn +bun add zod # bun +pnpm add zod # pnpm +``` + +Zod는 모든 커밋마다 카나리(canary) 버전도 배포합니다. 카나리 버전을 설치하려면: + +```sh +npm install zod@canary # npm +deno add npm:zod@canary # deno +yarn add zod@canary # yarn +bun add zod@canary # bun +pnpm add zod@canary # pnpm +``` + +> 이 README의 나머지 부분은 여러분이 npm을 사용하고 `"zod"` 패키지에서 직접 임포트한다고 가정합니다. + +## 기본 사용법 + +### 간단한 문자열 스키마 생성하기 + +```ts +import { z } from "zod"; + +// 문자열을 위한 스키마 생성 +const mySchema = z.string(); + +// 파싱 +mySchema.parse("tuna"); // => "tuna" +mySchema.parse(12); // => ZodError 발생 + +// "안전한" 파싱 (검증 실패 시 에러를 던지지 않음) +mySchema.safeParse("tuna"); // => { success: true; data: "tuna" } +mySchema.safeParse(12); // => { success: false; error: ZodError } +``` + +### 객체 스키마 생성하기 + +```ts +import { z } from "zod"; + +const User = z.object({ + username: z.string(), +}); + +User.parse({ username: "Ludwig" }); + +// 추론된 타입 추출 +type User = z.infer; +// { username: string } +``` + +## 기본 타입(Primitives) + +```ts +import { z } from "zod"; + +// 기본 값 +z.string(); +z.number(); +z.bigint(); +z.boolean(); +z.date(); +z.symbol(); + +// 빈 타입 +z.undefined(); +z.null(); +z.void(); // undefined를 허용 + +// 모든 타입 허용 +// 어떤 값이든 허용 +z.any(); +z.unknown(); + +// never 타입 +// 어떤 값도 허용하지 않음 +z.never(); +``` + +## 기본 타입 강제 변환(Coercion) + +Zod는 이제 기본 타입 값을 더 편리하게 강제 변환할 수 있는 방법을 제공합니다. + +```ts +const schema = z.coerce.string(); +schema.parse("tuna"); // => "tuna" +schema.parse(12); // => "12" +``` + +파싱 단계에서 입력값은 `String()` 함수를 통해 전달됩니다. 이 함수는 데이터를 문자열로 강제 변환하는 자바스크립트 내장 함수입니다. + +```ts +schema.parse(12); // => "12" +schema.parse(true); // => "true" +schema.parse(undefined); // => "undefined" +schema.parse(null); // => "null" +``` + +반환된 스키마는 일반적인 `ZodString` 인스턴스이므로 모든 문자열 메서드를 사용할 수 있습니다. + +```ts +z.coerce.string().email().min(5); +``` + +**강제 변환 작동 방식** + +모든 기본 타입은 강제 변환을 지원합니다. Zod는 내장 생성자를 사용하여 모든 입력값을 강제 변환합니다: `String(input)`, `Number(input)`, `new Date(input)` 등. + +```ts +z.coerce.string(); // String(input) +z.coerce.number(); // Number(input) +z.coerce.boolean(); // Boolean(input) +z.coerce.bigint(); // BigInt(input) +z.coerce.date(); // new Date(input) +``` + +**주의** — `z.coerce.boolean()`을 사용한 불리언 강제 변환은 예상과 다르게 동작할 수 있습니다. 모든 [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy) 값은 `true`로 변환되고, 모든 [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) 값은 `false`로 변환됩니다. + +```ts +const schema = z.coerce.boolean(); // Boolean(input) + +schema.parse("tuna"); // => true +schema.parse("true"); // => true +schema.parse("false"); // => true +schema.parse(1); // => true +schema.parse([]); // => true + +schema.parse(0); // => false +schema.parse(""); // => false +schema.parse(undefined); // => false +schema.parse(null); // => false +``` + +강제 변환 로직을 더 세밀하게 제어하려면 [`z.preprocess`](#전처리preprocess) 또는 [`z.pipe()`](#pipe)를 사용하는 것을 고려해보세요. + +## 리터럴(Literals) + +리터럴 스키마는 `"hello world"`나 `5`와 같은 [리터럴 타입](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types)을 나타냅니다. + +```ts +const tuna = z.literal("tuna"); +const twelve = z.literal(12); +const twobig = z.literal(2n); // bigint 리터럴 +const tru = z.literal(true); + +const terrificSymbol = Symbol("terrific"); +const terrific = z.literal(terrificSymbol); + +// 리터럴 값 가져오기 +tuna.value; // "tuna" +``` + +> 현재 Zod에서는 Date 리터럴을 지원하지 않습니다. 이 기능이 필요한 경우 이슈를 등록해 주세요. + +## 문자열 + +Zod는 문자열에 특화된 여러 가지 유효성 검사를 제공합니다. + +```ts +// 유효성 검사 +z.string().max(5); // 최대 5자 +z.string().min(5); // 최소 5자 +z.string().length(5); // 정확히 5자 +z.string().email(); // 이메일 형식 +z.string().url(); // URL 형식 +z.string().emoji(); // 이모지 포함 +z.string().uuid(); // UUID 형식 +z.string().nanoid(); // Nano ID 형식 +z.string().cuid(); // CUID 형식 +z.string().cuid2(); // CUID2 형식 +z.string().ulid(); // ULID 형식 +z.string().regex(regex); // 정규식 검사 +z.string().includes(string); // 특정 문자열 포함 +z.string().startsWith(string); // 특정 문자열로 시작 +z.string().endsWith(string); // 특정 문자열로 끝남 +z.string().datetime(); // ISO 8601 형식 (기본적으로 'Z' 시간대만 허용) +z.string().ip(); // IPv4 및 IPv6 허용 (기본값) +z.string().cidr(); // IPv4 및 IPv6 CIDR 허용 (기본값) + +// 변환 +z.string().trim(); // 공백 제거 +z.string().toLowerCase(); // 소문자로 변환 +z.string().toUpperCase(); // 대문자로 변환 + +// Zod 3.23에서 추가됨 +z.string().date(); // ISO 날짜 형식 (YYYY-MM-DD) +z.string().time(); // ISO 시간 형식 (HH:mm:ss[.SSSSSS]) +z.string().duration(); // ISO 8601 기간 형식 +z.string().base64(); // Base64 형식 +``` + +> [Refinements](#refine)와 함께 사용할 수 있는 다양한 유용한 문자열 유효성 검사 함수는 [validator.js](https://github.com/validatorjs/validator.js)에서 확인할 수 있습니다. + +문자열 스키마를 생성할 때 일반적인 오류 메시지를 커스터마이징할 수 있습니다. + +```ts +const name = z.string({ + required_error: "이름은 필수입니다", + invalid_type_error: "이름은 문자열이어야 합니다", +}); +``` + +유효성 검사 메서드를 사용할 때, 추가 인자로 커스텀 오류 메시지를 제공할 수 있습니다. + +```ts +z.string().min(5, { message: "5자 이상이어야 합니다" }); +z.string().max(5, { message: "5자 이하여야 합니다" }); +z.string().length(5, { message: "정확히 5자여야 합니다" }); +z.string().email({ message: "유효하지 않은 이메일 주소입니다" }); +z.string().url({ message: "유효하지 않은 URL입니다" }); +z.string().emoji({ message: "이모지가 아닌 문자가 포함되어 있습니다" }); +z.string().uuid({ message: "유효하지 않은 UUID입니다" }); +z.string().includes("tuna", { message: "'tuna'가 포함되어야 합니다" }); +z.string().startsWith("https://", { message: "보안 URL이어야 합니다" }); +z.string().endsWith(".com", { message: ".com 도메인만 허용됩니다" }); +z.string().datetime({ + message: "유효하지 않은 날짜/시간 문자열입니다! UTC여야 합니다.", +}); +z.string().date({ message: "유효하지 않은 날짜 문자열입니다!" }); +z.string().time({ message: "유효하지 않은 시간 문자열입니다!" }); +z.string().ip({ message: "유효하지 않은 IP 주소입니다" }); +z.string().cidr({ message: "유효하지 않은 CIDR입니다" }); +``` + +### 날짜와 시간 + +Zod 문자열에는 몇 가지 날짜/시간 관련 유효성 검사가 포함되어 있습니다. 이 유효성 검사는 정규 표현식을 기반으로 하기 때문에, 완전한 날짜/시간 라이브러리만큼 엄격하지는 않습니다. 하지만 사용자 입력을 검증하는 데 매우 편리합니다. + +`z.string().datetime()` 메서드는 ISO 8601을 강제합니다. 기본적으로 시간대 오프셋은 허용되지 않으며, 소수점 초 단위의 임의 정밀도를 지원합니다. + +```ts +const datetime = z.string().datetime(); + +datetime.parse("2020-01-01T00:00:00Z"); // 통과 +datetime.parse("2020-01-01T00:00:00.123Z"); // 통과 +datetime.parse("2020-01-01T00:00:00.123456Z"); // 통과 (임의 정밀도) +datetime.parse("2020-01-01T00:00:00+02:00"); // 실패 (오프셋 허용 안 됨) +``` + +`offset` 옵션을 `true`로 설정하면 시간대 오프셋을 허용할 수 있습니다. + +```ts +const datetime = z.string().datetime({ offset: true }); + +datetime.parse("2020-01-01T00:00:00+02:00"); // 통과 +datetime.parse("2020-01-01T00:00:00.123+02:00"); // 통과 (밀리초 선택적) +datetime.parse("2020-01-01T00:00:00.123+0200"); // 통과 (밀리초 선택적) +datetime.parse("2020-01-01T00:00:00.123+02"); // 통과 (시간 오프셋만) +datetime.parse("2020-01-01T00:00:00Z"); // 통과 (Z 지원) +``` + +`local` 플래그를 사용하면 시간대가 없는 날짜/시간을 허용할 수 있습니다. + +```ts +const schema = z.string().datetime({ local: true }); +schema.parse("2020-01-01T00:00:00"); // 통과 +``` + +추가로 허용 가능한 `precision`을 제한할 수 있습니다. 기본적으로 임의의 소수점 초 단위 정밀도를 지원하지만 선택적입니다. + +```ts +const datetime = z.string().datetime({ precision: 3 }); + +datetime.parse("2020-01-01T00:00:00.123Z"); // 통과 +datetime.parse("2020-01-01T00:00:00Z"); // 실패 +datetime.parse("2020-01-01T00:00:00.123456Z"); // 실패 +``` + +### 날짜 + +> Zod 3.23에서 추가됨 + +`z.string().date()` 메서드는 `YYYY-MM-DD` 형식의 문자열을 검증합니다. + +```ts +const date = z.string().date(); + +date.parse("2020-01-01"); // 통과 +date.parse("2020-1-1"); // 실패 +date.parse("2020-01-32"); // 실패 +``` + +### 시간 형식 검증 + +> Zod 3.23에서 추가됨 + +`z.string().time()` 메서드는 `HH:MM:SS[.s+]` 형식의 문자열을 검증합니다. 초 단위는 소수점 이하 임의의 정밀도를 포함할 수 있습니다. 하지만 시간대 오프셋은 허용하지 않습니다. + +```ts +const time = z.string().time(); + +time.parse("00:00:00"); // 통과 +time.parse("09:52:31"); // 통과 +time.parse("23:59:59.9999999"); // 통과 (임의의 정밀도 허용) + +time.parse("00:00:00.123Z"); // 실패 (`Z` 허용 안 됨) +time.parse("00:00:00.123+02:00"); // 실패 (오프셋 허용 안 됨) +``` + +`precision` 옵션을 설정하여 허용 가능한 소수점 정밀도를 제한할 수 있습니다. + +```ts +const time = z.string().time({ precision: 3 }); + +time.parse("00:00:00.123"); // 통과 +time.parse("00:00:00.123456"); // 실패 +time.parse("00:00:00"); // 실패 +``` + +### IP 주소 + +기본적으로 `.ip()`는 IPv4와 IPv6를 모두 허용합니다. + +```ts +const ip = z.string().ip(); + +ip.parse("192.168.1.1"); // 통과 +ip.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // 통과 +ip.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:192.168.1.1"); // 통과 + +ip.parse("256.1.1.1"); // 실패 +ip.parse("84d5:51a0:9114:gggg:4cfa:f2d7:1f12:7003"); // 실패 +``` + +추가적으로 IP `version`을 설정할 수 있습니다. + +```ts +const ipv4 = z.string().ip({ version: "v4" }); +ipv4.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // 실패 + +const ipv6 = z.string().ip({ version: "v6" }); +ipv6.parse("192.168.1.1"); // 실패 +``` + +### IP 범위 (CIDR) + +[CIDR 표기법](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing)으로 지정된 IP 주소 범위를 검증합니다. 기본적으로 `.cidr()`은 IPv4와 IPv6를 모두 허용합니다. + +```ts +const cidr = z.string().cidr(); +cidr.parse("192.168.0.0/24"); // 통과 +cidr.parse("2001:db8::/32"); // 통과 +``` + +`version` 매개변수를 사용하여 특정 버전을 지정할 수 있습니다. + +```ts +const ipv4Cidr = z.string().cidr({ version: "v4" }); +ipv4Cidr.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // 실패 + +const ipv6Cidr = z.string().cidr({ version: "v6" }); +ipv6Cidr.parse("192.168.1.1"); // 실패 +``` + +## 숫자 + +숫자 스키마를 생성할 때 특정 오류 메시지를 커스텀할 수 있습니다. + +```ts +const age = z.number({ + required_error: "나이는 필수입니다", + invalid_type_error: "나이는 숫자여야 합니다", +}); +``` + +Zod는 숫자에 특화된 여러 유효성 검사를 제공합니다. + +```ts +z.number().gt(5); // 5보다 커야 함 +z.number().gte(5); // 5보다 크거나 같아야 함 (.min(5)와 동일) +z.number().lt(5); // 5보다 작아야 함 +z.number().lte(5); // 5보다 작거나 같아야 함 (.max(5)와 동일) + +z.number().int(); // 정수여야 함 + +z.number().positive(); // 0보다 커야 함 +z.number().nonnegative(); // 0보다 크거나 같아야 함 +z.number().negative(); // 0보다 작아야 함 +z.number().nonpositive(); // 0보다 작거나 같아야 함 + +z.number().multipleOf(5); // 5로 나누어 떨어져야 함 (.step(5)와 동일) + +z.number().finite(); // 유한한 값이어야 함 (Infinity 또는 -Infinity 불가) +z.number().safe(); // Number.MIN_SAFE_INTEGER와 Number.MAX_SAFE_INTEGER 사이의 값이어야 함 +``` + +선택적으로, 두 번째 인자로 커스텀 오류 메시지를 제공할 수 있습니다. + +```ts +z.number().lte(5, { message: "이👏값은👏너무👏큽니다" }); +``` + +## BigInts + +Zod는 BigInt 타입을 위한 몇 가지 유효성 검사를 제공합니다. + +```ts +z.bigint().gt(5n); // 5n보다 큰지 확인 +z.bigint().gte(5n); // 5n보다 크거나 같은지 확인 (`.min(5n)`과 동일) +z.bigint().lt(5n); // 5n보다 작은지 확인 +z.bigint().lte(5n); // 5n보다 작거나 같은지 확인 (`.max(5n)`과 동일) + +z.bigint().positive(); // 0n보다 큰지 확인 +z.bigint().nonnegative(); // 0n보다 크거나 같은지 확인 +z.bigint().negative(); // 0n보다 작은지 확인 +z.bigint().nonpositive(); // 0n보다 작거나 같은지 확인 + +z.bigint().multipleOf(5n); // 5n으로 나누어 떨어지는지 확인 +``` + +## NaN (Not a Number) + +NaN 스키마를 생성할 때 특정 오류 메시지를 커스텀할 수 있습니다. + +```ts +const isNaN = z.nan({ + required_error: "isNaN은 필수입니다", + invalid_type_error: "isNaN은 '숫자가 아님'이어야 합니다", +}); +``` + +## 불리언(Booleans) + +불리언 스키마를 생성할 때 특정 오류 메시지를 커스텀할 수 있습니다. + +```ts +const isActive = z.boolean({ + required_error: "isActive는 필수 항목입니다", + invalid_type_error: "isActive는 불리언 타입이어야 합니다", +}); +``` + +## 날짜 + +`Date` 인스턴스를 검증하려면 `z.date()`를 사용하세요. + +```ts +z.date().safeParse(new Date()); // 성공: true +z.date().safeParse("2022-01-12T00:00:00.000Z"); // 성공: false +``` + +날짜 스키마를 생성할 때 특정 오류 메시지를 커스터마이징할 수 있습니다. + +```ts +const myDateSchema = z.date({ + required_error: "날짜와 시간을 선택해 주세요", + invalid_type_error: "날짜 형식이 아닙니다!", +}); +``` + +Zod는 몇 가지 날짜 관련 유효성 검사를 제공합니다. + +```ts +z.date().min(new Date("1900-01-01"), { message: "너무 오래된 날짜입니다" }); +z.date().max(new Date(), { message: "너무 미래의 날짜입니다!" }); +``` + +**Date로 강제 변환** + +[zod 3.20](https://github.com/colinhacks/zod/releases/tag/v3.20)부터는 [`z.coerce.date()`](#coercion-for-primitives)를 사용하여 입력을 `new Date(input)`로 변환할 수 있습니다. + +```ts +const dateSchema = z.coerce.date(); +type DateSchema = z.infer; +// 타입 DateSchema = Date + +/* 유효한 날짜 */ +console.log(dateSchema.safeParse("2023-01-10T00:00:00.000Z").success); // true +console.log(dateSchema.safeParse("2023-01-10").success); // true +console.log(dateSchema.safeParse("1/10/23").success); // true +console.log(dateSchema.safeParse(new Date("1/10/23")).success); // true + +/* 유효하지 않은 날짜 */ +console.log(dateSchema.safeParse("2023-13-10").success); // false +console.log(dateSchema.safeParse("0000-00-00").success); // false +``` + +이전 버전의 zod를 사용하는 경우, [이 스레드](https://github.com/colinhacks/zod/discussions/879#discussioncomment-2036276)에서 설명한 대로 [`z.preprocess`](#전처리preprocess)를 사용하세요. + +## Zod 열거형(enum) + +```ts +const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]); +type FishEnum = z.infer; +// 'Salmon' | 'Tuna' | 'Trout' +``` + +`z.enum`은 Zod에서 허용 가능한 고정된 문자열 값 집합을 선언하는 방법입니다. 값 배열을 직접 `z.enum()`에 전달합니다. 또는 `as const`를 사용하여 열거형 값을 문자열 튜플로 정의할 수도 있습니다. 자세한 내용은 [const assertion 문서](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions)를 참고하세요. + +```ts +const VALUES = ["Salmon", "Tuna", "Trout"] as const; +const FishEnum = z.enum(VALUES); +``` + +다음은 허용되지 않습니다. Zod가 각 요소의 정확한 값을 추론할 수 없기 때문입니다. + +```ts +const fish = ["Salmon", "Tuna", "Trout"]; +const FishEnum = z.enum(fish); +``` + +**`.enum`** + +Zod 열거형에서 자동 완성을 사용하려면 스키마의 `.enum` 속성을 사용하세요. + +```ts +FishEnum.enum.Salmon; // => 자동 완성 + +FishEnum.enum; +/* +=> { + Salmon: "Salmon", + Tuna: "Tuna", + Trout: "Trout", +} +*/ +``` + +`.options` 속성을 사용하여 옵션 목록을 튜플로 가져올 수도 있습니다. + +```ts +FishEnum.options; // ["Salmon", "Tuna", "Trout"]; +``` + +**`.exclude/.extract()`** + +`.exclude`와 `.extract` 메서드를 사용하여 Zod 열거형의 부분 집합을 만들 수 있습니다. + +```ts +const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]); +const SalmonAndTrout = FishEnum.extract(["Salmon", "Trout"]); +const TunaOnly = FishEnum.exclude(["Salmon", "Trout"]); +``` + +## 네이티브 열거형(Native Enums) + +Zod 열거형은 열거형을 정의하고 검증하는 데 권장되는 방법입니다. 하지만 서드파티 라이브러리의 열거형을 검증해야 하거나 기존 열거형을 다시 작성하고 싶지 않은 경우 `z.nativeEnum()`을 사용할 수 있습니다. + +**숫자 열거형(Numeric Enums)** + +```ts +enum Fruits { + Apple, + Banana, +} + +const FruitEnum = z.nativeEnum(Fruits); +type FruitEnum = z.infer; // Fruits + +FruitEnum.parse(Fruits.Apple); // 통과 +FruitEnum.parse(Fruits.Banana); // 통과 +FruitEnum.parse(0); // 통과 +FruitEnum.parse(1); // 통과 +FruitEnum.parse(3); // 실패 +``` + +**문자열 열거형(String Enums)** + +```ts +enum Fruits { + Apple = "apple", + Banana = "banana", + Cantaloupe, // 숫자와 문자열 열거형을 혼합할 수 있음 +} + +const FruitEnum = z.nativeEnum(Fruits); +type FruitEnum = z.infer; // Fruits + +FruitEnum.parse(Fruits.Apple); // 통과 +FruitEnum.parse(Fruits.Cantaloupe); // 통과 +FruitEnum.parse("apple"); // 통과 +FruitEnum.parse("banana"); // 통과 +FruitEnum.parse(0); // 통과 +FruitEnum.parse("Cantaloupe"); // 실패 +``` + +**상수 열거형(Const Enums)** + +`.nativeEnum()` 함수는 `as const` 객체에서도 동작합니다. ⚠️ `as const`는 TypeScript 3.4 이상이 필요합니다! + +```ts +const Fruits = { + Apple: "apple", + Banana: "banana", + Cantaloupe: 3, +} as const; + +const FruitEnum = z.nativeEnum(Fruits); +type FruitEnum = z.infer; // "apple" | "banana" | 3 + +FruitEnum.parse("apple"); // 통과 +FruitEnum.parse("banana"); // 통과 +FruitEnum.parse(3); // 통과 +FruitEnum.parse("Cantaloupe"); // 실패 +``` + +`.enum` 속성을 사용하여 기본 객체에 접근할 수 있습니다: + +```ts +FruitEnum.enum.Apple; // "apple" +``` + +## 옵셔널(Optional) + +`schema`를 옵셔널로 만들고 싶다면 `z.optional()`을 사용하면 됩니다. 이 함수는 스키마를 `ZodOptional` 인스턴스로 감싼 후 결과를 반환합니다. + +```ts +const schema = z.optional(z.string()); + +schema.parse(undefined); // => undefined 반환 +type A = z.infer; // string | undefined +``` + +편의를 위해, 기존 스키마에서 `.optional()` 메서드를 직접 호출할 수도 있습니다. + +```ts +const user = z.object({ + username: z.string().optional(), +}); +type C = z.infer; // { username?: string | undefined }; +``` + +`ZodOptional` 인스턴스에서 감싸진 스키마를 추출하려면 `.unwrap()`을 사용하면 됩니다. + +```ts +const stringSchema = z.string(); +const optionalString = stringSchema.optional(); +optionalString.unwrap() === stringSchema; // true +``` + +## Nullable 타입 + +`z.nullable()`을 사용하면 nullable 타입을 만들 수 있습니다. + +```ts +const nullableString = z.nullable(z.string()); +nullableString.parse("asdf"); // => "asdf" +nullableString.parse(null); // => null +``` + +또는 `.nullable()` 메서드를 사용할 수도 있습니다. + +```ts +const E = z.string().nullable(); // nullableString과 동일 +type E = z.infer; // string | null +``` + +`.unwrap()`을 사용하면 내부 스키마를 추출할 수 있습니다. + +```ts +const stringSchema = z.string(); +const nullableString = stringSchema.nullable(); +nullableString.unwrap() === stringSchema; // true +``` + +## 객체 + +```ts +// 기본적으로 모든 속성은 필수입니다. +const Dog = z.object({ + name: z.string(), + age: z.number(), +}); + +// 추론된 타입을 이렇게 추출할 수 있습니다. +type Dog = z.infer; + +// 이는 다음과 동일합니다: +type Dog = { + name: string; + age: number; +}; +``` + +### `.shape` + +특정 키에 대한 스키마에 접근하려면 `.shape`를 사용하세요. + +```ts +Dog.shape.name; // => 문자열 스키마 +Dog.shape.age; // => 숫자 스키마 +``` + +### `.keyof` + +`.keyof`를 사용하면 객체 스키마의 키들로부터 `ZodEnum` 스키마를 생성할 수 있습니다. + +```ts +const keySchema = Dog.keyof(); +keySchema; // ZodEnum +``` + +이 코드는 `Dog` 객체 스키마의 키들을 기반으로 `ZodEnum` 스키마를 만듭니다. `keySchema`는 `ZodEnum` 타입이 됩니다. + +### `.extend` + +`.extend` 메서드를 사용하면 객체 스키마에 추가 필드를 넣을 수 있습니다. + +```ts +const DogWithBreed = Dog.extend({ + breed: z.string(), +}); +``` + +`.extend`는 기존 필드를 덮어쓸 때도 사용할 수 있습니다. 이 기능을 사용할 때는 주의가 필요합니다! + +### `.merge` + +`.merge`는 `A.extend(B.shape)`와 동일한 기능을 합니다. + +```ts +const BaseTeacher = z.object({ students: z.array(z.string()) }); +const HasID = z.object({ id: z.string() }); + +const Teacher = BaseTeacher.merge(HasID); +type Teacher = z.infer; // => { students: string[], id: string } +``` + +> 두 스키마가 동일한 키를 공유할 경우, B의 속성이 A의 속성을 덮어씁니다. 반환된 스키마는 B의 "unknownKeys" 정책(strip/strict/passthrough)과 catchall 스키마를 상속받습니다. + +### `.pick` / `.omit` + +TypeScript의 내장 `Pick`과 `Omit` 유틸리티 타입에서 영감을 받아, 모든 Zod 객체 스키마는 `.pick`과 `.omit` 메서드를 제공합니다. 이 메서드들은 수정된 버전의 스키마를 반환합니다. 다음은 Recipe 스키마 예제입니다: + +```ts +const Recipe = z.object({ + id: z.string(), + name: z.string(), + ingredients: z.array(z.string()), +}); +``` + +특정 키만 유지하려면 `.pick`을 사용하세요. + +```ts +const JustTheName = Recipe.pick({ name: true }); +type JustTheName = z.infer; +// => { name: string } +``` + +특정 키를 제거하려면 `.omit`을 사용하세요. + +```ts +const NoIDRecipe = Recipe.omit({ id: true }); + +type NoIDRecipe = z.infer; +// => { name: string, ingredients: string[] } +``` + +### `.partial` + +TypeScript의 내장 유틸리티 타입인 [Partial](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)에서 영감을 받은 `.partial` 메서드는 모든 프로퍼티를 선택적으로 만듭니다. + +다음과 같은 객체에서 시작해 보겠습니다: + +```ts +const user = z.object({ + email: z.string(), + username: z.string(), +}); +// { email: string; username: string } +``` + +이 객체를 부분적으로 만들 수 있습니다: + +```ts +const partialUser = user.partial(); +// { email?: string | undefined; username?: string | undefined } +``` + +또한, 특정 프로퍼티만 선택적으로 만들 수도 있습니다: + +```ts +const optionalEmail = user.partial({ + email: true, +}); +/* +{ + email?: string | undefined; + username: string +} +*/ +``` + +### `.deepPartial` + +`.partial` 메서드는 얕은(shallow) 동작만 수행합니다. 즉, 한 단계 깊이만 적용됩니다. 이에 더해 "깊은(deep)" 버전도 존재합니다: + +```ts +const user = z.object({ + username: z.string(), + location: z.object({ + latitude: z.number(), + longitude: z.number(), + }), + strings: z.array(z.object({ value: z.string() })), +}); + +const deepPartialUser = user.deepPartial(); + +/* +{ + username?: string | undefined, + location?: { + latitude?: number | undefined; + longitude?: number | undefined; + } | undefined, + strings?: { value?: string}[] +} +*/ +``` + +> **중요한 제한 사항**: 깊은 부분 적용(deep partials)은 객체, 배열, 튜플로 구성된 계층 구조에서만 예상대로 동작합니다. + +### `.required` + +`.partial` 메서드와 반대로, `.required` 메서드는 모든 속성을 필수로 만듭니다. + +다음과 같은 객체에서 시작해 보겠습니다: + +```ts +const user = z + .object({ + email: z.string(), + username: z.string(), + }) + .partial(); +// { email?: string | undefined; username?: string | undefined } +``` + +이제 필수 버전을 만들 수 있습니다: + +```ts +const requiredUser = user.required(); +// { email: string; username: string } +``` + +특정 속성만 필수로 만들 수도 있습니다: + +```ts +const requiredEmail = user.required({ + email: true, +}); +/* +{ + email: string; + username?: string | undefined; +} +*/ +``` + +### `.passthrough` + +기본적으로 Zod 객체 스키마는 파싱 과정에서 인식되지 않는 키를 제거합니다. + +```ts +const person = z.object({ + name: z.string(), +}); + +person.parse({ + name: "bob dylan", + extraKey: 61, +}); +// => { name: "bob dylan" } +// extraKey가 제거됨 +``` + +만약 알려지지 않은 키를 그대로 유지하고 싶다면 `.passthrough()`를 사용하세요. + +```ts +person.passthrough().parse({ + name: "bob dylan", + extraKey: 61, +}); +// => { name: "bob dylan", extraKey: 61 } +``` + +### `.strict` + +기본적으로 Zod 객체 스키마는 파싱 과정에서 인식되지 않는 키를 제거합니다. `.strict()`를 사용하면 알려지지 않은 키를 허용하지 않을 수 있습니다. 입력에 알려지지 않은 키가 있으면 Zod가 에러를 발생시킵니다. + +```ts +const person = z + .object({ + name: z.string(), + }) + .strict(); + +person.parse({ + name: "bob dylan", + extraKey: 61, +}); +// => ZodError 발생 +``` + +### `.strip` 메서드 + +`.strip` 메서드를 사용하면 객체 스키마를 기본 동작으로 재설정할 수 있습니다. 이때, 인식되지 않는 키는 제거됩니다. + +### `.catchall` + +객체 스키마에 "catchall" 스키마를 전달할 수 있습니다. 이 경우, 알려지지 않은 모든 키는 이 스키마에 따라 검증됩니다. + +```ts +const person = z + .object({ + name: z.string(), + }) + .catchall(z.number()); + +person.parse({ + name: "bob dylan", + validExtraKey: 61, // 정상 동작 +}); + +person.parse({ + name: "bob dylan", + validExtraKey: false, // 실패 +}); +// => ZodError 발생 +``` + +`.catchall()`을 사용하면 `.passthrough()`, `.strip()`, `.strict()`가 필요 없어집니다. 모든 키가 "알려진" 것으로 간주됩니다. + +## 배열 + +```ts +const stringArray = z.array(z.string()); + +// 동일한 표현 +const stringArray = z.string().array(); +``` + +`.array()` 메서드를 사용할 때 주의해야 합니다. 이 메서드는 새로운 `ZodArray` 인스턴스를 반환합니다. 따라서 메서드를 호출하는 **순서**가 중요합니다. 예를 들어: + +```ts +z.string().optional().array(); // (string | undefined)[] +z.string().array().optional(); // string[] | undefined +``` + +위 예제에서 첫 번째 줄은 각 요소가 `string` 또는 `undefined`일 수 있는 배열을 생성합니다. 반면 두 번째 줄은 `string` 배열 자체가 `undefined`일 수 있음을 의미합니다. + +### `.element` + +배열의 요소에 대한 스키마에 접근하려면 `.element`를 사용합니다. + +```ts +stringArray.element; // => string 스키마 +``` + +### `.nonempty` + +배열에 최소한 하나의 요소가 포함되어 있는지 확인하려면 `.nonempty()`를 사용하세요. + +```ts +const nonEmptyStrings = z.string().array().nonempty(); +// 추론된 타입은 이제 다음과 같습니다. +// [string, ...string[]] + +nonEmptyStrings.parse([]); // 오류 발생: "배열이 비어 있을 수 없습니다" +nonEmptyStrings.parse(["Ariana Grande"]); // 통과 +``` + +필요한 경우 커스텀 오류 메시지를 지정할 수도 있습니다: + +```ts +// 커스텀 오류 메시지 지정 +const nonEmptyStrings = z.string().array().nonempty({ + message: "비어 있을 수 없습니다!", +}); +``` + +### `.min` / `.max` / `.length` + +```ts +z.string().array().min(5); // 배열은 5개 이상의 항목을 포함해야 함 +z.string().array().max(5); // 배열은 5개 이하의 항목을 포함해야 함 +z.string().array().length(5); // 배열은 정확히 5개의 항목을 포함해야 함 +``` + +`.nonempty()`와 달리, 이 메서드들은 타입 추론에 영향을 주지 않습니다. + +## 튜플(Tuples) + +배열과 달리 튜플은 고정된 수의 요소를 가지며, 각 요소는 서로 다른 타입을 가질 수 있습니다. + +```ts +const athleteSchema = z.tuple([ + z.string(), // 이름 + z.number(), // 등번호 + z.object({ + pointsScored: z.number(), + }), // 통계 +]); + +type Athlete = z.infer; +// type Athlete = [string, number, { pointsScored: number }] +``` + +`.rest` 메서드를 사용하면 가변 인수("rest")를 추가할 수 있습니다. + +```ts +const variadicTuple = z.tuple([z.string()]).rest(z.number()); +const result = variadicTuple.parse(["hello", 1, 2, 3]); +// => [string, ...number[]]; +``` + +## 유니온 타입 + +Zod는 "OR" 타입을 구성하기 위해 내장된 `z.union` 메서드를 제공합니다. + +```ts +const stringOrNumber = z.union([z.string(), z.number()]); + +stringOrNumber.parse("foo"); // 통과 +stringOrNumber.parse(14); // 통과 +``` + +Zod는 입력값을 각 "옵션"에 대해 순서대로 테스트하고, 유효성 검사를 통과한 첫 번째 값을 반환합니다. + +편의를 위해 [`.or` 메서드](#or)를 사용할 수도 있습니다. + +```ts +const stringOrNumber = z.string().or(z.number()); +``` + +**선택적 문자열 유효성 검사:** + +선택적인 폼 입력을 검증하기 위해, 원하는 문자열 유효성 검사를 빈 문자열 [리터럴](#리터럴literals)과 함께 유니온으로 결합할 수 있습니다. + +다음 예제는 선택적이지만 [유효한 URL](#문자열)을 포함해야 하는 입력을 검증합니다. + +```ts +const optionalUrl = z.union([z.string().url().nullish(), z.literal("")]); + +console.log(optionalUrl.safeParse(undefined).success); // true +console.log(optionalUrl.safeParse(null).success); // true +console.log(optionalUrl.safeParse("").success); // true +console.log(optionalUrl.safeParse("https://zod.dev").success); // true +console.log(optionalUrl.safeParse("not a valid url").success); // false +``` + +## 구별된 유니온(Discriminated Unions) + +구별된 유니온은 특정 키를 공유하는 객체 스키마들의 유니온입니다. + +```ts +type MyUnion = + | { status: "success"; data: string } + | { status: "failed"; error: Error }; +``` + +이러한 유니온은 `z.discriminatedUnion` 메서드를 사용하여 표현할 수 있습니다. 이 방법은 Zod가 `discriminator key`(위 예제에서는 `status`)를 확인하여 어떤 스키마를 사용하여 입력을 파싱할지 결정할 수 있게 해주기 때문에 더 빠른 평가를 가능하게 합니다. 이는 파싱을 더 효율적으로 만들고, Zod가 더 친절한 오류를 보고할 수 있게 해줍니다. + +기본 유니온 메서드를 사용하면 입력이 제공된 "옵션" 각각에 대해 테스트되고, 유효하지 않은 경우 모든 "옵션"에 대한 문제가 Zod 오류에 표시됩니다. 반면, 구별된 유니온은 하나의 "옵션"만 선택하여 테스트하고, 해당 "옵션"과 관련된 문제만 표시할 수 있습니다. + +```ts +const myUnion = z.discriminatedUnion("status", [ + z.object({ status: z.literal("success"), data: z.string() }), + z.object({ status: z.literal("failed"), error: z.instanceof(Error) }), +]); + +myUnion.parse({ status: "success", data: "yippie ki yay" }); +``` + +`.options` 속성을 사용하여 스키마 배열에 대한 참조를 추출할 수 있습니다. + +```ts +myUnion.options; // [ZodObject, ZodObject] +``` + +두 개 이상의 구별된 유니온을 병합하려면, `.options`와 구조 분해 할당을 사용하세요. + +```ts +const A = z.discriminatedUnion("status", [ + /* options */ +]); +const B = z.discriminatedUnion("status", [ + /* options */ +]); + +const AB = z.discriminatedUnion("status", [...A.options, ...B.options]); +``` + +## 레코드(Records) + +레코드 스키마는 `Record`와 같은 타입을 검증하는 데 사용됩니다. 이는 ID로 아이템을 저장하거나 캐싱할 때 특히 유용합니다. + +```ts +const User = z.object({ name: z.string() }); + +const UserStore = z.record(z.string(), User); +type UserStore = z.infer; +// => Record +``` + +스키마와 추론된 타입은 다음과 같이 사용할 수 있습니다: + +```ts +const userStore: UserStore = {}; + +userStore["77d2586b-9e8e-4ecf-8b21-ea7e0530eadd"] = { + name: "Carlotta", +}; // 통과 + +userStore["77d2586b-9e8e-4ecf-8b21-ea7e0530eadd"] = { + whatever: "Ice cream sundae", +}; // TypeError 발생 +``` + +**숫자 키에 대한 주의사항** + +`z.record(keyType, valueType)`는 숫자 키 타입을 허용할 수 있고, TypeScript의 내장 `Record` 타입은 `Record`입니다. 하지만 TypeScript의 `Record` 타입을 Zod에서 표현하는 것은 어렵습니다. + +TypeScript의 `[k: number]` 동작은 약간 직관적이지 않습니다: + +```ts +const testMap: { [k: number]: string } = { + 1: "one", +}; + +for (const key in testMap) { + console.log(`${key}: ${typeof key}`); +} +// 출력: `1: string` +``` + +보시다시피, JavaScript는 모든 객체 키를 내부적으로 문자열로 변환합니다. Zod는 정적 타입과 런타임 타입 간의 간극을 메우려고 하기 때문에, 런타임 JavaScript에서는 숫자 키가 존재하지 않으므로 숫자 키를 가진 레코드 스키마를 생성하는 방법을 제공하는 것은 의미가 없습니다. + +## 맵(Map) + +```ts +const stringNumberMap = z.map(z.string(), z.number()); + +type StringNumberMap = z.infer; +// type StringNumberMap = Map +``` + +위 코드는 `zod` 라이브러리를 사용하여 문자열을 키로, 숫자를 값으로 가지는 맵을 정의하는 예제입니다. `z.map()` 함수는 첫 번째 인자로 키의 타입을, 두 번째 인자로 값의 타입을 받습니다. 이렇게 정의된 맵의 타입은 `Map`가 됩니다. + +## Set(집합) + +```ts +const numberSet = z.set(z.number()); +type NumberSet = z.infer; +// type NumberSet = Set +``` + +Set 스키마는 다음과 같은 유틸리티 메서드로 추가 제약을 걸 수 있습니다. + +```ts +z.set(z.string()).nonempty(); // 최소 하나 이상의 항목을 포함해야 함 +z.set(z.string()).min(5); // 5개 이상의 항목을 포함해야 함 +z.set(z.string()).max(5); // 5개 이하의 항목을 포함해야 함 +z.set(z.string()).size(5); // 정확히 5개의 항목을 포함해야 함 +``` + +## 교차 타입(Intersections) + +교차 타입은 "논리적 AND" 타입을 생성할 때 유용합니다. 두 객체 타입을 교차시키는 데 사용할 수 있습니다. + +```ts +const Person = z.object({ + name: z.string(), +}); + +const Employee = z.object({ + role: z.string(), +}); + +const EmployedPerson = z.intersection(Person, Employee); + +// 다음과 동일합니다: +const EmployedPerson = Person.and(Employee); +``` + +하지만 많은 경우, 두 객체를 병합할 때 `A.merge(B)`를 사용하는 것이 권장됩니다. `.merge` 메서드는 새로운 `ZodObject` 인스턴스를 반환하는 반면, `A.and(B)`는 `pick`이나 `omit`과 같은 일반적인 객체 메서드가 없는 `ZodIntersection` 인스턴스를 반환합니다. + +```ts +const a = z.union([z.number(), z.string()]); +const b = z.union([z.number(), z.boolean()]); +const c = z.intersection(a, b); + +type c = z.infer; // => number +``` + + + + + +## 재귀 타입 + +Zod에서 재귀 스키마를 정의할 수 있지만, TypeScript의 한계로 인해 해당 타입을 정적으로 추론할 수 없습니다. 대신 타입 정의를 수동으로 작성하고, Zod에 "타입 힌트"로 제공해야 합니다. + +```ts +const baseCategorySchema = z.object({ + name: z.string(), +}); + +type Category = z.infer & { + subcategories: Category[]; +}; + +const categorySchema: z.ZodType = baseCategorySchema.extend({ + subcategories: z.lazy(() => categorySchema.array()), +}); + +categorySchema.parse({ + name: "People", + subcategories: [ + { + name: "Politicians", + subcategories: [ + { + name: "Presidents", + subcategories: [], + }, + ], + }, + ], +}); // 통과 +``` + +이 예제는 [crasite](https://github.com/crasite)에게 감사드립니다. + +### ZodType과 ZodEffects 함께 사용하기 + +`z.ZodType`과 `z.ZodEffects`를 함께 사용할 때 ( +[`.refine`](https://github.com/colinhacks/zod#refine), +[`.transform`](https://github.com/colinhacks/zod#transform), +[`preprocess`](https://github.com/colinhacks/zod#preprocess), +등... +), 스키마의 입력과 출력 타입을 정의해야 합니다. `z.ZodType` + +```ts +const isValidId = (id: string): id is `${string}/${string}` => + id.split("/").length === 2; + +const baseSchema = z.object({ + id: z.string().refine(isValidId), +}); + +type Input = z.input & { + children: Input[]; +}; + +type Output = z.output & { + children: Output[]; +}; + +const schema: z.ZodType = baseSchema.extend({ + children: z.lazy(() => schema.array()), +}); +``` + +이 예제는 [marcus13371337](https://github.com/marcus13371337)와 [JoelBeeldi](https://github.com/JoelBeeldi)에게 감사드립니다. + +### JSON 타입 검증 + +JSON 값을 검증하고 싶다면 아래 코드 조각을 사용할 수 있습니다. + +```ts +const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]); +type Literal = z.infer; +type Json = Literal | { [key: string]: Json } | Json[]; +const jsonSchema: z.ZodType = z.lazy(() => + z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]), +); + +jsonSchema.parse(data); +``` + +이 코드는 [ggoodman](https://github.com/ggoodman)이 제안한 내용을 기반으로 작성되었습니다. + +### 순환 객체 + +Zod는 재귀 스키마를 지원하지만, 순환 데이터를 전달하면 경우에 따라 무한 루프가 발생할 수 있습니다. + +> 문제가 발생하기 전에 순환 객체를 감지하려면 [이 방법](https://gist.github.com/colinhacks/d35825e505e635df27cc950776c5500b)을 고려해 보세요. + +## Promise + +```ts +const numberPromise = z.promise(z.number()); +``` + +Promise 스키마를 사용할 때 "파싱"은 약간 다르게 동작합니다. 검증은 두 단계로 이루어집니다: + +1. Zod는 입력값이 Promise의 인스턴스인지 동기적으로 확인합니다. 즉, `.then`과 `.catch` 메서드를 가진 객체인지 확인합니다. +2. Zod는 `.then`을 사용해 기존 Promise에 추가 검증 단계를 연결합니다. 반환된 Promise에서 `.catch`를 사용해 검증 실패를 처리해야 합니다. + +```ts +numberPromise.parse("tuna"); +// ZodError: Non-Promise type: string + +numberPromise.parse(Promise.resolve("tuna")); +// => Promise + +const test = async () => { + await numberPromise.parse(Promise.resolve("tuna")); + // ZodError: Non-number type: string + + await numberPromise.parse(Promise.resolve(3.14)); + // => 3.14 +}; +``` + + + +## Instanceof + +`z.instanceof`를 사용하면 입력값이 특정 클래스의 인스턴스인지 확인할 수 있습니다. 이는 외부 라이브러리에서 내보낸 클래스에 대해 입력값을 검증할 때 유용합니다. + +```ts +class Test { + name: string; +} + +const TestSchema = z.instanceof(Test); + +const blob: any = "whatever"; +TestSchema.parse(new Test()); // 통과 +TestSchema.parse(blob); // 오류 발생 +``` + +위 예제에서 `TestSchema`는 `Test` 클래스의 인스턴스인지 검증합니다. `new Test()`는 검증을 통과하지만, `blob`은 문자열이므로 오류가 발생합니다. + +## 함수 + +Zod는 "함수 스키마"를 정의할 수 있게 해줍니다. 이를 통해 검증 코드와 "비즈니스 로직"을 섞지 않고도 함수의 입력과 출력을 쉽게 검증할 수 있습니다. + +`z.function(args, returnType)`를 사용해 함수 스키마를 만들 수 있습니다. + +```ts +const myFunction = z.function(); + +type myFunction = z.infer; +// => ()=>unknown +``` + +입력과 출력을 정의해 보겠습니다. + +```ts +const myFunction = z + .function() + .args(z.string(), z.number()) // 임의의 수의 인자를 받음 + .returns(z.boolean()); + +type myFunction = z.infer; +// => (arg0: string, arg1: number)=>boolean +``` + +함수 스키마는 `.implement()` 메서드를 가지고 있습니다. 이 메서드는 함수를 인자로 받아 입력과 출력을 자동으로 검증하는 새로운 함수를 반환합니다. + +```ts +const trimmedLength = z + .function() + .args(z.string()) // 임의의 수의 인자를 받음 + .returns(z.number()) + .implement((x) => { + // TypeScript는 x가 문자열임을 알고 있습니다! + return x.trim().length; + }); + +trimmedLength("sandwich"); // => 8 +trimmedLength(" asdf "); // => 4 +``` + +입력만 검증하고 싶다면 `.returns()` 메서드를 호출하지 않으면 됩니다. 출력 타입은 구현에서 추론됩니다. + +> 함수가 아무것도 반환하지 않는다면 특별한 `z.void()` 옵션을 사용할 수 있습니다. 이렇게 하면 Zod가 void 반환 함수의 타입을 올바르게 추론할 수 있습니다. (void 반환 함수는 실제로 undefined를 반환합니다.) + +```ts +const myFunction = z + .function() + .args(z.string()) + .implement((arg) => { + return [arg.length]; + }); + +myFunction; // (arg: string)=>number[] +``` + +함수 스키마에서 입력과 출력 스키마를 추출할 수 있습니다. + +```ts +myFunction.parameters(); +// => ZodTuple + +myFunction.returnType(); +// => ZodBoolean +``` + +## 전처리(Preprocess) + +> Zod는 이제 `.preprocess()` 없이도 기본 타입 강제 변환을 지원합니다. 자세한 내용은 [강제 변환 문서](#기본-타입-강제-변환coercion)를 참고하세요. + +일반적으로 Zod는 "파싱 후 변환" 패러다임으로 동작합니다. Zod는 먼저 입력값을 검증한 다음, 변환 함수 체인을 통해 전달합니다. (변환에 대한 자세한 내용은 [.transform 문서](#transform)를 참고하세요.) + +하지만 때로는 파싱이 일어나기 `전에` 입력값에 어떤 변환을 적용하고 싶을 때가 있습니다. 일반적인 사용 사례는 타입 강제 변환입니다. Zod는 `z.preprocess()`를 통해 이를 가능하게 합니다. + +```ts +const castToString = z.preprocess((val) => String(val), z.string()); +``` + +이 코드는 `ZodEffects` 인스턴스를 반환합니다. `ZodEffects`는 전처리, 개선(refinements), 변환과 관련된 모든 로직을 포함하는 래퍼 클래스입니다. + +## 커스텀 스키마 + +Zod에서 `z.custom()`을 사용하면 TypeScript의 어떤 타입이든 스키마로 만들 수 있습니다. 이 기능은 Zod에서 기본적으로 지원하지 않는 타입, 예를 들어 템플릿 문자열 리터럴 같은 경우에 유용합니다. + +```ts +const px = z.custom((val) => { + return typeof val === "string" ? /^\d+px$/.test(val) : false; +}); + +type px = z.infer; // `${number}px` + +px.parse("42px"); // "42px" +px.parse("42vw"); // 에러 발생 +``` + +만약 검증 함수를 제공하지 않으면, Zod는 모든 값을 허용합니다. 이는 위험할 수 있습니다! + +```ts +z.custom(); // 검증을 수행하지 않음 +``` + +두 번째 인자로 에러 메시지나 다른 옵션을 커스텀할 수 있습니다. 이 매개변수는 [`.refine`](#refine)의 params 매개변수와 동일하게 작동합니다. + +```ts +z.custom((val) => ..., "커스텀 에러 메시지"); +``` + +## 스키마 메서드 + +모든 Zod 스키마는 특정 메서드를 포함하고 있습니다. + +### `.parse` + +`.parse(data: unknown): T` + +Zod 스키마가 주어지면, `.parse` 메서드를 호출하여 `data`가 유효한지 확인할 수 있습니다. 유효하다면, 완전한 타입 정보와 함께 값이 반환됩니다! 그렇지 않으면 오류가 발생합니다. + +> 중요: `.parse`가 반환하는 값은 여러분이 전달한 변수의 **깊은 복사본(deep clone)**입니다. + +```ts +const stringSchema = z.string(); + +stringSchema.parse("fish"); // => "fish" 반환 +stringSchema.parse(12); // 오류 발생 +``` + +### `.parseAsync` + +`.parseAsync(data:unknown): Promise` + +비동기 [검증](#refine)이나 [변환](#transform)을 사용할 경우, `.parseAsync`를 사용해야 합니다. + +```ts +const stringSchema = z.string().refine(async (val) => val.length <= 8); + +await stringSchema.parseAsync("hello"); // => "hello" 반환 +await stringSchema.parseAsync("hello world"); // => 에러 발생 +``` + +### `.safeParse` + +`.safeParse(data:unknown): { success: true; data: T; } | { success: false; error: ZodError; }` + +Zod에서 유효성 검사가 실패할 때 에러를 던지지 않도록 하려면 `.safeParse`를 사용하세요. 이 메서드는 성공적으로 파싱된 데이터를 포함하는 객체 또는 유효성 검사 문제에 대한 상세 정보를 담은 ZodError 인스턴스를 반환합니다. + +```ts +stringSchema.safeParse(12); +// => { success: false; error: ZodError } + +stringSchema.safeParse("billie"); +// => { success: true; data: 'billie' } +``` + +결과는 **판별 유니온(discriminated union)**이기 때문에, 에러를 매우 편리하게 처리할 수 있습니다: + +```ts +const result = stringSchema.safeParse("billie"); +if (!result.success) { + // 에러 처리 후 반환 + result.error; +} else { + // 작업 수행 + result.data; +} +``` + +### `.safeParseAsync` + +> 별칭: `.spa` + +`safeParse`의 비동기 버전입니다. + +```ts +await stringSchema.safeParseAsync("billie"); +``` + +편의를 위해 `.spa`로 별칭이 지정되었습니다: + +```ts +await stringSchema.spa("billie"); +``` + +### `.refine` + +`.refine(validator: (data:T)=>any, params?: RefineParams)` + +Zod는 **커스텀 검증 로직**을 제공할 수 있도록 `refinements` 기능을 지원합니다. (여러 이슈를 생성하거나 에러 코드를 커스텀하는 등 고급 기능을 원한다면 [`.superRefine`](#superrefine)을 참고하세요.) + +Zod는 TypeScript와 최대한 비슷하게 동작하도록 설계되었습니다. 하지만 TypeScript의 타입 시스템으로는 표현할 수 없는 다양한 **리파인먼트 타입**이 존재합니다. 예를 들어, 숫자가 정수인지 확인하거나 문자열이 유효한 이메일 주소인지 검사하는 경우가 있습니다. + +예를 들어, `.refine`을 사용해 **어떤 Zod 스키마**에든 커스텀 검증을 추가할 수 있습니다: + +```ts +const myString = z.string().refine((val) => val.length <= 255, { + message: "문자열은 255자를 초과할 수 없습니다.", +}); +``` + +> ⚠️ 리파인먼트 함수는 **에러를 던지지 않아야 합니다**. 대신 검증 실패 시 `falsy` 값을 반환해야 합니다. + +#### 인자 + +`.refine`은 두 가지 인자를 받습니다. + +1. 첫 번째는 검증 함수입니다. 이 함수는 하나의 입력(`T` 타입 — 스키마에서 추론된 타입)을 받고 `any`를 반환합니다. 참 같은 값(truthy value)이면 검증을 통과합니다. (zod@1.6.2 이전 버전에서는 검증 함수가 불리언 값을 반환해야 했습니다.) +2. 두 번째는 몇 가지 옵션을 받습니다. 이 옵션을 사용해 특정 오류 처리 동작을 커스터마이징할 수 있습니다: + +```ts +type RefineParams = { + // 오류 메시지 재정의 + message?: string; + + // 오류 경로에 추가 + path?: (string | number)[]; + + // 오류 맵에서 메시지를 커스터마이징할 때 사용할 수 있는 파라미터 객체 + params?: object; +}; +``` + +고급 사용 사례에서는 두 번째 인자가 `RefineParams`를 반환하는 함수일 수도 있습니다. + +```ts +const longString = z.string().refine( + (val) => val.length > 10, + (val) => ({ message: `${val}은 10자를 초과하지 않습니다.` }), +); +``` + +#### 에러 경로 커스텀하기 + +```ts +const passwordForm = z + .object({ + password: z.string(), + confirm: z.string(), + }) + .refine((data) => data.password === data.confirm, { + message: "비밀번호가 일치하지 않습니다", + path: ["confirm"], // 에러 경로 + }); + +passwordForm.parse({ password: "asdf", confirm: "qwer" }); +``` + +`path` 매개변수를 제공했기 때문에, 발생한 에러는 다음과 같습니다: + +```ts +ZodError { + issues: [{ + "code": "custom", + "path": [ "confirm" ], + "message": "비밀번호가 일치하지 않습니다" + }] +} +``` + +#### 비동기 정제(Refinements) + +정제는 비동기로도 사용할 수 있습니다: + +```ts +const userId = z.string().refine(async (id) => { + // 데이터베이스에서 ID가 존재하는지 확인 + return true; +}); +``` + +> ⚠️ 비동기 정제를 사용할 경우, 데이터를 파싱할 때 반드시 `.parseAsync` 메서드를 사용해야 합니다! 그렇지 않으면 Zod에서 에러가 발생합니다. + +#### transform과 refine의 관계 + +transform과 refine은 서로 교차하여 사용할 수 있습니다: + +```ts +z.string() + .transform((val) => val.length) // 문자열을 길이로 변환 + .refine((val) => val > 25); // 길이가 25보다 큰지 검증 +``` + + + +### `.superRefine` + +`.refine` 메서드는 사실 더 강력하고(그리고 더 장황한) `superRefine`이라는 메서드 위에 구축된 문법적 설탕(syntactic sugar)입니다. 다음은 예제입니다: + +```ts +const Strings = z.array(z.string()).superRefine((val, ctx) => { + if (val.length > 3) { + ctx.addIssue({ + code: z.ZodIssueCode.too_big, + maximum: 3, + type: "array", + inclusive: true, + message: "Too many items 😡", + }); + } + + if (val.length !== new Set(val).size) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `No duplicates allowed.`, + }); + } +}); +``` + +여러분은 원하는 만큼 이슈를 추가할 수 있습니다. 만약 함수 실행 중에 `ctx.addIssue`가 호출되지 않으면, 검증은 통과됩니다. + +일반적으로 리파인먼트는 항상 `ZodIssueCode.custom` 에러 코드를 가진 이슈를 생성하지만, `superRefine`을 사용하면 어떤 `ZodIssueCode`의 이슈든 던질 수 있습니다. 각 이슈 코드는 [ERROR_HANDLING.md](ERROR_HANDLING.md) 문서에서 자세히 설명되어 있습니다. + +#### 조기 중단 + +기본적으로, 파싱은 검증 체크가 실패한 후에도 계속 진행됩니다. 예를 들어, 여러 개의 검증을 연쇄적으로 연결하면 모든 검증이 실행됩니다. 하지만, 이후의 검증이 실행되지 않도록 **조기 중단**하는 것이 바람직할 수 있습니다. 이를 위해 `ctx.addIssue`에 `fatal` 플래그를 전달하고 `z.NEVER`를 반환하면 됩니다. + +```ts +const schema = z.number().superRefine((val, ctx) => { + if (val < 10) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "10 이상이어야 합니다", + fatal: true, + }); + + return z.NEVER; + } + + if (val !== 12) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "12여야 합니다", + }); + } +}); +``` + +#### 타입 정제 + +`.refine()` 또는 `.superRefine()`에 [타입 술어(type predicate)](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates)를 제공하면, 결과 타입이 해당 술어의 타입으로 좁혀집니다. 이는 여러 체이닝된 정제와 변환을 혼합하여 사용할 때 유용합니다: + +```ts +const schema = z + .object({ + first: z.string(), + second: z.number(), + }) + .nullable() + .superRefine((arg, ctx): arg is { first: string; second: number } => { + if (!arg) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, // 이슈 커스터마이징 + message: "객체가 존재해야 합니다.", + }); + } + + return z.NEVER; // 반환값은 사용되지 않지만, 타입을 만족시키기 위해 반환해야 함 + }) + // 여기서 TS는 arg가 null이 아니라는 것을 알고 있음 + .refine((arg) => arg.first === "bob", "`first`가 `bob`이 아닙니다!"); +``` + +> ⚠️ 검증이 통과되었는지 여부를 나타내기 위해 불리언 값을 반환하는 대신 **반드시** `ctx.addIssue()`를 사용해야 합니다. 함수 실행 중에 `ctx.addIssue`가 호출되지 않으면 검증이 통과됩니다. + +### `.transform` + +데이터를 파싱한 후 변환하려면 `transform` 메서드를 사용합니다. + +```ts +const stringToNumber = z.string().transform((val) => val.length); + +stringToNumber.parse("string"); // => 6 +``` + +이 예제에서는 문자열의 길이를 반환하는 변환 함수를 적용했습니다. `"string"`이라는 문자열을 파싱하면 그 길이인 `6`이 반환됩니다. + +#### 체이닝 순서 + +위의 `stringToNumber`는 `ZodEffects` 서브클래스의 인스턴스입니다. 이는 `ZodString`의 인스턴스가 아닙니다. 만약 `ZodString`의 내장 메서드(예: `.email()`)를 사용하려면, **변환(transform)을 적용하기 전에** 해당 메서드를 먼저 적용해야 합니다. + +```ts +const emailToDomain = z + .string() + .email() + .transform((val) => val.split("@")[1]); + +emailToDomain.parse("colinhacks@example.com"); // => example.com +``` + +#### 변환 중에 유효성 검사하기 + +`.transform` 메서드는 값을 변환하면서 동시에 유효성을 검사할 수 있습니다. 이 방법은 `transform`과 `refine`을 체이닝하는 것보다 간단하고 중복을 줄일 수 있습니다. + +`.superRefine`과 마찬가지로, 변환 함수는 `addIssue` 메서드를 포함한 `ctx` 객체를 받습니다. 이 메서드를 사용해 유효성 검사 문제를 등록할 수 있습니다. + +```ts +const numberInString = z.string().transform((val, ctx) => { + const parsed = parseInt(val); + if (isNaN(parsed)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "숫자가 아닙니다.", + }); + + // 이는 변환 함수에서 조기에 반환하기 위해 사용할 수 있는 + // 특별한 심볼입니다. `never` 타입을 가지므로 반환 타입에 + // 영향을 미치지 않습니다. + return z.NEVER; + } + return parsed; +}); +``` + +#### 변환과 정제의 관계 + +변환(transform)과 정제(refine)는 서로 교차하여 사용할 수 있습니다. 이들은 선언된 순서대로 실행됩니다. + +```ts +const nameToGreeting = z + .string() + .transform((val) => val.toUpperCase()) // 값을 대문자로 변환 + .refine((val) => val.length > 15) // 길이가 15를 초과하는지 검증 + .transform((val) => `Hello ${val}`) // 값 앞에 "Hello"를 추가 + .refine((val) => val.indexOf("!") === -1); // 값에 "!"가 포함되지 않았는지 검증 +``` + +위 예제에서 각 단계는 순차적으로 실행됩니다. 먼저 문자열을 대문자로 변환한 후, 길이를 검증하고, 다시 문자열을 변환한 뒤, 마지막으로 특정 문자가 포함되지 않았는지 검증합니다. + +#### 비동기 변환(Async Transforms) + +변환(transform)은 비동기로도 동작할 수 있습니다. + +```ts +const IdToUser = z + .string() + .uuid() + .transform(async (id) => { + return await getUserById(id); + }); +``` + +> ⚠️ 스키마에 비동기 변환이 포함된 경우, 데이터를 파싱할 때 반드시 `.parseAsync()` 또는 `.safeParseAsync()`를 사용해야 합니다. 그렇지 않으면 Zod가 에러를 발생시킵니다. + +### `.default` + +Zod에서 "기본값" 개념을 구현하기 위해 변환(transforms)을 사용할 수 있습니다. + +```ts +const stringWithDefault = z.string().default("tuna"); + +stringWithDefault.parse(undefined); // => "tuna" +``` + +선택적으로, `.default`에 함수를 전달할 수도 있습니다. 이 함수는 기본값이 필요할 때마다 다시 실행됩니다. + +```ts +const numberWithRandomDefault = z.number().default(Math.random); + +numberWithRandomDefault.parse(undefined); // => 0.4413456736055323 +numberWithRandomDefault.parse(undefined); // => 0.1871840107401901 +numberWithRandomDefault.parse(undefined); // => 0.7223408162401552 +``` + +개념적으로, Zod는 기본값을 다음과 같이 처리합니다: + +1. 입력값이 `undefined`인 경우, 기본값을 반환합니다. +2. 그렇지 않으면, 기본 스키마를 사용해 데이터를 파싱합니다. + +### `.describe` + +`.describe()`를 사용하면 결과 스키마에 `description` 속성을 추가할 수 있습니다. + +```ts +const documentedString = z + .string() + .describe("유용한 텍스트 조각입니다. 어떻게 사용할지 알고 있다면 말이죠."); +documentedString.description; // 유용한 텍스트 조각… +``` + +이 기능은 필드를 문서화할 때 유용합니다. 예를 들어, [`zod-to-json-schema`](https://github.com/StefanTerdell/zod-to-json-schema)와 같은 라이브러리를 사용해 JSON 스키마를 생성할 때 활용할 수 있습니다. + +### `.catch` + +`.catch()`를 사용하면 파싱 오류가 발생했을 때 반환할 "기본값"을 지정할 수 있습니다. + +```ts +const numberWithCatch = z.number().catch(42); + +numberWithCatch.parse(5); // => 5 +numberWithCatch.parse("tuna"); // => 42 +``` + +선택적으로, `.catch`에 함수를 전달할 수도 있습니다. 이 함수는 기본값이 필요할 때마다 다시 실행됩니다. 이때, 발생한 오류를 담고 있는 `ctx` 객체가 함수에 전달됩니다. + +```ts +const numberWithRandomCatch = z.number().catch((ctx) => { + ctx.error; // 발생한 ZodError + return Math.random(); +}); + +numberWithRandomCatch.parse("sup"); // => 0.4413456736055323 +numberWithRandomCatch.parse("sup"); // => 0.1871840107401901 +numberWithRandomCatch.parse("sup"); // => 0.7223408162401552 +``` + +Zod가 "기본값"을 처리하는 방식은 다음과 같습니다: + +1. 기본 스키마를 사용해 데이터를 파싱합니다. +2. 파싱이 실패하면 "기본값"을 반환합니다. + +### `.optional` + +스키마의 옵셔널 버전을 반환하는 편의 메서드입니다. + +```ts +const optionalString = z.string().optional(); // string | undefined + +// 아래와 동일합니다 +z.optional(z.string()); +``` + +### `.nullable` + +이 메서드는 스키마의 nullable 버전을 반환하는 편의 기능입니다. + +```ts +const nullableString = z.string().nullable(); // string | null + +// 위 코드는 아래와 동일합니다. +z.nullable(z.string()); +``` + +이 메서드를 사용하면 특정 타입에 `null`을 허용하는 스키마를 쉽게 정의할 수 있습니다. 예를 들어, `z.string().nullable()`은 문자열 또는 `null` 값을 허용하는 스키마를 생성합니다. 이는 `z.nullable(z.string())`과 동일한 결과를 제공합니다. + +### `.nullish` + +스키마의 "nullish" 버전을 반환하는 편의 메서드입니다. Nullish 스키마는 `undefined`와 `null`을 모두 허용합니다. "nullish" 개념에 대해 더 자세히 알아보려면 [TypeScript 3.7 릴리스 노트](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#nullish-coalescing)를 참고하세요. + +```ts +const nullishString = z.string().nullish(); // string | null | undefined + +// 위 코드는 아래와 동일합니다. +z.string().nullable().optional(); +``` + +### `.array()` + +주어진 타입에 대한 배열 스키마를 반환하는 편의 메서드입니다. + +```ts +const stringArray = z.string().array(); // string[] + +// 위 코드는 아래와 동일합니다 +z.array(z.string()); +``` + +이 메서드를 사용하면 특정 타입의 배열을 간단하게 정의할 수 있습니다. 예를 들어, 문자열 배열을 만들고 싶다면 `z.string().array()`를 사용하면 됩니다. 이는 `z.array(z.string())`과 동일한 결과를 반환합니다. + +### `.promise` + +Promise 타입을 위한 편의 메서드입니다: + +```ts +const stringPromise = z.string().promise(); // Promise + +// 위 코드는 아래와 동일합니다 +z.promise(z.string()); +``` + +이 메서드는 주어진 스키마를 Promise로 감싸는 역할을 합니다. 예를 들어, `z.string().promise()`는 `Promise` 타입을 반환합니다. 이는 `z.promise(z.string())`과 동일한 결과를 제공합니다. + +### `.or` + +[유니온 타입](#유니온-타입)을 편리하게 사용할 수 있는 메서드입니다. + +```ts +const stringOrNumber = z.string().or(z.number()); // string | number + +// 위 코드는 아래와 동일합니다. +z.union([z.string(), z.number()]); +``` + +- `z.string().or(z.number())`는 문자열 또는 숫자 타입을 허용하는 유니온 타입을 생성합니다. +- 이 메서드는 `z.union([z.string(), z.number()])`와 동일한 기능을 제공하지만, 더 간결하게 작성할 수 있습니다. + +### `.and` + +교차 타입(intersection type)을 쉽게 만들 수 있는 편의 메서드입니다. + +```ts +const nameAndAge = z + .object({ name: z.string() }) + .and(z.object({ age: z.number() })); // { name: string } & { age: number } + +// 위 코드는 아래와 동일합니다. +z.intersection(z.object({ name: z.string() }), z.object({ age: z.number() })); +``` + +### `.brand` + +`.brand() => ZodBranded` + +TypeScript의 타입 시스템은 구조적(structural)입니다. 이는 구조적으로 동일한 두 타입을 같은 타입으로 간주한다는 의미입니다. + +```ts +type Cat = { name: string }; +type Dog = { name: string }; + +const petCat = (cat: Cat) => {}; +const fido: Dog = { name: "fido" }; +petCat(fido); // 문제 없이 동작 +``` + +어떤 경우에는 TypeScript 내에서 `명목적 타이핑(nominal typing)`을 흉내 내고 싶을 수 있습니다. 예를 들어, Zod로 검증된 입력만 받는 함수를 작성하고 싶을 수 있습니다. 이는 `브랜드 타입(branded types)`(또는 `불투명 타입(opaque types)`)을 사용하여 달성할 수 있습니다. + +```ts +const Cat = z.object({ name: z.string() }).brand(); +type Cat = z.infer; + +const petCat = (cat: Cat) => {}; + +// 이 코드는 동작 +const simba = Cat.parse({ name: "simba" }); +petCat(simba); + +// 이 코드는 동작하지 않음 +petCat({ name: "fido" }); +``` + +내부적으로 이는 교차 타입(intersection type)을 사용하여 추론된 타입에 "브랜드"를 붙이는 방식으로 동작합니다. 이렇게 하면 일반적인/브랜드가 없는 데이터 구조는 더 이상 스키마의 추론된 타입에 할당할 수 없게 됩니다. + +```ts +const Cat = z.object({ name: z.string() }).brand(); +type Cat = z.infer; +// {name: string} & {[symbol]: "Cat"} +``` + +브랜드 타입은 `.parse`의 런타임 결과에 영향을 미치지 않는다는 점에 유의하세요. 이는 오직 정적(static) 구조일 뿐입니다. + +### `.readonly` + +`.readonly() => ZodReadonly` + +이 메서드는 기본 스키마를 사용해 입력을 파싱한 후, 결과에 `Object.freeze()`를 호출하는 `ZodReadonly` 스키마 인스턴스를 반환합니다. 추론된 타입도 `readonly`로 표시됩니다. + +```ts +const schema = z.object({ name: z.string() }).readonly(); +type schema = z.infer; +// Readonly + +const result = schema.parse({ name: "fido" }); +result.name = "simba"; // 오류 발생 +``` + +추론된 타입은 관련된 경우 TypeScript의 내장 `readonly` 타입을 사용합니다. + +```ts +z.array(z.string()).readonly(); +// readonly string[] + +z.tuple([z.string(), z.number()]).readonly(); +// readonly [string, number] + +z.map(z.string(), z.date()).readonly(); +// ReadonlyMap + +z.set(z.string()).readonly(); +// ReadonlySet +``` + +### `.pipe` + +스키마는 유효성 검사 "파이프라인"으로 연결할 수 있습니다. 이는 `.transform()` 이후의 결과를 쉽게 검증하는 데 유용합니다: + +```ts +z.string() + .transform((val) => val.length) // 문자열을 길이로 변환 + .pipe(z.number().min(5)); // 길이가 5 이상인지 검증 +``` + +`.pipe()` 메서드는 `ZodPipeline` 인스턴스를 반환합니다. + +#### `.pipe()`를 사용하여 `z.coerce`의 일반적인 문제를 해결할 수 있습니다. + +입력을 원하는 타입으로 제한한 후 `.pipe()`를 사용하여 강제 변환을 적용할 수 있습니다. + +**입력이 제한되지 않은 경우:** + +```ts +const toDate = z.coerce.date(); + +// 직관적으로 동작 +console.log(toDate.safeParse("2023-01-01").success); // true + +// 원하지 않는 결과가 나올 수 있음 +console.log(toDate.safeParse(null).success); // true +``` + +**입력이 제한된 경우:** + +```ts +const datelike = z.union([z.number(), z.string(), z.date()]); +const datelikeToDate = datelike.pipe(z.coerce.date()); + +// 여전히 직관적으로 동작 +console.log(datelikeToDate.safeParse("2023-01-01").success); // true + +// 더 원하는 결과를 얻을 수 있음 +console.log(datelikeToDate.safeParse(null).success); // false +``` + +이 기법을 사용하면 잡히지 않는 오류를 던지는 강제 변환을 피할 수도 있습니다. + +**입력이 제한되지 않은 경우:** + +```ts +const toBigInt = z.coerce.bigint(); + +// 직관적으로 동작 +console.log(toBigInt.safeParse("42")); // true + +// 원하지 않는 결과가 나올 수 있음 +console.log(toBigInt.safeParse(null)); // 잡히지 않는 오류 발생 +``` + +**입력이 제한된 경우:** + +```ts +const toNumber = z.number().or(z.string()).pipe(z.coerce.number()); +const toBigInt = z.bigint().or(toNumber).pipe(z.coerce.bigint()); + +// 여전히 직관적으로 동작 +console.log(toBigInt.safeParse("42").success); // true + +// Zod가 오류를 처리하며, 더 원하는 결과를 얻을 수 있음 +console.log(toBigInt.safeParse(null).success); // false +``` + +이렇게 `.pipe()`를 사용하면 입력을 제한하고, 원하는 타입으로 안전하게 변환할 수 있습니다. + +## Guides and concepts + +### 타입 추론 + +여러분은 `z.infer`를 사용하여 어떤 스키마의 TypeScript 타입도 추출할 수 있습니다. + +```ts +const A = z.string(); +type A = z.infer; // string + +const u: A = 12; // TypeError +const u: A = "asdf"; // 컴파일 성공 +``` + +**변환(transform)은 어떻게 될까요?** + +실제로 각 Zod 스키마는 내부적으로 **두 가지** 타입을 추적합니다: 입력 타입과 출력 타입입니다. 대부분의 스키마(예: `z.string()`)에서는 이 두 타입이 동일합니다. 하지만 변환을 추가하면 이 두 값이 달라질 수 있습니다. 예를 들어, `z.string().transform(val => val.length)`는 입력 타입이 `string`이고 출력 타입이 `number`입니다. + +입력 타입과 출력 타입을 따로 추출할 수 있습니다: + +```ts +const stringToNumber = z.string().transform((val) => val.length); + +// ⚠️ 중요: z.infer는 출력 타입을 반환합니다! +type input = z.input; // string +type output = z.output; // number + +// z.output와 동일합니다! +type inferred = z.infer; // number +``` + +### 제네릭 함수 작성하기 + +TypeScript의 제네릭을 사용하면 Zod 스키마를 매개변수로 받는 재사용 가능한 함수를 작성할 수 있습니다. 이를 통해 타입 안전성과 타입 추론을 유지하면서 커스텀 유효성 검사 로직이나 스키마 변환 등을 만들 수 있습니다. + +Zod 스키마를 입력으로 받는 함수를 작성하려고 할 때, 다음과 같은 코드를 시도해볼 수 있습니다: + +```ts +function inferSchema(schema: z.ZodType) { + return schema; +} +``` + +하지만 이 방법은 올바르지 않으며, TypeScript가 인자의 타입을 제대로 추론하는 데 제한을 줍니다. 무엇을 전달하든 `schema`의 타입은 `ZodType`의 인스턴스로 고정됩니다. + +```ts +inferSchema(z.string()); +// => ZodType +``` + +이 방식은 타입 정보를 잃어버리게 됩니다. 특히 입력이 실제로 어떤 하위 클래스(`ZodString` 등)인지 알 수 없기 때문에, 결과값에서 `.min()`과 같은 문자열 전용 메서드를 호출할 수 없습니다. + +더 나은 방법은 스키마의 추론된 타입만이 아니라 **스키마 전체를 추론**하는 것입니다. 이를 위해 `z.ZodTypeAny`라는 유틸리티 타입을 사용할 수 있습니다. + +```ts +function inferSchema(schema: T) { + return schema; +} + +inferSchema(z.string()); +// => ZodString +``` + +> `ZodTypeAny`는 `ZodType`의 축약형으로, 모든 Zod 스키마와 일치할 수 있을 만큼 넓은 타입입니다. + +이제 결과는 완전히 타입이 지정되며, 타입 시스템은 스키마의 특정 하위 클래스를 정확히 추론할 수 있습니다. + +#### 추론된 타입 유추하기 + +스키마의 제네릭 매개변수로 `z.ZodTypeAny`를 사용하는 것이 권장 사항이지만, 이 경우 파싱된 데이터가 스키마의 추론된 타입 대신 `any`로 타입 지정되는 문제가 발생할 수 있습니다. + +```ts +function parseData(data: unknown, schema: T) { + return schema.parse(data); +} + +parseData("sup", z.string()); +// => any +``` + +TypeScript의 타입 추론 방식 때문에 `schema`가 추론된 타입이 아닌 `ZodTypeAny`로 처리됩니다. 이 문제는 `z.infer`를 사용한 타입 캐스팅으로 해결할 수 있습니다. + +```ts +function parseData(data: unknown, schema: T) { + return schema.parse(data) as z.infer; + // ^^^^^^^^^^^^^^ <- 이 부분 추가 +} + +parseData("sup", z.string()); +// => string +``` + +#### 허용 가능한 입력 제한하기 + +`ZodType` 클래스는 세 가지 제네릭 매개변수를 가지고 있습니다. + +```ts +class ZodType< + Output = any, + Def extends ZodTypeDef = ZodTypeDef, + Input = Output +> { ... } +``` + +이 제네릭 매개변수를 제한함으로써, 여러분의 함수에 허용 가능한 스키마 입력을 제한할 수 있습니다: + +```ts +function makeSchemaOptional(schema: T) { + return schema.optional(); +} + +makeSchemaOptional(z.string()); +// 정상적으로 동작 + +makeSchemaOptional(z.number()); +// 오류: 'ZodNumber'는 'ZodType' 타입의 매개변수에 할당할 수 없음 +``` + +### 에러 처리 + +Zod는 `ZodError`라는 Error의 하위 클래스를 제공합니다. ZodError는 유효성 검사 문제에 대한 상세 정보를 담고 있는 `issues` 배열을 포함합니다. + +```ts +const result = z + .object({ + name: z.string(), + }) + .safeParse({ name: 12 }); + +if (!result.success) { + result.error.issues; + /* [ + { + "code": "invalid_type", + "expected": "string", + "received": "number", + "path": [ "name" ], + "message": "Expected string, received number" + } + ] */ +} +``` + +> 가능한 에러 코드와 에러 메시지를 커스터마이징하는 방법에 대한 자세한 정보는 전용 에러 처리 가이드를 참고하세요: [ERROR_HANDLING.md](ERROR_HANDLING.md) + +Zod의 에러 보고는 **완전성**과 **정확성**을 강조합니다. 최종 사용자에게 유용한 에러 메시지를 제공하려면, 에러 맵을 사용해 Zod의 에러 메시지를 재정의하거나, [`zod-validation-error`](https://github.com/causaly/zod-validation-error)와 같은 서드파티 라이브러리를 사용하는 것이 좋습니다. + +### 에러 포맷팅 + +`.format()` 메서드를 사용하여 이 에러를 중첩된 객체로 변환할 수 있습니다. + +```ts +const result = z + .object({ + name: z.string(), + }) + .safeParse({ name: 12 }); + +if (!result.success) { + const formatted = result.error.format(); + /* { + name: { _errors: [ '문자열이어야 하는데, 숫자가 입력되었습니다' ] } + } */ + + formatted.name?._errors; + // => ["문자열이어야 하는데, 숫자가 입력되었습니다"] +} +``` + +이 코드는 `zod` 라이브러리를 사용하여 객체의 유효성을 검사하고, 에러가 발생했을 때 해당 에러를 포맷팅하는 예제입니다. `name` 필드에 숫자 대신 문자열이 입력되어야 하는데, 숫자가 입력되면 에러 메시지가 반환됩니다. + +## 비교 + +다양한 검증 라이브러리가 널리 사용되고 있지만, 대부분 개발자 경험을 저해하는 설계상의 한계를 가지고 있습니다. + + + + + + + +이 표와 설명은 각 라이브러리의 기능을 비교하며, 개발자가 프로젝트에 적합한 도구를 선택할 때 도움을 줍니다. 각 라이브러리의 강점과 약점을 이해하면 더 나은 결정을 내릴 수 있습니다. + +### Joi + +[https://github.com/hapijs/joi](https://github.com/hapijs/joi) + +정적 타입 추적을 지원하지 않습니다 😕 + +### Yup + +[https://github.com/jquense/yup](https://github.com/jquense/yup) + +Yup은 처음에 바닐라 자바스크립트로 구현되었고, 이후 타입스크립트로 다시 작성된 기능이 풍부한 라이브러리입니다. + +- 캐스팅(casting)과 변환(transforms)을 지원합니다. +- 모든 객체 필드는 기본적으로 선택 사항(optional)입니다. + +- Promise 스키마를 지원하지 않습니다. +- 함수 스키마를 지원하지 않습니다. +- 유니온(union) 및 인터섹션(intersection) 스키마를 지원하지 않습니다. + + + +### io-ts + +[https://github.com/gcanti/io-ts](https://github.com/gcanti/io-ts) + +io-ts는 gcanti가 만든 훌륭한 라이브러리입니다. io-ts의 API는 Zod의 디자인에 큰 영감을 주었습니다. + +우리의 경험상, io-ts는 많은 경우에 개발자 경험보다 함수형 프로그래밍의 순수성을 우선시합니다. 이는 타당하고 존경할 만한 디자인 목표이지만, io-ts를 기존의 절차적 또는 객체 지향적 코드베이스에 통합하기 어렵게 만듭니다. 예를 들어, io-ts에서 선택적 속성을 가진 객체를 정의하는 방법을 살펴보겠습니다. + +```ts +import * as t from "io-ts"; + +const A = t.type({ + foo: t.string, +}); + +const B = t.partial({ + bar: t.number, +}); + +const C = t.intersection([A, B]); + +type C = t.TypeOf; +// 반환 타입: { foo: string; bar?: number | undefined } +``` + +필수 속성과 선택적 속성을 별도의 객체 검증기로 정의하고, 선택적 속성을 `t.partial`을 통해 표시한 후, `t.intersection`으로 결합해야 합니다. + +Zod에서 동일한 작업을 수행하는 방법은 다음과 같습니다. + +```ts +const C = z.object({ + foo: z.string(), + bar: z.number().optional(), +}); + +type C = z.infer; +// 반환 타입: { foo: string; bar?: number | undefined } +``` + +이러한 선언적 API는 스키마 정의를 훨씬 더 간결하게 만듭니다. + +`io-ts`는 또한 결과를 파싱하고 오류를 처리하기 위해 gcanti의 함수형 프로그래밍 라이브러리인 `fp-ts`를 사용해야 합니다. 이는 코드베이스를 엄격하게 함수형으로 유지하려는 개발자에게 훌륭한 리소스입니다. 하지만 `fp-ts`에 의존하는 것은 필연적으로 많은 지적 부담을 동반합니다. 개발자는 함수형 프로그래밍 개념과 `fp-ts`의 명명법에 익숙해야 라이브러리를 사용할 수 있습니다. + +- 직렬화 및 역직렬화 변환을 지원하는 코덱 지원 +- 브랜드 타입 지원 +- 고급 함수형 프로그래밍, 고차 타입, `fp-ts` 호환성 지원 +- 객체 메서드 부재: (pick, omit, partial, deepPartial, merge, extend) +- 적절한 타입을 가진 비어 있지 않은 배열 부재 (`[T, ...T[]]`) +- Promise 스키마 부재 +- 함수 스키마 부재 + +### Runtypes + +[Runtypes GitHub 페이지](https://github.com/pelotom/runtypes) + +Runtypes는 **좋은 타입 추론**을 지원합니다. + +- **패턴 매칭** 지원: 유니온 타입에 대해 분배되는 계산된 속성 +- **누락된 객체 메서드**: `deepPartial`, `merge`와 같은 기능이 없음 +- **비어 있지 않은 배열**에 대한 적절한 타입 지원 부재 (`[T, ...T[]]` 형태) +- **Promise 스키마** 지원 미흡 +- **에러 커스터마이징** 기능 부재 + +Runtypes는 타입 추론에 강점이 있지만, 몇 가지 기능이 아직 구현되지 않았습니다. 특히 객체 메서드와 비어 있지 않은 배열에 대한 타입 지원, Promise 스키마, 그리고 에러 커스터마이징 기능이 추가되면 더욱 완벽한 라이브러리가 될 것입니다. + +### Ow + +[https://github.com/sindresorhus/ow](https://github.com/sindresorhus/ow) + +Ow는 함수 입력값 검증에 초점을 맞춘 라이브러리입니다. 복잡한 조건문을 쉽게 표현할 수 있게 해주지만, 타입이 없는 데이터를 파싱하는 기능은 제공하지 않습니다. Ow는 다양한 타입을 지원하며, Zod가 TypeScript의 타입 시스템과 거의 일대일로 매핑되는 반면, Ow는 `int32Array`와 같은 매우 구체적인 타입들을 바로 사용할 수 있게 해줍니다. (자세한 목록은 README에서 확인할 수 있습니다.) + +함수 입력값을 검증하고 싶다면, Zod의 함수 스키마를 사용해보세요. 이 방법은 함수 타입 선언을 재사용할 수 있게 해주며, 매번 함수 시작 부분에 Ow 검증문을 복사해 붙여넣는 번거로움을 줄여줍니다. 또한 Zod는 반환 타입도 검증할 수 있기 때문에, 예상치 못한 데이터가 전달되지 않도록 보장할 수 있습니다. + +## 변경 내역 + +변경 내역은 [CHANGELOG.md](CHANGELOG.md)에서 확인할 수 있습니다. diff --git a/packages/docs-v3/README_ZH.md b/packages/docs-v3/README_ZH.md new file mode 100644 index 0000000000..00b9fc8e61 --- /dev/null +++ b/packages/docs-v3/README_ZH.md @@ -0,0 +1,2052 @@ +

+ Zod logo +

Zod

+

利用静态类型推断进行 TypeScript 优先模式验证 +
+ https://zod.dev +

+
+

+Zod CI status +Created by Colin McDonnell +License +npm +stars +discord server +

+ +
+ 文档 +   •   + Discord +   •   + NPM +   •   + 讨论 +   •   + @colinhacks +   •   + tRPC +
+
+ +
+
+ +# 內容 + +- [什么是 Zod](#什么是-zod) +- [生态体系](#生态体系) +- [安装](#安装) +- [基本用法](#基本用法) +- [原始类型](#原始类型) +- [原始类型的强制转换](#原始类型的强制转换) +- [字面量](#字面量) +- [字符串](#字符串) + - [ISO 日期](#iso-日期) + - [IP 地址](#ip-地址) +- [Numbers](#numbers) +- [Objects](#objects) + - [.shape](#shape) + - [.extend](#extend) + - [.merge](#merge) + - [.pick/.omit](#pickomit) + - [.partial](#partial) + - [.deepPartial](#deepPartial) + - [.passthrough](#passthrough) + - [.strict](#strict) + - [.strip](#strip) + - [.catchall](#catchall) +- [Records](#records) +- [Maps](#maps) +- [Sets](#sets) +- [Arrays](#arrays) + - [.nonempty](#nonempty) + - [.min/.max/.length](#minmaxlength) +- [Unions](#unions) +- [Discriminated unions](#discriminated-unions) +- [Optionals](#optionals) +- [Nullables](#nullables) +- [Enums](#enums) + - [Zod enums](#zod-enums) + - [Native enums](#native-enums) +- [Tuples](#tuples) +- [Recursive types](#recursive-types) + - [JSON type](#json-type) + - [Cyclical data](#cyclical-objects) +- [Promises](#promises) +- [Instanceof](#instanceof) +- [Function schemas](#function-schemas) +- [基础类方法 (ZodType)](#zodtype-methods-and-properties) + - [.parse](#parse) + - [.parseAsync](#parseasync) + - [.safeParse](#safeparse) + - [.safeParseAsync](#safeparseasync) + - [.refine](#refine) + - [.superRefine](#superRefine) + - [.transform](#transform) + - [.default](#default) + - [.optional](#optional) + - [.nullable](#nullable) + - [.nullish](#nullish) + - [.array](#array) + - [.or](#or) + - [.and](#and) +- [类型推断](#type-inference) +- [Errors](#errors) +- [比较](#comparison) + - [Joi](#joi) + - [Yup](#yup) + - [io-ts](#io-ts) + - [Runtypes](#runtypes) +- [Changelog](#changelog) + + + +# 什么是 Zod + +Zod 是一个 TypeScript 优先的模式声明和验证库。我使用术语 "模式" 来广义地指任何数据类型,从简单的 `字符串` 到复杂的嵌套对象。 + +Zod 围绕尽可能友好的开发体验而设计。其目的是消除重复的类型声明。使用 Zod,你只需声明 _一次_ 验证器,Zod 就会自动推断出静态 TypeScript 类型。将简单类型组合成复杂的数据结构非常容易。 + +其他一些重要方面: + +- 零依赖 +- 适用于 Node.js 和所有现代浏览器 +- 小巧: 压缩后仅 8kb +- 不可变: 方法 (如 `.optional()` ) 返回一个新的实例 +- 简洁的、可链式调用的接口 +- 函数式方法: [解析,不验证](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/) +- 也可用于纯 JavaScript! 你不需要使用 TypeScript。 + +## 赞助 + +我们感谢并鼓励任何级别的赞助。Zod 是由一个单独的开发者维护的 ([hi!](https://twitter.com/colinhacks))。对于个人开发者,可以考虑[一杯咖啡级别](https://github.com/sponsors/colinhacks)。如果你使用 Zod 建立了一个付费产品,可以考虑[领奖台级别](https://github.com/sponsors/colinhacks)。 + +### 黄金 + + + + + + + + + +
+ + Astro + +
+ Astro +
+ astro.build +
+

+ Astro is a new kind of static
+ site builder for the modern web.
+ Powerful developer experience meets
+ lightweight output.

+
+ + + +
+ Glow Wallet +
+ glow.app +
+

Your new favorite +
+ Solana wallet.

+
+ + + +
+ Deletype +
+ deletype.com +
+ +### 白银 + + + + + + + + + + + +
+ + + +
+ Snaplet +
+ snaplet.dev +
+ + Marcato Partners + +
+ Marcato Partners +
+ marcatopartners.com +
+ + Trip + +
+ Trip +
+ + + +
+ Seasoned Software +
+ seasoned.cc +
+ + + +
+ Interval +
+ interval.com +
+ +### 青铜 + + + + + + + + + + + +
+ + + +
+ Brandon Bayer +
+ @flybayer, + creator of Blitz.js +
+
+ + + +
+ Jiří Brabec +
+ @brabeji +
+
+ + + +
+ Alex Johansson +
+ @alexdotjs +
+ + + +
+ Adaptable +
+ adaptable.io +
+
+ + Avana Wallet logo + +
+ Avana Wallet +
+ avanawallet.com
+ Solana non-custodial wallet +
+
+ +_要在这里看到你的名字 + Twitter + 網站 , 请在[Freelancer](https://github.com/sponsors/colinhacks) 或 [Consultancy](https://github.com/sponsors/colinhacks)赞助 Zod ._ + +# 生态体系 + +有越来越多的工具是建立在 Zod 之上或原生支持 Zod 的! 如果你在 Zod 的基础上建立了一个工具或库,请在[Twitter](https://twitter.com/colinhacks) 或者 [Discussion](https://github.com/colinhacks/zod/discussions)上告诉我。我会把它添加到下面,并在推特上发布。 + +- [`tRPC`](https://github.com/trpc/trpc): 在没有 GraphQL 的情况下建立端到端的类型安全 API +- [`react-hook-form`](https://github.com/react-hook-form/resolvers): 使用 React Hook Form 和 Zod 解析器轻松构建类型安全的表单。 +- [`ts-to-zod`](https://github.com/fabien0102/ts-to-zod): 将 TypeScript 定义转换成 Zod 模式。 +- [`zod-mocking`](https://github.com/dipasqualew/zod-mocking): 从你的 Zod 模式中生成模拟数据。 +- [`zod-fast-check`](https://github.com/DavidTimms/zod-fast-check): 从 Zod 模式中生成 `fast-check` 的任意数据。 +- [`zod-endpoints`](https://github.com/flock-community/zod-endpoints): 约定优先的严格类型的端点与 Zod。兼容 OpenAPI。 +- [`express-zod-api`](https://github.com/RobinTail/express-zod-api): 用 I/O 模式验证和自定义中间件构建基于 Express 的 API 服务 +- [`zod-i18n-map`](https://github.com/aiji42/zod-i18n): 有助于翻译 zod 错误信息。 +- [`mobx-zod-form`](https://github.com/MonoidDev/mobx-zod-form): 以数据为中心的表格构建工具,基于 MobX 和 Zod。 +- [`zodock`](https://github.com/ItMaga/zodock): 基於 Zod 模式生成模擬數據。 +- [`GQLoom`](https://github.com/modevol-com/gqloom): 使用 ZOD 编织 GraphQL Schema 和解析器。 + +# 安装 + +### 必要条件 + +- TypeScript 4.5+! +- 你必须在你的`tsconfig.json`中启用`strict`模式。这是所有 TypeScript 项目的最佳实践。 + +```ts +// tsconfig.json +{ + // ... + "compilerOptions": { + // ... + "strict": true + } +} +``` + +### 从`npm` 安装 + +```sh +npm install zod +deno add npm:zod # deno +yarn add zod # yarn +bun add zod # bun +pnpm add zod # pnpm +``` + +> README 的剩余部分假定你是直接通过 npm 安装的`zod`包。 + +# 基本用法 + +创建一个简单的字符串模式 + +```ts +import { z } from "zod"; + +// 创建一个字符串的模式 +const mySchema = z.string(); + +// 解析 +mySchema.parse("tuna"); // => "tuna" +mySchema.parse(12); // => throws ZodError + +// "安全"解析(如果验证失败不抛出错误) +mySchema.safeParse("tuna"); // => { success: true; data: "tuna" } +mySchema.safeParse(12); // => { success: false; error: ZodError } +``` + +创建一个对象模式 + +```ts +import { z } from "zod"; + +const User = z.object({ + username: z.string(), +}); + +User.parse({ username: "Ludwig" }); + +// 提取出推断的类型 +type User = z.infer; +// { username: string } +``` + +## 原始类型 + +```ts +import { z } from "zod"; + +// 原始值类型 +z.string(); +z.number(); +z.bigint(); +z.boolean(); +z.date(); +z.symbol(); + +// 空类型 +z.undefined(); +z.null(); +z.void(); // 接受 undefined + +// 任意类型 +// 允许任意类型的值 +z.any(); +z.unknown(); + +// never 类型 +// 不允许值类型存在 +z.never(); +``` + +## 原始类型的强制转换 + +Zod 现在提供了一种更方便的方法来强制转换原始类型 + +```ts +const schema = z.coerce.string(); +schema.parse("tuna"); // => "tuna" +schema.parse(12); // => "12" +schema.parse(true); // => "true" +``` + +在解析步骤中,输入将通过 `String()` 函数传递,该函数是 JavaScript 的内置函数,用于将数据强制转换为字符串。请注意,返回的模式是一个 `ZodString` 实例,因此可以使用所有字符串方法 + +```ts +z.coerce.string().email().min(5); +``` + +所有的原始类型都支持强制转换 + +```ts +z.coerce.string(); // String(input) +z.coerce.number(); // Number(input) +z.coerce.boolean(); // Boolean(input) +z.coerce.bigint(); // BigInt(input) +z.coerce.date(); // new Date(input) +``` + +**布尔类型的强制转换** + +Zod 的布尔强制非常简单!它将值传入 `Boolean(value)` 函数,仅此而已。任何真值都将解析为 `true`,任何假值都将解析为 `false` + +```ts +z.coerce.boolean().parse("tuna"); // => true +z.coerce.boolean().parse("true"); // => true +z.coerce.boolean().parse("false"); // => true +z.coerce.boolean().parse(1); // => true +z.coerce.boolean().parse([]); // => true + +z.coerce.boolean().parse(0); // => false +z.coerce.boolean().parse(undefined); // => false +z.coerce.boolean().parse(null); // => false +``` + +## 字面量(literal) + +```ts +const tuna = z.literal("tuna"); +const twelve = z.literal(12); +const twobig = z.literal(2n); // bigint literal +const tru = z.literal(true); + +const terrificSymbol = Symbol("terrific"); +const terrific = z.literal(terrificSymbol); + +// 检索字面量的值 +tuna.value; // "tuna" +``` + +> 目前在 Zod 中不支持 Date 字面量。如果你有这个功能的用例,请提交一个 Issue。 + +## 字符串 + +Zod 包括一些针对字符串的验证。 + +```ts +// 验证 +z.string().max(5); +z.string().min(5); +z.string().length(5); +z.string().email(); +z.string().url(); +z.string().emoji(); +z.string().uuid(); +z.string().cuid(); +z.string().cuid2(); +z.string().ulid(); +z.string().duration(); +z.string().regex(regex); +z.string().includes(string); +z.string().startsWith(string); +z.string().endsWith(string); +z.string().datetime(); // ISO 8601;默认值为无 UTC 偏移,选项见下文 +z.string().ip(); // 默认为 IPv4 和 IPv6,选项见下文 + +// 转变 +z.string().trim(); // 减除空白 +z.string().toLowerCase(); // 小写化 +z.string().toUpperCase(); // 大写化 +``` + +> 请查看 [validator.js](https://github.com/validatorjs/validator.js),了解可与 [Refinements](#refine) 结合使用的大量其他有用字符串验证函数。 + +创建字符串模式时,你可以自定义一些常见的错误信息 + +```ts +const name = z.string({ + required_error: "Name is required", + invalid_type_error: "Name must be a string", +}); +``` + +使用验证方法时,你可以传递一个附加参数,以提供自定义错误信息 + +```ts +z.string().min(5, { message: "Must be 5 or more characters long" }); +z.string().max(5, { message: "Must be 5 or fewer characters long" }); +z.string().length(5, { message: "Must be exactly 5 characters long" }); +z.string().email({ message: "Invalid email address" }); +z.string().url({ message: "Invalid url" }); +z.string().emoji({ message: "Contains non-emoji characters" }); +z.string().uuid({ message: "Invalid UUID" }); +z.string().includes("tuna", { message: "Must include tuna" }); +z.string().startsWith("https://", { message: "Must provide secure URL" }); +z.string().endsWith(".com", { message: "Only .com domains allowed" }); +z.string().datetime({ message: "Invalid datetime string! Must be UTC." }); +z.string().ip({ message: "Invalid IP address" }); +``` + +### ISO 日期 + +`z.string().datetime()` 方法执行 ISO 8601;默认为无时区偏移和任意的小数点后几秒精度 + +```ts +const datetime = z.string().datetime(); + +datetime.parse("2020-01-01T00:00:00Z"); // pass +datetime.parse("2020-01-01T00:00:00.123Z"); // pass +datetime.parse("2020-01-01T00:00:00.123456Z"); // pass (任意精度) +datetime.parse("2020-01-01T00:00:00+02:00"); // fail (不允许偏移) +``` + +将 `offset` 选项设置为 `true`,可允许时区偏移 + +```ts +const datetime = z.string().datetime({ offset: true }); + +datetime.parse("2020-01-01T00:00:00+02:00"); // pass +datetime.parse("2020-01-01T00:00:00.123+02:00"); // pass (毫秒数可选) +datetime.parse("2020-01-01T00:00:00.123+0200"); // pass (毫秒数可选) +datetime.parse("2020-01-01T00:00:00.123+02"); // pass (只偏移小时) +datetime.parse("2020-01-01T00:00:00Z"); // pass (仍支持 Z) +``` + +你还可以限制允许的 "精度"。默认情况下,支持任意亚秒精度(但可选) + +```ts +const datetime = z.string().datetime({ precision: 3 }); + +datetime.parse("2020-01-01T00:00:00.123Z"); // pass +datetime.parse("2020-01-01T00:00:00Z"); // fail +datetime.parse("2020-01-01T00:00:00.123456Z"); // fail +``` + +### IP 地址 + +默认情况下,`z.string().ip()` 方法会验证 IPv4 和 IPv6 + +```ts +const ip = z.string().ip(); + +ip.parse("192.168.1.1"); // pass +ip.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // pass +ip.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:192.168.1.1"); // pass + +ip.parse("256.1.1.1"); // fail +ip.parse("84d5:51a0:9114:gggg:4cfa:f2d7:1f12:7003"); // fail +``` + +你还可以设置 IP `版本` + +```ts +const ipv4 = z.string().ip({ version: "v4" }); +ipv4.parse("84d5:51a0:9114:1855:4cfa:f2d7:1f12:7003"); // fail + +const ipv6 = z.string().ip({ version: "v6" }); +ipv6.parse("192.168.1.1"); // fail +``` + +## Numbers + +在创建数字模式时,你可以自定义某些错误信息 + +```ts +const age = z.number({ + required_error: "Age is required", + invalid_type_error: "Age must be a number", +}); +``` + +Zod 包括一些特定的数字验证。 + +```ts +z.number().gt(5); +z.number().gte(5); // alias .min(5) +z.number().lt(5); +z.number().lte(5); // alias .max(5) + +z.number().int(); // value must be an integer + +z.number().positive(); // > 0 +z.number().nonnegative(); // >= 0 +z.number().negative(); // < 0 +z.number().nonpositive(); // <= 0 + +z.number().multipleOf(5); // Evenly divisible by 5. Alias .step(5) + +z.number().finite(); // value must be finite, not Infinity or -Infinity +z.number().safe(); // value must be between Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER +``` + +你可以选择传入第二个参数来提供一个自定义的错误信息。 + +```ts +z.number().max(5, { message: "this👏is👏too👏big" }); +``` + +## Dates + +```ts +z.date().safeParse(new Date()); // success: true + +z.date({ + required_error: "Please select a date and time", + invalid_type_error: "That's not a date!", +}); + +z.date().min(new Date("1900-01-01"), { message: "Too old" }); +z.date().max(new Date(), { message: "Too young!" }); +``` + +## Objects + +```ts +// 所有属性都是默认需要的 +const Dog = z.object({ + name: z.string(), + age: z.number(), +}); + +// 像这样提取推断出的类型 +type Dog = z.infer; + +// 相当于: +type Dog = { + name: string; + age: number; +}; +``` + +### `.shape` + +使用`.shape`来访问特定键的模式。 + +```ts +Dog.shape.name; // => string schema +Dog.shape.age; // => number schema +``` + +### `.extend` + +你可以用`.extend`方法在对象模式中添加额外的字段。 + +```ts +const DogWithBreed = Dog.extend({ + breed: z.string(), +}); +``` + +你可以使用`.extend`来覆盖字段! 要小心使用这种方式! + +### `.merge` + +相当于 `A.extend(B.shape)`. + +```ts +const BaseTeacher = z.object({ students: z.array(z.string()) }); +const HasID = z.object({ id: z.string() }); + +const Teacher = BaseTeacher.merge(HasID); +type Teacher = z.infer; // => { students: string[], id: string } +``` + +> 如果两个模式共享 keys,那么 B 的属性将覆盖 A 的属性。返回的模式也继承了 "unknownKeys 密钥 "策略(strip/strict/passthrough+)和 B 的全面模式。 + +### `.pick/.omit` + +受 TypeScript 内置的`Pick`和`Omit`工具类型的启发,所有 Zod 对象模式都有`.pick`和 `.omit`方法,可以返回一个修改后的版本。考虑一下这个 Recipe 模式。 + +```ts +const Recipe = z.object({ + id: z.string(), + name: z.string(), + ingredients: z.array(z.string()), +}); +``` + +要想只保留某些 Key,使用 `.pick` . + +```ts +const JustTheName = Recipe.pick({ name: true }); +type JustTheName = z.infer; +// => { name: string } +``` + +要删除某些 Key,请使用 `.omit` . + +```ts +const NoIDRecipe = Recipe.omit({ id: true }); + +type NoIDRecipe = z.infer; +// => { name: string, ingredients: string[] } +``` + +### `.partial` + +受 TypeScript 内置的实用类型[Partial](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)的启发, `.partial` 方法使所有属性都是可选的。 + +从这个对象开始: + +```ts +const user = z.object({ + username: z.string(), +}); +// { username: string } +``` + +我们可以创建一个 Partial 版本: + +```ts +const partialUser = user.partial(); +// { username?: string | undefined } +``` + +### `.deepPartial` + +T`.partial` 只是一个浅层的使用 — 它只适用于一个层次的深度。还有一个 "深层" 版本: + +```ts +const user = z.object({ + username: z.string(), + location: z.object({ + latitude: z.number(), + longitude: z.number(), + }), +}); + +const deepPartialUser = user.deepPartial(); + +/* +{ + username?: string | undefined, + location?: { + latitude?: number | undefined; + longitude?: number | undefined; + } | undefined +} +*/ +``` + +> 重要的限制: `deep partials` 只在对象模式的直接层次中按预期工作。嵌套的对象模式不能是可选的,不能是空的,不能包含细化,不能包含转换,等等... + +#### 未被识别的 keys + +默认情况下,Zod 对象的模式在解析过程中会剥离出未被识别的 keys + +```ts +const person = z.object({ + name: z.string(), +}); + +person.parse({ + name: "bob dylan", + extraKey: 61, +}); +// => { name: "bob dylan" } +// extraKey已经被剥离 +``` + +### `.passthrough` + +相反,如果你想通过未知的 keys,使用`.passthrough()`。 + +```ts +person.passthrough().parse({ + name: "bob dylan", + extraKey: 61, +}); +// => { name: "bob dylan", extraKey: 61 } +``` + +### `.strict` + +你可以用`.strict()`来 _禁止_ 未知键。如果输入中存在任何未知的 keys,Zod 将抛出一个错误。 + +```ts +const person = z + .object({ + name: z.string(), + }) + .strict(); + +person.parse({ + name: "bob dylan", + extraKey: 61, +}); +// => throws ZodError +``` + +### `.strip` + +你可以使用`.strip`方法将一个对象模式重置为默认行为(剥离未识别的 keys)。 + +### `.catchall` + +你可以将一个 "catchall "模式传递给一个对象模式。所有未知的 keys 都将根据它进行验证。 + +```ts +const person = z + .object({ + name: z.string(), + }) + .catchall(z.number()); + +person.parse({ + name: "bob dylan", + validExtraKey: 61, // 运行良好 +}); + +person.parse({ + name: "bob dylan", + validExtraKey: false, // 未能成功 +}); +// => throws ZodError +``` + +使用`.catchall()`可以避免`.passthrough()`,`.strip()`,或`.strict()`。现在所有的键都被视为 "已知(known)"。 + +## Arrays + +```ts +const stringArray = z.array(z.string()); + +// 相当于 +const stringArray = z.string().array(); +``` + +要小心使用`.array()`方法。它返回一个新的`ZodArray`实例。这意味着你调用方法的 _顺序_ 很重要。比如说: + +```ts +z.string().optional().array(); // (string | undefined)[] +z.string().array().optional(); // string[] | undefined +``` + +### `.nonempty` + +如果你想确保一个数组至少包含一个元素,使用 `.nonempty()`. + +```ts +const nonEmptyStrings = z.string().array().nonempty(); +// 现在推断的类型是 +// [string, ...string[]] + +nonEmptyStrings.parse([]); // throws: "Array cannot be empty" +nonEmptyStrings.parse(["Ariana Grande"]); // passes +``` + +### `.min/.max/.length` + +```ts +z.string().array().min(5); // 必须包含5个或更多元素 +z.string().array().max(5); // 必须包含5个或更少元素 +z.string().array().length(5); // 必须正好包含5个元素 +``` + +与`.nonempty()`不同,这些方法不会改变推断的类型。 + +## Unions + +Zod 包括一个内置的`z.union`方法,用于合成 "OR" 类型。 + +```ts +const stringOrNumber = z.union([z.string(), z.number()]); + +stringOrNumber.parse("foo"); // 通过 +stringOrNumber.parse(14); // 通过 +``` + +Zod 将按照每个 "选项" 的顺序测试输入,并返回第一个成功验证的值。 + +为了方便,你也可以使用`.or`方法: + +```ts +const stringOrNumber = z.string().or(z.number()); +``` + +## Discriminated unions + +判别联合模式是指联合类型有一个特定键,根据该键值命中对应的对象模式。 + +```ts +type MyUnion = + | { status: "success"; data: string } + | { status: "failed"; error: Error }; +``` + +这种特殊的联合类型可以用 `z.discriminatedUnion` 方法来表示。Zod 可以检查判别键(上例中的 `status` ),以确定应使用哪种模式来解析输入。这不仅提高了解析效率,还让 Zod 可以更友好地报告错误。 + +如果使用基础的联合模式,输入会根据所提供的每个 "选项 "进行测试,如果无效,所有 "选项 "的问题都会显示在 zod 错误中。对于判别联合模式,只会对特定键值对应的 "选项" 进行测试,并只显示与该 "选项 "相关的问题。 + +```ts +const myUnion = z.discriminatedUnion("status", [ + z.object({ status: z.literal("success"), data: z.string() }), + z.object({ status: z.literal("failed"), error: z.instanceof(Error) }), +]); + +myUnion.parse({ status: "success", data: "yippie ki yay" }); +``` + +可以使用 `.options` 属性获取选项列表。 + +```ts +myUnion.options; // [ZodObject<...>, ZodObject<...>] +``` + +要合并两个或更多判别联合模式,请展开所有模式中的 `.options`。 + +```ts +const A = z.discriminatedUnion("status", [ + /* options */ +]); +const B = z.discriminatedUnion("status", [ + /* options */ +]); + +const AB = z.discriminatedUnion("status", [...A.options, ...B.options]); +``` + +## Optionals + +你可以用`z.optional()`使任何模式成为可选: + +```ts +const schema = z.optional(z.string()); + +schema.parse(undefined); // => returns undefined +type A = z.infer; // string | undefined +``` + +你可以用`.optional()`方法使一个现有的模式成为可选的: + +```ts +const user = z.object({ + username: z.string().optional(), +}); +type C = z.infer; // { username?: string | undefined }; +``` + +#### `.unwrap` + +```ts +const stringSchema = z.string(); +const optionalString = stringSchema.optional(); +optionalString.unwrap() === stringSchema; // true +``` + +## Nullables + +类似地,你可以这样创建 nullable 类型: + +```ts +const nullableString = z.nullable(z.string()); +nullableString.parse("asdf"); // => "asdf" +nullableString.parse(null); // => null +``` + +你可以用`nullable`方法使一个现有的模式变成 nullable: + +```ts +const E = z.string().nullable(); // equivalent to D +type E = z.infer; // string | null +``` + +#### `.unwrap` + +```ts +const stringSchema = z.string(); +const nullableString = stringSchema.nullable(); +nullableString.unwrap() === stringSchema; // true +``` + + + +## Records + +Record 模式用于验证诸如`{ [k: string]: number }`这样的类型。 + +如果你想根据某种模式验证一个对象的 _value_ ,但不关心 keys,使用`Record'。 + +```ts +const NumberCache = z.record(z.number()); + +type NumberCache = z.infer; +// => { [k: string]: number } +``` + +这对于按 ID 存储或缓存项目特别有用。 + +```ts +const userSchema = z.object({ name: z.string() }); +const userStoreSchema = z.record(userSchema); + +type UserStore = z.infer; +// => type UserStore = { [ x: string ]: { name: string } } + +const userStore: UserStore = {}; + +userStore["77d2586b-9e8e-4ecf-8b21-ea7e0530eadd"] = { + name: "Carlotta", +}; // passes + +userStore["77d2586b-9e8e-4ecf-8b21-ea7e0530eadd"] = { + whatever: "Ice cream sundae", +}; // TypeError +``` + +#### 关于数字键的说明 + +你可能期望`z.record()`接受两个参数,一个是 key,一个是 value。毕竟,TypeScript 的内置 Record 类型是这样的:`Record` 。否则,你如何在 Zod 中表示 TypeScript 类型`Record`? + +事实证明,TypeScript 围绕`[k: number]`的行为有点不直观: + +```ts +const testMap: { [k: number]: string } = { + 1: "one", +}; + +for (const key in testMap) { + console.log(`${key}: ${typeof key}`); +} +// prints: `1: string` +``` + +正如你所看到的,JavaScript 会自动将所有对象 key 转换为字符串。 + +由于 Zod 试图弥合静态类型和运行时类型之间的差距,提供一种创建带有数字键的记录模式的方法是没有意义的,因为在 JavaScript runtime 中没有数字键这回事。 + +## Maps + +```ts +const stringNumberMap = z.map(z.string(), z.number()); + +type StringNumberMap = z.infer; +// type StringNumber = Map +``` + +## Sets + +```ts +const numberSet = z.set(z.number()); +type numberSet = z.infer; +// Set +``` + +## Enums + +在 Zod 中,有两种方法来定义枚举。 + +### Zod enums + + + +```ts +const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]); +type FishEnum = z.infer; +// 'Salmon' | 'Tuna' | 'Trout' +``` + +你必须将数值数组直接传入`z.enum()`。这样做是不行的: + +```ts +const fish = ["Salmon", "Tuna", "Trout"]; +const FishEnum = z.enum(fish); +``` + +在这种情况下,Zod 无法推断出各个枚举元素;相反,推断出的类型将是 `string` 而不是`'Salmon'|'Tuna'|'Trout'`。 + +另一种可行的方式是使用`as const`,这样 Zod 就可以推断出正确的类型。 + +```ts +const VALUES = ["Salmon", "Tuna", "Trout"] as const; +const FishEnum = z.enum(VALUES); +``` + +**自动补全** + +为了获得 Zod 枚举的自动完成,请使用你的模式的`.enum`属性: + +```ts +FishEnum.enum.Salmon; // => 自动补全 + +FishEnum.enum; +/* +=> { + Salmon: "Salmon", + Tuna: "Tuna", + Trout: "Trout", +} +*/ +``` + +你也可以用`.options`属性检索选项列表,作为一个元组: + +```ts +FishEnum.options; // ["Salmon", "Tuna", "Trout"]); +``` + +### Native enums + +Zod 枚举是定义和验证枚举的推荐方法。但是如果你需要对第三方库的枚举进行验证(或者你不想重写你现有的枚举),你可以使用`z.nativeEnum()`。 + +**数字枚举** + +```ts +enum Fruits { + Apple, + Banana, +} + +const FruitEnum = z.nativeEnum(Fruits); +type FruitEnum = z.infer; // Fruits + +FruitEnum.parse(Fruits.Apple); // 通过 +FruitEnum.parse(Fruits.Banana); // 通过 +FruitEnum.parse(0); // 通过 +FruitEnum.parse(1); // 通过 +FruitEnum.parse(3); // 未通过 +``` + +**String enums** + +```ts +enum Fruits { + Apple = "apple", + Banana = "banana", + Cantaloupe, // 你可以混合使用数字和字符串的枚举 +} + +const FruitEnum = z.nativeEnum(Fruits); +type FruitEnum = z.infer; // Fruits + +FruitEnum.parse(Fruits.Apple); // 通过 +FruitEnum.parse(Fruits.Cantaloupe); // 通过 +FruitEnum.parse("apple"); // 通过 +FruitEnum.parse("banana"); // 通过 +FruitEnum.parse(0); // 通过 +FruitEnum.parse("Cantaloupe"); // 未通过 +``` + +**常量枚举** + +`.nativeEnum()`函数也适用于`as const`对象。 ⚠️ `as const`需要 TypeScript 3.4+! + +```ts +const Fruits = { + Apple: "apple", + Banana: "banana", + Cantaloupe: 3, +} as const; + +const FruitEnum = z.nativeEnum(Fruits); +type FruitEnum = z.infer; // "apple" | "banana" | 3 + +FruitEnum.parse("apple"); // passes +FruitEnum.parse("banana"); // passes +FruitEnum.parse(3); // passes +FruitEnum.parse("Cantaloupe"); // fails +``` + +## Intersections + + + +交叉类型对于创建 "logical AND"类型很有用。这对于两个对象类型的相交很有用。 + +```ts +const Person = z.object({ + name: z.string(), +}); + +const Employee = z.object({ + role: z.string(), +}); + +const EmployedPerson = z.intersection(Person, Employee); + +// equivalent to: +const EmployedPerson = Person.and(Employee); +``` + +虽然在很多情况下,建议使用`A.merge(B)`来合并两个对象。`.merge`方法返回一个新的`ZodObject`实例,而`A.and(B)`返回一个不太有用的`ZodIntersection`实例,它缺乏像`pick`和`omit`这样的常用对象方法。 + +```ts +const a = z.union([z.number(), z.string()]); +const b = z.union([z.number(), z.boolean()]); +const c = z.intersection(a, b); + +type c = z.infer; // => number +``` + + + + + +## Tuples + +与数组不同,tuples 有固定数量的元素,每个元素可以有不同的类型。 + +```ts +const athleteSchema = z.tuple([ + z.string(), // name + z.number(), // jersey number + z.object({ + pointsScored: z.number(), + }), // statistics +]); + +type Athlete = z.infer; +// type Athlete = [string, number, { pointsScored: number }] +``` + +## Recursive types + +你可以在 Zod 中定义一个递归模式,但由于 TypeScript 的限制,它们的类型不能被静态推断。相反,你需要手动定义类型,并将其作为 "类型提示" 提供给 Zod。 + +```ts +interface Category { + name: string; + subcategories: Category[]; +} + +// cast to z.ZodSchema +const Category: z.ZodSchema = z.lazy(() => + z.object({ + name: z.string(), + subcategories: z.array(Category), + }), +); + +Category.parse({ + name: "People", + subcategories: [ + { + name: "Politicians", + subcategories: [{ name: "Presidents", subcategories: [] }], + }, + ], +}); // 通过 +``` + +不幸的是,这段代码有点重复,因为你声明了两次类型:一次在接口中,另一次在 Zod 定义中。 + + + +#### JSON type + +如果你想验证任何 JSON 值,你可以使用下面的片段。 + +```ts +const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]); +type Literal = z.infer; +type Json = Literal | { [key: string]: Json } | Json[]; +const jsonSchema: z.ZodSchema = z.lazy(() => + z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]), +); + +jsonSchema.parse(data); +``` + +感谢[ggoodman](https://github.com/ggoodman)的建议。 + +#### Cyclical objects + +尽管支持递归模式,但将一个循环数据传入 Zod 会导致无限循环。 + +## Promises + +```ts +const numberPromise = z.promise(z.number()); +``` + +"Parsing"的工作方式与 promise 模式有点不同。验证分两部分进行: + +1. Zod 同步检查输入是否是 Promise 的实例(即一个具有`.then`和`.catch`方法的对象)。 +2. Zod 使用`.then`在现有的 Promise 上附加一个额外的验证步骤。你必须在返回的 Promise 上使用`.catch`来处理验证失败的问题。 + +```ts +numberPromise.parse("tuna"); +// ZodError: Non-Promise type: string + +numberPromise.parse(Promise.resolve("tuna")); +// => Promise + +const test = async () => { + await numberPromise.parse(Promise.resolve("tuna")); + // ZodError: Non-number type: string + + await numberPromise.parse(Promise.resolve(3.14)); + // => 3.14 +}; +``` + + + +## Instanceof + +你可以使用`z.instanceof`来检查输入是否是一个类的实例。这对于验证从第三方库中导出的类的输入很有用。 + +```ts +class Test { + name: string; +} + +const TestSchema = z.instanceof(Test); + +const blob: any = "whatever"; +TestSchema.parse(new Test()); // passes +TestSchema.parse(blob); // throws +``` + +## Function schemas + +Zod 还允许你定义 "函数模式(function schemas)"。这使得验证一个函数的输入和输出变得很容易,而不需要把验证代码和 "业务逻辑(business logic)"混在一起。 + +你可以用`z.function(args, returnType)`创建一个函数模式。 + +```ts +const myFunction = z.function(); + +type myFunction = z.infer; +// => ()=>unknown +``` + +**定义输入和输出** + +```ts +const myFunction = z + .function() + .args(z.string(), z.number()) // 接受任意数量的参数 + .returns(z.boolean()); +type myFunction = z.infer; +// => (arg0: string, arg1: number)=>boolean +``` + +**提取输入和输出模式** +你可以提取一个函数模式的参数和返回类型。 + +```ts +myFunction.parameters(); +// => ZodTuple<[ZodString, ZodNumber]> + +myFunction.returnType(); +// => ZodBoolean +``` + + + +> 如果你的函数没有返回任何东西,你可以使用特殊的`z.void()`选项。这将让 Zod 正确地推断出无效返回的函数的类型。(无效返回的函数实际上可以返回未定义或空。) + + + +函数模式有一个`.implement()`方法,它接受一个函数并返回一个自动验证其输入和输出的新函数。 + +```ts +const trimmedLength = z + .function() + .args(z.string()) // accepts an arbitrary number of arguments + .returns(z.number()) + .implement((x) => { + // TypeScript knows x is a string! + return x.trim().length; + }); + +trimmedLength("sandwich"); // => 8 +trimmedLength(" asdf "); // => 4 +``` + +如果你只关心验证输入,那就好了: + +```ts +const myFunction = z + .function() + .args(z.string()) + .implement((arg) => { + return [arg.length]; // + }); +myFunction; // (arg: string)=>number[] +``` + +# ZodType: methods and properties + +所有的 Zod 模式都包含一些方法。 + +### `.parse` + +`.parse(data:unknown): T` + +给定任何 Zod 模式,你可以调用其`.parse`方法来检查`data`是否有效。如果是的话,就会返回一个带有完整类型信息的值。否则,会产生一个错误。 + +> IMPORTANT: 在 Zod 2 和 Zod 1.11+中,`.parse`返回的值是你传入的变量的 _deep clone_ 。这在zod@1.4 和更早的版本中也是如此。 + +```ts +const stringSchema = z.string(); +stringSchema.parse("fish"); // => returns "fish" +stringSchema.parse(12); // throws Error('Non-string type: number'); +``` + +### `.parseAsync` + +`.parseAsync(data:unknown): Promise` + +如果你使用异步的[refinements](#refine)或[transforms](#transform)(后面会有更多介绍),你需要使用`.parseAsync` + +```ts +const stringSchema = z.string().refine(async (val) => val.length > 20); +const value = await stringSchema.parseAsync("hello"); // => hello +``` + +### `.safeParse` + +`.safeParse(data:unknown): { success: true; data: T; } | { success: false; error: ZodError; }` + +如果你不希望 Zod 在验证失败时抛出错误,请使用`.safeParse`。该方法返回一个包含成功解析的数据的对象,或者一个包含验证问题详细信息的 ZodError 实例。 + +```ts +stringSchema.safeParse(12); +// => { success: false; error: ZodError } + +stringSchema.safeParse("billie"); +// => { success: true; data: 'billie' } +``` + +结果是一个 _discriminated union_ ,所以你可以非常方便地处理错误: + +```ts +const result = stringSchema.safeParse("billie"); +if (!result.success) { + // handle error then return + result.error; +} else { + // do something + result.data; +} +``` + +### `.safeParseAsync` + +> Alias: `.spa` + +一个异步版本的`safeParse`。 + +```ts +await stringSchema.safeParseAsync("billie"); +``` + +为方便起见,它已被别名为`.spa`: + +```ts +await stringSchema.spa("billie"); +``` + +### `.refine` + +`.refine(validator: (data:T)=>any, params?: RefineParams)` + +Zod 允许你通过 _refinements_ 提供自定义验证逻辑。(关于创建多个问题和自定义错误代码等高级功能,见[`.superRefine`](#superrefine))。 + +Zod 被设计为尽可能地反映 TypeScript。但有许多所谓的 "细化类型",你可能希望检查不能在 TypeScript 的类型系统中表示。例如:检查一个数字是否是一个整数,或者一个字符串是否是一个有效的电子邮件地址。 + +例如,你可以用`.refine`对任何 Zod 模式定义一个自定义验证检查: + +```ts +const myString = z.string().refine((val) => val.length <= 255, { + message: "String can't be more than 255 characters", +}); +``` + +> ⚠️ 精细化函数不应该抛出。相反,它们应该返回一个虚假的值来表示失败。 + +#### Arguments + +正如你所看到的,`.refine`需要两个参数。 + +1. 第一个是验证函数。这个函数接受一个输入(类型为`T`--模式的推断类型)并返回`any`。任何真实的值都会通过验证。(在zod@1.6.2 之前,验证函数必须返回一个布尔值。) +2. 第二个参数接受一些选项。你可以用它来定制某些错误处理行为: + +```ts +type RefineParams = { + // 覆盖错误信息 + message?: string; + + // 附加到错误路径中 + path?: (string | number)[]; + + // params对象,你可以用它来定制消息 + // 在错误map中 + params?: object; +}; +``` + +对于高级情况,第二个参数也可以是一个返回`RefineParams`的函数 + +```ts +z.string().refine( + (val) => val.length > 10, + (val) => ({ message: `${val} is not more than 10 characters` }), +); +``` + +#### Customize error path + +```ts +const passwordForm = z + .object({ + password: z.string(), + confirm: z.string(), + }) + .refine((data) => data.password === data.confirm, { + message: "Passwords don't match", + path: ["confirm"], // path of error + }) + .parse({ password: "asdf", confirm: "qwer" }); +``` + +因为你提供了一个`路径(path)`参数,产生的错误将是: + +```ts +ZodError { + issues: [{ + "code": "custom", + "path": [ "confirm" ], + "message": "Passwords don't match" + }] +} +``` + +#### Asynchronous refinements + +细化也可以是异步的: + +```ts +const userId = z.string().refine(async (id) => { + // verify that ID exists in database + return true; +}); +``` + +> ⚠️ 如果你使用异步细化,你必须使用`.parseAsync`方法来解析数据! 否则 Zod 会抛出一个错误。 + +#### Relationship to transforms + +变换(transforms)和细化(refinements)可以交错进行: + +```ts +z.string() + .transform((val) => val.length) + .refine((val) => val > 25); +``` + + + +### `.superRefine` + +`.refine`方法实际上是在一个更通用的(也更啰嗦)的`superRefine`方法之上的语法糖。下面是一个例子: + +```ts +const Strings = z.array(z.string()).superRefine((val, ctx) => { + if (val.length > 3) { + ctx.addIssue({ + code: z.ZodIssueCode.too_big, + maximum: 3, + type: "array", + inclusive: true, + message: "Too many items 😡", + }); + } + + if (val.length !== new Set(val).size) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `No duplicated allowed.`, + }); + } +}); +``` + +你可以随心所欲地添加问题(issues)。如果`ctx.addIssue`在函数的执行过程中没有被调用,则验证通过。 + +通常情况下,细化总是创建具有`ZodIssueCode.custom`错误代码的问题,但通过`superRefine`你可以创建任何代码的任何问题。每个问题代码在错误处理指南 [ERROR_HANDLING.md](ERROR_HANDLING.md) 中都有详细描述。 + +### `.transform` + +要在解析后转换数据,请使用`transform`方法。 + +```ts +const stringToNumber = z.string().transform((val) => myString.length); +stringToNumber.parse("string"); // => 6 +``` + +> ⚠️ 转化函数不得抛出。确保在转化器之前使用细化功能,以确保输入可以被转化器解析。 + +#### Chaining order + +注意,上面的`stringToNumber`是`ZodEffects`子类的一个实例。它不是`ZodString`的实例。如果你想使用`ZodString`的内置方法(例如`.email()`),你必须在进行任何转换 _之前_ 应用这些方法。 + +```ts +const emailToDomain = z + .string() + .email() + .transform((val) => val.split("@")[1]); + +emailToDomain.parse("colinhacks@example.com"); // => example.com +``` + +#### Relationship to refinements + +转换和细化可以交错进行: + +```ts +z.string() + .transform((val) => val.length) + .refine((val) => val > 25); +``` + +#### Async transformations + +转换也可以是异步的。 + +```ts +const IdToUser = z + .string() + .uuid() + .transform(async (id) => { + return await getUserById(id); + }); +``` + +> ⚠️ 如果你的模式包含异步变换器,你必须使用.parseAsync()或.safeParseAsync()来解析数据。否则,Zod 将抛出一个错误。 + +### `.default` + +你可以使用变换器来实现 Zod 中 "默认值 "的概念。 + +```ts +const stringWithDefault = z.string().default("tuna"); + +stringWithDefault.parse(undefined); // => "tuna" +``` + +你可以选择在`.default`中传递一个函数,当需要生成默认值时,该函数将被重新执行: + +```ts +const numberWithRandomDefault = z.number().default(Math.random); + +numberWithRandomDefault.parse(undefined); // => 0.4413456736055323 +numberWithRandomDefault.parse(undefined); // => 0.1871840107401901 +numberWithRandomDefault.parse(undefined); // => 0.7223408162401552 +``` + +### `.optional` + +一个方便的方法,返回一个模式的可选版本。 + +```ts +const optionalString = z.string().optional(); // string | undefined + +// equivalent to +z.optional(z.string()); +``` + +### `.nullable` + +一个方便的方法,返回一个模式的可空版本。 + +```ts +const nullableString = z.string().nullable(); // string | null + +// equivalent to +z.nullable(z.string()); +``` + +### `.nullish` + +一个方便的方法,用于返回模式的 "nullish "版本。空白模式将同时接受`undefined`和`null`。阅读更多关于 "nullish "的概念[这里](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#nullish-coalescing). + +```ts +const nullishString = z.string().nullish(); // string | null | undefined + +// equivalent to +z.string().nullable().optional(); +``` + +### `.array` + +一个方便的方法,为给定类型返回一个数组模式: + +```ts +const nullableString = z.string().array(); // string[] + +// equivalent to +z.array(z.string()); +``` + +### `.or` + +一个用于联合类型的方便方法。 + +```ts +z.string().or(z.number()); // string | number + +// equivalent to +z.union([z.string(), z.number()]); +``` + +### `.and` + +一个方便的方法,用于创建交叉类型。 + +```ts +z.object({ name: z.string() }).and(z.object({ age: z.number() })); // { name: string } & { age: number } + +// equivalent to +z.intersection(z.object({ name: z.string() }), z.object({ age: z.number() })); +``` + +# Type inference + +你可以用`z.infer`提取任何模式的 TypeScript 类型。 + +```ts +const A = z.string(); +type A = z.infer; // string + +const u: A = 12; // TypeError +const u: A = "asdf"; // compiles +``` + +#### What about transforms? + +在现实中,每个 Zod 模式实际上都与**两种**类型相关:一个输入和一个输出。对于大多数模式(例如`z.string()`),这两种类型是相同的。但是一旦你把转换添加到混合中,这两个值就会发生分歧。例如,`z.string().transform(val => val.length)`的输入为`string`,输出为`number`。 + +你可以像这样分别提取输入和输出类型: + +```ts +const stringToNumber = z.string().transform((val) => val.length); + +// ⚠️ Important: z.infer返回OUTPUT类型! +type input = z.input; // string +type output = z.output; // number + +// equivalent to z.output! +type inferred = z.infer; // number +``` + +# Errors + +Zod 提供了一个名为 `ZodError` 的错误子类。ZodErrors 包含一个`issues` 数组,包含关于验证问题的详细信息。 + +```ts +const data = z + .object({ + name: z.string(), + }) + .safeParse({ name: 12 }); + +if (!data.success) { + data.error.issues; + /* [ + { + "code": "invalid_type", + "expected": "string", + "received": "number", + "path": [ "name" ], + "message": "Expected string, received number" + } + ] */ +} +``` + +#### Error formatting + +你可以使用`.format()`方法将这个错误转换为一个嵌套对象。 + +```ts +data.error.format(); +/* { + name: { _errors: [ 'Expected string, received number' ] } +} */ +``` + +关于可能的错误代码和如何定制错误信息的详细信息,请查看专门的错误处理指南: [ERROR_HANDLING.md](ERROR_HANDLING.md) + +# Comparison + +还有一些其他广泛使用的验证库,但它们都有一定的设计局限性,使开发者的体验不理想。 + + + + + + + +#### Joi + +[https://github.com/hapijs/joi](https://github.com/hapijs/joi) + +不支持静态类型推理 😕 + +#### Yup + +[https://github.com/jquense/yup](https://github.com/jquense/yup) + +Yup 是一个全功能的库,首先用 vanilla JS 实现,后来又用 TypeScript 重写。 + +不同之处 + +- 支持铸造和转换 +- 所有的对象字段默认都是可选的 +- 缺少方法: (partial, deepPartial) + +- 缺少 promise 模式 +- 缺少 function 模式 +- 缺少联合和交叉模式 + + + +#### io-ts + +[https://github.com/gcanti/io-ts](https://github.com/gcanti/io-ts) + +io-ts 是 gcanti 的一个优秀库。io-ts 的 API 极大地启发了 Zod 的设计。 + +根据我们的经验,在许多情况下,io-ts 优先考虑功能编程的纯洁性,而不是开发者的经验。这是一个有效的和令人钦佩的设计目标,但它使 io-ts 特别难以集成到一个现有的程序化或面向对象的代码库中。例如,考虑如何在 io-ts 中定义一个具有可选属性的对象: + +```ts +import * as t from "io-ts"; + +const A = t.type({ + foo: t.string, +}); + +const B = t.partial({ + bar: t.number, +}); + +const C = t.intersection([A, B]); + +type C = t.TypeOf; +// returns { foo: string; bar?: number | undefined } +``` + +你必须在不同的对象验证器中定义必需的和可选的道具,通过`t.partial`(它将所有属性标记为可选)传递选项,然后用`t.intersection`组合它们。 + +考虑在 Zod 中的对应关系: + +```ts +const C = z.object({ + foo: z.string(), + bar: z.number().optional(), +}); + +type C = z.infer; +// returns { foo: string; bar?: number | undefined } +``` + +这种更具声明性的 API 使模式定义更加简明。 + +`io-ts`也需要使用 gcanti 的函数式编程库`fp-ts`来解析结果和处理错误。对于希望严格保持代码库功能的开发者来说,这是另一个极好的资源。但是,依赖`fp-ts`必然带来大量的知识开销;开发人员必须熟悉函数式编程的概念和`fp-ts`的命名,才能使用这个库。 + +- 支持具有序列化和反序列化转换功能的编解码器 +- 支持 branded types +- 支持高级函数式编程、高级类型、`fp-ts`。compatibility +- 缺少的方法:(pick, omit, partial, deepPartial, merge, extend) +- 缺少具有正确类型的非空数组(`[T, ...T[]])。 +- 缺少 promise 模式 +- 缺少 function 模式 + +#### Runtypes + +[https://github.com/pelotom/runtypes](https://github.com/pelotom/runtypes) + +良好的类型推理支持,但对象类型屏蔽的选项有限(没有`.pick`,`.omit`,`.extend`,等等)。不支持 `Record`(他们的 `Record` 等同于 Zod 的 `object` )。他们确实支持 branded 和 readonly 类型,而 Zod 不支持。 + +- 支持 "模式匹配(pattern matching)":分布在联合体上的计算属性 +- 支持只读类型 +- 缺少的方法:(deepPartial, merge) +- 缺少具有适当类型的非空数组(`[T, ...T[]])。 +- 缺少 promise 模式 +- 缺少错误定制功能 + +#### Ow + +[https://github.com/sindresorhus/ow](https://github.com/sindresorhus/ow) + +Ow 专注于函数输入验证。它是一个使复杂的断言语句容易表达的库,但它不能让你解析未定型的数据。他们支持更多的类型;Zod 与 TypeScript 的类型系统几乎是一对一的映射,而 Ow 可以让你验证几个高度特定的类型(例如`int32Array`,见他们的 README 中的完整列表)。 + +如果你想验证函数输入,请在 Zod 中使用函数模式! 这是一个更简单的方法,让你可以重复使用一个函数类型声明,而不需要重复自己(即在每个函数的开头复制粘贴一堆 ow assertions)。此外,Zod 还可以让你验证你的返回类型,所以你可以确保不会有任何意外的数据传递到下游。 + +# Changelog + +查看更新日志点击 [CHANGELOG.md](CHANGELOG.md) diff --git a/packages/docs-v3/_redirects b/packages/docs-v3/_redirects new file mode 100644 index 0000000000..158bdd1191 --- /dev/null +++ b/packages/docs-v3/_redirects @@ -0,0 +1,2 @@ +/blog/* /blog/index.html 200 +/* / 200 diff --git a/packages/docs-v3/blog/clerk-fellowship.md b/packages/docs-v3/blog/clerk-fellowship.md new file mode 100644 index 0000000000..1d6715c244 --- /dev/null +++ b/packages/docs-v3/blog/clerk-fellowship.md @@ -0,0 +1,63 @@ +

Joining Clerk as an OSS Fellow to work on Zod 4

+ +

+Colin McDonnell +
+June 11, 2024 +

+ +I'm thrilled to announce that I'm the inaugural recipient of [Clerk's](https://go.clerk.com/zod-clerk) OSS Fellowship! This fellowship is kind of like a "summer internship"—Clerk is paying me a full-time wage (think entry-level software engineer) to work on Zod full-time throughout summer 2024. + +In the context of both my own career path and Zod's development, this is a perfect arrangement, and I'm beyond grateful that Clerk was willing to experiment with some alternative funding arrangements for OSS. + +Let's look at some of the context here. + +## On deck: Zod 4 + +The current major version of Zod (v3) was released in 2021. In terms of structure and implementation, I got a lot of things right with Zod 3. The codebase has been versatile enough to supporting 23(!) minor releases, each with new features and enhancements, with no breaking changes to the public API. + +But there are a couple recurring DX papercuts that will require structural changes to address, and that will involve breaking changes. (It's worth noting upfront that most Zod users will not be affected, but a lot of the libraries in Zod's ecosystem rely on internal APIs and will need to be updated.) + +- To simplify the codebase and enable easier code generation tooling, some subclasses of `ZodType` will be split or consolidated. +- To improve performance, the signature of the (quasi-)internal `_parse` method will be changed. Any user-defined subclasses of `ZodType` will need to be updated accordingly. +- To clean up autocompletion, some internal methods and properties will be made `protected`. Some current APIs will be deprecated; some deprecated APIs will be removed. +- To improve error reporting, I'll be simplifying Zod's error map system. The new system will also be more amenable to internationalization (RFC forthcoming). +- To enable `exactOptionalPropertyTypes` semantics, the logic used to determine key optionality in `ZodObject` will change. Depending on the value of `exactOptionalPropertyTypes` in your `tsconfig.json`, some inferred types may change (RFC forthcoming). +- To improve TypeScript server performance, some generic class signatures (e.g. `ZodUnion`) will be changed or simplified. Other type utilities will be re-implemented for efficiency, but may result in marginally different behavior in some contexts. + +All told, Zod 4 will be a ground-up rewrite of the library with few breaking changes for typical users, dramatic speed improvements, a simpler internal structure, and a big slate of new features. + +## Zod's current funding story + +Zod's has [many generous donors](https://github.com/sponsors/colinhacks) and is likely one of the most well-sponsored TypeScript utility libraries of its kind. Right now, that works out to just over $2600/mo. I'm grateful for this level of support, and it far exceeds the expectations I had when I first set up my GitHub Sponsors profile. + +But with much love and appreciation to all the people and companies that support Zod, that's far from replacing a full-time salary in the US. + +I left Bun early this year and spent a couple months traveling, learning new things, and recovering from burnout. Starting in April, I spent about 6 weeks merging PRs and fixing issues, culminating in the release of Zod 3.23 (the final 3.x version). I've spent the last month or so spec'ing out Zod 4. + +In my estimation it will take about three more months of full-time work to complete the rewrite and roll out the new release responsibly to Zod's now-massive base of users and third-party ecosystem libraries. I'm beyond excited to do all this work, but that's a long time to be without an income. + +So I reached out to a few companies with an experimental proposal: an "OSS incubator" where the company would sponsor the development of Zod for 12 weeks (my timeline for the release of Zod 4). During this pre-determined window, I'd get paid some reasonable wage, and the company would be Zod's primary patron. The cost to the company is known upfront, since everything is term-limited; it's like an incubator or an internship. + +## The Clerk fellowship + +Much to my delight, [Colin](https://twitter.com/tweetsbycolin) from Clerk (AKA "other Colin") was enthusiastically on board. I've admired Clerk for a long time for their product, eye for developer experience, and commitment to open source. In fact, I covered them on my podcast the day they launched on HN in February 2021. They've already been sponsoring [Auth.js](https://authjs.dev) (formerly NextAuth) for some time and were immediately open to discussing the terms of a potential "fellowship". + +In exchange for the support, Clerk is getting a super-charged version of the perks that Zod's other sponsors already get: + +1. Diamond-tier placement in the README and the docs 💎 Big logo. Big. Huge. +2. Updating my Twitter bio for the duration of the fellowship to reflect my new position as a Clerk OSS Fellow 💅 +3. Mentions in the forthcoming Zod 4 RFCs (Requests for Comment). Historically Zod's RFCs have attracted a lot of attention and feedback from the TypeScript community (or at least TypeScript Twitter). This is a perfect place to shout out the company that is (effectively) paying me to implement these new features. +4. A small ad at the bottom of the sidebar of Zod's new docs site (under construction now). You can see what this might look like in the [Auth.js](https://authjs.dev/getting-started) docs. +5. For continuity after the release of Zod 4, Clerk gets "first dibs" (right of first refusal) on a new ongoing "diamond tier" sponsor slot for 6 months. The idea is that this is an exclusive "slot"—only one company can hold it at a time.The perks of this tier include the big README logo and the sidebar ad placement. +6. This announcement post! Yes, you've been reading marketing material this whole time. Gotcha. + +## OSS, funding models, and trying new things + +This model represents an interesting middle ground between the traditional sponsorship model and the "maintainer-in-residence" approach that companies like Vercel have taken with Rich Harris/Svelte. Zod doesn't need a full-time maintainer in perpetuity (actually, I wouldn't mind that... 🙄) but it does need full-time attention to get this major version out the door. + +This fellowship is a way to bridge that gap. All-in-all, I'm beyond excited to have found a partner in Clerk that is interested in trying something like this. + +> I encourage other companies to try similar things! There is no shortage of invaluable libraries with full-time (or nearly full-time) maintainers who are forgoing a regular income to build free tools. ArkType, Valibot, and tRPC come to mind. + +So if you're building an app sometime soon, be smart—validate your `Request` bodies (or, uh, Server Action arguments?) and don't roll your own auth. diff --git a/packages/docs-v3/blog/index.html b/packages/docs-v3/blog/index.html new file mode 100644 index 0000000000..057c24d909 --- /dev/null +++ b/packages/docs-v3/blog/index.html @@ -0,0 +1,287 @@ + + + + + Joining Clerk as an OSS Fellow | Zod Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + diff --git a/packages/docs-v3/index.html b/packages/docs-v3/index.html new file mode 100644 index 0000000000..7ecf3296f7 --- /dev/null +++ b/packages/docs-v3/index.html @@ -0,0 +1,349 @@ + + + + + Zod | Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + diff --git a/packages/docs-v3/logo.svg b/packages/docs-v3/logo.svg new file mode 100644 index 0000000000..0595f511bf --- /dev/null +++ b/packages/docs-v3/logo.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/docs-v3/robots.txt b/packages/docs-v3/robots.txt new file mode 100644 index 0000000000..ee7e117c29 --- /dev/null +++ b/packages/docs-v3/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/packages/docs-v3/static/android-chrome-192x192.png b/packages/docs-v3/static/android-chrome-192x192.png new file mode 100644 index 0000000000..610932b6c3 Binary files /dev/null and b/packages/docs-v3/static/android-chrome-192x192.png differ diff --git a/packages/docs-v3/static/android-chrome-512x512.png b/packages/docs-v3/static/android-chrome-512x512.png new file mode 100644 index 0000000000..09ab1e8b9b Binary files /dev/null and b/packages/docs-v3/static/android-chrome-512x512.png differ diff --git a/packages/docs-v3/static/apple-touch-icon.png b/packages/docs-v3/static/apple-touch-icon.png new file mode 100644 index 0000000000..05476e7880 Binary files /dev/null and b/packages/docs-v3/static/apple-touch-icon.png differ diff --git a/packages/docs-v3/static/browserconfig.xml b/packages/docs-v3/static/browserconfig.xml new file mode 100644 index 0000000000..b3930d0f04 --- /dev/null +++ b/packages/docs-v3/static/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/packages/docs-v3/static/favicon-16x16.png b/packages/docs-v3/static/favicon-16x16.png new file mode 100644 index 0000000000..5ecb49dbc0 Binary files /dev/null and b/packages/docs-v3/static/favicon-16x16.png differ diff --git a/packages/docs-v3/static/favicon-32x32.png b/packages/docs-v3/static/favicon-32x32.png new file mode 100644 index 0000000000..f4451502f3 Binary files /dev/null and b/packages/docs-v3/static/favicon-32x32.png differ diff --git a/packages/docs-v3/static/favicon.ico b/packages/docs-v3/static/favicon.ico new file mode 100644 index 0000000000..4a95e7923f Binary files /dev/null and b/packages/docs-v3/static/favicon.ico differ diff --git a/packages/docs-v3/static/moon.svg b/packages/docs-v3/static/moon.svg new file mode 100644 index 0000000000..ee737124a5 --- /dev/null +++ b/packages/docs-v3/static/moon.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/packages/docs-v3/static/mstile-144x144.png b/packages/docs-v3/static/mstile-144x144.png new file mode 100644 index 0000000000..f4cd4853aa Binary files /dev/null and b/packages/docs-v3/static/mstile-144x144.png differ diff --git a/packages/docs-v3/static/mstile-150x150.png b/packages/docs-v3/static/mstile-150x150.png new file mode 100644 index 0000000000..da3cdba9dc Binary files /dev/null and b/packages/docs-v3/static/mstile-150x150.png differ diff --git a/packages/docs-v3/static/mstile-310x150.png b/packages/docs-v3/static/mstile-310x150.png new file mode 100644 index 0000000000..294e30e316 Binary files /dev/null and b/packages/docs-v3/static/mstile-310x150.png differ diff --git a/packages/docs-v3/static/mstile-310x310.png b/packages/docs-v3/static/mstile-310x310.png new file mode 100644 index 0000000000..d4bad2b71b Binary files /dev/null and b/packages/docs-v3/static/mstile-310x310.png differ diff --git a/packages/docs-v3/static/mstile-70x70.png b/packages/docs-v3/static/mstile-70x70.png new file mode 100644 index 0000000000..512bb04f6d Binary files /dev/null and b/packages/docs-v3/static/mstile-70x70.png differ diff --git a/packages/docs-v3/static/safari-pinned-tab.svg b/packages/docs-v3/static/safari-pinned-tab.svg new file mode 100644 index 0000000000..3ad6f79f9d --- /dev/null +++ b/packages/docs-v3/static/safari-pinned-tab.svg @@ -0,0 +1,113 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/docs-v3/static/site.webmanifest b/packages/docs-v3/static/site.webmanifest new file mode 100644 index 0000000000..adaf3a2d7c --- /dev/null +++ b/packages/docs-v3/static/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "Zod", + "short_name": "Zod", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/packages/docs-v3/static/sun.svg b/packages/docs-v3/static/sun.svg new file mode 100644 index 0000000000..33bbc8b297 --- /dev/null +++ b/packages/docs-v3/static/sun.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + \ No newline at end of file