Typed key-value-pair object in Typescript with saving keyof the object

Viewed 302

I have the key-value-pair object in the following example


interface ItemType {
    a: number;
    b: string;
}

const list = {
    first: {a: 1, b: 'one'},
    second: {a: 2, b: 'two'},
    third: {a: 3, b: 'three'},
} as { [key in keyof typeof list]: ItemType }

But it raises an error such as TS2313: Type parameter 'key' has a circular constraint..

I want all items to be of type of ItemType, but still want the list to save keys that I have inserted. If I cast it to { [key in string]: ItemType }, I will loose key names of the list. :(

1 Answers

In cases where you want to verify that a value is assignable to a type without widening it to that type and possibly throwing away information you care about, you can use a helper identity function, like this:

const itemDict = <K extends PropertyKey>(dict: { [P in K]: ItemType }) => dict;

Here itemDict() should only accept as an argument an object of the type you're looking for: whose keys are anything key-like K extends PropertyKey which will be inferred by the compiler when you call it, and whose properties are ItemTypes. Because the set of keys, K, is part of the type, it will not be forgotten:

const list = itemDict({
  first: { a: 1, b: 'one' },
  second: { a: 2, b: 'two' },
  third: { a: 3, b: 'three' },
});

list.second.a.toFixed(); // okay

list.fourth; // error!
// Property 'fourth' does not exist on type 
// '{ first: ItemType; second: ItemType; third: ItemType; }'

Notice that list is inferred by the compiler to be type {first: ItemType, second: ItemType, third: ItemType}, as desired.

Playground link to code

Related