8

I was looking over some of the cooler type functions that are included in the standard library and I got around to playing. Is something like this actually possible in TypeScript?

interface OriginalType {
    A: { a : string }
    B: { b1 : string, b2: number }
    C: {}
    D: {c: string}
}


const r : {a: string, b1: string, b2: string} = pickAndFlatten<OriginalType>(['A', 'B'])
Anthony Naddeo
  • 2,497
  • 25
  • 28

1 Answers1

11

We can so this by creating an intersection of the picked property types.

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
type PickAndFlatten<T, K extends keyof T> = UnionToIntersection<T[K]>;

UnionToIntersection was provided by @jcalz here and you should see the explanation there (don't forget to thank him with an upvote :) ). T[K] will give us the type of the key K. If K is a union of keys, T[K] will be a union of all property types in the union.

To use this in a function we will need two type parameters, one representing the type we are picking from an one to represent the keys we are picking.

interface OriginalType {
  A: { a: string };
  B: { b1: string, b2: number };
  C: {};
  D: { c: string };
}

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
type PickAndFlatten<T, K extends keyof T> = UnionToIntersection<T[K]>;

function pickAndFlatten<T, K extends keyof T>(o: T, keys: K[]): PickAndFlatten<T, K> {
  // Untested implementation
  const entries = Object.entries(o)
      .filter(([k, v]) => keys.includes(k as K))
      .map(([k, v]) => v);

  return Object.assign({}, entries) as any;
}

let o: OriginalType;
const r = pickAndFlatten(o, ['A', 'B']); // { a: string; } & { b1: string; b2: number; }
Saugat
  • 1,309
  • 14
  • 22
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • That's great! Just what I was looking for. The only remaining issue: TypeScript's structural typing makes it so that people can specify an object that accepts the items in the resulting intersection and a few more. I was hoping it would be a compile time error if they didn't match exactly. I'm not sure if there is any way around that. – Anthony Naddeo Jul 20 '18 at 20:42
  • Hi Titian! I noticed you often use a function `(k: U) => void` to infer `U`. But isn't it that we can do it simpler using `[U] extends [infer I] ? ...` or something? – Nurbol Alpysbayev Dec 20 '18 at 04:11
  • @NurbolAlpysbayev if you are reffering to `UnionToIntersection` that is really jcalz's type (as I mention in the answer). The reason he uses function types there instead of tuples is because of the way conditional types and their inference behavior interact with function parameters. The idea is to get `I` to be an intersection of all union members. – Titian Cernicova-Dragomir Dec 20 '18 at 04:14
  • I missed the note about the type is not yours. Nevertheless, I didn't quite get the idea in the last sentence... Can you pls explain, so inferring a function parameter works differently from inferring a tuple item? How is that possible.. – Nurbol Alpysbayev Dec 20 '18 at 04:18
  • Ah, is it [because](https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type#comment89905160_50375286) tuple is not a naked/bare type parameter? – Nurbol Alpysbayev Dec 20 '18 at 04:21
  • @NurbolAlpysbayev if you read in https://www.typescriptlang.org/docs/handbook/advanced-types.html when inffring a type "multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred.". The function parameter makes the inference site a contra-variant position. This is why the behavior is different – Titian Cernicova-Dragomir Dec 20 '18 at 04:21
  • @NurbolAlpysbayev jcalz explains his reasoning in the answer https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type (make sure you give him an upvote :) ) – Titian Cernicova-Dragomir Dec 20 '18 at 04:22
  • Yeah, I've gave the UV long time ago, even If I still don't understand it completely :D BTW I almost always UV you and jcalz – Nurbol Alpysbayev Dec 20 '18 at 04:23
  • 1
    @NurbolAlpysbayev 10x :) not too much though we don't want to run afoul of SO rules ;) – Titian Cernicova-Dragomir Dec 20 '18 at 04:29