Hayden Braxton
Hayden Braxton

Reputation: 1161

Typescript: Place constraint on specific lookup type of generic object type

Alright so here's the problem. I have the following type and function definitions:

export type compareFunction<T> = (t1: T, t2: T) => boolean

function createCompareFunctionCreator<P>(customCompare: compareFunction<P>) {
    return <
        T,
        K extends keyof T
    >(propName: K) => {
        return (t1: T, t2: T) => customCompare(t1[propName], t2[propName]) as compareFunction<P>
    }
}

My question is, how would place a particular constraint such that T[propName] is of type P?

I've tried the following:

function createCompareFunctionCreator<P>(customCompare: compareFunction<P>) {
    return <
        T extends { [keys in keyof T]: P },
        K extends keyof T
    >(propName: K) => {
        return (t1: T, t2: T) => customCompare(t1[propName], t2[propName]) as compareFunction<P>
    }
}

But this forces ALL properties in T to map to type P.

Upvotes: 1

Views: 301

Answers (1)

Maciej Sikora
Maciej Sikora

Reputation: 20142

Force T to have all properties of T

The problem is lack of relation between P and T. We can solve that but setting the relation. Consider following code:

export type compareFunction<T> = (t1: T, t2: T) => boolean

function createCompareFunctionCreator<P>(customCompare: compareFunction<P>) {
    return <
        T extends Record<K, P>, // pay attention here
        K extends keyof T
    >(propName: K) => {
        return (t1: T, t2: T) => customCompare(t1[propName], t2[propName])
    }
}

T extends Record<K, P> is saying that our type T is an object which all properties are type of P. Thanks to that we can do t1[propName] and we know that its type of P.

Force T to have properties of P with other properties

We can achieve that by some additional typing consider:

// utility type which gives us only keys which values in T1 are T2
type OnlyKeysOfT<T1, T2> = {
    [K in keyof T1]: T1[K] extends T2 ? K : never
}[keyof T1]

function createCompareFunctionCreator<P>(customCompare: compareFunction<P>) {
    return <
        T extends Record<K, P>,
        K extends OnlyKeysOfT<T, P> = OnlyKeysOfT<T, P>,
    >(propName: K) => {
        return (t1: T, t2: T) => customCompare(t1[propName], t2[propName])
    }
}
// below only a is correct
createCompareFunctionCreator<string>((a,b) => true)<{a: string, b: number}>('a')

Upvotes: 4

Related Questions