Use parse and safeParse Runtime Type-checks with zod

Tomasz Ducin
InstructorTomasz Ducin
Share this video with your friends

Social Share Links

Send Tweet

You might want to run zod schema checks in different styles.

Sometimes you prefer to conditionally throw the error - that's what parse does. But another time you'd prefer to receive a boolean property that determines whether the validation was successful or not - and perform further actions accordingly.

Additionally, the error-throwing API of parse works extremely well with TypeScript assertion functions. Their combination is just... perfect! 👌

That's what we learn in this lesson.

[00:00] I have copied some code from the previous lessons and so we have a booking schema which is exactly the same as before, exactly the same shape. Now we have the type booking which is inferred from the schema, again same as before. We have also the same example booking object in this case annotated to the inferred type which includes everything that is required within the booking type so we can also collapse this one and there is a new object an invalid booking example object that includes all the properties that are required but some of the values under these properties are invalid, like their values are invalid against the schema itself. Because we can see that TypeScript does compile but for instance what the schema expects is that a due date is a string. And here it is a string, but it's a special string, which has a certain format that can be verified in runtime.

[00:58] And the number of guests is indeed a number, but it should be between one and four. And what we have here is 5 and finally that the price should be a positive number but what we pass here is a negative one. And as we can see TypeScript is pretty often incapable of finding these small nuances. So again this depends on how much precise, how precise we want our Zot Schemas to become. Anyway we're running the schema.parse method over here against the valid booking object so let's just run ts.node against this file and we will see that there is no output since this object conforms to the schema.

[01:41] So there is nothing interesting going on here. And if we just make sure that when we run it against the invalid booking one, then we'll get an array of errors. And here we can see that there is a problem with due date because it's an invalid date. There is a problem with number of guests because it's basically too big maximum for what we passed is five. And finally, that the value of the price is too small, since we expected a positive number, and we passed a negative one.

[02:11] Now running parse directly is probably the simplest way to run Zod Schema validation but probably not the best one. And let's see an example over here. Let's say that we have a variable of type unknown. We just don't know what it is and I just don't want to assign any value over here so I'll just use the declare keyword. Now if we run the same validation API which is basically schema.parse and here we pass against x the thing is if the object was correct then the program would basically carry on and execute the next lines just below the parse line.

[02:51] Now we've had no data, no knowledge about what X is, but after we run parse itself, we do know that x needs to be a booking schema, or from type 2 perspective that this is a booking. Now what do we know after this line? We still know nothing. Also what we know is that if x didn't conform to the schema, then basically an error would be thrown so we would not carry on with executing the code anyway. All in all, the negative path is correct but in the positive path when x conforms to the schema we would like to know that this is of type booking.

[03:30] What we can use is we can just wrap this call with a TypeScript assertion function. So this is basically a function that asserts that something is a room booking. And we can also add that the room booking schema is matched just to make the name more precise. So this is basically the candidate and this asserts that whatever we pass over here, so here is the data, is of whatever is the type that we are going to verify over here. So this is going to be of type booking.

[04:02] And what we can do is simply run the validation. So this is going to be the booking schema dot parse and what we verify over here is booking itself. So the way TypeScript assertion functions work is that if the thing does conform to whatever are the conditions then we simply do nothing. But if it doesn't conform that an error should be thrown. And this is exactly what the schema.parse() method is doing.

[04:34] So all in all, instead of this case, where we still have no information about the type, what we can do over here is we can just assert room booking schema matched or whatever name of the assertion function we pass and now we know that if the assertion was correct then we can carry on with the rest of the code knowing more about what x is and if it didn't succeed then there would be an error that would basically wipe out the code anyway. Now there is one more way to run the validation which is different than parse itself, the safeParse method which is going to be pretty similar to parse itself though the difference is that this is not going to throw an error, but return the safe parse return type, which is going to include the type of the result, like success or a failure, and either the data or the error itself. And let's just rerun the script to see that there is no output and we can take a closer look at what is the safe parse going to return in both the positive and the negative scenario. So what we can see here is that there is an object that always includes success which if true includes the data and if the success is false then there is an error, the reason for this failure.

[06:04] Now if we also make an if statement in TypeScript then we could make a type guard over this discriminant property since if success is true then the data needs to be here if success is false then the error needs to be here so if we store the validation result in a variable and let's just copy and it doesn't matter whether it's going to be the valid one or the invalid one, and we want to take a look at the validation result, then we would see that success is a Boolean. So again, not sure whether it's true or false yet. And the error could be here or not, so it could be undefined. And the data could be here or not undefined because we don't know yet. However, if we walk through an if statement, just checking whether the validation result success is true, then we need to be in this very branch, so that we know that the validation result.success is true, obviously, but we also know that data cannot be undefined at this point.

[07:15] And what you can see over here that we're not doing the type guard over data we're doing the type guard over success so essentially internally there is a TypeScript object union type over the validation result of the safe parse In the same way if we did the opposite check and we would see that there is a validation result and we are sure that success needs to be false over here then error cannot be undefined because if it failed then there needs to be an error. This is also quite useful when using Zot in production applications.