justcodin
justcodin

Reputation: 1014

Is there a way to create a type where all keys of an object are required?

This my code :

interface Props {
    id: string;
    name: string;
    age: number;
};

const keysOfProps: (keyof Props)[] = ['id', 'name']; // This should show a warning because the ``age`` string is missing

I expect this keysOfProps to be of type ['id, 'name', 'age'] and not ('id' | 'name' | 'age')[]

How can I achieve this ?

Upvotes: 1

Views: 87

Answers (1)

ccarton
ccarton

Reputation: 3666

While it's not possible to define the desired type for the tuple, it is possible to assert that the type for a given tuple matches all of the keys. We can use a variation on the Pick utility type to build a type from Props which contains the keys defined by a tuple and then we can assert that the result is the same as the original Props. The solution takes a few steps and a function call, but the following should work:

type Tuple<T> = readonly T[]
type TuplePick<T, K extends Tuple<keyof T>> = Pick<T, K[number]> // like Pick, but we pass a tuple of the keys
type IfEqualThen<T, U, R> = T extends U ? (U extends T ? R : never) : never // 'R' if T and U are mutually assignable
type AssertAllKeys<P, T extends Tuple<keyof P>> = IfEqualThen<P, TuplePick<P, T>, T>

function assertKeys <P> () {
    return function <T extends Tuple<keyof P>> (value: AssertAllKeys<P, T>): T 
    {
        return value
    }
}

interface Props {
    id: string;
    name: string;
    age: number;
};

const keys1 = assertKeys<Props>()(['id', 'name'] as const) // Error, argument not assignable to never
const keys2 = assertKeys<Props>()(['id', 'name', 'age'] as const) // Works

Upvotes: 1

Related Questions