type User = { name: string; verified: boolean; email?: string; lastName: string; birthday?: string | { year: string; month: string; date: string; }}
type Birthday = Required<Pick<User, 'birthday'>>;
type UserWithBirthday = User & { birthday: Birthday }
type VerifiedUser = User & { verified: true; email: string; }
type VerifiedUserWithBirthday = User & UserWithBirthday & VerifiedUser;
const userHasBDayAndEmail = (user: User): user is VerifiedUserWithBirthday => {
if (user.email === undefined || user.birthday === undefined) {
return false
}
return true
}
Any caller of userHasBDayAndEmail knows for the rest of its nested call stack if the provided user is a User object or a VerifiedUserWithBirthday.The types are cheap to write (they're all derived) and have no runtime impact (types are erased at build/compile time) and these parsing functions are quite small to write
https://www.typescriptlang.org/play/?#code/FAFwngDgpgBAqgZyg...