constconcatToField=<TextendsRecord<string,any>,KextendskeyofT&T[K]extendsstring?string:never>(obj:T,key:K,payload:string):T=>{constprop=obj[key];// compile error should not be herereturn{...obj,[key]:prop.concat(payload)};// compile error should not be here}// testsconsttest={fieldStr:'text',fieldNum:1,fieldStr2:'text'};concatToField(test,'fieldStr','test');// should be ok 👌concatToField(test,'fieldNum','test');// should be error fieldNum is not string field 🛑concatToField(test,'notExistingField','test');// should be error - no such field 🛑concatToField(test,'fieldStr2','test');// should be ok 👌
Explanation:
T extends Record<string, any>
Pretty straight forward. The test object is a map of string keys to any values, though of course, if those values are strings, then we want to be able to apply the concatToField method to it.
K extends keyof T & T[K] extends string ? string : never
I'll break this into two parts
K extends keyof T
By itself, this is just saying 'the value of K is going to be one of the keys of that T object'.
& T[K] extends string ? string : never
We're saying that 'is the value at T[K] of type string? If so, then the type of K is string, if not, then the type of K is never'.
The never type allows us to return compile errors when something is illogical/not allowed - in this case it is not allowed to to have a T[K] value that is not of type string.
Note:
It would be nice if we could just do
K extends keyof T & T[K] extends string
instead of using that ternary - but this apparently is not valid typescript.
I am Software Developer, currently interested in static type languages (TypeScript, Elm, ReScript) mostly in the frontend land, but working actively in Python also. I am available for mentoring.
Hi David thank you for the answer.
But your code doesn't make errors when it should, so there are no compile time guarantees. I put in the snippet two places where the error should occur, you see there 🛑 icon. Even more the code you have provided compiles for any second argument like concatToField(test, 'anything', 'test');.
constconcatToField=<TextendsRecord<string,any>,KextendskeyofT&(T[K]extendsstring?string:never)>(obj:T,key:K,payload:string):T=>{constprop=obj[key];// compile error should not be herereturn{...obj,[key]:prop.concat(payload)};// compile error should not be here}
Cool challenge.
Explanation:
Pretty straight forward. The test object is a map of
string
keys to any values, though of course, if those values are strings, then we want to be able to apply theconcatToField
method to it.I'll break this into two parts
By itself, this is just saying 'the value of K is going to be one of the keys of that T object'.
We're saying that 'is the value at
T[K]
of type string? If so, then the type of K is string, if not, then the type of K isnever
'.The
never
type allows us to return compile errors when something is illogical/not allowed - in this case it is not allowed to to have aT[K]
value that is not of type string.Note:
It would be nice if we could just do
instead of using that ternary - but this apparently is not valid typescript.
Hi David thank you for the answer.
But your code doesn't make errors when it should, so there are no compile time guarantees. I put in the snippet two places where the error should occur, you see there 🛑 icon. Even more the code you have provided compiles for any second argument like
concatToField(test, 'anything', 'test');
.So try again! And good luck.
Ah, was just missing some brackets. :/
Playground