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; }
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>;