【问题标题】:Union of objects with arrays as a common value以数组作为公共值的对象的联合
【发布时间】:2020-12-08 17:54:00
【问题描述】:

从这些类型开始:

type A = { commonKey: { a: string }[] };
type B = { commonKey: { b: number }[] };

是否可以获得以下类型?不知道commonKey

type C = { commonKey: { a: string, b: number }[] }

我的尝试是type C = A & B,但生成的类型 C 不可用:

const c: C = // ...
c.commonKey.map(x => x.a) // `a` exists here, but not `b`

我需要一种通用的方法来做到这一点,独立于commonKey

type ArrayContent = A['commonKey'][number] & B['commonKey'][number]
type C = { commonKey: ArrayContent[] };

上下文

使用 TypeScript 4.1 模板文字类型和递归条件类型,我正在尝试改进 Elasticsearch 查询的类型。 我们为 Elasticsearch 集群中的文档生成了一个类型,如下所示:

interface Post {
  id: string;
  title: string | null;
  author: {
    name: string;
  };
  comments: {
    id: string;
    message: string;
  }[];
}

使用 Elasticsearch 在运行时,您可以通过路径限制检索的字段。如果键是数组或普通对象,则语法没有区别。

const sourceFields = ['id', 'author.name', 'comments.message'];

我正在尝试创建一个新类型,使用文档类型和源字段将构建实际检索到的类型。这是我目前所拥有的:

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>>

用法:

// 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;
  }[];
} */

即使键可以是undefinednull,或者用于嵌套对象,它也能正常工作。但是,一旦我想检索数组中的两个字段 (['comments.id', 'comments.message']),我就会遇到上述问题。我只能访问第一个定义的键。有什么想法吗?

【问题讨论】:

  • 其实我把数组的交集作为TypeScript的功能请求提交:github.com/microsoft/TypeScript/issues/41874
  • 很抱歉,但这不是最好的主意。实际上,您正在为数组元素的交集提出一个边缘情况。这不是理想的行为
  • 我认为我有一个有效的用例,而且我认为这种行为很奇怪,只考虑交集的第一个元素。但是,是的,也许我的用法并不常见。

标签: typescript typescript-generics


【解决方案1】:

希望对你有帮助:

type Base = { commonKey: unknown[] }

// credits goes to https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type
type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

type A = { commonKey: { a: string }[] };

type B = { commonKey: { b: number }[] };

/**
 * 1) iterate through unions of array elements [A, B]
 * 2) T[number]['commonKey'][number] -> get element of commonKey array
 * 3) UnionToIntersection<T[number]['commonKey'][number]> -> convert union to UnionToIntersection
 * 4) ReadonlyArray<UnionToIntersection<T[number]['commonKey'][number]>> -> make array of intersected elements
 */
type MakeMagic<T extends ReadonlyArray<Base>> = {
  [P in keyof T[number]]: ReadonlyArray<UnionToIntersection<T[number]['commonKey'][number]>>
}

type Result = MakeMagic<[A, B]>
/**
 * 
 type Result = {
    commonKey: readonly ({
        a: string;
    } & {
        b: number;
    })[];
}
 */

你可以阅读更多关于UnionToIntersectionhere的信息

我假设commonKey 始终是一个数组

【讨论】:

  • 你的意思是A可以有commonKey和BcommonKey2
  • TS 应该知道前面的属性名称。在其他情况下,对象应该只有一个属性
猜你喜欢
  • 2022-11-27
  • 1970-01-01
  • 2023-02-13
  • 1970-01-01
  • 2012-12-19
  • 2023-02-26
  • 1970-01-01
  • 1970-01-01
  • 2015-06-01
相关资源
最近更新 更多