const isTrue = <T>(arg: T): { arg: T; is: boolean } => {
if (Array.isArray(arg) && !arg.length) {
return { arg, is: false };
}
if (isObject(arg) && !Object.keys(arg as keyof T).length) {
return { arg, is: false };
}
return { arg, is: !!arg };
};
In this code, why do we use arg as keyof T? We checked it is an object with the statement 'isObject(arg)' then it should be an object. But as keyof T means that arg's type will be the keys of the object. So how can arg still act like an object despite we used 'as keyof T'?
I added isObject function here:
const isObject = <T>(par: T): boolean => {
return typeof par === "object" && !Array.isArray(par) && par !== null;
};
I don't know which tutorial that code came from, but you are right to be confused; given
argof typeT, it is almost certainly meaningless to later assert thatargis of typekeyof T, especially if you've already checked thatargis anobjectand not astringorsymbol. It's a fun exercise to try to devise situations in whichT & keyof Tis non-empty, like the literal type"length". But this is not the case here, and even if it were, it's a bizarre thing to do.Again, the assertion
arg as keyof Tisn't true.If you don't assert then the compiler complains because
argisn't known to be something thatObject.keys()accepts. If you look at the most permissive call signature forObject.keys(), it isThe parameter is
{}, the so-called "empty object type", and while it usually refers to objects, it also is applicable to even non-object values that can be indexed into (e.g.,stringis allowed because you can index into them with keys like"toUpperCase"and"length"). In other words, the only values that are not assignable to{}arenullandundefined. (See How to undestand relations between types any, unknown, {} and between them and other types?)Since the
isObject()function you presented does not narrow its input (it returnsbooleanas opposed to a type predicate; another indication that the tutorial might be problematic), then the compiler has no idea thatargwill be an object. And thus if you callObject.keys(arg)directly, the compiler will complain becauseargis of typeTwhich might allownullorundefined:The mistaken assertion
arg as keyof Tsuppresses that error becausekeyof Tis some subtype ofstring | number | symbol, each of which is non-nullish.The only thing
as keyof Tdoes for you is that it narrowsargto something known to be non-nullish. So it suppresses the error, but in a very strange way. It would work just as well to sayObject.keys(arg as Date)orObject.keys(arg as Math)orObject.keys(arg as "whoopsieDoodle"). And then you'd be asking similar questions about whyMathis relevant. It isn't.A reasonable thing to do here would be to assert that
argis theobjecttype, which is presumably whatisObject()is meant to do:Even more reasonable would be if
isObject()were a type guarding function as presumably is intended:But this is essentially a digression from the question as asked.
To reiterate:
Q: Why do we use
arg as keyof T?A: Most likely because we are confused and used the first thing we found that suppressed a compiler error. We should really have used
arg as objector some other approach entirely. We should probably edit our tutorial, now that we think about it.Playground link to code