Parsing a numeric string #330
-
I think that something like this would be awesome const numeric = '123'; // typeof string;
const number = zod.string().numeric().int().parse(numeric); // typeof number; numeric() would convert it to a ZodNumber if it was actually a numeric value. Or maybe, to preserve original string type: const numeric = '123'; // typeof string;
const numericString = zod.string().numeric().int().parse(numeric); // typeof string;
const number = zod.number().stringified().int().parse(numeric); // typeof number; What do you think? |
Beta Was this translation helpful? Give feedback.
Replies: 17 comments 45 replies
-
i need it |
Beta Was this translation helpful? Give feedback.
-
I'm not a fan of this because it introduces a new method specifically for the string->number conversion. But there are a polynomial number of pairwise conversions (number to string, number to boolean, string to boolean, etc), and introducing a new method for each will get messy. More generally, the idea of creating "pipelines" of schemas is is discussed in depth here: #264 (comment). In fact, it's how transformers were initially implemented but I moved away from it in Zod 3 for reasons described in that thread. For now, Zod follows a simple "validate, then transform" approach. Validate the data (e.g. check that it's a "numeric string") then convert it into a const numeric = z.string().regex(/^\d+$/).transform(Number);
console.log(numeric.safeParse("1234")); // => 1234
console.log(numeric.safeParse("12345.6")); // => ZodError: invalid_string
console.log(numeric.safeParse("asdf")); // => ZodError: invalid_string |
Beta Was this translation helpful? Give feedback.
-
import { z, ZodTypeAny } from 'zod';
export const numericString = (schema: ZodTypeAny) => z.preprocess((a) => {
if (typeof a === 'string') {
return parseInt(a, 10)
} else if (typeof a === 'number') {
return a;
} else {
return undefined;
}
}, schema);
const FindMany = z.object({
take: numericString(z.number().max(10))
}); |
Beta Was this translation helpful? Give feedback.
-
I wonder why this is not built in like in joi? Zod is much better with TS support but I always stumble over such things when converting my schemas. |
Beta Was this translation helpful? Give feedback.
-
Given the following schema: // A number schema, that also accepts strings as input and tries to parse them before validating.
export const numberSchema = z.preprocess((val) => {
if (typeof val === 'string') return parseInt(val, 10)
return val
}, z.number()) I would expect that it is possible to use number schema methods, e.g. |
Beta Was this translation helpful? Give feedback.
-
How about this?
|
Beta Was this translation helpful? Give feedback.
-
For those coming here looking for a a import { z, ZodType } from 'zod'
const stringToNumberSchema = (def: number) => (z.string().default(`${def}`).transform(Number))
const safePreprocessor = <O, Z extends ZodType<O>> (preprocessorSchema: Z) => (val: unknown): O | null => {
const parsed = preprocessorSchema.safeParse(val)
if (!parsed.success) {
return null
}
return parsed.data
}
const _paginationSchema = z.object({
skip: z.preprocess(
safePreprocessor(stringToNumberSchema(0)),
z.number().min(0)
),
limit: z.preprocess(
safePreprocessor(stringToNumberSchema(20)),
z.number().min(0).max(100)
)
}).strict()
export type PaginationQuery = z.infer<typeof _paginationSchema> The default value can also be |
Beta Was this translation helpful? Give feedback.
-
They should add the support for numericString I think |
Beta Was this translation helpful? Give feedback.
-
v3.20 just released with |
Beta Was this translation helpful? Give feedback.
-
I think this would be the answer, zod has it's own utility to quickly coerce the type: console.log(z.coerce.string().parse(1)); // "1"
console.log(z.coerce.string().parse('1')); // "1"
console.log(z.coerce.number().parse(1)); // 1
console.log(z.coerce.number().parse('1')); // 1 If you want a bit more control you should look into console.log(z.preprocess((x) => '' + x, z.string()).parse(1)); // "1"
console.log(z.preprocess((x) => Number(x), z.number()).parse('1')); // 1 or console.log(z.union([z.string(), z.number()]).transform((x) => '' + x).pipe(z.string()).parse(1)); // "1"
console.log(z.union([z.string(), z.number()]).transform((x) => Number(x)).pipe(z.number()).parse('1')); // 1 added by @JacobWeisenburgeror console.log(z.union([z.string(), z.number()]).pipe(z.coerce.string()).parse(1)); // "1"
console.log(z.union([z.string(), z.number()]).pipe(z.coerce.number()).parse('1') ); // 1 |
Beta Was this translation helpful? Give feedback.
-
In case anyone is interested in a different approach: z.custom<number>()
.refine((value) => value ?? false, "Required")
.refine((value) => Number.isFinite(Number(value)), "Invalid number")
.transform((value) => Number(value)); |
Beta Was this translation helpful? Give feedback.
-
My case differs when passing an empty z.coerce.number().optional(); // ❌ get '0' z.preprocess((val) => (val ? val : undefined), z.coerce.number().optional()); // ✅ get 'undefined' |
Beta Was this translation helpful? Give feedback.
-
Now we can simply do:
|
Beta Was this translation helpful? Give feedback.
-
It still seems like having a way to indicate a decimal string, with precision, would be very useful. It's very common to return decimal values as strings, like Am I missing something? |
Beta Was this translation helpful? Give feedback.
-
I just spent a few hours stuck on an downstream issue due to default value of x being 0. The full form of the preprocess should be the following since x ? x : undefined will resolve to undefined when x = 0:
If anyone else faces a similar issue, hopefully this will resolve it for you :) |
Beta Was this translation helpful? Give feedback.
-
I need to parse query strings all the time. These can be undefined, empty strings or numbers like in the example of the page number. That's an hour of my life I'll never get back, so hopefully I can save someone else all that time with this handy one liner.
|
Beta Was this translation helpful? Give feedback.
-
I wonder - what would be the best way to check for a string having a length of 7 chars and only contains numbers? The issue here is, that leading zeros have to be considered. For example "0000001" is valid. Converting it to number first does not work. I tried this first:
But this leads to "Intersection results could not be merged" when evaluating the and part.
But I wonder - are there any better solutions I could use to face the problem? |
Beta Was this translation helpful? Give feedback.
I think this would be the answer, zod has it's own utility to quickly coerce the type:
If you want a bit more control you should look into
preprocess
or
transform
andpipe