I would like to define an interface (or other type?) that describes another object (whose type should be provided as a generic parameter).
Basically, it should look something like this:
interface IDescriptor<T extends object> {
propSet1?: (keyof T)[];
propSet2?: (keyof T)[];
}
So far, so good - now I can only list properties that are actually found in whichever type is provided for type parameter T
in propSet1
and propSet2
.
As a simple example, imagine a type Person
:
class Person {
firstName?: string;
lastName?: string;
age?: number;
}
Now, in type IDescriptor<Person>
, propSet1
and propSet2
can only contain either of the values 'firstName'
, 'lastName'
, and 'age'
.
However, I now require IDescriptor
to optionally contain some additional information on each of the fields from T
.
Concretely, I would like to specify the following object literal:
let desc: IDescriptor<Person> = {
propSet1: ['firstName', 'lastName'],
propSet2: ['lastName', 'age'],
propInfo: {
lastName: {
suggestionList: 'surnames'
},
age: {
maxValue: 120
}
}
};
In the above example, the possible options are defined in the following interface:
interface IPropOptions {
suggestionList?: string;
maxValue?: number;
}
Now, how do I declare the propInfo
property?
The two approaches I have thought of fail for different reasons:
I have tried uing the in
operator:
propInfo?: {
[key in T]: IPropOptions;
};
This fails with the message
TS2322: Type 'T' is not assignable to type 'string | number | symbol'.
Type 'object' is not assignable to type 'string | number | symbol'.
I have tried using a keyof
expression:
propInfo?: {
[key: keyof T]: IPropOptions;
};
Unfortunately, this fails with the message
TS1337: An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.
Then, I have also tried using a map, as suggested:
propInfo?: Map<keyof T, IPropOptions>
This in itself appears to be valid, but here, I don't know how to specify the value as an object literal.
Is there any way to solve this as desired? I am especially unsure as there appear to be subtle differences in what is or is not allowed depending on e.g. whether I declare something as an interface or as a "type".