Decode FormData
into arbitrary Zod schemas
#967
-
Hi y'all! 🙂 I'm currently trying to write a small helper that decodes Is there a simple solution to this? Any pointers would be appreciated export const decodeForm = async <T extends z.ZodRawShape>(
formDataOrRequest: FormData | Request,
schema: z.ZodObject<T>,
) => {
const formData =
formDataOrRequest instanceof FormData
? formDataOrRequest
: // We wanna `clone` the request here so that its body can be reused
// https://developer.mozilla.org/en-US/docs/Web/API/Request/clone
await formDataOrRequest.clone().formData();
return schema.parse(Object.fromEntries(formData));
};
// this works:
const schema = z.object({
street: z.string(),
zipCode: z.preprocess(
(zipCode) => parseInt(z.string().parse(zipCode), 10),
z.number().positive(),
),
city: z.string(),
});
const { street, zipCode, city } = await decodeForm(request, schema);
// ^^^^^^ ^^^^^^^ ^^^^
// string number string
// this, for example, does not work:
const schema = z.union([
z.object({
name: z.string(),
}),
z.object({
foo: z.string(),
}),
]);
const { name, foo } = await decodeForm(request, schema);
// ^^^^ ^^^
// any any |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 4 replies
-
I hope this helps, let me know if you have any questions. export const decodeForm = async <Schema extends z.ZodTypeAny> (
formDataOrRequest: FormData | Request,
schema: Schema,
) => {
const formData = formDataOrRequest instanceof FormData
? formDataOrRequest
: await formDataOrRequest.clone().formData()
return schema.parse( Object.fromEntries( formData ) ) as z.infer<Schema>
} const schema1 = z.object( {
street: z.string(),
zipCode: z.preprocess(
( zipCode ) => parseInt( z.string().parse( zipCode ), 10 ),
z.number().positive(),
),
city: z.string(),
} )
type Schema1 = z.infer<typeof schema1>
const { street, zipCode, city } = await decodeForm( request, schema1 )
// => street: string
// => zipCode: number
// => city: string const schema2 = z.union( [
z.object( {
name: z.string(),
} ),
z.object( {
foo: z.string(),
} ),
] )
type Schema2 = z.infer<typeof schema2>
const result = await decodeForm( request, schema2 )
// => result: { name: string } | { foo: string }
/*
because `result` is a union, you have to check which type of
object you have before you can access its properties
*/
const name = 'name' in result ? result.name : null
const foo = 'foo' in result ? result.foo : undefined
/*
you can return `null` or `undefined` for the false case
depending on which you prefer
*/ |
Beta Was this translation helpful? Give feedback.
-
That's perfect, thanks! As a follow up, say we have another helper enum AbstractEnum {}
export const decodeFormAction = async (
request: Request,
formActionsEnum: typeof AbstractEnum,
) => {
const { _action } = await decodeForm(
request,
z.object({
_action: z.nativeEnum(formActionsEnum),
}),
);
return _action;
}; This has the signature (request: Request, formActionsEnum: typeof AbstractEnum) => Promise<string> Now, when calling the function, e.g. enum Actions {
AddUser = 'AddUser',
EditUser = 'EditUser',
DeleteUser = 'DeleteUser',
}
const formAction = await decodeFormAction(request, Actions);
// ^^^^^^^^^^
// string it'd be ideal if the type was I know that type ActionUnion = `${Actions}`; gives me exactly that. I just struggle with defining the corresponding |
Beta Was this translation helpful? Give feedback.
-
If it helps anyone, I wrote a library specifically to deal with decoding |
Beta Was this translation helpful? Give feedback.
I hope this helps, let me know if you have any questions.