Passing a object's property by name for reading can be done in a variety of ways. However, none of them seems to allow updating the property without explicit type conversions.
The function should be able to get and set the value of the property type-safe. The original use case was to edit the value; for simplicity, let's assume we just want to append a space.
All of these behave the same (get OK, set ERR):
{ [k in K]: string }
andkeyof T
function appendSpace1< T extends { [k in K]: string }, K extends keyof T >(obj: T, path: K) { let x = obj[path]; // `x` is `T[K]` let y = x + " "; // `y` is `string` obj[path] = y; // Type 'string' is not assignable to type 'T[K]'.(2322) return x; }
Record<K, string>
andstring
function appendSpace2< T extends Record<K, string>, K extends string >(obj: T, path: K) { let x = obj[path]; // `x` is `T[K]` let y = x + " "; // `y` is `string` obj[path] = y; // Type 'string' is not assignable to type 'T[K]'.(2322) return x; }
Record<K, string
andKeysWithValsOfType<T, V>
type KeysWithValsOfType<T, V> = keyof { [P in keyof T as T[P] extends V ? P : never]: P; }; function appendSpace3< T extends { [k in K]: string }, K extends KeysWithValsOfType<T, string> >(obj: T, path: K) { let x = obj[path]; // `x` is `T[K]` let y = x + " "; // `y` is `string` obj[path] = y; // Type 'string' is not assignable to type 'T[K]'.(2322) return x; }
These work (get and set OK), but…
Using a class
This works when using a class, but it fails when transforming the code into type with generics:
type MyClass = { a: number; b: number ; c: string ; } function increment<T>(obj: T, key: { [K in keyof T]-?: number extends T[K] ? K : never }[keyof T] ) { obj[key]++; // fail } const test: MyClass = {a: 0, b: 1, c: "two"}; increment(test, "a"); increment(test, "b"); increment(test, "c"); // fail increment(test, "d"); // fail
Constant name
function appendSpace4< T extends { x: string }, K extends "x" >(obj: T, path: K) { let x = obj[path]; // `x` is `T[K]` let y = x + " "; // `y` is `string` obj[path] = y; // OK! return x; }
… but it is not parameterizable. As soon as one of the
x
es is changed (first to[k in K]
or second tokeyof T
),obj[path] = y
turns back into ts2322.
I seem to be able to circle the solution very closely, but just not getting to it. Why is the set operation so different?