Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a discriminated union using the property names of another type

Tags:

typescript

I am trying to make a generic type that takes the property names and property types from a type and use them to make a discriminated union type. For example:

type FooBar = {
    foo: string;
    bar: number;
};

would map to:

type FooBarPair = {
    key: "foo",
    value: string
} | {
    key: "bar",
    value: number
}

My initial attempt is:

type Pairs<T> = {
    [TKey in keyof T]: {
        key: TKey;
        value: T[TKey];
    };
};

type Pair<T> = Pairs<T>[keyof T];

but when I try to apply this to the type above:

let pair: Pair<FooBar> = {
    key: "foo",
    value: 3
};

I would expect a compile error but there isn't one. When I inspect the type of Pair<FooBar> I find it equates to:

{
    key: "foo" | "bar";
    value: string | number;
}

I think this might be a bug in typescript but I wanted to see if there was some other way to achieve this.

like image 803
Matthew Popat Avatar asked Oct 17 '22 12:10

Matthew Popat


1 Answers

Yes there is a way to achieve this. It turns out that Typescript infers the desired type

 `{ key: "foo"; value: string; } | { key: "bar"; value: number; }`

when you remove intermediate generic type type Pair<T> = Pairs<T>[keyof T]; and expand it inline like Pairs<FooBar>[keyof FooBar] every time you would have used it:

type Pairs<T> = {
    [TKey in keyof T]: {
        key: TKey;
        value: T[TKey];
    };
};

type FooBar = {
    foo: string;
    bar: number;
};


let pair: Pairs<FooBar>[keyof FooBar] = {
    key: "foo",
    value: 3
};

the complete error message is

Type '{ key: "foo"; value: number; }' is not assignable to type '{ key: "foo"; value: string; } | { key: "bar"; value: number; }'.
  Type '{ key: "foo"; value: number; }' is not assignable to type '{ key: "foo"; value: string; }'.
    Types of property 'value' are incompatible.
      Type 'number' is not assignable to type 'string'.

I'm not sure what's the "correct" type to infer in either case, but introduction of shorthand type like Pair<T> = Pairs<T>[keyof T] should not affect type inference IMO. Seems like a bug.

like image 156
artem Avatar answered Oct 23 '22 08:10

artem