Derivative schema usage, undocumented methods, and generic functions
See original GitHub issueHey! in the last few days I became obsessed with zod after years of being driven crazy with working with “soft” interfaces from trash api. nevermind that, I have a proposal(?) and a question.
There are a few situations that I encountered with zod which I hit a dead end. The first is a generic wrapper. Lets say the data I’m working on has this repeated pattern:
{
"data": {
"id": "1",
"attributes": {
"name": "zod",
"author": {
"id": "2",
"attributes" : {
"name": "colinhacks"
}
}
}
}
}
or, in other words
interface Payload<T> {
data: {
id: string
attributes: T
}
}
I’d wish to make generic helper or generic schema for these situation. With an important distinction. I want the schema to transform (read strip) these boilerplate fields. But TS breaks and won’t let me 🤷🏻♂️
// this doesn't work
export const gqlPayload = <T extends object>(shape: T) =>
z.object({
data: z.object({
id: z.string(),
attributes: z.object(shape),
}),
}).transform(({data}) => ({id: data.id, ...data.attributes}))
// nor does this
export const gqlPayload = <T extends z.ZodTypeAny>(schema: T) =>
z.object({
data: z.object({
id: z.string(),
attributes: schema,
}),
}).transform(({data}) => ({id: data.id, ...data.attributes}))
very sad, next issue
I also hit a wall when I wanted to continue working from an already-parsed object.
for example lets say I have a Dragon
schema, and the schema makes a few transformations, and now I have a startQuery
function, that should recieve that transformed Dragon
, and extend/transform it again. but I could only set the input as the untransformed Dragon
. Which sucks eggs.
example:
const Wings = z.object({
leftHp: z.number(),
rightHp: z.number()
}).transform(({leftHp, rightHp}) => [leftHp, rightHp])
const Dragon = z.object({
wings: Wings,
legs: z.number(),
eyes: z.enum(['red', 'blue'])
})
// requires a Dragon with array wings instead of object wings
const SlainDragon = Dragon.outputSchema.refine(/** check for less than 5 legs or something */).transform(value => ({...value, wings: [0,0]}))
type DragonToSlay = z.input<typeof SlainDragon>
// {
// wings: [number, number];
// legs: number;
// eyes: "red" | "blue";
// }
function slayDragon(dragon: Dragon) {
try {
const slainDragon = SlainDragon.parse(dragon)
return slainDragon
} catch (e) {
throw new Error('you cannot hope to slay a beast with that many legs!')
}
}
I know the example is a bit out there. The real-world example is just a bit more complicated. But basically it’s getting an Item
from somewhere, transforming it to be usable in my app, and in some other interaction take that transformed item and transform it more before sending it back. I could use 2 schemas, but then I’ll have to make sure they are synced all the time. I just want the output of one schema to be the input of a different schema… 😢
last but not least, whats up with all the undocumented stuff? shape
, innerShape()
, describe
, tell me what they doooooooo why do I have to figure it out myselfffffff (no need for all the _
prefixed ones, you can have your secrets 😉 )
I love zod, and sorry for the long issue. I’d open a PR instead, but I know nothing about the inner workings of zod, so I thought it’d approach you first
Issue Analytics
- State:
- Created a year ago
- Comments:6 (1 by maintainers)
Top GitHub Comments
@datner
I’m not sure I follow the issue here. You can declare as many chained
transform
s as you want and thez.output
mapped type will correctly give you the output type. If you need some intermediate type, you’ll have to break apart the schema since there isn’t a straightforward way to query likez.thirdTransform<typeof Dragon>
.Can you give us a more straightforward example of your use case here? Going off of your description sounds something like this:
CodeSandbox
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.