zod
zod copied to clipboard
Use default value on error
I'd like to validate a value, and if that validation fails I don't care about the error - I want to use a default value.
const strZ = z.string().default('hello world');
strZ.parse() // 'hello world'
strZ.parse('hi') // 'hi'
strZ.parse(23) // error, but I want 'hello world'
I can't figure out a clean way to do this without writing my own wrapper around zod. Is this supported already by the library?
I am running into the same issue. Mine is an object schema like this:
export const urlQueryParams = z.object({
queryParam1: z.preprocess(
(value) => normalizeIntegerPriceAmount(value),
z.number().positive().max(1000),
), // good - has transformation
queryParam2: z.string(),
queryParam3: z.string(),
queryParam4: z.string(),
queryParam5: z.string(),
queryParam6: z.string(),
queryParam7: z.string().nullish().refine(isUUID).default(null),
});
urlQueryParams.parse({
queryParam1: '10032', // transformed to 100.32 (decimal number)
queryParam2: 'good',
queryParam3: 'good',
queryParam4: 'good',
queryParam5: 'good',
queryParam6: 'good',
queryParam7: '0000', // not a valid UUID, but the default `null` will do
});
Because queryParam7
is not a UUID, the whole validation fails, even though a default value could have been returned. In addition, any transformations that are done on the rest of the properties that have passed the validation are lost.
My current best idea is to cycle through the schema.shape
keys (subschemas) and run them individually, so that I also get the transformed values for the "good" fields. Perhaps you can recommend a better approach.
Thank you, Zod Authors, for this amazing library!
Throwing in some ideas here:
type Parser<T> = (val: unknown) => T;
const customSchema = <T>(parser: Parser<T>) => z.unknown().transform(parser);
// Now you can define your own zod schema like this:
const catOrDogSchema = customSchema((val) => {
if (val === "cat" || val === "dog") return val;
throw new Error("Not cat or dog");
});
catOrDogSchema.parse("cat") // type: "cat" | "dog"
We can extend this and make withFallback
helper:
const withFallback = <T>(schema: z.ZodType<T>, fallback: T) =>
customSchema((val) => {
const parsed = schema.safeParse(val);
if (parsed.success) return parsed.data;
return fallback;
});
@matthewoates
const strZ = withFallback(z.string().default("hello world"), "fallback");
strZ.parse() // "hello world"
strZ.parse("hi") // "hi"
strZ.parse(23) // "fallback"
@zavarka
const urlQueryParams = z.object({
// ...
queryParam7: withFallback(
z.string().nullish().refine(isUUID),
null
),
});
urlQueryParams.parse({
// ...
queryParam7: 13 // INVALID
}); // but it parses successfully. { queryParam7: null }
This works, but it would be nice if zod provided corresponding methods like z.custom()
and z.string().fallback()
.
I think fallback
would be a nice feature for sure! FWIW, Zod actually does have a z.custom
schema that is just undocumented. It takes a (unknown) => boolean
function.
Another idea is we could leverage the pre-existing functionality of disable
and have something like:
const strZ = z.string().ignoreErrors().default('hello world');
strZ.parse() // 'hello world'
strZ.parse('hi') // 'hi'
strZ.parse(23) // 'hello world'
I think something like default
and fallback
could be confusing - especially since the functionality is similar, but my suggestion probably breaks other semantics.
Maybe this could be an option to default
, assuming default()
is still invoked if there's an error earlier in the function chain.
const strZ = z.string().default('hello world', { ignoreErrors: true });
strZ.parse() // 'hello world'
strZ.parse('hi') // 'hi'
strZ.parse(23) // 'hello world'
My use case is trying to parse and validate query strings:
'?d=1-3&c=2-4'
// into:
{
difficulty: [1, 3],
complexity: [2, 4]
}
Looks like this might be a good workaround:
const withFallback = <T>(schema: z.ZodType<T>, fallback: T) =>
z.preprocess(
(value) => {
const parseResult = schema.safeParse(value);
if (parseResult.success) return value;
return fallback;
},
z.custom((v) => true)
);
Indeed, withFallback
is working exactly as desired:
const urlQueryParams = z.object({
queryParam1: z.preprocess(
(value) => Number(value) / 100,
z.number().positive().max(1000)
), // good - has transformation
queryParam2: z.string(),
queryParam3: z.string(),
queryParam4: z.string(),
queryParam5: z.string(),
queryParam6: z.string(),
queryParam7: withFallback(z.string().nullish().refine(isUUID), null),
});
Example Usage - based on @altitudems code sample
Thank you!
@scotttrinh, do you know what is needed to incorporate the suggested withFallback
API into the library, so that we don't need to replicate the definition of it in every project?
I think having both fallback
and default
is very confusing. Also, default
adds ?
to infered input type afaik, which might not be desirable if you're defining a function's input and want your consumer to pass that field.
Maybe .catch()
that accepts either a fallback value or a callback that takes a faulty value (or ZodIssue?) and returns fallback value?
const urlQueryParams = z.object({
queryParam7: z.string().nullish().refine(isUUID).catch(null), // string | null
queryParam8: z.number().integer().catch((faultyValue) => /* conditionally return some values or rethrow */)
});
Has this issue been resolved? If so, I would like to close this issue.
Yes, I think this is resolved by the catch
method.
catch
is exactly what I had in mind - brilliant naming and API. 👏
Is there anyway to get the data if safeparse fails (for example any other field that suceeded)?