I’m trying to create a mapped type that filters out object keys based on the value of a boolean property in a class. I have the following code:
class Test {
readonly attribute: boolean;
constructor(attribute?: boolean) {
this.attribute = attribute ?? true;
}
}
type TestType2<T extends Record<string, Test>> = {
[K in keyof T as T[K]['attribute'] extends true ? never : K]: number;
};
const a = {
test1: new Test(),
test2: new Test(false),
};
type FinalTest = TestType2<typeof a>;
I expected FinalTest to be { test2: number } since test1 is constructed with the default attribute (i.e., true), and only test2 (with attribute equal to false) should remain. However, the resulting type is { test1: number, test2: number }.
Why does the conditional type T[K]['attribute'] extends true ? never : K not filter out test1 as expected? Is it an issue with how the class property is typed, or is there another approach to achieve the desired filtering? Any insights would be appreciated!
Thank you!
I’m trying to create a mapped type that filters out object keys based on the value of a boolean property in a class. I have the following code:
class Test {
readonly attribute: boolean;
constructor(attribute?: boolean) {
this.attribute = attribute ?? true;
}
}
type TestType2<T extends Record<string, Test>> = {
[K in keyof T as T[K]['attribute'] extends true ? never : K]: number;
};
const a = {
test1: new Test(),
test2: new Test(false),
};
type FinalTest = TestType2<typeof a>;
I expected FinalTest to be { test2: number } since test1 is constructed with the default attribute (i.e., true), and only test2 (with attribute equal to false) should remain. However, the resulting type is { test1: number, test2: number }.
Why does the conditional type T[K]['attribute'] extends true ? never : K not filter out test1 as expected? Is it an issue with how the class property is typed, or is there another approach to achieve the desired filtering? Any insights would be appreciated!
Thank you!
Share Improve this question asked Feb 18 at 1:26 machadinhosmachadinhos 133 bronze badges1 Answer
Reset to default 0The reason test1
is not filtered out is that the type of attribute
is evaluated at the time of type definition:
class Test {
readonly attribute: boolean;
// ...
}
At that time, attribute
is simply of type boolean
, which means TypeScript cannot determine whether it is true
or false
based on the constructor's input. Which means, no keys will be filtered out because boolean
cannot extend true
or false
.
To achieve the desired filtering, you can use generic types like this:
class Test<Attr extends boolean = true> {
readonly attribute: Attr;
constructor(attribute: Attr = true as any) {
this.attribute = attribute;
}
}
type TestType2<T extends Record<string, Test<boolean>>> = {
[K in keyof T as T[K]['attribute'] extends true ? never : K]: number;
};
const a = {
test1: new Test(),
test2: new Test(false),
};
type FinalTest = TestType2<typeof a>; // { test2: number }
TS Playground for the solution
Now, the type of attribute
is evaluated when you instantiate the Test
class. If you don't pass any argument to the constructor, the type will default to true
; otherwise, it will be inferred from the provided attribute
. This allows TypeScript to correctly filter out keys based on the actual value of attribute
.
Just make sure that the Attr
default type matches the attribute
default value.