Home > Blockchain >  Intersection types with arrays and array methods cannot find all fields
Intersection types with arrays and array methods cannot find all fields

Time:06-28

I've got two types that are both an array of objects, with their fields, in an intersection type in Typescript.

If I take an element from the array I can access the second field, but if I use an array method (forEach, map, filter etc) the compiler doesn't work.

Is there a particular reason why in a case work but fails in the other?

Here a sample code:

var obj: {
  foo?: string;
}[] & {
  bar?: boolean;
}[] = [{foo: "a", bar: true}];

obj.forEach((q) => q.bar); // bar does not exist
obj.map((q) => q.bar) // bar does not exist
obj[0].bar; // OK

TS Playground Link

CodePudding user response:

Per Ryan Cavanaugh (active member fo the TypeScript team), it is a bad idea to have an intersection of 2 array types.

This happens because we just merge the signatures of forEach in order, whereas element access produces the recursively merged property type. You could specify a type annotation on item, or write type foo3 = foo2 & foo1 (!).

But in general it's really unclear how type TU = T[] & U[] should be interpreted -- is it a heterogeneous array, the same as Array<T | U> ? Or the same as Array<T & U> ? An axiom is that for any operation x.y(z), that should still be legal for x if x becomes an intersection type containing the original constituent, but that wouldn't be true if we interpreted the array to be Array<T & U> (since z would have to be the intersected element type as well).

So overall... probably a bad idea to have the type T[] & U[] floating around.

What you probably should have is Array<{ foo?: string;} & {bar?: boolean}>

Playground

CodePudding user response:

It would be more obvious to understand if you write down array types:

type ItemFoo = {
  foo?: string;
};
type ArrFoo = ItemFoo[];

type ItemBar = {
  bar?: boolean;
};
type ArrBar = ItemBar[];


const obj: ArrFoo & ArrBar = [{foo: "a", bar: true}];

So you can notice ArrFoo has forEach(cb: (x: ItemFoo, i?) => void) method, and ArrBar has forEach(cb: (x:ItemBar, i?) => void) method. And when the type merge happens, only one signature is taken.

If you have access just to the ArrFoo and ArrBar types, make the intersection of item types

const obj: (ArrFoo[0] & ArrBar[0])[] = [{foo: "a", bar: true}];

Or with the Helper Type.

type ArrIntersection<T1 extends Array<any>, T2 extends Array<any>> = (T1[0] & T2[0])[];

const obj: ArrIntersection<ArrFoo, ArrBar> = [{foo: "a", bar: true}];
  • Related