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: 15 comments 44 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 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