Comments (12)
This is the recommended way to do tagged/branded types in TypeScript/Zod:
type Tagged<T, Tag> = T & { __tag: Tag };
type UUID = Tagged<string, 'UUID'>;
const uuid: z.Schema<UUID> = z.string().uuid() as any;
uuid.safeParse('ae6cd9c2-f2e0-43c5-919c-0640b719aacf'); // Success, data has type UUID
uuid.safeParse(5); // Fail
uuid.safeParse('foo'); // Fail
from zod.
This is the recommended way to do tagged/branded types in TypeScript/Zod:
type Tagged<T, Tag> = T & { __tag: Tag }; type UUID = Tagged<string, 'UUID'>; const uuid: z.Schema<UUID> = z.string().uuid() as any; uuid.safeParse('ae6cd9c2-f2e0-43c5-919c-0640b719aacf'); // Success, data has type UUID uuid.safeParse(5); // Fail uuid.safeParse('foo'); // Fail
Is there any way without using any?
from zod.
@danielo515 did you happen to find a type safe workaround?
There is now a native way for this https://github.com/colinhacks/zod#brand
from zod.
@colinhacks what did you decide regarding branded type? I've just attempted migrating from Runtypes because I really want strict/excess property check (which Runtypes lacks) but I'm stuck on branded types.
We use them mainly for simple types like UnsignedInteger, JsonDate etc. that are respectively number
and string
with extra checks:
const zJsonDate = z.string().refine((str) => isValid(parseJSON(str)));
With branding, we "know" down the line that they have been checked. Without them, we have to recheck locally and then cast to the branded type, but then it defeats the purpose of the initial check, we might as well just expect string for JsonDate for example and then have:
type JsonDate = z.infer<typeof zJsonDate> & {__jsonDateBrand: true};
function checkJsonDate(val: string) {
return zJsonDate.parse(val) as JsonDate;
}
I don't love all the boilerplate associated with io-ts branded type
Might be worth checking the Runtypes implementation, I'm not an expert but it didn't look too convoluted:
from zod.
@jraoult someone provided an example of how to implemented branded types on top of Zod here, might be useful for you. #210 (comment)
from zod.
Hey @vriad, I actually made an internal library where we had to solve for this because we validate large nested objects. I've wrote up some stuff that may be of use https://github.com/ryansmith94/rulr/blob/v6/readme.md
from zod.
@colinhacks great thx for pinging me! I'll try that.
from zod.
My "option 2" is misguided; it doesn't actually achieve the same thing as true branded/opaque types. @krzkaczor
from zod.
You can do opaque types with intersections upon any types - and can be very useful if you use symbols throughout. The way they show there is less useful as it can't be saved and queried (you cant add multiple "brands" or "flags" dynamically for the type system to later use to infer from).
For example, I am playing right now with seeing if I can build out a syntax that would still type statically just for fun, doubting I'd actually try to go past typing it all out -- but this is what i came up with, which basically utilizes opaque typing to achieve it:
const one = { one: wrap.optional.string(), four: 3 } as const;
const two = { two: 2 } as const;
const validator = {
foo: wrap.optional.string(),
bar: wrap.nullable.optional.literal('bar'),
baz: 123,
qux: 'qux',
blah: wrap.unequal(2),
union: wrap.optional.nullable.union([wrap.string(), wrap.number()]),
intersect: wrap.intersection([one, two]),
key: wrap.forbidden(),
undef: wrap.undefined(),
} as const;
Notice that it is simply property accessing - which actually does not need to be done in any given order.
Technically just using
unique symbol
does work -- using interface like they did just seems excessively verbose
type CheckNullable<
T extends AnyObj,
V
> = T['__nullable'] extends typeof NULLABLE ? null | V : V;
type CheckOptional<
T extends AnyObj,
V
> = T['__optional'] extends typeof OPTIONAL ? undefined | V : V;
type CheckFlags<T extends AnyObj, V> = V extends infer O
? CheckNullable<T, O> | CheckOptional<T, O>
: never;
type TypedPrototype<T extends AnyObj> = Readonly<{
assert(value: unknown): asserts value is T;
guard(value: unknown): value is T;
union<V extends unknown[]>(arr: V): CheckFlags<T, V[number]>;
intersection<A extends unknown[] | readonly unknown[]>(
arr: A,
): IntersectionOf<A[number]>;
optional: TypedPrototype<T & { readonly __optional: typeof OPTIONAL }>;
nullable: TypedPrototype<T & { readonly __nullable: typeof NULLABLE }>;
negative: TypedPrototype<T>;
nonnegative: TypedPrototype<T>;
positive: TypedPrototype<T>;
nonpositive: TypedPrototype<T>;
string<V extends string>(): CheckFlags<T, V>;
number<V extends number>(): CheckFlags<T, V>;
bigint<V extends bigint>(): CheckFlags<T, V>;
regexp<V extends RegExp>(): CheckFlags<T, V>;
symbol<V extends symbol>(): CheckFlags<T, V>;
primitive<V extends Primitive>(): CheckFlags<T, V>;
unknown<V extends unknown>(): CheckFlags<T, V>;
undefined(): undefined;
forbidden(): never;
any<A extends any>(): A;
literal<V>(value: V): CheckFlags<T, V>;
equal<V>(value: V): V;
unequal<R, V = unknown>(value: V): R;
}>;
which has a schema like:
but finally infers into:
All that being said, I am not sure I personally see that much use in having this library provided branded types. I think they just sound like they'd be cool but in practice have very little actual use. The larger issue usally becomes once they are branded they no longer pass as their wider type, so it makes your entire application insanely complex trying to manage all the cases and potential brands things may have.
A brand may be useful for the top-level object which indicates that it has been validated, however -- and that can even work at runtime. For example, you could return a new object when validate()
is called which is Inferred & typeof VALIDATED
then a fn can add the brand to any so that the compiler will only let you call the fn if the validation has ran:
const obj = {
positive: 2,
foo: 'one',
bar: 'bar',
baz: 123,
qux: 'qux',
blah: 3,
union: 2,
intersect: {
one: 'hi',
four: 3,
two: 2,
},
} as const;
const validatedSchema = schema.validate(obj);
function example(o: Inferred & typeof VALIDATED) {
console.log('Validated Only Can Call');
}
validatedSchema.union;
example(validatedSchema);
example(obj); // <--- not assignable to typeof VALIDATED
from zod.
Hi @colinhacks, is the support for ReadOnly types planned?
from zod.
@maneetgoyal this approach should work: #210 (comment)
from zod.
@danielo515 did you happen to find a type safe workaround?
from zod.
Related Issues (20)
- ipv6: case insensitive comparison
- String is hex
- Feature: support multiple parameters in `.startsWith()` HOT 3
- Proper inference of a string to typeof keyof Object
- Misleading Error: "Property 'infer' Does Not Exist" When Using const Instead of type in Zod HOT 4
- Creating a large schema from a subset
- File zod/index.d.ts is not a module.
- The above code how to verify whether newPwd and confirmPwd are consistent, and can get a custom message when the verification fails, just like two passwords are inconsistent; Just like if this check doesn't pass, it gets it in errors.message
- z.infer<> fails to infer nested union type. HOT 1
- Using generic schemas from generic functions makes enum fields optional
- Email validation fails on "=" equals sign HOT 1
- variadic rest in the tuple with min & max capability
- Inferred type imported from library has all fields as optional HOT 3
- Add support for `base64url` strings
- `z.ref`
- Improve regex DX by adding babel-plugin-transform-regex as dev dependency
- `z.lazy` docs
- Workarounds for TS 7056?
- Problems with .superRefine using discriminated unions HOT 2
- Question: is it correct to assume that ZodIssue[] will always equal NonEmptyArray<ZodIssue> if the result of safeParse(schema).succes is false?
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from zod.