Reputation: 7632
I have an endpoint that should get a parameter method
which should comply with the Axios type Method
.
How can I create a schema with Zod that validates that the value is using the type Schema
?
import { Method } from 'axios';
const Schema = zod.object({
method: zod.someHowUseTheTypeFrom(Method),
});
The type of Method
from the Axios package is:
export type Method =
| 'get' | 'GET'
| 'delete' | 'DELETE'
| 'head' | 'HEAD'
| 'options' | 'OPTIONS'
| 'post' | 'POST'
| 'put' | 'PUT'
| 'patch' | 'PATCH'
| 'purge' | 'PURGE'
| 'link' | 'LINK'
| 'unlink' | 'UNLINK'
Upvotes: 41
Views: 113650
Reputation: 1
Something that worked well for me was to create an enum, create a type from the enum, and use nativeEnum()
zod's function :
enum MethodEnum {
'get', 'GET', 'post', 'POST', ...
}
export type Method = keyof typeof MethodEnum // will export type 'get' | 'GET' | 'post' | 'POST' | ...
const Schema = zod.object({
method: zod.nativeEnum(MethodEnum),
});
Upvotes: 0
Reputation: 635
if you'v existing type from say an orm schema or gql generated or some sort and you want to add zod currently i found this working from me.
export type InferZodMap<T extends abstract new (...args: any) => any> = {
[k in keyof Partial<InstanceType<T>>]?: unknown;
};
// then use it like
type User {
email: string;
}
const UserInsertValidation = z.object({
email: z.string(),
} satisfies InferZodMap<User>);
Upvotes: 0
Reputation: 268
I have found out that using z.custom<ExistingType>()
is working for me, and would be appropriate for this kind of problem.
[Edited]
According to @esteban-toress, we still have to add a validation function at the end like this z.custom<ExistingType>((value) => //do something & return a boolean)
. Otherwise, just z.custom<ExistingType>()
only returns ZodAny
type which allows any
value.
[Edited] This solution is only preferable for type inferences if you wanted to use this to please Typescript types, but I would not recommend this for Zod validation due to the above behavior.
See docs: https://zod.dev/?id=custom-schemas
Upvotes: 26
Reputation: 11
You can use ts-to-zod to export your typescript
type into a zod
schema.
Upvotes: 1
Reputation: 10006
Reading your comment it sounds like you want to ensure that your schema is in sync with the Method
type from axios. I would suggest doing the following:
import { z } from 'zod';
import type { Method } from 'axios';
const methods: z.ZodType<Method> = z.enum(['get', 'GET', ...]);
Which will at least enforce that the schema on the right hand side of the expression will parse valid axios Method
results. Unfortunately, anything more may be out of reach unless axios
also exports an array containing the strings that correspond to the values in the Method
type.
The original thing that you were looking for z.something(<type here>)
can't work because zod is using actual runtime objects, and types like Method
don't exist at runtime. If axios
exported an array containing the methods, then that would be a runtime value and you could use that (perhaps with some type casting) to generate your methods
schema (more on this in a moment).
The other shortcoming to this approach is that something like this will typecheck:
const methods z.ZodType<Method> = z.enum(['get']);
The reason for that is because of how types work in TypeScript. That enum
schema will only ever parse successfully for 'get'
but because the literal 'get'
is a subtype of the larger union type defined in Method
, the resulting schema is also assignable.
So, the next option I'm going to pose feels slightly self-defeating in that it's going to require redeclaring all the values in Method
, however, you can continue to use the axios
Method
type and you will definitely have a schema that parses all of the values in Method
(ie, does not succumb to the issue mentioned above):
import { z } from "zod";
import { Method } from "axios";
const METHOD_MAP: { [K in Method]: null } = {
get: null,
GET: null,
delete: null,
DELETE: null,
head: null,
HEAD: null,
options: null,
OPTIONS: null,
post: null,
POST: null,
put: null,
PUT: null,
patch: null,
PATCH: null,
purge: null,
PURGE: null,
link: null,
LINK: null,
unlink: null,
UNLINK: null
};
const METHODS = (Object.keys(METHOD_MAP) as unknown) as readonly [
Method,
...Method[]
];
const methods: z.ZodType<Method> = z.enum(METHODS);
The type assertion for METHODS
is safe here because the METHODS_MAP
is not exported and we know exactly what keys it has. Now, the METHOD_MAP
object will cause a type error if any Method
value is missing though which means the resulting schema will parse all Method
values as a guarantee enforced at compile time.
Upvotes: 33
Reputation: 26404
If you want to use the type directly you can use this:
const methods = ['get','GET',...] as const;
export type Method = (typeof methods)[number];
zod.enum(methods);
You get the best of both worlds this way; having the methods in a value you can use (array), and the type that you originally wanted.
Upvotes: 1