Branded Types give you stronger input validation

  Рет қаралды 18,859

Andrew Burgess

Andrew Burgess

Күн бұрын

Пікірлер: 76
@samroelants
@samroelants 2 жыл бұрын
Really wish typescript had a built-in way of doing this kind of nominal typing, rather than having to hack it using type intersections. Really well explained! Looking forward to more tips!
@noxiifoxi
@noxiifoxi 2 жыл бұрын
yep, I won't use this method because it's ridiculous, I hope they add something like that in a normal way to ts in the future.
@khai96x
@khai96x 2 жыл бұрын
New type pattern: Create a wrapper object type with a unique private symbol as property. Unlike his 'Branded Types', this wrapper type will match its runtime object.
@DemanaJaire
@DemanaJaire Жыл бұрын
@@khai96x Runtime fetishists.
@vaap
@vaap Жыл бұрын
they already have the "unique" keyword, i can easily imageine "type EmailAddress = unique string"
@herzog0
@herzog0 Жыл бұрын
This just saved my freaking life. We had like 30 interfaces that extended Record and their attributes had to be changed. We ended up with dozens of calls to non-existing attributes throughout our code that would not throw a compilation error. I saw this video a long time ago and remembered it now. Great explanation!
@toddymikey
@toddymikey 2 жыл бұрын
Alternatively, (heavy approach) repackage a string email address as a class and pass that on past the point of initial checks ... thereby always being sure whenever it is reused by any function that handles email addresses that it is an actual email address.
@KadenCartwright
@KadenCartwright Жыл бұрын
The downside of this is the way that typescript recognizes whether something fulfills the requirements of a type when that type is really a class is based off the public api of an instance of the class so it’s fairly easy to just construct an object literal that looks similar enough for the compiler not to complain but has bypassed the validation, especially when it’s a simple object with one field like `const str: SpecialString={value : “some special string”}` The intellisence could actually guide you down the wrong path pretty easily that way
@KirkWaiblinger
@KirkWaiblinger Жыл бұрын
​@@KadenCartwright actually classes are treated more or less nominally in TS as long as they have at least one non-public member. It's wonky. So if you have class Clazz { private x: number = 3 } declare function f(x: Clazz): void; Then f({}) and f({ x: 3}) will both be errors. Even this will fail class ImposterClazz { private x: number = 3 } f(new ImposterClazz ()) So yeah wrapping validated objects in classes (with a non public member) would probably successfully prevent false negatives in most circumstances in TS. Not necessarily recommending it but just saying you can't actually lie to it that easily with structurally similar objects. One of those things that the TS docs are extremely misleading about.
@hyperprotagonist
@hyperprotagonist 2 жыл бұрын
How have I only just discovered you? Love your approach to explaining stuff. Really, really helpful! Keep it up!
@nefthy
@nefthy 2 ай бұрын
The big caveat is, that any operation on a branded type will return the underlying type. so if you have two branded numbers and add them, you will get a number and not a branded number. That makes the branded types far less useful.
@martinemanuel8239
@martinemanuel8239 Жыл бұрын
The last one was awesome, thanks for sharing!!
@DASPRiD
@DASPRiD 3 күн бұрын
If you work with objects and need branded types, there's a better approach though: Use custom symbols as a branding keys, that way you avoid any kind of possible collision.
@tezza48
@tezza48 2 жыл бұрын
that's pretty sweet, I didn't know you could coerce types like that using `foo is bar`. another tool for the belt!
@hut_fire1490
@hut_fire1490 Жыл бұрын
I stumbled upon a goldmine, thank you Andrew !
@sam.kendrick
@sam.kendrick Жыл бұрын
Thanks for your effort! I will use this at work!
@webstuffzak
@webstuffzak 2 жыл бұрын
Great content Andrew, don't know why KZbin recommended you to me but, it sure was right. Subbed 👍
@magicjuand
@magicjuand 2 жыл бұрын
this is interesting for strings which you need a function to validate. for a lot of strings, especially IDs and such, template literal types are probably the way to go
@PatricioHondagneuRoig
@PatricioHondagneuRoig Жыл бұрын
You sir just earned a subscriber
@antonpieper
@antonpieper Жыл бұрын
You could also use a template literal `${string}@{string}.${string}`
@natanaelaitonean3867
@natanaelaitonean3867 Жыл бұрын
Super helpful explanation! Thanks!
@softwaresips3454
@softwaresips3454 4 ай бұрын
VERY HELPFULl, GREAT TRICKS
@mahadevovnl
@mahadevovnl Жыл бұрын
But why do you need that object with __brand at all? After validating or asserting it, it should be good enough to just keep the type as a string, no?
@DavidAguileraMoncusi
@DavidAguileraMoncusi Жыл бұрын
The __brand object only exists at a type level. Your code will only contain strings. If you didn't include the object in the type definitions, string and EmailAddress are synonyms. Sure, the safeguard function would let YOU know that the variable is an email, but at runtime and not at the type level (it'd be equivalent of the function simply returned a boolean).
@jeetchheda8916
@jeetchheda8916 2 ай бұрын
but i was able to directly set a regex as the type for my variable.🙂 It worked for me
@Q_20
@Q_20 Жыл бұрын
Specifying type check in return type automatically promotes type in function usage
@ChrisAthanas
@ChrisAthanas Жыл бұрын
This is useful to know and I would suggest reducing the jargon and verbosity and simplifying the explanation a bit more
@pwall
@pwall 2 жыл бұрын
Amazing content, undervalued by the algorithm even though it got on my feed.
@rahimco-su3sc
@rahimco-su3sc Жыл бұрын
your videos are really helpfull | thanks a lot for your efforts
@bobobo1673
@bobobo1673 Жыл бұрын
Thank you
@edgarabgaryan8989
@edgarabgaryan8989 2 жыл бұрын
you are the best
@nickolaizein7465
@nickolaizein7465 11 ай бұрын
Great!
@Azoraqua
@Azoraqua Жыл бұрын
What about string templates? Like "type Email = `${name}@${domain}.${tld}`" That will only accept strings that are in that exact format. Any usecase you think?
@vukkulvar9769
@vukkulvar9769 Жыл бұрын
But how would it knows that name/domain/tld must not contains @ ?
@Azoraqua
@Azoraqua Жыл бұрын
@@vukkulvar9769 it doesn’t, any strong suffices, I don’t think you can prevent that. Although you might be able to provide a type that removes all invalid characters.
@robertotonino2916
@robertotonino2916 Жыл бұрын
Interesting approach! Why is the “& { … }” necessary? Is it because the type alias would be recognised as a string even after checking isEmailAddress? Also, what about string literal types? Are they too strict for this use case?
@vytah
@vytah Жыл бұрын
type EmailAddress = string means that EmailAddress is the exact same type as string, so it's just a clunky alias. All strings would be EmailAddresses in that situation.
@tezza48
@tezza48 2 жыл бұрын
you could even use the assert on a function that mutates the input into the new type if that's your jam I guess. like something adding some component or entry to the argument.
@andrew-burgess
@andrew-burgess 2 жыл бұрын
Hmm, yeah, I guess that would work! Personally, I’d be more likely to just have it return a new type, but you’ve got to love the flexibility of TS!
@jethrolarson
@jethrolarson Жыл бұрын
They can't have my brand, I have special eyes!
@paulholsters7932
@paulholsters7932 Жыл бұрын
Thx
@zahash1045
@zahash1045 2 жыл бұрын
Why not just have a class "EmailAddress" that has a private constructor and a static factory method that takes a string as input and returns Option as output. That way, the only way to get an "EmailAddress" object is to call the static factory that does all the checks. So, it's a guarantee that you checked the string if you have an "EmailAddress" object If we do it your way then there it is possible to just do "'asdf' as EmailAddress". So, even if you have a branded type, there is no guarantee that it came from the validator function.
@aarondewindt
@aarondewindt 2 жыл бұрын
The branded type doesn't actually change the underlying type of the variable. It's still a string, so you can use it as any other string. All string methods will be available (eg. replace, split, etc) and you can pass it to functions expecting strings. The object type you're intersecting will never instantiated, and the string is never copied or wrapped. The only thing you're doing is checking if the value of the string is valid, and if it is, you tell the static type checker that after this point, I have a string with a valid email in it.
@zahash1045
@zahash1045 2 жыл бұрын
@@aarondewindt I can do the same after unwrapping the inner value with a getter to get access to the string and call all the methods I want But what I want you to focus on is If we do it your way then it is possible to just do "'asdf' as EmailAddress" (maybe vscode even suggests it as a "quick fix"). So, even if you have a branded type, there is no guarantee that it came from the validator
@andrew-burgess
@andrew-burgess 2 жыл бұрын
I kinda like your approach here, but I don't think it solves the `as` problem. This bit of TypeScript is working for me: class EmailAddress { } const a = "test" as EmailAddress; If you look at the type of `a`, it's `EmailAddress`.
@redcrafterlppa303
@redcrafterlppa303 Жыл бұрын
@@andrew-burgess but I think thats just the downside of supporting the underlying dynamic type system of js. If you forbid the as casting you would also lose the is casting. The new type pattern works without casting values by simply guaranteeing the correctness of the object by being of a certain type. This technique is heavily used in type oriented languages like rust.
@vanish3408
@vanish3408 2 жыл бұрын
I read this as "Braindead types"
@andrew-burgess
@andrew-burgess 2 жыл бұрын
😂🤣
@Danielo515
@Danielo515 Жыл бұрын
This is the only way to have properly safe types. Too bad most people don’t ever see beyond number, Boolean and string
@ricardodasilva9241
@ricardodasilva9241 2 жыл бұрын
Genuine doubt here, why you would call runtime code with emails, only place I can see it would help are on tests or writing libraries maybe? How is this better than a schema validator? Are there other use cases for this?
@ricardodasilva9241
@ricardodasilva9241 2 жыл бұрын
ah, I see. You are just explaining what you can do with the typing. But I think this is a bad use case.
@andrew-burgess
@andrew-burgess 2 жыл бұрын
Yeah, this pattern would make sense within a schema validation library. But also, there are cases (like one off bulk processing jobs) where adding a validation dependency is a little too heavy, and I just want something lighter.
@DavidAguileraMoncusi
@DavidAguileraMoncusi Жыл бұрын
​@@ricardodasilva9241 I can think of other use cases where this is extremely useful like. For example, imagine I have an API that returns a list of objects with an attribute name "slug." If I defined type ObjectSlug = string, I'd be able to pass any string as an object slug. With branded types, however, only* slug attributes extracted from objects retrieved via the API would be valid. An error messages would also be more helpful. * That's probably not 100% sure, but you get the point
@echobucket
@echobucket 2 жыл бұрын
Is the function mutating the type of the email string? Is there a way to do this without mutating? Like make isEmailAddress return an Optional EmailAddress?
@andrew-burgess
@andrew-burgess 2 жыл бұрын
The function casts the string to a new type. You could wrap it in an Optional, but if you want the type EmailAddress, you will need to cast a string to that type.
@erice.3892
@erice.3892 2 ай бұрын
this is cool
@stanstrum
@stanstrum Жыл бұрын
Why not use a "unique symbol" e.g. & { __brand: unique symbol }; ?
@isomorphic97
@isomorphic97 3 ай бұрын
“There is actual regex for that”… Don’t go down that rabbit hole!
@formyeve
@formyeve Жыл бұрын
I don't get why one needs to do this when there are things called classes 😂 oop solved these problems a long time ago
@spead
@spead 2 жыл бұрын
nice
@mbehboodian
@mbehboodian 2 жыл бұрын
There are still things to improve in content of your videos, but subscribed. Keep it up 🙂
@andrew-burgess
@andrew-burgess 2 жыл бұрын
Thanks! Would love to hear what I can improve!
@RM-bg5cd
@RM-bg5cd 2 жыл бұрын
Aren’t these called type guards?
@loko1944
@loko1944 2 жыл бұрын
typeguards are jus the tool here to use branded type. You can't use branded type without typeguards. Thats the point of using branded types - safety. If that is no sufficient...idk, watch again
@RM-bg5cd
@RM-bg5cd 2 жыл бұрын
@@loko1944 That literally is not what I'm asking. Maybe try actually reading properly? Branded types as shown in the video are documented as typed guards in their docs.
@andrew-burgess
@andrew-burgess Жыл бұрын
I think Loko is right here, actually. Type guards, according to the TS docs, and functions that narrow the type of the argument they accept. So you’re right, we do use type guards here. The branded type is the type that the guard function will narrow its argument to. The core idea here is that the only way to get a value of a branded type is via the associated guard function. There’s no other was to get a value of that type, apart from explicitly casting it via ‘as’.
@gosnooky
@gosnooky Жыл бұрын
It's not much, but it's an honest hack.
@anatolydyatlov963
@anatolydyatlov963 2 жыл бұрын
Or... just use Zod
@andrew-burgess
@andrew-burgess 2 жыл бұрын
Oh, I’ve been meaning to give Zod a try! Thanks for the push :)
@DavidAguileraMoncusi
@DavidAguileraMoncusi Жыл бұрын
Would Zod help, though? Would I really be able to have an EmailAddress type in my app that, if I get an attribute of said type, I know for sure* (provided there aren't any explicit casts) it's an email address?
@anatolydyatlov963
@anatolydyatlov963 Жыл бұрын
@@DavidAguileraMoncusi Yes, that's one of the main features of Zod - it can parse untyped entities and throw errors when they don't meet the specification. For example: ``` const TEmail = zod.string().email(); const untypedUnknownString = "..."; const possibleEmail = TEmail.safeParse(untypedUnknownString); // if possibleEmailString.success is true, then possibleEmail [dot] data (links aren't allowed in the comments) will be a typed email address previously stored in untypedUnknownString. The type will be: TEmail // if possibleEmailString.success is false, then possibleEmail [dot] error will contain a parsing error message ``` This is an example of a predefined type feature of Zod (string.email), but of course, you can use your own type-checking logic, regexes and primitive types. You can also create Zod object types where each property has its own type parser. In this case, parsing the whole object will handle all the properties recursively, assuring that the object meets the schema.
@redcrafterlppa303
@redcrafterlppa303 Жыл бұрын
This seems similar to "if (o instanceof Foo f) {}" casting in java but implicit on callsite and not really using the type system. Why not create a simple type EmailAddress with a constructor or factory function that validates the email address? This way you have a real type representing an email and not just a botched string that's implicitly casted by the check function.
@nomadshiba
@nomadshiba Жыл бұрын
actually brands are not that useful with Emails. also you can define emails like this. type Email = `${string}@${string}.${string[0]}${string}` i would use brand for things, such as Ids or similar things and i would define a brand like this const enum UserId { _ = "" } export type { UserId } const enum PostId { _ = "" } export type { PostId } unlike & { __brand ... } pattern, by using enum you dont have to find a name for your brand
@CodingEnjoyer
@CodingEnjoyer 7 күн бұрын
just use zod
@praktycznewskazowki6733
@praktycznewskazowki6733 Жыл бұрын
I still don't understand a use case for this... What can goes wrong with simple isEmail: (email) => boolean ??
@tak68tak
@tak68tak Жыл бұрын
Zod validation is easier like z.string().email
any vs unknown vs never: TypeScript demystified
8:01
Andrew Burgess
Рет қаралды 24 М.
Interfaces vs Type Aliases: what's the difference?
10:20
Andrew Burgess
Рет қаралды 22 М.
Как Я Брата ОБМАНУЛ (смешное видео, прикол, юмор, поржать)
00:59
Натурал Альбертович
Рет қаралды 4,1 МЛН
ТВОИ РОДИТЕЛИ И ЧЕЛОВЕК ПАУК 😂#shorts
00:59
BATEK_OFFICIAL
Рет қаралды 6 МЛН
Мама у нас строгая
00:20
VAVAN
Рет қаралды 10 МЛН
When Cucumbers Meet PVC Pipe The Results Are Wild! 🤭
00:44
Crafty Buddy
Рет қаралды 58 МЛН
How to use TypeScript Enums and why not to, maybe
12:43
Andrew Burgess
Рет қаралды 19 М.
How does ZOD work? Build it yourself!
14:12
Andrew Burgess
Рет қаралды 12 М.
JavaScript Prototypes: The interview question that stumped me
17:16
techgirlinstyle
Рет қаралды 1,4 М.
will i never understand this? unknown.
12:05
Andrew Burgess
Рет қаралды 3,9 М.
How to use generics in TypeScript
11:46
Andrew Burgess
Рет қаралды 37 М.
TypeScript Wizardry: Recursive Template Literals
14:47
Tech Talks with Simon
Рет қаралды 38 М.
Function Overloading in TypeScript (I was wrong)
10:24
Andrew Burgess
Рет қаралды 14 М.
Are your TypeScript Unions broken? | Advanced TypeScript
7:36
Andrew Burgess
Рет қаралды 8 М.
An Option type in TypeScript (inspired by Rust)
12:53
Andrew Burgess
Рет қаралды 17 М.
Как Я Брата ОБМАНУЛ (смешное видео, прикол, юмор, поржать)
00:59
Натурал Альбертович
Рет қаралды 4,1 МЛН