简体   繁体   English

如何制作一种对象类型,它将包含联合类型的所有对象的属性,但值将是交集

[英]How to make one object type which will contain properties of all objects of union type, but values will are intersections

How can I make a type which will make a one object type from unions of objects, which will contain properties of all objects of union type, but values will intersections?如何创建一种类型,该类型将从对象的联合中创建一个对象类型,该类型将包含联合类型的所有对象的属性,但值将相交?

Example: I need to make from type { foo: 1 } | { foo: 2; bar: 3 } | { foo: 7; bar: 8 }示例:我需要从 type { foo: 1 } | { foo: 2; bar: 3 } | { foo: 7; bar: 8 } { foo: 1 } | { foo: 2; bar: 3 } | { foo: 7; bar: 8 } { foo: 1 } | { foo: 2; bar: 3 } | { foo: 7; bar: 8 } a type {foo: 1 | 2 | 7; bar: 3 | 8} { foo: 1 } | { foo: 2; bar: 3 } | { foo: 7; bar: 8 }一个类型{foo: 1 | 2 | 7; bar: 3 | 8} {foo: 1 | 2 | 7; bar: 3 | 8} {foo: 1 | 2 | 7; bar: 3 | 8} . {foo: 1 | 2 | 7; bar: 3 | 8}

Important note : I want to make one object type instead of an intersection like {foo: 1 | 2} & {bar: 3}重要说明:我想创建一种对象类型,而不是像{foo: 1 | 2} & {bar: 3} {foo: 1 | 2} & {bar: 3}

I have written a type, ComplexUnionToIntersection , which should do it, but it's ignoring properties which do not exist in all objects of the union ( bar in my examples).我写了一个类型ComplexUnionToIntersection ,它应该这样做,但它忽略了联合的所有对象中不存在的属性(我的示例中的bar )。

My code:我的代码:

/**
 * More info: https://fettblog.eu/typescript-union-to-intersection/
 */
export type UnionToIntersection<U> = (
    U extends any ? (k: U) => void : never
) extends (k: infer I) => void
    ? I
    : never;

/**
 * Target type
 */
export type ComplexUnionToIntersection<U> = { o: U } extends { o: infer X }
    ? {
            [K in keyof (X & U)]: (X & U)[K];
      }
    : UnionToIntersection<U>;

Test cases:测试用例:

// TODO: result of test case must be `{foo: 1 | 2; bar: 3}`
type testCase1 = ComplexUnionToIntersection<{ foo: 1 } | { foo: 2; bar: 3 }>; // actually return `{ foo: 1 | 2; }`

// TODO: result of test case must be `{foo: 1 | 2 | 7; bar: 3 | 8}`
type testCase2 = ComplexUnionToIntersection<
    { foo: 1 } | { foo: 2; bar: 3 } | { foo: 7; bar: 8 }
>;

// TODO: result of test case must be `{foo: 1 | 2; bar: 3 | 8}`
type testCase3 = ComplexUnionToIntersection<
    { foo: 1 } | { foo: 2; bar: 3 } | { bar: 8 }
>;

// TODO: result of test case must be `{foo?: 1 | 2; bar: 3 | 8}`
type testCase4 = ComplexUnionToIntersection<
    { foo: 1 } | { foo?: 2; bar: 3 } | { bar: 8 }
>;

TS Playground is here TS游乐场在这里

So the operation is to merge a union of object types into a single object type, where each property key in any of the input union members will appear in the output type, and the value type of that property will be the union of all property value types for that key wherever it appears in the input.所以操作是将对象类型的并集合并为单个对象类型,其中任何输入并集成员中的每个属性键都会出现在输出类型中,并且该属性的值类型将是所有属性值的并集输入中出现该键的任何位置的类型。 And you want a property to be optional in the ouput if it's optional in any of the input union members.如果某个属性在任何输入联合成员中是可选的,那么您希望该属性在输出中是可选的。

If you just collapse the union into a single object type, you'll have the problem you see where a property will only exist in the output if it exists in every input, but otherwise the property types will be correct.如果您只是将联合折叠为单个对象类型,您将遇到问题,您会看到一个属性仅在每个输入中都存在时才会存在于输出中,否则属性类型将是正确的。 So one approach here would be to first augment each member of the union so it contains all the keys from any union member.所以这里的一种方法是首先增加联合的每个成员,使其包含来自任何联合成员的所有密钥。 So if a property is missing, we should add it, and give it the value type of the never type , which is absorbed in any union.因此,如果缺少某个属性,我们应该添加它,并为其赋予never类型的值类型,该类型被任何联合所吸收。

For example, we start with something like:例如,我们从以下内容开始:

{ foo: 1 } | { foo?: 2; bar: 3 } | { bar: 8 }

And then augment each member of the union to contain all the keys, something like:然后扩充联合的每个成员以包含所有键,例如:

{ foo: 1; bar: never } | { foo?: 2; bar: 3 } | { foo: never; bar: 8 }

And then we merge this new union into a single object, like:然后我们将这个新联合合并到一个对象中,例如:

{ foo?: 1 | 2 | never; bar: never | 3 | 8 }

which collapses to崩溃到

{ foo?: 1 | 2; bar: 3 | 8 }

So let's do it:所以让我们这样做:


type AllKeys<T> = T extends unknown ? keyof T : never

type AddMissingProps<T, K extends PropertyKey = AllKeys<T>> =
    T extends unknown ? (T & Record<Exclude<K, keyof T>, never>) : never;

type MyMerge<T> = { [K in keyof AddMissingProps<T>]: AddMissingProps<T>[K] }

The AllKeys<T> type is a distributive conditional type which gathers every key from every union member: AllKeys<T>类型是一种分布式条件类型,它从每个联合成员那里收集每个键:

type TestAllKeys = AllKeys<{ foo: 1 } | { foo?: 2; bar: 3 } | { bar: 8 }>
// type TestAllKeys = "foo" | "bar"

The AddMissingProps<T, K> type is also distributive, and for each element of the input union T it adds any keys from K which are not already present in keyof T , and gives them a never type, and note that K defaults to AllKeys<T> : AddMissingProps<T, K>类型也是可分配的,对于输入联合T的每个元素,它会添加K中尚未出现在keyof T中的任何键,并给它们一个never类型,并注意K默认为AllKeys<T> :

type TestAddMissingProps = AddMissingProps<{ foo: 1 } | { foo?: 2; bar: 3 } | { bar: 8 }>
/* type TestAddMissingProps = 
    ({ foo: 1; } & Record<"bar", never>) | 
    ({ foo?: 2 | undefined; bar: 3; } & Record<never, never>) | 
    ({ bar: 8; } & Record<"foo", never>) */

That is equivalent to the { foo: 1; bar: never } | { foo?: 2; bar: 3 } | { foo: never; bar: 8 }这相当于{ foo: 1; bar: never } | { foo?: 2; bar: 3 } | { foo: never; bar: 8 } { foo: 1; bar: never } | { foo?: 2; bar: 3 } | { foo: never; bar: 8 } { foo: 1; bar: never } | { foo?: 2; bar: 3 } | { foo: never; bar: 8 } type mentioned above, although it's not written the same way. { foo: 1; bar: never } | { foo?: 2; bar: 3 } | { foo: never; bar: 8 }上面提到的类型,虽然写法不一样。 Since we are still going to process the type it's not important to reduce it here.由于我们仍然要处理类型,所以在这里减少它并不重要。

Finally, the MyMerge<T> type is an identity mapped type over AddMissingProps<T> .最后, MyMerge<T>类型是AddMissingProps<T>上的标识映射类型 Its only purpose is to iterate over each property and produce a single object type output:它的唯一目的是遍历每个属性并生成单个对象类型输出:

type TestMyMerge = MyMerge<{ foo: 1 } | { foo?: 2; bar: 3 } | { bar: 8 }>
/* type TestMyMerge = {
    foo?: 1 | 2 | undefined;
    bar: 3 | 8;
} */

Looks good!看起来不错!

Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM