typescript simplify a type guard

const validationResult = UserLoginSchema.safeParse(req.body);

if (
validationResult.error instanceof ZodError ||
validationResult.data === undefined
) {
res.status(400).json(validationResult.error);
return;
}

const { name, password } = validationResult.data;
const validationResult = UserLoginSchema.safeParse(req.body);

if (
validationResult.error instanceof ZodError ||
validationResult.data === undefined
) {
res.status(400).json(validationResult.error);
return;
}

const { name, password } = validationResult.data;
I have this code. I want the if statement to be able to verify that validationResullt.error is a ZodError and validation.data is not undefined but there doesn't seem a way to do them in one statement. should I use as to explicitly assert the type of validation.data? I don't want another if statement because it would just duplicate the logic inside first if
45 Replies
ἔρως
ἔρως3mo ago
that is one statement it has 1 expression inside
Ganesh
GaneshOP3mo ago
it will not work. validationResult.error doesn't have it's type narrowed it can be undefined like this and if i use && then validationResult.data can be undefined
ἔρως
ἔρως3mo ago
is "error" a real error?
Ganesh
GaneshOP3mo ago
what do you mean
ἔρως
ἔρως3mo ago
an instance of Error as if as exception was throen thrown
Ganesh
GaneshOP3mo ago
I think so. I am using safe parse so it isn't throwing an exception
ἔρως
ἔρως3mo ago
thrown exceptions only have 2 valid types: any or unknown
Ganesh
GaneshOP3mo ago
using parse will throw it
ἔρως
ἔρως3mo ago
i know why the code is tripping it is possible for .error to be any value and .data being empty that is why the type isnt narrowing
Ganesh
GaneshOP3mo ago
No description
ἔρως
ἔρως3mo ago
that is what i said
Ganesh
GaneshOP3mo ago
you said any so I thought you meant any type
ἔρως
ἔρως3mo ago
yeah, it is possible
Ganesh
GaneshOP3mo ago
hmmm
ἔρως
ἔρως3mo ago
if there's a bug in the code that does the validation, it is theorically possible to be any but assuming it can only be a zod error or undefined, the type cant be narrowed assuming only those 2 types, the code can run in these situations: - data is undefined - error is a zod error
Rägnar O'ock
Rägnar O'ock3mo ago
If you know the type of what you want to get inside the if you can create a type guard function. Let me find you the doc real quick
ἔρως
ἔρως3mo ago
if error is undefined and data is undefined, it will run and send undefined handle the data being undefined separatedly and the error being undefined separatedly
Rägnar O'ock
Rägnar O'ock3mo ago
Documentation - Narrowing
Understand how TypeScript uses JavaScript knowledge to reduce the amount of type syntax in your projects.
ἔρως
ἔρως3mo ago
the type is already narrowed as far as possible i would check if the error is undefined first, then check if data is undefined i mean, if it is a zod error
Ganesh
GaneshOP3mo ago
if (validationResult.error instanceof ZodError) {
res.status(400).json(validationResult.error);
return;
} else if (validationResult.data === undefined) {
res.status(400).json("something bad has happened");
return;
}
if (validationResult.error instanceof ZodError) {
res.status(400).json(validationResult.error);
return;
} else if (validationResult.data === undefined) {
res.status(400).json("something bad has happened");
return;
}
this duplicates the code hmmm
ἔρως
ἔρως3mo ago
no, the oposite wait, no, like that also, i dont see any repeated code
Ganesh
GaneshOP3mo ago
I'm gonna res.json(validationResult.error) in both cases because if the data is undefined then there is definitely a zod error
ἔρως
ἔρως3mo ago
no, there isnt the type is ZodError | undefined in case the error is undefined, you are sending undefined over json
Ganesh
GaneshOP3mo ago
yeah but the validation itself doesn't work that way. If the safeParse function finds that there is data that violates constraints then it will not set data (undefined) and set error to a zodError. And vice versa if the data doesn't violate constraints or are you saying I shouldn't assume such and still plan for it to happen?
ἔρως
ἔρως3mo ago
plan for it to happen, because the property's value doesn't show the type without an undefined imagine that, for some reason, the error is undefined and the data too, like in a case where it is optional
Ganesh
GaneshOP3mo ago
hmm the current way I have it is not possible but ok if types is asserting it then it's better to be prepared
ἔρως
ἔρως3mo ago
exactly also, what will you send in case the error and data are undefind?
Ganesh
GaneshOP3mo ago
No description
Ganesh
GaneshOP3mo ago
alright there we go
ἔρως
ἔρως3mo ago
now, you are guaranteed that data has something, and that the error is undefined i know it is meh to do it this way but it is so important to take types seriously, even when "impossible"
Ganesh
GaneshOP3mo ago
better to be safe than sorry later. I haven't dealt with types before this so it is a bit hard to wrap my head around
ἔρως
ἔρως3mo ago
it's fine
Ganesh
GaneshOP3mo ago
also did you mean in the case of data and error both being undefined here?
ἔρως
ἔρως3mo ago
yes but you already answered that
Ganesh
GaneshOP3mo ago
gotcha yeah both got covered
ἔρως
ἔρως3mo ago
exactly and when you check ahead, the only possible type for the error is undefined
Ganesh
GaneshOP3mo ago
yeah thanks again for the help. you too ragnar although I don't think I need a predicate for something this simple
ἔρως
ἔρως3mo ago
i know it is a bad looking solution, since you need 2 if statementd but you are welcome
Ganesh
GaneshOP3mo ago
there is more if statements later that check if username exists in database or passwordHash matches so don't think it did much worse maybe I will extract them in another function or middleware to organise it more
Rägnar O'ock
Rägnar O'ock3mo ago
ah, I misunderstood to question (back from a nap BTW) you can collapse your 2 conditions into a single one. zod.safeParse signature looks like this according to the doc :
interface zod {
safeParse(data:unknown):
| { success: true; data: T; }
| { success: false; error: ZodError; }
}
interface zod {
safeParse(data:unknown):
| { success: true; data: T; }
| { success: false; error: ZodError; }
}
That means that the object you get out of it always has a success property which can be used to collapse the union into either one of the member depending on if it's true or false. So you can use a guard close like this :
function loginUser(req: Request, res: Response): void {
const validationResult = UserLoginSchema.safeParse(req.body);

if (validationResult.success === false) {
// sad path
// set the error response stuff here
return; // finish the function execution
}

// happy path
// starting here data is always a User
console.log(validationResult.data)
// do you logic if data is valid here
}
function loginUser(req: Request, res: Response): void {
const validationResult = UserLoginSchema.safeParse(req.body);

if (validationResult.success === false) {
// sad path
// set the error response stuff here
return; // finish the function execution
}

// happy path
// starting here data is always a User
console.log(validationResult.data)
// do you logic if data is valid here
}
interactive example on the typescript playground
TS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
Rägnar O'ock
Rägnar O'ock3mo ago
No description
Ganesh
GaneshOP3mo ago
it works dammit it was easy I should've just read the docs thanks a lot also if I am reading that safe parse error right it is saying the method returns object with either success : true and data with T type or success: false and error with zodError type
Rägnar O'ock
Rägnar O'ock3mo ago
yeah T is your schema
Ganesh
GaneshOP3mo ago
gotcha thanks
ἔρως
ἔρως3mo ago
that is a much better answer than i could give

Did you find this page helpful?