The best TypeScript library just got better
ฝัง
- เผยแพร่เมื่อ 11 พ.ย. 2024
- Zod is an extraordinary library for TypeScript validation, and with 3.20 it just got even better.
Matt's Zod Course: totaltypescrip...
GitHub Release: github.com/col...
Become a TypeScript Wizard with Matt's upcoming TypeScript Course:
www.totaltypes...
Follow Matt on Twitter
/ mattpocockuk
Thanks Matt! Incredible as always!
Florian mentioned a couple uses cases for z.symbol() below. I've also not used it much personally, but it always irked me as an inconsistency between Zod and TS. Symbols are one of three kinds of primitive values that can be used as keys in JS objects so it was particularly odd that Zod didn't represent them in any way:
type anyKey = keyof any; // string | number | symbol
Though `z.object` doesn't actually support them yet as keys...something for a future release!
const matt = Symbol("mattpocock");
const schema = z.object({
[matt]: z.string().catch("cool dude"),
});
These are friggin awesome changes. The functional programmer deep inside of me feels satisfied. I honestly don't know which one to be the most hyped for.
always good to hear new updates from matt channel
One of my favorites libraries is ts-pattern. Worth a look!
Love that lib, I've always loved pattern matching and it really feels natural in TS.
The lack of composability was my biggest annoyance vs io-ts - glad to see the pipe function!
Thanks, Matt, for the amazing video. I didn't know about transform function.
I started using Zod for the first time literally this weekend, and one of the things I wanted to do was parse a stringified `DateOnly`(C#) value ("2022-12-15"), and I was surprised to found out I had to do it manually. Good to see it's now been implemented (Albeit seemingly requiring some semi-arbitrary precision option?)
This still isn't implemented actually, only full ISO datestring validation, which is complex enough that I figured it merited a dedicated API. For things like date strings, I refer people to the `.regex()` method, which can be used in conjunction with a library like validator.js that exposes a ton of common validations (more than would be feasible to expose in Zod's relatively minimal API)
I'm not a huge fan of zod doing things like transforming the data with fallbacks etc. To me it's a validation library, which gives me a clear mental model about what it does and what it returns (tells me if the data is valid or not). I feel like adding bells and whistles on for "convenience features" has a way of gradually polluting very simple concept libraries into grab-bag hodge podge frameworks. Hope they can keep it under control because zod is one of my favorite libraries
You don't have to use it, if you don't find it useful. But I like it because it means that you can more easily reuse schemas without needing to manually parse and check each value every time, especially considering that you now also have "pipe".
@@dealloc The pipe() feature is fine, I'm talking about the default() function and other things that transform the returned data during parsing.
@@MachineYearning I know, and that was the first thing I talked about :) But the type makes more sense with pipe also for easy reusability.
@@dealloc My misunderstanding. Anyway the thing about "if you don't like it, don't use it" is that no one really codes in a vacuum. You are bound to either inherit some code, or have someone touching code you wrote, trying to use one of these features. Not to mention that people will be wondering what other kinds of data transformation zod can do during parsing/validation now, and requesting the next "convenience" feature. At some point you have to balance whether the library itself has the responsibility to support however people wanna use it, or to keep a clean and principled layer of abstraction.
@@MachineYearning In this case it doesn't really matter, because if you're consuming a schema from a third-party source, you already decided to adhere to said schema-that's what schemas are for.
In case you use a library that adds validation, you shouldn't have to care what method they use for validating your input. The rest is implementation details and would otherwise be a leaky abstraction, if they exposed these details in their API.
5:00 I’ve used Symbols in zod for some shared form prop validation in vue where some fields didn’t support specific props. Due to Vue’s lack of real TS support, it needs to be done at runtime unfortunately, so those props default to Symbol(NEVER) and the runtime validation only passes when that specific symbol is still passed (so, when nothing else is passed to the prop).
This ensures devs don’t ever pass props that aren’t supported which is misleading otherwise
Do you mean vue 2?
@@quintencaboYea i think so. Vue has excellent TS support since v3
@@quintencabo I mostly mean vue 2, but with @vue/composition-api. However, this also isn’t entirely fixed in vue 3 for props. .vue files unfortunately will always be a hassle due to not being a standard like .tsx.
But yes, vue3 is significantly better than vue2 for TS, and even vue2 with composition-api is already miles ahead of vue2.
@@daheck81 Unfortunately TS support for the template itself (including component props, which is what I’m talking about here) is still lacking. I still think the decision to invent a new file format and templating language is one of the worst choices vue made since it means that stuff like TS support is NOT out-of-the-box like it would be with JSX and styles via tagged template literals etc..
@@FlorianWendelborn You can define props as interfaces in vue 3. You might want to check out the "script setup" declaration. The only thing i currently miss is being able to import types from other files and use them to declare the props, this will be added in vue 3.3 tho.
For the templating part vue relies on volar (which is an overhead, i agree). But as a type nerd I am overall satisfied with its current state
2:15 I’ve previously used z.preprocess for similar things. I think the pipe example isn’t great since I see no reason to use pipe over preprocess just from the example.
I immediatly remembered a usecase for coerce. I have a yaml file with some numeric IDs mapping release dates of something, which is either an Enum (for reuseable dates) or a number. I've had problems where zod saw the keys (my numeric IDs) as string, but I really wanted a Record in the end.
Edit: here's my current workaround: z.string().regex(/^\d+$/).transform(val => +val)
Zod has one of the coolest logos in open source.
That illustration was actually made for Total TypeScript! I'd love it if they adopted it, though.
Wow, this is really cool, i have so many problem working with query param for paging due to query param has to be string but paging needs number, so i have to write a lot of parseint and toString to solve this
At 3:00 when you are changing the input to parse() how are you getting those dynamic updates in the test? Video edits? If not this is a tool I need in my toolbox (that I’m missing).😢
It's vitest, running with vitest -u to automatically update the snapshots.
@@mattpocockuk You are a rockstar. Thank you for sharing this.
The first code is good for setting and checking the length of the password for the use case.
Really liked the video, just one point of feedback. Using inline snapshots to validate the result is a bit confusing because it appears as if everything you parse is a string while it’s not.
The only thing where symbol would make sense if you would like to validate that an object is iterable (because then it has to support Symbol.iterable key). Maybe there is a use case for that.
Date/time support is great. Does this enable transforming from string to date? So if I have const schema = z.object({ value: z.datetime().transform(d=>new Date(d)) }) and I use it will it then return an object where 'value' has been converted from string to javascript Date?
Yes!
Hmm, I just keep waiting for an excuse to switch from Joi to Zod (because a lot of people seems to go on about it), but that first example is actually super easy and intuitive in Joi 🤷..
how secure is Zod for validating things like product releases or NFT drops?
could it be exploited to trick the ‘z.datetime’ or things like that?
If that is the case, then you should report it as a bug in Zod's issues. Zod is meant for validation of _user_ input. If it provides a built-in mechanism to validate a specific type, you can be sure it is safe (except RegEx, RegEx matching directly on user input should always be considered unsafe). But if you use general validators for things that doesn't have built-in support, you will need to sanitize yourself.
Nice review :) what abou error friendly ? I use Boom but we have some alternative? Thanks
Which one do you suggest to use? Joi or zod? especially for nestjs.
Have you misspoked? You have indeed misspoked.
Where's your course link? Been waiting for more than a year lol
Here it is! totaltypescript.com
Zod is cool, but I prefer superstruct. It’s smaller, faster a had coercion for ages.
How does it compare to yup?
Thank you for this
I chose to use types even though I use c# on the backend
But I use types cuz I was like fuck it, I’m not using inheritance 😆
Is this an alternative of yup?
Yes, exactly! It's very very good.
yup!
@@benslv teehee
@@mattpocockuk It is a shame that it doesn't support i18n
@@abduraufsherkulov1393 You mean for error messages? Interesting.
🎉
What's this `vitest` REPL black magic going on here? A vscode extension?
Just running vitest -u in the background!
Isn’t it similar to YUP
Wonder what your thoughts are on Typebox, which does similar thing but additionally outputs a valid JSONSchema
I'm in love with Zod, so I'd need a reason to use JSONSchema to consider Typebox I think.
@@mattpocockuk there is also zod to json schema pkg
@@twitchizle Yes, true!
Just wondered if you ever have used it and what your thoughts were.
@@mattpocockuk have a look at Deepkit.
I think ZOD look like class-validator in NestJs
💯
having coerce would have made a task I had earlier a lot easier
Still no z.number().integer(), oof
.int() exists
@@vieruuuu So do you know if ‘z.number.int()’ works or do you need to use ‘.pipe’ ?
@@blankcheckguy69 you need to use z.number().int(), no need for pipe
Not sure why zod does not have a way to abort early. Such a massive dealbreaker for me.
Is there any way to convert class object to zod object ? z.object( class name). Please reply
Only thing I don't like about Zod is it blurs the boundaries from a really advanced and precise asserting library, to a transformation library..
I just wished there is a zod-lite that ONLY deals with testing the value.
That's true!
Zod isn't near the best TypeScript library. It's like JSON schema. It's pretty shit imho.
Anyhow, it's nothing compared to Deepkit.
Found your content. At least advanced stuff! Not tutorials:(
typescript-json is better.
Deepkit is even better lol
that's an interesting approach to the same problem, not sure I like the idea of completely changing typescript just to get better developer ergo-dynamics though.
Seems like something that would make C# developers feel a lot more at home, but not necessarily provide a huge amount of value to JS natives.
@@jon1867 as one such person, I just looked at those libraries and dislike them for making things too magical. It breaks the mental model of the language, and adds yet another compile step for a use case that works fine in a normal library… no thanks.
@@ulterior_web exactly my thoughts. It is pretty cool though
I feel like an excellent use case for .coerce will be Mongo ObjectIDs. You often want to convert them to strings with the "toString" method, e.g. when passing them to the frontend, but allow them to be ObjectIDs when they're still handled by the backend, e.g. when you receive them from the database. For now, I have a custom type:
export const TObjectId = z.union([z.instanceof(ObjectID), z.string()]).transform((arg, ctx) => {
try {
if (typeof arg === "string") return arg;
if (arg instanceof ObjectID) return arg.toString();
throw new Error(`Wrong _id type: ${typeof arg}`);
} catch (error) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: getErrorMessage(error),
});
}
});
export type TObjectId = z.infer;
I think I can convert it to a much simpler .coerce type in 3.20. Unfortunately, I first have to resolve some breaking issues between 3.18 and 3.19, which result in z.pick not working as previously.
Bug report..
z.coerce.boolean().parse("false") returns true and not false, because any string other than "" is truthy.
I know "why" it does it, but the example in your video tested for "true" which can confuse people.
I would expect it to return false here, I think that's a genuine bug.
@@mattpocockuk I don’t think it’s a bug. I think it’s simply isn’t clear what the coercion does. As far as I understand, it literally just applies `Boolean()` or `String()` or `Number()` to the respective value
Coding some custom logic that does `Boolean()` but IF it says "false" it’s suddenly false would mean unexpected bugs when people don’t expect anything but `Boolean()`
@@FlorianWendelborn Gotcha, this makes sense. I see what Colin was tilting at now.
I'm not a huge fan of zod doing things like transforming the data with fallbacks etc. To me it's a validation library, which gives me a clear mental model about what it does and what it returns (tells me if the data is valid or not). I feel like adding bells and whistles on for "convenience features" has a way of gradually polluting very simple concept libraries into grab-bag hodge podge frameworks. Hope they can keep it under control because zod is one of my favorite libraries
Great point!