Gerrit Begher
Gerrit Begher

Reputation: 403

Typescript: Merging types (vs intersecting them)

Consider the following

type MergeFn = <K1 extends string, V1, K2 extends string, V2>(
   k1: K1, v1: V1,
   k2: K2, v2: V2
) => ???

let mergeFn: MergeFn // implementation not relevant to question

What would I have to fill in for ??? so that

mergeFn(
  "hello", 1,
  "typescript", 2
)

has type { hello: number, typescript: number }.

I tried

??? = { [k in K1]: V1 } & { [k in K2]: V2 }

but the result will be

{ hello: number } & { typescript: number }.

(Applying type Id<T> = { [k in keyof T]: T[k] } as proposed here did not help either.)

Example in the typescript playground

Upvotes: 1

Views: 191

Answers (1)

jcalz
jcalz

Reputation: 327964

One way you could do it is to use a single conditional mapped type instead of an intersection:

type MergeFn = <K1 extends string, V1, K2 extends string, V2>(
    k1: K1, v1: V1,
    k2: K2, v2: V2
) => { [K in K1 | K2]: K extends K1 ? V1 : V2}

which produces

const test = mergeFn(
    "hi", 4,
    "there", 4
)
/* const test: {
    hi: number;
    there: number;
} */

When you say the Id<T> "didn't help" I assume you mean that the compiler decided to show you a type alias name like Id<A & B> instead of expanding it out for you. I've found in such cases that the way to deal with it is to pass through an intermediate conditional type, like this:

type Expand<T> = T extends infer U ? { [K in keyof U]: U[K] } : never

If I use that on your original code:

type MergeFn = <K1 extends string, V1, K2 extends string, V2>(
    k1: K1, v1: V1,
    k2: K2, v2: V2
) => Expand<Record<K1, V1> & Record<K2, V2>>

I get the same output:

const test = mergeFn(
    "hi", 4,
    "there", 4
)
/* const test: {
    hi: number;
    there: number;
} */

So either way should work for you.


Okay, hope that helps; good luck!

Link to code

Upvotes: 2

Related Questions