I have the following interfaces and object set up:
interface ServicesList {
[key: string]: Service;
}
interface Service {
uuid: string;
characteristics: CharacteristictList;
}
interface CharacteristictList {
[key: string]: string;
}
const ServiceAndCharacteristicMap: ServicesList = {
firstService: {
uuid: '0x100',
characteristics: {
characteristicOne: '0x0101',
},
},
secondService: {
uuid: '0x200',
characteristics: {
secondCharacteristic: '0x0201'
}
}
};
Then, I have the following function defined:
function sendCharacteristic<T extends Service, K extends keyof T['characteristics']>(props: {
service: T;
characteristic: K;
}) {
console.log(props.service.uuid,
props.service.characteristics[props.characteristic])
}
Currently, TypeScript complains about this with the following compile-time error:
Type 'K' cannot be used to index type 'CharacteristictList'
My goal is to constrain the second parameter (characteristic
) so that I have type safety for which keys I use. For example, the following should succeed:
//should succeed
sendCharacteristic({
service: ServiceAndCharacteristicMap.firstService,
characteristic: 'characteristicOne'
});
But, this should fail, since characteristicOne
belongs to firstService
and I'm passing secondService
for the first parameter:
//should fail since characteristicOne belongs to firstService
sendCharacteristic({
service: ServiceAndCharacteristicMap.secondService,
characteristic: 'characteristicOne'
});
Right now, neither of the two call sites for sendCharacteristic
complain about compilation errors.
How can I correctly constrain the characteristic
parameter so that I get type safety for the particular instance of Service
that I'm passing in?
TypeScript playground with all included code
CodePudding user response:
The main problem with the code is that you are nor preserving the type of the constant you are assigning to ServiceAndCharacteristicMap
If you completely remove the annotation, the code works as expected. Playground Link
You could also use a function to constrain ServiceAndCharacteristicMap
to be ServicesList
while preserving the type
function makeServicesList<T extends ServicesList>(o: T){
return o;
}
const ServiceAndCharacteristicMap = makeServicesList({
firstService: {
uuid: '0x100',
characteristics: {
characteristicOne: '0x0101',
},
},
secondService: {
uuid: '0x200',
characteristics: {
secondCharacteristic: '0x0201'
}
}
});