Intersection of arrays
See original GitHub issueSearch Terms
intersection array
Suggestion
Intersections of arrays are weird in my opinion. In TypeScript 4.1, only the first part of the intersection is considered. I think TypeScript should merge the array types, it would be especially convenient for objects:
type A = { a: string }[];
type B = { b: number }[];
type C = A & B;
// Behaves like
C = { a: string }[];
// Desired
C = { a: string, b: number }[];
Playground to try it out.
Use Cases
I want to be able to cherry-pick some paths from an interface. I have an Elasticsearch cluster and a TypeScript interface for the shape of the documents in the cluster. With Elasticsearch, I’m able to retrieve only some values with their paths.
// With some documents of the following shape:
interface Post {
id: string;
views: number;
comments: {
id: string;
content: string;
author: {
id: string;
}
}[];
}
// And a list of source fields
const sourceFields = ['id', 'comments.id'];
// When we do the query with these source fields, at runtime we only have a subset of the interface, like this:
{ id: '1', messages: [{ id: '1' }] };
Using features introduced in TypeScript 4.1 I’m able to generate a type based on the interface and the source fields. And it works almost perfectly except for arrays. While it works fine with nested objects, and nullable and optional keys.
Usage:
// Source fields are known at build time
const sourceFields = ['id', 'comments.id'] as const;
type ActualPost = PartialObjectFromSourceFields<Post, typeof sourceFields[number]>;
/* ActualPost behaves like
{
id: string;
comments: {
id: string;
}[];
}
*/
PartialObjectFromSourceFields
declaration:
type ExtractPath<Obj, Path extends string> =
Obj extends undefined ? ExtractPath<NonNullable<Obj>, Path> | undefined :
Obj extends null ? ExtractPath<NonNullable<Obj>, Path> | null :
Obj extends any[] ? ExtractPath<Obj[number], Path>[] :
Path extends `${infer FirstKey}.${infer OtherPath}`
? (FirstKey extends keyof Obj
? { [k in FirstKey]: ExtractPath<Obj[FirstKey], OtherPath> }
: never)
: Path extends keyof Obj
? { [K in Path]: Obj[Path] }
: never;
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
type Distribute<Obj, Fields> = Fields extends string ? ExtractPath<Obj, Fields> : never;
export type PartialObjectFromSourceFields<Obj, Fields> = UnionToIntersection<Distribute<Obj, Fields>>
But because of the limitation on array intersection, as soon as 2 paths on an array key are used, only the first one is considered:
const sourceFields = ['id', 'comments.id', 'comments.content'] as const;
type ActualPost = PartialObjectFromSourceFields<Post, typeof sourceFields[number]>;
// ActualPost is in fact:
{ id: string } & { comments: { id: string }[] } & { comments: { content: string }[] };
// And `content` cannot be accessed inside `comments`, only `id` is available
Examples
I think the same merging rules should apply as without arrays:
type A = string[] & number[];
// Behaves like
A = string[]
// Desired?
A = never[];
Checklist
My suggestion meets these guidelines:
- This wouldn’t be a breaking change in existing TypeScript/JavaScript code
- This wouldn’t change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn’t a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
- This feature would agree with the rest of TypeScript’s Design Goals.
I don’t know if it would be a breaking change 🤔 I wonder if it might conflict with this behavior https://github.com/microsoft/TypeScript/issues/38348
Issue Analytics
- State:
- Created 3 years ago
- Comments:10 (5 by maintainers)
Top GitHub Comments
I found the workaround way to solve an issue I created
ArrayIntersect<T1, T2>
like thishope it helps someone who needs the same use case like mine
@RyanCavanaugh I see, thanks for clarification!
although I have 2 more questions:
bug
tag attached to this issue (since if you access thruarr[0]
you get botha
andb
but get onlya
in the map function?)