-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Similar to Yup’s meta properties? #76
Comments
I'm considering this but I'm not sure I understand the use case. Can you elaborate on how exactly you hope to use this? Error customization? Custom validations? |
primarily using it as part of refine's validation is one:
but equally important if zod would implement something like yup's describe: so in our UI we can easily build our input based on that. |
Seems like there are quite of lot of arbitrary places where UI could be driven from metadata in some way:
More complex metadata could be something like data driven UI allowing for selection of various complex forms based on a union type. At some level it seems like there could be a question of whether zod:
|
I use Yup's
|
I'm afraid I'm getting more and more opposed to this over time. All of these use cases are out of scope for Zod and should be implemented with programmatic constructs external to Zod. Form UI
Then you can loop over the form fields to build your form. To get the type of each ZodSchema you can use const input: MyFormInput = "whatever" as any;
input.validator._def.t; // => z.ZodTypes If you want to get really fancy and still get type inference, use a class. Here's a toy example of a class ZodForm<T extends MyFormInput[] = []> {
inputs: T;
constructor(inputs: T) {
this.inputs = inputs;
}
input = <Input extends MyFormInput>(input: Input): ZodForm<[...T, Input]> => {
return new ZodForm([...this.inputs, input]);
};
} For metadata driven validations You should use language-level constructs for this too. const maxRefinement = (max: number) => ({
check: (val: string) => val.length <= max,
message: `String can't be more than ${max} characters`,
})
const myString = z.string().refinement(maxRefinement(12)); More generally, I think of Zod as a foundation for typesafety in an application, designed to be built on top of. The use cases you gave are certainly valid, though they're the sort of domain-specific/application-specific tooling you should build yourself on top of Zod. Zod shouldn't be a wrapper for it, it should be a component within it. 👍 |
I kinda agree. This is solvable in user-land so maybe it's really not worth adding at the moment (if ever). |
given the lack of dissent, i'm gonna close this |
There are problems I've only been able to solve by attaching metadata at multiple levels of the Zod schema and deeply inspecting the Zod schema at type time/runtime. Wrapping a Zod schema in something else to attach metadata isn't deeply composable. What if you need to do something special on an array schema if the element schema has certain metadata attached to it? Well, now you have to make your own array wrapper type. And then you have to make your own set wrapper type, object wrapper type, etc... essentially you end up completely reimplementing the whole Zod hierarchy. So, I was determined to find a way to keep the metadata within my Zod hierarchy. After a lot of experimentation, I landed on extending a no-op refinement import z from 'zod'
export class ZodMetadata<
T extends z.ZodTypeAny,
M extends object
> extends z.ZodEffects<T> {
constructor(def: ZodEffectsDef<T>, public metadata: M) {
super(def)
}
unwrap() {
return this._def.schema
}
}
export function zodMetadata<T extends z.ZodTypeAny, M extends object>(
schema: T,
metadata: M
): ZodMetadata<T, M> {
return new ZodMetadata(schema.refine(() => true)._def, metadata)
} With this, you can do any deep mapping or filtering on the Zod schema you want. And not only can you inspect the metadata at runtime; you can even operate on it at type time! type OutputForVersion<
Z extends z.ZodTypeAny,
V extends number
> =
S extends ZodMetadata<infer T, infer M>
? M extends { version: infer SchemaVersion extends number }
? IsGreaterThanOrEqual<SchemaVersion, V> extends true
? OutputForVersion<T, V>
: never
: OutputForVersion<T, V>
: IsAny<z.output<S>> extends true
? any
: S extends z.ZodArray<infer T>
? OutputForVersion<T, V> extends never
? never
: Array<OutputForVersion<T, V>>
: S extends z.ZodUnion<infer T>
? OutputForVersion<T[number], V>
: S extends z.ZodOptional<infer T>
? OutputForSingleVersion<T, V> extends never
? never
: OutputForSingleVersion<T, V> | undefined
// etc
: z.output<S> |
@jedwards1211, how do you use the |
@vadimyen here's my blog post about the whole system: https://www.jcore.io/articles/schema-versioning-with-zod At the bottom of the "Normalizing to the latest version with helper functions" section it shows how I'm indirectly using the |
This is an extremely useful feature yup had that is keeping me from transitioning to zod, basically each schema object have additional properties that can be used for validation and other use case.
The text was updated successfully, but these errors were encountered: