Issue
Not Working Type Guard
I'm trying to write a generic type guard for different generic Token
types but it's not working. My current approach looks like:
export function isToken<T extends Token>(token: any): token is T {
for (const key in keyof T) { // (1) for every key in the given token type T
if ( // check if one of the two criteria are not
!(key in token) || // the key is not in the token or
typeof token[key] !== typeof T[key] // (2) the type for that key-value-pair is not the
// same as in the given token type T
) {
return false;
}
}
return true;
}
But TSC complains that:
- in line
(1)
:Cannot find name 'keyof'.ts(2304)
- so thekeyof
keyword does not work - in line
(2)
:'T' only refers to a type, but is being used as a value here.ts(2693)
How can I fix my type guard?
The Tokens
The tokens are simply types to describe different JWT payloads:
const type Token = {
userId: string;
}
const type LoginToken extends Token = {
role: 'User' | 'Admin'
}
const type EmailVerificationToken extends Token = {
email: string;
}
Current Type Guard
I was able to write a generic token verification function but I still have an extra type guard for token type:
// for each token type I have a function like below
// this is what I'm trying to handle with a single generic type guard
export function isVerificationToken(token: any): token is VerificationToken {
return typeof token.userId === 'string' && typeof token.email === 'string';
}
export function verifyVerificationToken(token: string) {
return verifyToken<VerificationToken>(token, isVerificationToken);
}
function verifyToken<T>(token: string, tokenTypeCheck: (token: any) => token is T) {
const decoded = jwt.verify(token, config.jwtSecret);
if (tokenTypeCheck(decoded)) {
return decoded;
}
return null;
}
Solution
The problem here is that Typescript doesn't exist anymore at runtime. It is transpiled into JavaScript and therefore you cannot do runtime type evaluations (like you might do in C# with Reflection or similar), since JavaScript doesn't have any types. So passing T
as a generic parameter to a function and then using it in a non-static way (to dynamically compare property types, etc.) isn't possible. To make this work you'd have to provide an object of that type to the function and then use it to evaluate member types.
The other problem is that you're using keyof
to get a list of all keys in an object, although that's not what the keyof
keyword does. see: TS docs. As we've established: there are no types at runtime, so keyof
will only work in a static context.
A working solution to your problem (although obviously not as elegant as you might expect it to be from looking at concepts present in other languages like c#, etc.) would be to pass an object of the type T
to your function, and instead of using keyof
you would use Object.keys
to get all the keys of the object.
export function isToken<T extends Token>(expected: T, token: any): token is T {
for (const key in Object.keys(expected)) {
if (!(key in token) || typeof token[key] !== typeof (expected as any)[key]) {
return false;
}
}
return true;
}
// Or a bit shorter:
export function isToken<T extends Token>(expected: T, token: any): token is T {
return Object
.keys(expected)
.every((key) => key in token && typeof token[key] === typeof expected[key]);
}
Answered By - MrCodingB
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.