Home > database >  Mapping types in TypeScript
Mapping types in TypeScript

Time:06-24

Is it possible in TS to transform this type

type input = {
    a: 1;
    b: 2;
    c: 3;
};

into this type

type output = { a: 1 } | { b: 2 } | { c: 3 };

?

CodePudding user response:

You want a type function that converts Input to Output; let's call it "Split<T>", for want of a better name. Here's one approach:

type Split<T extends object> =
  { [K in keyof T]-?: Pick<T, K> }[keyof T];

On the inside we're using the Pick<T, K> utility type to represent the same type as T but only with the keys from K. So Pick<Input, "a"> is {a: 1}:

type Test = Pick<Input, "a">
// type Test = { a: 1; }

On the outside is a distributive object type (as coined in microsoft/TypeScript#47109), which is what you get when you make a mapped type over some set of keys and then immediately index into it with the same set of keys. I

Distributive object types let you take a type function F<K> for some keylike type K, and produce the union of F<K> for each union member K in some set of keys. In the above case, keyof T is the set of keys. So the above will compute the union of Pick<T, K> for each K in keyof T, which is what you want:

type Input = {
  a: 1;
  b: 2;
  c: 3;
};

type Output = Split<Input>;
// type Output = Pick<Input, "a"> | Pick<Input, "b"> | Pick<Input, "c">

If you don't like seeing Pick in the IntelliSense output, you can inline the definition of Pick<T, K> into Split<T>:

type Split<T extends object> =
  { [K in keyof T]-?: { [P in K]: T[P] } }[keyof T];

type Output = Split<Input>;
// type Output = { a: 1; } | { b: 2; } | { c: 3; }

Playground link to code

CodePudding user response:

You can do this:

type input = {
a: 1;
b: 2;
c: 3;
};

type output = { a: 1 } | { b: 2 } | { c: 3 };

let i : input = {
    a: 1,
    b: 2,
    c: 3
};

let o : output;

let {a, b, c} = i;

o = {a} || {b} || {c};

CodePudding user response:

Not sure if this scales to more complicated input types, might get weird with nested things

type input = {
    a: 1;
    b: 2;
    c: 3;
};

// given key K, construct {[K]: T[K]}
type m2<T, K extends keyof T> = {[k in K]: T[k]}

// for each key K in T, construct {[K]: T[K]}, and union the results
type m1<T, K extends keyof T> = K extends any ? m2<T, K> : never;

type map<T> = m1<T, keyof T>;
type result = map<input>

declare const result : result;
if ("a" in result) {
    result.a // type 1
}
else if ("b" in result) {
    result.b; // type 2
}
else {
    result.c // type 3
}

CodePudding user response:

Honestly, I have no idea how to implement this from scratch. But here's a library that does exactly this and much more (ts-toolbelt):

import { Object } from 'ts-toolbelt';

type Input = {
  a: 1;
  b: 2;
  c: 3;
};

type Output = Object.Either<Input, keyof Input>;
  • Related