Apply Schema Custom Refinement with Zod

In this lesson, we dive into the Zod's refine utility, allowing defining more complex schema validation on top of existing schemas and utilities.

Sometimes, we need special validation rules for our applications to work correctly. The refine utility lets developers create their own validation rules. This method's uses a callback to check if the input is valid. We also create meaningful messages when something doesn’t meet the validation rules.

We use a practical example involving a room booking schemawith dueDate and price property refinement.

Share with a coworker

Transcript

[00:00] Sometimes using the built-in ZOD schemas could be not enough. For instance, making a string represent a date, making a number represent an integer value from a specific range of values, making a number positive, negative, etc. Could be not enough as our application might need to perform some more complex, more advanced, more domain specific data validation. And in this case, we're going to use the refine utility provided with Zod. So in our case, we've got a room booking schema example, which provides some basic room booking information such as room type, due date, number of guests and price.

[00:38] The two properties that are important for us in this case are the due date and price since we've got two objects which are going to differ in terms of the values under these properties. So the due date here is the 30th December, and here we've got the 31st December. Also, price changes, price differs, as here we've got an integer and here we've got a float value with some fraction part. Now both are correct due dates in terms of what we have in our schema right now and both prices also pass the validation since both are positive numbers. So all objects pass right here right now But we want to make the other object the second object fail validation Despite the fact that this is a proper date, and this is a proper positive number So let's start with the date one.

[01:35] So here we're going to remove the date utility and replace it with the refine. And refine accepts a callback, which is going to accept what is the value of the property that we want to refine and it's supposed to return a boolean value or any other value that is going to be casted to a boolean, determining whether the validation itself, the expression itself is valid or not. So we could use the date parse method, which is going to turn this date string representation into a number. If the date is properly formatted, then this is just going to be a number of milliseconds that passed since the Unix epoch. And if it's not correct, then it's going to return a NAN value.

[02:24] So all in all, this string representation is correct if it's not a NAN. So let's run this file against ts-node and we would see that there is empty output as both date strings are correct and there is no error to be reported. However, if we deliberately mess something up, then we would see that there would be some error output and the code is going to be custom as this is our custom validation and the message is simply very generic and abstract invalid input as we haven't provided any specific message, any specific error message. So we can just define the error message in the second parameter over here saying that the date has invalid format and if we now rerun this file then we would see that this error message is being used over here. Now let's make our example slightly more interesting as this function is not doing anything more interesting rather than the built in date schema.

[03:30] So let's provide an alternative implementation. Let's say that for some reason a room booking cannot happen on Wednesday and it turns out that the 31st of December of that year is Wednesday. So we're going to turn this string into a new date object. So let's run new date of date string and let's use that get day method, which is going to return three if it is Wednesday. So what we need to do is just to return that this should not be a Wednesday.

[04:08] And our message could be that the date shouldn't for some reason be Wednesday. So these are some corner cases that we can use the refine in order to provide some more specific data validation. Now we can see that both are correct date formats. However, some of them might just not fulfill some very custom criteria and to be honest our applications are full of custom criterias. Now let's also modify the price property.

[04:41] So Z number positive is yet again not enough in our case let's say so we're going to put a refine over here and the refine is going to accept a value that is either this 300 integer or this float value over here And let's run the number is integer function and we're also going to multiply the value itself by 100 saying that this is going to be just the number of cents, number of whatever your currency is. So this would be the amount. So we want to cut off all the fractions from the third digit, the fourth digits of the fraction and so on and so forth. So this would be correct, but this one is not correct anymore. And we just need to return this Boolean expression.

[05:39] So isInteger is returning a Boolean over here. And our custom message is going message is going to be message that is price must have at most two decimal places. And here we go, we can now run the validation and we would see that first date shouldn't be Wednesday, but also if we take a look at what is the other criteria, then we would see that the price must have at most two decimal places.