Reputation: 6531
I have the following class.....
export class Person<T, C = T> {
lens: any = null;
private value: T;
constructor(value: T, newLens?: any) {
this.value = value;
this.lens = newLens;
}
at<K extends keyof C>(path: keyof C): Person<T, C[K]> {
if(!this.lens) {
return new Person<T, C[K]>(this.value, Lens.lens(path));
}
return new Person<T, C[K]>(this.value, Lens.compose(this.lens)(Lens.lens(path)));
}
get(): any {
if(!this.lens) return this.value;
return Lens.get(this.lens)(this.value);
}
set(f: (newValue: any) => any): T {
return Lens.set(this.lens)(f(this.get()))(this.value);
}
}
my problem is that when i try and use my new Person class on an object i get the incorrect behaviour.....
const TestPerson = {
name: {
name: "steve"
},
siblings: [{name: "shanon"}]
age: Infinity
}
const test = new Person(TestPerson).at("name").at("name") // works....
const test2 = new Person(TestPerson).at("siblings").at(0) // fails
const test3 = new Person(TestPerson).at(siblings").at("0") // still fails.
const test4 = new Person(TestPerson).at("nonexistantproperty") //correctly fails.
my problem is that i need a "AT" function that can handle keyof objects and keyof arrays, but it seems like no matter how i rework it i can't achieve this.
to me this seems like a massive flaw with typescript, both arrays and objects are just objects under the hood so keyof on an object type and keyof on a array type should work the same way.
Upvotes: 1
Views: 1624
Reputation: 249676
The simplest solution is to wait until typescript 2.9 (in RC at the time of writing, should be released soon). Prior to 2.9 keyof
only returned string indexes, in 2.9 this will include numeric and symbol keys. See the PR. In 2.9 your code will work as expected without any change.
In 2.8 you can use conditional types to achieve a similar effect
type CReturn<C, K extends keyof C> = C extends Array<any> ? C[number] : C[K];
export class Person<T, C = T> {
lens: any = null;
private value: T;
constructor(value: T, newLens?: any) {
this.value = value;
this.lens = newLens;
}
at<K extends keyof C>(path: C extends Array<any> ? number : K): Person<T, CReturn<C, K>> {
if(!this.lens) {
return new Person<T, CReturn<C, K>>(this.value, Lens.lens(path));
}
return new Person<T, CReturn<C, K>>(this.value, Lens.compose(this.lens)(Lens.lens(path)));
}
}
const TestPerson = {
name: {
name: "steve"
},
siblings: [{ name: "shanon" }],
age: Infinity
}
const test = new Person(TestPerson).at("name").at("name") // works....
const test2 = new Person(TestPerson).at("siblings").at(0) // ok
const test21 = new Person(TestPerson).at("siblings").at(0).at("name") //ok
const test4 = new Person(TestPerson).at("nonexistantproperty") //correctly fails.
Upvotes: 1