zod
zod copied to clipboard
Add method to remove defaults
It would be helpful to have a method that removes all defaults from an object. Similar to .partial()
or required()
.
My use case is that I have a type for validating API input for both create and update. On update, I want to accept partial input but not have the defaults applied again. I can construct this type manually, but it's painful on a large schema.
Seems a reasonable ask! Also shouldn't be hard to do a PR. In the meanwhile I wonder how you go about doing this manually? Naively, if I had to do this I'd invert the problem and it may simplify things quite a bit. You may already be doing this:
- Start out with your base schema, no defaults nor partials
- make a
createSchema
, via an object of typeRecord<string, z.ZodType>
:
const test: Record<string, z.ZodType> = {
id: z.string().min(3),
user: z.string(),
//...
createdAt: z.date(),
};
const defaults = {
id: "111",
user: "bob",
createdAt: () => new Date(),
};
const createSchema = z.object(
Object.entries(test).reduce<Record<string, z.ZodType>>(
(prev, [key, val]) => {
if (defaults[key]) {
prev[key] = val.default(defaults[key]);
} else {
prev[key] = val;
}
return prev;
},
{}
)
);
const updateSchema = z.object(test).partial();
createSchema.parse({});
updateSchema.parse({ id: "111" });
console.log("success");
}
However I can see if you have deeply nested objects or some convoluted use case this could be painful :) .
Given the popularity of this type of request, I've opened a PR that implements ZodRequired
. It removes the undefined
from the inference and throws an error if the input is undefined
, meaning that your defaults will never get called (since defaults only get called on undefined
inputs).
https://github.com/colinhacks/zod/pull/1738
I'm experimenting with a utility class that generates convenience methods for variations on a schema, and I've found the easiest path is to define defaults on the schema and recursively unwrap them via ZodDefault["removeDefault"]
(implementation below).
Currently, this implementation only supports passing a ZodObject
as the base schema because it was designed to work with tRPC
, where it's more idiomatic to pass ZodObject
schemas as an input parser. With some work, though, this could be made to work with any base schema, I believe.
function unwrapDefaultsFromSchemas<
TRawShape extends z.ZodRawShape,
TUnknownKeys extends z.UnknownKeysParam,
TCatchall extends z.ZodTypeAny,
TOutput extends z.objectOutputType<TRawShape, TCatchall>,
TInput extends z.objectInputType<TRawShape, TCatchall>,
TSchema extends z.ZodObject<
TRawShape,
TUnknownKeys,
TCatchall,
TOutput,
TInput
>
>(schema: TSchema): UnwrapDefaultsRecursive<TSchema> {
return z.object(
Object.entries(schema.shape).reduce((acc, [key, value]) => {
let base = value;
if (base instanceof z.ZodDefault) base = base.removeDefault();
if (base instanceof z.ZodObject) base = unwrapDefaultsFromSchemas(base);
acc[key] = base;
return acc;
}, {} as z.ZodRawShape)
) as any;
}
type UnwrapDefaultsRecursive<TSchema extends z.AnyZodObject> =
TSchema extends z.ZodObject<
infer Shape extends z.ZodRawShape,
infer UnknownKeys extends z.UnknownKeysParam,
infer Catchall extends z.ZodTypeAny
>
? z.ZodObject<
{
[K in keyof Shape]: Shape[K] extends z.ZodDefault<any>
? ReturnType<Shape[K]["removeDefault"]>
: Shape[K] extends z.AnyZodObject
? UnwrapDefaultsRecursive<Shape[K]>
: Shape[K];
},
UnknownKeys,
Catchall
>
: never;