Skip to content
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

Creating a custom type for number, strings, etc #1331

Closed
mmahalwy opened this issue Aug 11, 2022 · 4 comments
Closed

Creating a custom type for number, strings, etc #1331

mmahalwy opened this issue Aug 11, 2022 · 4 comments

Comments

@mmahalwy
Copy link

We use numbers all over our codebase to represent numbers, dollar value, percentages, etc. Ideally, we'd like to keep the zod type along with a typescript type that represents that type.

Here's an example:

type DollarValue = number;

const dollarValueSchema = z.number();

Ideally, when using z.infer<typeof dollarValueSchema> we get DollarValue type, instead of number.

Thoughts?

@scotttrinh
Copy link
Collaborator

This is similar to the idea behind "branded" types, which is discussed here: #3 (comment)

TL;DR you can get that with roughly:

type Tagged<T, Tag> = T & { __tag: Tag };
type DollarValue = Tagged<string, 'DollarValue'>;

const dollar: z.Schema<DollarValue> = z.number() as any;

@scotttrinh
Copy link
Collaborator

The major difficulty here is the TypeScript uses structural type rather than nominal typing so if something is structurally compatible, they are equivalent and the compiler doesn't treat them differently. This branding trick lies to the compiler and tells it that it has a special __tag property (when it doesn't really) but that forces the compiler to treat these number values differently than other numbers.

@colinhacks
Copy link
Owner

colinhacks commented Sep 6, 2022

Branded types were added in the latest release: #1279

@jeremyisatrecharm
Copy link

jeremyisatrecharm commented Jan 24, 2023

Is #3 (comment) still the recommended way to brand strings-only or is brand ok?

I am using .brand for a string. For example - const userIdSchema = z.string().brand<"userId">() but am finding these brands get ignored when used with other schemas and infer.

So I am using this as part of my API and later re-using that API type, only to get an error like:

Type 'Partial<Record<"9" | "4" | "1", Partial<Record<(string & BRAND<"EditedUserId">) | (string & BRAND<"NoneditedUserId">), string>>>>' is not assignable to type 'Partial<Record<"9" | "4" | "1", Record<string, string>>>'

I see the .brand documentation doesn't have branded strings (but rather has z.object), is that because they are not supported?

edit: I was using z.input and not z.infer which is incorrect as is excellently described here. Brands are very helpful - thank you for adding them!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants