-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support type inference for ZodString
#1730
Conversation
implement method `includes()` for `ZodString`
✅ Deploy Preview for guileless-rolypoly-866f8a ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify site settings. |
Thanks for the PR and cool concept, but I'm not sure about it. IMO it's not worth muddying the type definition of Also narrowing the type signature like this would potentially break a lot of existing code and might be unexpected. const schema = z.object({
name: z.string().startsWith("asdf").endsWith("qwer"),
});
type Schema = z.infer<typeof schema>;
function doStuff(_: Schema) {}
const value = { name: "asdf_qwer" };
doStuff(value);
// Argument of type '{ name: string; }' is not assignable to parameter of type '{ name: StartMidEndParts<"asdf", "", "qwer">; }'. I think template literals are sufficiently advanced that users should stick to using |
Thanks for the awesome feedback! For readability, I think it is convenient to see type parameters when the user calls methods such as // ... Rename the old ZodString to ZodSubString. I would like to rename StartMidEndParts to SubString
type MapZSubString<T> = T extends (...args: infer P) => NoSubString ? (...args: P) => ZodString : T
type NoSubString = ZodSubString<"", "", "">
type Excludes = "startsWith" | "endsWith" | "includes"
type ZodString = {
[TProperty in keyof NoSubString]: TProperty extends Excludes
? NoSubString[TProperty]
: MapZSubString<NoSubString[TProperty]>;
} & {
_parse(input: zod.ParseInput): zod.ParseReturnType<string>;
};
static create = (params?: RawCreateParams & { coerce?: true }): ZodString => {
return new ZodSubString({
checks: [],
typeName: ZodFirstPartyTypeKind.ZodString,
coerce: params?.coerce ?? false,
...processCreateParams(params),
}) as ZodString;
}; After these changes, if the user doesn't call the method mentioned above, // const x: ZodString
const x = z.string().max(100)
// const y: ZodSubString<"hello", "", "">
const y = z.string().startsWith("hello")
// const yx: ZodSubString<"hello", "", "">
const yx = z.string().startsWith("hello").max(100); For type narrowing that might cause issues, I think the input should be validated before giving it to the function const value = schema.parse({ name: "asdf_qwer" });
doStuff(value); In case the user absolutely wants to use the input without validation, they can use the const value = { name: "asdf_qwer" } as Schema;
doStuff(value); People use EDIT: I entirely forgot about existing code bases that potentially use the pattern you described. To avoid breaking changes, we could create methods specifically for templated literal: |
I strongly believe #1786 can be a great alternative to these (they add complexity, will confuse people, and will break "what you write is what you get" - I used |
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. |
We could use Typescript template literal to narrow the type provided by
z.infer<T>
. This is especially useful when working with discriminated unions where part of the discriminator is dynamic. For example, the type should be`Hello${string}world!` | undefined
instead ofstring | undefined
:Here is an example of discriminated unions:
Here's an example after these changes:
While we are at it, I think it is beneficial to implement a method that checks for a substring. I will call it
includes
for now for its similarity with Javascript standard library.