Starting from these types:
type A = { commonKey: { a: string }[] };
type B = { commonKey: { b: number }[] };
Is it possible to obtain the following type? Without knowing about commonKey
.
type C = { commonKey: { a: string, b: number }[] }
My attempt was type C = A & B
, but the resulting type C isn't usable:
const c: C = // ...
c.commonKey.map(x => x.a) // `a` exists here, but not `b`
I would need a generic way to do this, independent of commonKey
:
type ArrayContent = A['commonKey'][number] & B['commonKey'][number]
type C = { commonKey: ArrayContent[] };
Context
With TypeScript 4.1 template literal types and recursive conditional types, I'm trying to improve the types for our Elasticsearch queries. We have a generated type for the documents in our Elasticsearch cluster, like this:
interface Post {
id: string;
title: string | null;
author: {
name: string;
};
comments: {
id: string;
message: string;
}[];
}
And with Elasticsearch at runtime you can limit which fields you retrieve, with the paths. There's no distinction in the syntax if the key is an array or a plain object.
const sourceFields = ['id', 'author.name', 'comments.message'];
I'm trying to create a new type, that using the document type and the source fields will build the type of what is actually retrieved. This is what I have so far:
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>>
Usage:
// Reusing the `Post` interface described above
const sourceFields = ['id', 'author.name', 'comments.message'] as const;
type ActualPost = PartialObjectFromSourceFields<Post, typeof sourceFields[number]>;
/* `ActualPost` is equivalent to:
{
id: string;
author: {
name: string;
};
comments: {
message: string;
}[];
} */
It works well even if the key can be undefined
or null
, or for nested objects. But as soon as I want to retrieve two fields inside an array (['comments.id', 'comments.message']
), I'm facing the issue described above. I can only access the first defined key. Any idea?