I try to create function working like "in" operator and has key suggestion in TypeScript and I don't know how to type predicate at return type Pick<T, K>
is warning but code is working just fine.
const users = {
"Alice": {
"id": 1,
"name": "Alice",
"age": 30,
"isActive": true
},
"Bob": {
"id": 2,
"name": "Bob",
"birthYear": 1985,
"hasSubscription": false
},
"Charlie": {
"id": 3,
"name": "Charlie",
"isAdmin": false,
"age": 34
},
"Diana": {
"id": 4,
"name": "Diana",
"age": 28,
"isVerified": true
},
"Eve": {
"id": 5,
"name": "Eve",
"dob": "1992-05-15",
"isEmployee": true
}
}
type AllKeys<T> = T extends unknown ? keyof T : never
/** Pick<T, K> is warning*/
//@ts-ignore
function hasKey<T extends object, K extends AllKeys<T>>(obj: T, key: K): obj is Pick<T, K> {
return key in obj
}
const userKey = "Alice" as keyof typeof users
const user = users[userKey]
if ("hasSubscription" in user) {
user.hasSubscription
}
if (hasKey(user, "isVerified")) {
user.isVerified
}
/** Pick<T, K> is warning
fixing or better solution
*/
//@ts-ignore
function hasKey<T extends object, K extends AllKeys<T>>(obj: T, key: K): obj is Pick<T, K> {
return key in obj
}
You want to use the Extract<T, U>
utility type, not the Pick<T, K>
utility type:
function hasKey<T extends object, K extends AllKeys<T>>(
obj: T, key: K
): obj is Extract<T, Record<K, any>> {
return key in obj
}
The Pick<T, K>
utility type is meant to act on a single object type T
and results in a widened version which only has the K
properties. So Pick<{a: string, b: number}, "a">
is {a: string}
. That's not at all what you're looking to do.
The Extract<T, U>
type is meant to filter union type T
to just those members assignable to U
. So Extract<{a: string} | {b: number}, {a: any}>
is {a: string}
. That's a lot closer to what you're looking for.
You want to filter unions in T
to just those members which have a property at key K
. That can be performed by comparing each member of T
to Record<K, any>
, using the Record<K, V>
utility type.
You can verify that it works as desired:
if ("hasSubscription" in user) {
user.hasSubscription // okay
}
if (hasKey(user, "isVerified")) {
user.isVerified // okay
}