You can always bend the type system of your own free will, or using tricks (for example, index access, or the compiler, assuming that R[key] is read and write)
function getUpdateData<R extends BaseRecord>(record: R, key: keyof R, newItem: string) { var updateData: Partial<R> = {}; updateData[key] = [...record[key], newItem]; return updateData }
or brute force (go through type any ):
function getUpdateData<R extends BaseRecord>(record: R, key: keyof R, newItem: string) { const updateData: Partial<R> = <any> { [key]: [...record[key], newItem] } return updateData }
The above answer to your question, but be careful: this function is not safe. It is assumed that any passed record will have a string[] value for the key property, but the type R may not match. For instance:
interface EvilRecord extends BaseRecord { e: number; } var evil: EvilRecord = { a: ['hey', 'you'], e: 42 }; getUpdateData(evil, 'e', 'kaboom');
In addition, the return type of Partial<R> little wide: you know that it will have a key key , but you will need to check it so that the type system is happy:
var updatedData = getUpdateData<DerivedRecord>(record, "c", "first item in c") // Partial<DerivedRecord> updatedData.c[0] // warning, object is possibly undefined
I would suggest entering getUpdateData() as follows:
type KeyedRecord<K extends string> = { readonly [P in K]: ReadonlyArray<string> }; function getUpdateData<K extends string, R extends KeyedRecord<K>=KeyedRecord<K>>(record: R, key: K, newItem: string) { return <KeyedRecord<K>> <any> {[key as string]: [...record[key], newItem]}; }
(note that it is still hard to type correctly due to an error in TypeScript ). Now the function will only accept something where key is of type ReadonlyArray<string> and ensures that the key property is present in the return value:
var evil: EvilRecord = { a: ['hey', 'you'], e: 42 }; getUpdateData(evil, 'e', 'kaboom'); // error, number is not a string array var updatedData = getUpdateData(record, "c", "first item in c") // KeyedRecord<"c"> updatedData.c[0] // no error
Hope this helps.
Technical update
I changed the proposed declaration of getUpdateData() above to have two common parameters, because for some reason TypeScript outputted too wide a type for the key parameter before, forcing you to specify the key type when calling the site:
declare function oldGetUpdateData<K extends string>(record: KeyedRecord<K>, key: K, newItem: string): KeyedRecord<K>; oldGetUpdateData(record, "c", "first item in c"); // K inferred as 'a'|'b'|'c', despite the value of 'c' oldGetUpdateData<'c'>(record, "c", "first item in c"); // okay now
By adding a second generic file, I apparently delayed TypeScript output of the record type after the key was entered correctly:
getUpdateData(record, "c", "hello"); // K inferred as 'c' now
Feel free to ignore this, but so sausages are produced using the heuristic type.