1

This is kind of flatten (if I don't confuse terminology) function. What should be instead of question marks? Or am I doing this completely wrong?

const array = [{foo: 'val1'}, {bar: 'val2'}]

function flatten <ARRAYOFOBJECTS extends object[]>(array: ARRAYOFOBJECTS): Flat<ARRAYOFOBJECTS> {
  // implementation here, does not matter, all that matters is that it returns following:
  return {foo: 'val1', bar: 'val2'}
}

type Flat<ARRAYOFOBJECTS> = {
  [K in keyof ARRAYOFOBJECTS[????]]: string
}

flatten(array). // at this point IDE should suggest two values: "foo" and "bar"
Nurbol Alpysbayev
  • 19,522
  • 3
  • 54
  • 89

1 Answers1

1

You can just declare a type parameter for the array item type and return it as the return value for the function, and everything will work out for your particular example because of the way typescript infers the type for the array (it will be { foo: string; bar?: undefined; } | { bar: string; foo?: undefined;})[]). But to make sure that any union is flattened correctly we will need a bit of extra help from this answer on UnionToIntersection

type UnionToIntersection<U> = (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

const array = [{ foo: 'val1' }, { bar: 'val2' }]

function flatten<T> (array: T[]) : UnionToIntersection<T> {
// implementation here, does not matter, all that matter is that it returns following:
return {foo: 'val1', bar: 'val2'} as any
}

// at this point IDE suggests two values: "foo" and "bar"
flatten(array).bar 
flatten(array).foo 

Edit

The reason to use UnionToIntersection<T> is because the type of the array item very much depends on how you define the array:

Ex:

const array1 = [{foo: 'val1'}, {bar: 'val2'}] // { foo: string; bar?: undefined; } | { bar: string; foo?: undefined;})[]`)

const foo = { foo: 'val1' }
const bar = { bar: 'val2' }
const array2 = [foo, bar]// ({ foo: string; } | { bar: string; })[]

For array1 typescript will add properties of all object literals to all types in the union, we get a bunch of extra optional properties with type undefined on each member of the union ({ foo: string; bar?: undefined; }) And this is why we can access any member even though they are not present on all items in the array.

For array2, the type of each item is already known (ie decided before the array definition) so typescript will not add those extra properties and we get a union with no common members as the type for an item of the array ({ foo: string; } | { bar: string; }) and we will not be able to access any member.

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • Wow. You are helping me again :-) Thank you so much! But, as you said, everything still works (with no difference) if I replace return type `UnionToIntersection` with just `T`. So could you please elaborate a little, what benefit exactly UnionToIntersection gives? Because after almost an hour, I still don't get it. – Nurbol Alpysbayev Aug 02 '18 at 09:41
  • 1
    @NurbolAlpysbayev added an explanation, hope it makes things clear, let me know :) – Titian Cernicova-Dragomir Aug 02 '18 at 09:52
  • Wow, again. Because it is only word I can find. Great explanation! And I thought I knew TS well enough :-) Thank you again! – Nurbol Alpysbayev Aug 02 '18 at 10:18