0

If I have an array for this sake ['test', 'test2', 'test3'], I'm adding aliases to it using Object.defineProperty() that are defined in a different array ['a', 'b', 'c'].

Here is my current (shortened) example that does not allow me to access the defined property on the returned array.

How can I have typescript recognise that there are defined properties on this array that match the values of the array ['a', 'b', 'c']?

const aliasArr = ['a', 'b', 'c'];

const applyAliases = <Alias extends string[], Values extends any[]>(aliases: Alias) => (arr: Values) => {
    const definition = [...arr];
    aliases.forEach((key, i) =>
        Object.defineProperty(definition, key, {
            enumerable: false,
            get(): Values[number] {
                return this[i];
            },
        })
    );
    return definition as typeof arr & { [P in keyof Alias]: Values[number] };
};

const applied = applyAliases<typeof aliasArr, string[]>(aliasArr)(['test', 'test2', 'test3']);

const test = applied.a // Error, property 'a' does not exist on type string[]
1
  • Just noticed my own mistake in this, swapping [P in keyof Alias] to [key in Alias[number]] fixes this. However I feel that this is still not a good solution as applied.x does not throw an error. Commented Mar 14, 2020 at 13:34

1 Answer 1

1

You've already figured out that applying mapped type to array results in array, so
{ [P in keyof Alias]: Values[number] } should be { [P in Alias[number]]: Values[number] } or Record<Alias[number], Values[number]>.

The missing part is restricting Alias[number] to desired keys instead of just string[]. This can be achieved by defining aliasArr as readonly tuple:

const aliasArr = ['a', 'b', 'c'] as const; // aliasArr is of type readonly ["a", "b", "c"]

const applyAliases = <Alias extends ReadonlyArray<string>>(aliases: Alias) => <Values extends any[]>(arr: Values) => {
    const definition = [...arr];
    aliases.forEach((key, i) =>
        Object.defineProperty(definition, key, {
            enumerable: false,
            get(): Values[number] {
                return this[i];
            },
        })
    );
    return definition as Values & Record<Alias[number], Values[number]>;
};

const applied = applyAliases(aliasArr)(['test', 'test2', 'test3']);

const test = applied.a // now OK

applied.x // Property 'x' does not exist on type 'string[] & { a: string; b: string; c: string; }'

Playground


** Second generic parameter moved to inner function, so typescript will be able to infer it properly

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you this is exactly what I was hoping to do.

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.