1

In TypeScript, is it possible to pass each key in an object as a generic to each corresponding value in that object? For instance:

interface Items
{
    [key: Key extends string]: Item <Key>;
};

interface Item <Key>
{
    name: Key;
};

This can be achieved if the keys are a string literal:

type Name = 'a' | 'b' | 'c';

type Items =
{
    [Key in Name]?: Item<Key>;
};

interface Item <Name>
{
    name: Name;
};

const items: Items =
{
    // Type valid
    a:
    {
        name: 'a'
    },
    // Type error
    b:
    {
        name: 'c'
    }
};

I'm not sure how this can be extended to allow any string. Any help would be much appreciated.

Chris Talman
  • 1,059
  • 2
  • 14
  • 24

1 Answers1

2

You have a generic type that computes the value type from the key type:

interface Item<Key> {
    name: Key;
};

Given a collection, which is your Name of the types of the keys, you can compute the overall object type. So why not make Name generic?

type Items<Name extends string> = {
    [Key in Name]?: Item<Key>;
};

Now you want to validate if a given type, which is the type of your object literal, fits into the Items constraint. That is, whether there exists a type Name, such that the type of the object literal is exactly Items. There is no direct formation of existential type in TypeScript, but this can be done with inference from function parameters:

function checkType<Name extends string>(items: Items<Name>): Items<Name> {
    return items;
}

Use it like this:

const items = checkType({
    a: {
        name: 'a',
    },
    b: {
        name: 'c', // cannot pass the check
        // name: 'b', // passes the check
    },
});
Colliot
  • 1,522
  • 3
  • 16
  • 29
  • Thanks. This is an interesting solution. It's unfortunate, though, that it requires the use of a function. If there's no alternative, that may have to be accepted, but it would be preferable to avoid that. – Chris Talman Aug 27 '19 at 23:18