You wrote a self-answer, put a bounty on your question, and included this comment:
If the array of excluded keys is defined in a variable/constant before being passed to exclude, it causes issues...
It seems like the problem you're facing is related to this. When you modify your code like this, an error occurs on the second argument of exclude
:
const toExclude = ["foo", "bar"];
const b = exclude(a, toExclude)
.map(key => {
if (key === 'bar') { }
if (key === 'foo') { }
if (key === 'baz') { }
if (key === 'yay') { }
});
The issue arises because when you declare an array like this:
const toExclude = ["foo", "bar"];
TypeScript infers the "best common type," which is the most common type among the elements. If you want a narrower type, you must be explicit. In your case, where the array should contain a subset of the keys from the first argument of exclude
, you need to specify the type:
const toExclude = ["foo", "bar"] as ["foo", "bar"];
To avoid repeating this type assertion for multiple arrays, you can use a helper function like:
function asKeysOf<T extends (keyof typeof a)[]>(...values: T) { return values; }
const toExclude = asKeysOf("foo", "bar");
Another method that works with any object type is:
function asKeysOf<O, T extends (keyof O)[]>(o: O, ...values: T) { return values; }
const toExclude = asKeysOf(a, "foo", "bar");
Be aware that TypeScript 3.4 introduces a feature where you can infer the narrowest type for literal values using as const
. For example:
const toExclude = ["foo", "bar"] as const;
In this case, ensure the exclude
function expects a readonly
array for excludes
:
function exclude<T, K extends readonly (keyof T)[]>(obj: T, excludes: K) {
Here's a revised example compatible with TypeScript 3.4:
function getAllKeys<T>(obj: T): (keyof T)[] {
return Object.keys(obj) as (keyof T)[];
}
function exclude<T, K extends readonly (keyof T)[]>(obj: T, excludes: K) {
return getAllKeys(obj)
.filter((key: keyof T): key is Exclude<keyof T, K[number]> =>
!excludes.includes(key));
}
const a = {
foo: 'abc',
bar: 'abc',
baz: 'abc',
yay: true
};
const toExclude = ["foo", "bar"] as const;
const b = exclude(a, toExclude)
.map(key => {
if (key === 'bar') { }
if (key === 'foo') { }
if (key === 'baz') { console.log("Q"); }
if (key === 'yay') {console.log("F"); }
});
This example works as expected with TypeScript 3.4.0-rc.