Is it possible to narrow an output schema if the query optionally doesn't return all fields?
I have a router procedure that has an input schema that has an optional
filter
that changes the shape of the data to only include those fields (like sql select
), but those records will fail the output schema which contains all of the fields as required. is there a way to construct a .output()
specification that narrows the type, possibly using z.partial() so that these partial "rows" will pass output validation?16 Replies
union or discriminatedUnion probably
this is querying Azure Table Storage.
options.select narrows the columns returned to the list specified.
the use case is someone wants to display a <Select> of just the id and name fields from an entity that may have many more columns in the zod schema.
e.g. const { data } = api.entity.list.useQuery({ select: ["id", "name"] });
are the input fields available to the .output() step?
RIGHT sorry I did misunderstand
You might be best to leave output blank and do this in your query itself
Output is just a way to define a static validator, but you could build a Zod validator at runtime based on the input and validate the output if needed, or just set up some plain TS types which are built from the input
oh! that sounds promising...
so is there a zod method to construct a narrowed type?
For hints, take a look at how Zod handles Pick and Omit, it has some TS under the hood which does this, but also yes it has those methods
so as you can see above, i've declared my schema as a generic that extends z.ZodSchema...
but it doesn't have .pick().. have i chosen the wrong type?
You’re close
But you don’t need to be casting your Zod type
Just extend from ZodType and let the inference do the rest
Or ZodObject possibly in your case
function makeRouter<Schema extends z.AnyZodObject>(
tableName: string,
schema: Schema, ... like this?
That should give you access to the methods you want yes
then just split on if there are selected fields and call parse separately?
.query(async ({ input: options }) => {
const result = await queryRows(client, options);
if (options?.select) {
const picker = options.select.reduce<Record<string, true>>((a, f) => {
a[f] = true;
return a;
}, {});
const resultSchema = schema.pick(picker).array();
resultSchema.parse(result);
} else {
schema.array().parse(result);
}
return result;
}),
Looks like you’re on the right track yep 👍
How typescript infers this might be something to refine a bit
But you’ll figure out fast if it’s an issue! 😆
thanks for the tips!
ugh. some of my schema inputs are discriminated unions and ZodDiscriminatedUnion is not assignable to parameter of type 'AnyZodObject'.
You are probably doing something inadvisable
Might have to simplify you use case, or pass a Zod factory method instead so you can pull up the pick/omit to where the unions get created
But this does all smell a bit like you’re trying to rewrite GraphQL and Zod isn’t really a great choice for this
not coming from GraphQL... just trying to store discriminated unions in the same azure table...
this is where i ended up with the discriminated union.
it's this last schema that i can't call .pick on... because it's no longer a schema?
it it because of this?
GitHub
ZodObject methods (pick, omit, partial, etc.) for discriminatedUnio...
In issue #56 it was proposed to add ZodObject methods to intersections and unions. This was never implemented, because both z.intersection and z.union do not make any assumptions about the underlyi...
I’m sure what you want to do can be done, you’ll just have to figure out some patterns which let you achieve it, probably a lot of inverted control plugged into the router factory
I’ve done some similar stuff at work and it’s a pain to achieve, not really so easy to help with in a text chat