zod
zod copied to clipboard
[V3] Support template literals
Hello :wave:
I recently encountered a case, where my schema returned a string, but a function accept only a TS template string
Here is an example showing the issue: https://codesandbox.io/s/snowy-river-n2ih2?file=/src/index.ts
Is there a way, currently or in the future, to be able to do this:
const str = z.template(`${z.number()}.${z.number()}.${z.number()}`)
type Str = z.infer<typeof str>
// => Str: `${number}.${number}.${number}`
Good idea! I definitely want to support this eventually.
For now, you'll have to implement this with z.custom
:
const literal = z.custom<`${number}.${number}.${number}`>((val) =>
/^\d+\.\d+\.\d+$/g.test(val as string)
);
This issue should be renamed to "Support template literals". String literals are already supported in zod.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
No, I still want it StaleBot!
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
@colinhacks Any updates on this ?
I'm here to help in the battle against Stale Bot, I'd be interested in this feature too.
That being said, the z.custom
workaround is working perfectly 👍
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Stale Bot, no! We still care ❤️
Screw you stalebot!
This would be an amazing feature for formatted string identifiers that contain a type discriminator. Writing/extending custom regex/validation code isn't fun and can be quite error prone. I have a few similar cases where I would love to do something like this:
enum Process {
BatchJob1 = "BatchJob1",
BatchJob2 = "BatchJob2",
}
enum PrincipalType {
User = "User",
ApiToken = "ApiToken",
Anonymous = "Anonymous",
Process = "Process",
}
const Authority = z.string()
type Authority = z.infer<typeof Authority>
const UserId = z.string().uuid()
type UserId = z.infer<typeof UserId>
const ApiTokenId = z.number()
type ApiTokenId = z.infer<typeof ApiTokenId>
// `User:${string}:${string}`
const UserPrincipal = z.template([
z.literal(PrincipalType.User),
z.literal(":"),
Authority,
z.literal(":"),
UserId,
])
type UserPrincipal = z.infer<typeof UserPrincipal>
// `ApiToken:${number}`
const ApiTokenPrincipal = z.template([
z.literal(PrincipalType.ApiToken),
z.literal(":"),
ApiTokenId,
])
type ApiTokenPrincipal = z.infer<typeof ApiTokenPrincipal>
// "Anonymous"
const AnonymousPrincipal = z.template([
z.literal(PrincipalType.Anonymous),
])
type AnonymousPrincipal = z.infer<typeof AnonymousPrincipal>
// "Process:BatchJob1" | "Process:BatchJob2"
const ProcessPrincipal = z.template([
z.literal(PrincipalType.Process),
z.literal(":"),
z.nativeEnum(Process)
])
type ProcessPrincipal = z.infer<typeof ProcessPrincipal>
// `User:${string}:${string}` | `ApiToken:${number}` | "Anonymous" | "Process:BatchJob1" | "Process:BatchJob2"
const Principal = z.union([UserPrincipal, ApiTokenPrincipal, AnonymousPrincipal, ProcessPrincipal])
type Principal = z.infer<typeof Principal>
Enum value expansion looks like it might possibly be hard?
This would also allow to migrate this TS to zod right?
type SingleBlock = {
title: string;
description: string;
background: boolean;
};
type Block = {
[T in `block[${number}][${keyof SingleBlock}]`]: T extends `block[${number}][${infer Key extends keyof SingleBlock}]` ? SingleBlock[Key] : never;
};
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
No staley please don't 😰
I tried to implement templateLiteral
but did not succeed. Maybe this code will be helpful for those who want to work on this issue. Parsing kinda works. Type inference does not work(infers string
)
import { z } from 'zod'
function templateLiteral<S extends readonly string[]>(strings: S, ...schemas: z.ZodSchema[]) {
if (strings.length != 0 && strings.length !== schemas.length + 1) {
throw new Error('invalid template literal')
}
const len = schemas.length;
let regexpStr = '^'
for (let i = 0; i < len; i++) {
regexpStr += strings[i] + schemaToRegexp(schemas[i]);
}
regexpStr += strings[strings.length - 1];
regexpStr += '$'
console.log({regexpStr})
const regexp = new RegExp(regexpStr);
return z.string().refine(s => regexp.test(s))
}
function schemaToRegexp(schema: z.ZodSchema): string {
if (schema instanceof z.ZodNumber) {
return String.raw`(NaN|-?((\d*\.\d+|\d+)([Ee][+-]?\d+)?|Infinity))`
}
if (schema instanceof z.ZodString) {
return '(.*?)'
}
if (schema instanceof z.ZodBoolean) {
return '(true|false)'
}
if (schema instanceof z.ZodUndefined) {
return `(undefined)`
}
if (schema instanceof z.ZodNull) {
return `(null)`
}
throw new Error('invalid schema for template string' + JSON.stringify(schema._def))
}
const s1 = templateLiteral`hello ${z.string()}!`
s1.parse('hello BOB!')
const s2 = templateLiteral`hello ${z.string()}! ${z.number()}`
s2.parse('hello BOB! 2.2')
type s2 = z.infer<typeof s2>
templateLiteral`hello ${z.number()}${z.number()}`.parse('hello 1111')
console.log('OK')
Looks like we can take off the awaiting-pull-request
label @JacobWeisenburger !
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Oh trust me, it's not stale. 🙃
i have a dream
This would be awesome to have!
This is actually a superb feature to have. Please implement this.
@colinhacks thanks!
this would help me a ton because of some libraries that uses template literals as types for hexadecimals.
@colinhacks thanks Man!
web3 mfers:
const schema = z.object({
collection: z.literal(`0x${z.string()}`),
})
This feature is so important