1

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.

1 Answer 1

1

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.
Sign up to request clarification or add additional context in comments.

2 Comments

Running through the test cases now but looks like this works, literally spent 4-5 hrs trying to type this thank you so much. i'll probably comment if one of my test cases fail soon though
You're the man, legit thank you so much everything compiled becides some Omit and Diff errors that came from styled-component.d.ts from swapping to 2.9

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.