Search code examples
typescripttypestypescript-genericstypescript-types

Default type of keyof T cannot be used to index type T


I am trying to refactor this code to use static method because the class is only used for namespace and Query.equal<TodoModel>(...) is better than (new Query<TodoModel>).equal(...)

interface TodoModel {
    title: string;
    timeLeft: number;
    done: boolean;
}

class Query<T> {
    equal<K extends keyof T>(attr: K, val: T[K]) {
        return `${String(attr)}==${val}`;
    }
    // other methods for notEqual, lessThan, greaterThan, contains etc.
}

let q = (new Query<TodoModel>)
q.equal("unknownKey", 45) // unknownKey gives error

q.equal("title", 765)     // 765 is the wrong type
q.equal("title", "write notes")    // no errors

I tried:

class Query2 {
    static equal<T, K = keyof T>(attr: K, val: T[K]) {
        return `${String(attr)}==${val}`;
    }
}

This gives me an error saying K cannot be used to index T. Why would that be and is there a solution for this?

I use default type because with extends (equal<T, K extends keyof T>) the usage would be Query2.equal<TodoModel, "title">("title", "write notes") where the key needs to be repeated.

Here's the full typescript playground: Link to playground


Solution

  • If you want only to mention the model and not the type, the possible solution is to have a union of all possible key-value pairs, which can be achieved with the following type:

    type UnionArgs<T> = {
      [K in keyof T]: [K, T[K]];
    }[keyof T];
    

    Usage:

    
    // ["title", string] | ["timeLeft", number] | ["done", boolean]
    type Result = UnionArgs<TodoModel>
    

    The only thing left is to update the method/function as follows:

    function queryEqual<T>(...args: UnionArgs<T>) {
      return `${String(args[0])}==${args[1]}`;
    }
    

    playground