diff --git a/website/src/routes/api/(schemas)/exactOptional/index.mdx b/website/src/routes/api/(schemas)/exactOptional/index.mdx index 807908240..899a729e2 100644 --- a/website/src/routes/api/(schemas)/exactOptional/index.mdx +++ b/website/src/routes/api/(schemas)/exactOptional/index.mdx @@ -32,6 +32,8 @@ const Schema = v.exactOptional(wrapped, default_); With `exactOptional` the validation of your schema will pass missing object entries, and if you specify a `default_` input value, the schema will use it if the object entry is missing. For this reason, the output type may differ from the input type of the schema. +> **Important**: When used in object schemas, if a key is missing and no `default_` value is provided, the schema's pipe (including transformations) will not be executed. To ensure pipes run for missing keys, provide a `default_` value. + > The difference to `optional` is that this schema function follows the implementation of TypeScript's [`exactOptionalPropertyTypes` configuration](https://www.typescriptlang.org/tsconfig/#exactOptionalPropertyTypes) and only allows missing but not undefined object entries. ## Returns @@ -65,6 +67,31 @@ const OptionalNumberSchema = v.exactOptional(v.number()); const NumberSchema = v.unwrap(OptionalNumberSchema); ``` +### Exact optional with pipes + +When using `exactOptional` in a `pipe`, the pipe actions only execute if a `default_` value is provided or the key is present. This applies to all pipe actions including `transform`, `check`, and others. + +```ts +// ❌ Pipe actions will NOT execute for missing keys - result stays undefined +const SchemaWithoutDefault = v.object({ + isEnabled: v.pipe( + v.exactOptional(v.string()), + v.transform((value) => value === '1') // Won't run for missing keys + ), +}); + +// ✅ Pipe actions WILL execute for missing keys using the default value +const SchemaWithDefault = v.object({ + isEnabled: v.pipe( + v.exactOptional(v.string(), '0'), // Default value provided + v.transform((value) => value === '1') // Runs for all cases + ), +}); + +v.parse(SchemaWithoutDefault, {}); // { } - isEnabled is undefined (isEnabled?: boolean | undefined) +v.parse(SchemaWithDefault, {}); // { isEnabled: false } - transform executed (isEnabled: boolean) +``` + ## Related The following APIs can be combined with `exactOptional`. diff --git a/website/src/routes/api/(schemas)/optional/index.mdx b/website/src/routes/api/(schemas)/optional/index.mdx index 6282dd9f3..c4e13c75b 100644 --- a/website/src/routes/api/(schemas)/optional/index.mdx +++ b/website/src/routes/api/(schemas)/optional/index.mdx @@ -33,6 +33,8 @@ const Schema = v.optional(wrapped, default_); With `optional` the validation of your schema will pass `undefined` inputs, and if you specify a `default_` input value, the schema will use it if the input is `undefined`. For this reason, the output type may differ from the input type of the schema. +> **Important**: When used in object schemas, if a key is missing and no `default_` value is provided, the schema's pipe (including transformations) will not be executed. To ensure pipes run for missing keys, provide a `default_` value. + > Note that `optional` does not accept `null` as an input. If you want to accept `null` inputs, use `nullable`, and if you want to accept `null` and `undefined` inputs, use `nullish` instead. Also, if you want to set a default output value for any invalid input, you should use `fallback` instead. ## Returns @@ -80,6 +82,31 @@ const OptionalNumberSchema = v.optional(v.number()); const NumberSchema = v.unwrap(OptionalNumberSchema); ``` +### Optional with pipes + +When using `optional` in a `pipe`, the pipe actions only execute if a `default_` value is provided or the key is present. This applies to all pipe actions including `transform`, `check`, and others. + +```ts +// ❌ Pipe actions will NOT execute for missing keys - result stays undefined +const SchemaWithoutDefault = v.object({ + isActive: v.pipe( + v.optional(v.string()), + v.transform((value) => value === 'true') // Won't run for missing keys + ), +}); + +// ✅ Pipe actions WILL execute for missing keys using the default value +const SchemaWithDefault = v.object({ + isActive: v.pipe( + v.optional(v.string(), 'false'), // Default value provided + v.transform((value) => value === 'true') // Runs for all cases + ), +}); + +v.parse(SchemaWithoutDefault, {}); // { } - isActive is undefined (isActive?: boolean | undefined) +v.parse(SchemaWithDefault, {}); // { isActive: false } - transform executed (isActive: boolean) +``` + ## Related The following APIs can be combined with `optional`. diff --git a/website/src/routes/guides/(schemas)/optionals/index.mdx b/website/src/routes/guides/(schemas)/optionals/index.mdx index 4eb020f05..21b7d2056 100644 --- a/website/src/routes/guides/(schemas)/optionals/index.mdx +++ b/website/src/routes/guides/(schemas)/optionals/index.mdx @@ -88,3 +88,43 @@ const CalculationSchema = v.pipe( })) ); ``` + +## Pipe execution behavior + +When using `optional`, `exactOptional`, or other optional schemas with `pipe` containing actions, it's important to understand when the pipe executes: + +### Without default values + +If no `default_` value is provided, missing object keys are completely ignored and their pipes will **not** be executed. + +```ts +import * as v from 'valibot'; + +const Schema = v.object({ + value: v.pipe( + v.optional(v.string()), + v.transform((input) => input.toUpperCase()) // Won't run for missing keys + ), +}); + +const result = v.parse(Schema, {}); // { } - value is undefined (value?: string | undefined) +``` + +### With default values + +When a `default_` value is provided, the pipe will execute for missing keys using the default value. + +```ts +import * as v from 'valibot'; + +const Schema = v.object({ + value: v.pipe( + v.optional(v.string(), 'hello'), // Default value provided + v.transform((input) => input.toUpperCase()) // Runs with 'hello' for missing keys + ), +}); + +const result = v.parse(Schema, {}); // { value: 'HELLO' } - transform executed +``` + +This behavior ensures that the output type is consistent and transforms can reliably process values.