Home > Back-end >  why can I not use my generic in Record type
why can I not use my generic in Record type

Time:12-15

I'm currently trying to use a generic (that's based on an array of string) to create an object which has known keys.

I am getting the following error message:

Type '{}' is not assignable to type 'Record<T[number], string>'.

What I am trying to do is something like:

const a = ["find", "delete", open"];
const n = new NamedItems<typeof a>(a)
// n.named_items will show and intellisense
{
    "find" : 0,
    "delete" : 1,
    "open" : 2
}

with the following code:

class NamedItems<T extends string[]> {
    constructor (private order_of_named_items : T) {
    }

    GET_ITEMS (inNumber : number) {
        return inNumber.toString();
    }

    get named_items () {
        const { GET_ITEMS } = this;
        const i = this.order_of_named_items;
        const obj : Record<T[number], string> = {};
        for (const index in this.order_of_named_items) {
            const name = this.order_of_named_items[index];
            Object.defineProperty(obj, name, {
                get : () => {
                    return GET_ITEMS( index);
                },
            });
        }
        return obj;
    }
}

CodePudding user response:

Well, unless T happens to be an empty array, then the value {} is definitely not going to be assignable to type Record<T[number], string>. So the compiler is warning you about that.

It looks like your intent is to initialize obj as an empty object, and then gradually populate that object so that when you're done it truly will be of type Record<T[number], string>. Unfortunately the compiler isn't smart enough to follow how the type of obj would evolve during your loop, so you can't do this initialization in a purely type-safe way. If you want to pretend that obj is of type Record<T[number], string> even at the beginning when it's just an empty object, you can use a type assertion to tell it so:

const obj = {} as Record<T[number], string>; // okay

This will make the error go away.


Note that your desired usage

const a = ["find", "delete", "open"];
const n = new NamedItems(a);

will not work how you want since the compiler will infer that a is of type string[], and therefore n.named_items will be of type Record<string, string>. If you want T to be an array of string literal types, you will need to change how the compiler infers the type of a. The easiest way is with a const assertion:

const a = ["find", "delete", "open"] as const;
// const a: readonly ["find", "delete", "open"]

If you want to pass that into new NamedItems then you should make T allow readonly arrays (which will also allow regular arrays):

class NamedItems<T extends readonly string[]> { /* ... */ }

And now everything will work as you expect:

const n = new NamedItems(a)
console.log(n.named_items.find) // "0"
console.log(n.named_items["delete"]) // "1"
console.log(n.named_items.open)  // "2"

Playground link to code

  • Related