-
-
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
[Feature Request]: Meta data/labels #273
Comments
As I've suggested in other issues, I recommend doing this by wrapping Zod. type MyEndpoint<T extends z.Schema<any>> = {
validator: T;
label: string;
} Bringing this inside Zod is a huge footgun and encourages lots of anti-patterns that would be better solved by either creating higher-level constructs that include a Zod schema (like |
I cannot get the recommendation to work when you want to identify sub schema's. For example, take: const seat = z.object({
color: z.string(),
height: z.number(),
});
const car = z.object({
frontLeftSeat: seat,
frontRightSeat: seat,
rearLeftSeat: seat,
rearRightSeat: seat,
}); When generating documentation for |
Sorry, I worked it out in the end: import { z, AnyZodObject } from 'zod';
const seat = z.object({
color: z.string(),
height: z.number(),
});
const mirror = z.object({
color: z.string(),
width: z.number(),
});
interface Model {
schema: AnyZodObject;
label: string;
}
const models: Model[] = [
{ schema: seat, label: 'Seat' },
{ schema: mirror, label: 'Mirror' },
];
const car = z.object({
frontLeftSeat: seat,
frontRightSeat: seat,
rearLeftSeat: seat,
rearRightSeat: seat,
leftMirror: mirror,
rightMirror: mirror,
});
Object.entries(car.shape).forEach(([key, sub]) => {
const model = models.find((m) => m.schema === sub);
if (model !== undefined) {
console.log(`${key}: ${model.label}`);
}
}); gives
|
Yeah, but... What if I want to use the schema as a whole? For example the data of your example would be this: const data = {
frontLeftSeat: {
color: 'red',
height: 100,
},
frontRightSeat: {
color: 'blue',
height: 200,
},
rearLeftSeat: {
color: 'green',
height: 300,
},
rearRightSeat: {
color: 'yellow',
height: 400,
},
leftMirror: {
color: 'black',
width: 50,
},
rightMirror: {
color: 'white',
width: 100,
},
} How would I go about validating the data object? This is the main problem I currently have within my usage and I don't know a way to do this (without loosing type interference at least). I really like zod because of its simplicity, but this point is something that I have really no idea on how to accomplish this, which unfortunately makes me thinking about switching to yup which I actually like a bit less. Any ideas welcome |
Isn't that the |
Ah, you're right, I misinterpreted this. Anyways defining all the schemas as variables doesn't really cut it for me |
What I was imagining he had done was something like this: import { z, AnyZodObject } from 'zod';
const seatSchema = z.object({
color: z.string(),
height: z.number(),
});
const mirrorSchema = z.object({
color: z.string(),
width: z.number(),
});
interface Model {
schema: AnyZodObject;
label: string;
}
const seat:Model = {
schema: seatSchema,
label: "Seat",
}
const mirror:Model = {
schema: mirrorSchema,
label: "Mirror",
}
const car = z.object({
frontLeftSeat: seat,
frontRightSeat: seat,
rearLeftSeat: seat,
rearRightSeat: seat,
leftMirror: mirror,
rightMirror: mirror,
});
Object.entries(car.shape).forEach(([key, sub]) => {
console.log(`${key}: ${sub.label}`);
}); But this isn't really possible unfortunately. |
I think it makes sense for Zod to not store arbitrary metadata as it increases the complexity of what Zod is doing (what happens when you transform? etc) and moving that out into the consuming application (inverting control if you will) is usually a more scalable approach for libraries. If maintaining your own map wrapper structure every time feels too heavy you could look into making a library that uses zod under the hood for data parsing but adds additional functionality like this. If you think the general case is general enough for many use cases, we'd be happy to link to it as a part of the Zod ecosystem! |
Since it seems unlikely that this sort of functionality is going to be added to zod any time soon. declare module 'zod' {
interface ZodType {
metadata(): Record<string, any>
associateMetadata(meta: Record<string, any>): this
}
}
ZodType.prototype.metadata = function () {
return this._def.meta
}
ZodType.prototype.associateMetadata = function (meta: Record<string, any>) {
const This = (this as any).constructor
return new This({
...this._def,
meta,
})
}
const obj = z.object({}).associateMetadata({ label: 'hello' })
console.log(obj.metadata().label) All the usual disclaimers about how modifying third party code prototypes is bad. On the topic of, if such a feature should exist in zod, I think it's worth while. Wrapping zod doesn't seem like a workable solution for common use cases. Similar to other's use case, in my case, the zod schema describes an API contract.
It doesn't make sense for zod to have first party support for those specific use cases. I could probably just encode this stuff into a json object and serialize it into the description, but that seems pretty lame. |
Instead of using zod internal api to hack into its core I ended up just using z.string().describe(JSON.stringify({metadata: “I ❤️ zod”})) And then using Works like a charm!!! 😘 |
I cringe at the idea of hacking in a prototype footgun. I think wrapping Zod is elegant, but since I already have it surfaced all over the place in an app, I don't want to go change that - maybe later. So the suggestion by @valerii15298 seems to be the next elegant way to do this. In my app I want schema to provide the indicator of whether a field is rendered as a single-line text control or a multiline textarea. I know this outside the scope of Zod and that I'm piggy-backing functionality, but the schema is already in the same place as form fields, so I'm not seeing a significant downside to this. I already have the label for fields in the description as follows: export type SchemaShapeType = { // this ensures ZodObjects only have valid fields
[key in keyof typeof FieldNameKeys]: z.ZodTypeAny
}
export const schemaShape : SchemaShapeType = {
id: z.number().describe(PersonText.Labels.id), // labels are in a separate file/type with same key
first_name: z.string().nullable().describe(PersonText.Labels.first_name),
birth_date: z.date().nullable().describe(PersonText.Labels.birth_date),
} Now I just added some JSON and I don't need to change any form or context components. I can get the metadata from the schema when components are being rendered. export type SchemaMetaType = {
[key in keyof typeof FieldNameKeys]: any // might strongly type this later
}
export const schemaMeta: SchemaMetaType = { // no-hassle code here
id: JSON.stringify({
label: PersonText.Labels.id,
multiline: false, // meta objects are easily extensible (OK, sloppy)
}),
first_name: JSON.stringify({
label: PersonText.Labels.first_name,
multiline: true,
}),
birth_date: JSON.stringify({
label: PersonText.Labels.birth_date,
}),
}
export const schemaShape: SchemaShapeType = {
id: z.number().describe(schemaMeta.id), // trivial change to describe
first_name: z.string().nullable().describe(schemaMeta.first_name),
birth_date: z.date().nullable().describe(schemaMeta.birth_date),
} Finally, in rendering components: const meta = JSON.parse( fieldSchema.description )
const label : string = meta['label'] ?? ''
otherProps.multiline = meta['multiline'] ?? false This is a great solution without any effort by Colin and without any fuss in existing code, except where Thanks for the discussion folks. (Thanks for not closing these threads, Colin.) HTH |
I was dreaming about it. Unfortunately `zod` does not provide any way to make a custom or branded or third-party schema that can be identified as one programmatically. Developer of `zod` also does not want to make any method to store metadata in schemas, instead, recommends to wrap schemas in some other structures, which is not suitable for the purposes of `express-zod-api`. There are many small inconvenient things in making custom schema classes, that I'd like to replace into native methods, and use `withMeta` wrapper for storing proprietary identifier, so the generators and walkers could still handle it. Related issues: ``` colinhacks/zod#1718 colinhacks/zod#2413 colinhacks/zod#273 colinhacks/zod#71 colinhacks/zod#37 ``` PR I've been waiting for months to merged (programmatically distinguishable branding): ``` colinhacks/zod#2860 ```
Here's how I accomplish this: #76 (comment) |
I don't understand why this is regarded as an anti-pattern. If you look at it from that perspective, even the description or error messages are meta data and they should be anti-pattern as well. It is no coincidence some people suggest using As for how it should work, it should work exactly like how |
I saw this being mentioned before in another issue, but I'd like to make the request again with a slightly different user story.
My team is looking at replacing Joi with ZodV2 in its Hapi microservice. We really like that we can create an input validation for a handler, and have it tightly coupled to the handler's input's type definition. So there is no more worrying about, "Oh, is the validation wrong, or is the input's typing wrong". Its the same.
The only loose thread is documentation. We want to create something that parses our Hapi route objects, sees the Zod object we have passed to the validator, and generates a documentation page for that route.
Simply parsing the ZodObject isn't quite cutting it. It ends up lacking information and context that we'd like, between refinements, transformations, and things that we just want to have more explanation on. If individual Zod elements could have a
.label
or a.meta
property, it'd give us a bit more flexibility in creating documentation that is tightly coupled with our zod schema.The text was updated successfully, but these errors were encountered: