Properly typed omit function in TypeScript
Writing a properly typed omit function requires a few type casts. I think this
is the cleanest possible way (I’d be thrilled to learn of a cleaner way), and
it doesn’t include an allocation for an Object.keys(obj)
array.
function omit<T, K extends keyof T>(obj: T, ...keys: K[]): Omit<T, K> {
const ret = {} as Omit<T, K>
const omitKeys = new Set(keys)
for (const k in obj) {
const key = k as unknown as K
if (omitKeys.has(key)) {
continue
}
const presentKey = key as unknown as Exclude<keyof T, K>
ret[presentKey] = obj[presentKey]
}
return ret
}
Let’s look at the necessity and the safety of each type cast.
const ret = {} as Omit<T, K>
This is necessary at some point before returning. Typing it early (instead of
casting later in the return statement) also assists in typing at other
portions of the function. This is safe because we take care to ensure this is
true in the implementation before we return it (oh, well).
const key = k as unknown as K
This is necessary because k
has type string
. However, omitKeys
is a
Set<K>
, so a type K
is required to check membership. This is safe because
the type K
represents keys of T
, of the obj
var—as noted in the generic
type parameters of the function.
const presentKey = key as unknown as Exclude<keyof T, K>
This is necessary because key
(type K
) cannot be used directly to index
ret
. The compiler isn’t able to narrow the type on its own from the set
membership check. After we’ve checked membership, key
represents a key in
T
that is not one of the input keys: K[]
. This is represented by
Exclude<keyof T, K>
, and hence is safe.
And that’s the cleanest omit
implementation I’ve been able to write so far.
Related: A stricter Omit type