Skip to main content

conditionalValidate

Helper for doing conditional validation properly.

import { conditionalValidate } from '@jcoreio/zod-forms'
conditionalValidate<T extends z.ZodTypeAny>(schema: T): ConditionalValidator<T>

Rationale

Naively, you would apply .refine or .superRefine to an object schema, but the problem is, these refinements aren't checked if any unreleated fields in the object schema fail to parse. For example:

const schema = z
.object({
foo: z.string(),
min: z.number(),
max: z.number(),
})
.superRefine(({ min, max }, ctx) => {
if (min > max) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['min'],
message: 'must be <= max',
})
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['max'],
message: 'must be >= min',
})
}
})

schema.parse({ min: 2, max: 1 }) // thrown error only notes that `foo` is required

conditionalValidate solves this problem by allowing you to declare a validation that runs as long as min and max are valid by themselves:

const schema = conditionalValidate(
z.object({
foo: z.string(),
min: z.number().finite(),
max: z.number().finite(),
})
).conditionalRefine(
// Pick the fields the refinement depends on here
(s) => s.pick({ min: true, max: true }),
// This refinement will only be checked if min and max are successfully parsed
({ min, max }) => min <= max,
[
{ path: ['min'], message: 'must be <= max' },
{ path: ['max'], message: 'must be >= min' },
]
)

schema.parse({ min: 2, max: 1 }) // thrown error includes issues for `foo` being missing and `min`/`max` being wrong

Returns ConditionalValidator<T>

A subclass of ZodEffects (a preprocess effect) with the following additional methods:

conditionalRefine

Applies a conditional refinement

  conditionalRefine(
schema: ConditionalRefineSchema<T>,
check: (value: z.output<T>) => unknown,
message:
| string
| z.CustomErrorParams
| z.CustomErrorParams[]
| ((value: Output) => z.CustomErrorParams | z.CustomErrorParams[])
): ConditionalValidator<T>

Arguments

schema should be Zod schema requiring a subset of the fields in the base schema (which was passed to conditionalValidate) or a function that takes the base schema and returns such a schema.

message is similar to .refine except that it may be an array of messages/issues.

Returns

A schema that will safeParse the input with the given schema in a preprocess effect(https://zod.dev/?id=preprocess), and if successful, it will evaluate check on the parsed value. If check returns a falsy value, adds the specified issue(s) in message.

conditionalRefineAsync

Applies a conditional refinement with async parsing and/or check

  conditionalRefineAsync(
schema: ConditionalRefineSchema<T>,
check: (value: z.output<T>) => unknown | Promise<unknown>,
message:
| string
| z.CustomErrorParams
| z.CustomErrorParams[]
| ((value: Output) => z.CustomErrorParams | z.CustomErrorParams[])
): ConditionalValidator<T>

Arguments

schema should be Zod schema requiring a subset of the fields in the base schema (which was passed to conditionalValidate) or a function that takes the base schema and returns such a schema.

message is similar to .refine except that it may be an array of messages/issues.

Returns

A schema will safeParseAsync the input with the given schema in a preprocess effect(https://zod.dev/?id=preprocess), and if successful, it will evaluate check on the parsed value. If check returns a falsy value, adds the specified issue(s) in message.

conditionalSuperRefine

Applies a conditional superRefine

  conditionalSuperRefine(
schema: ConditionalRefineSchema<T>,
check: (value: z.output<T>, ctx: z.RefinementCtx) => void
) {
return new ConditionalValidator(this._def.schema, [
...this._def.checks,
{ schema: resolveSchema(this._def.schema, schema), check, async: false },
])
}

Arguments

schema should be Zod schema requiring a subset of the fields in the base schema (which was passed to conditionalValidate) or a function that takes the base schema and returns such a schema.

Returns

A schema will safeParse the input with the given schema in a preprocess effect(https://zod.dev/?id=preprocess), and if successful, it will evaluate check on the parsed value.

conditionalSuperRefineAsync

Applies a conditional superRefine with async parsing or check

  conditionalSuperRefineAsync(
schema: ConditionalRefineSchema<T>,
check: (value: z.output<T>, ctx: z.RefinementCtx) => void | Promise<void>
) {
return new ConditionalValidator(this._def.schema, [
...this._def.checks,
{ schema: resolveSchema(this._def.schema, schema), check, async: false },
])
}

Arguments

schema should be Zod schema requiring a subset of the fields in the base schema (which was passed to conditionalValidate) or a function that takes the base schema and returns such a schema.

Returns

The returned schema will safeParseAsync the input with the given schema in a preprocess effect(https://zod.dev/?id=preprocess), and if successful, it will evaluate check on the parsed value.