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