Reputation: 147
I'm using typescript 3.8.3 and try to dynamically find the keys of a certain type and use them to produce an other object.
I have a Detail
object and want to use it to dynamically generate a Column
object only based on the properties of type Desc
.
Here is a simplified code of what i want to do :
// Model
interface Desc {
creationDate: Date;
}
interface Address extends Desc {
zipCode: number;
adressName: string;
}
interface Order extends Desc {
id: number;
orderName: string;
}
interface Detail {
name: string;
address: Address[];
orders: Order[];
}
// Table column configuration
interface Column<T> {
field: keyof T;
active: boolean;
}
type ColumnConfig<T> = { [P in keyof T]: Column<T[P] extends Desc ? P : never>[] };
const config: ColumnConfig<Detail> = {
address: [
{
field: "zipCode",
active: true
},
{
field: "addressName",
active: true
}
],
order: [
{
field: "id",
active:false
},
{
field: "orderName",
active: false
}
],
// Detail.name should not be available but it is
}
How can i benefit from the help of the compiler to construct my config
object and thus be able to detect any errors if my Detail
object changes ?
Thanks !
Upvotes: 8
Views: 8978
Reputation: 145950
I don't think this is an answer to the original question but...
A Typescript 4.1 feature may help others finding this question needing to filter out properties based on the key and not the value of the property. It's called 'key remapping'.
For instance you could remove properties with a certain name or prefix.
https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#key-remapping-via-as
Upvotes: 2
Reputation: 3501
In order to exclude some columns for a mapped type, we need to use an intermediate helper type, allowing us to select only the keys respecting our condition, T[K] extends Desc
:
type ConfigurableColumnsKeys<T extends object> = {
[K in keyof T]: T[K] extends Desc ? K : never
}[keyof T];
Given this, ColumnConfig
just need to map over this generic type instead of the original keyof T
:
type ColumnConfig<T extends object> = {
[P in ConfigurableColumnsKeys<T>]: Column<P>[]
};
Then your config object will be correctly validated:
// Error
name: [
{ field: 'abc', active: true }
]
If you need to allows arrays too (i.e. Address[]
instead of Address
), you can change the helper type, checking for T extends Desc[]
. Furthermore, you will need an UnboxArray
helper too, to extract item value Order[]
-> Order
for your column config:
type ConfigurableColumnsKeys<T extends object> = {
[K in keyof T]: T[K] extends Desc ? K : T[K] extends Desc[] ? K : never
}[keyof T];
type UnboxArray<T> = T extends Array<infer V> ? V : T;
type ColumnConfig<T extends object> = {
[P in ConfigurableColumnsKeys<T>]: Column<UnboxArray<T[P]>>[]
};
You can see it at work in the following playground: Playground Link
Upvotes: 22