Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Is there a way to "reach into" a union nested inside an intersection?

Imagine that I have a type which is a union nested inside an intersection:

type UnionInsideIntersectionType = ({ foo: "foo" } | { foo: "bar" }) & {
  baz: "baz";
};

Is there a way to later extend the union portion of the type without rewriting it? In other words, can I somehow make the following from a transformation of UnionInsideIntersectionType?

type ExtendedUnionInsideIntersectionType = (
  | { foo: "foo" }
  | { foo: "bar" }
  | { foo: "qux" }
) & { baz: "baz" };

Playground

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

Conversely, is there a way to do this subtractively, i.e. to remove foo: "qux" from ExtendedUnionInsideIntersectionType to produce UnionInsideIntersectionType? I’m aware of Exclude, but if it can be applied to properties, I don’t see how.

>Solution :

This seems like an XY problem but I’ll indulge it.


Is there a way to later extend the union portion of the type without rewriting it?

From a pure logic, and type system, point of view this:

(A | B) & C

Is exactly equivalent to:

(A & C) | (B & C)

And typescript knows that. Which means figuring out what part is a union and which part is an intersection gets a bit muddy after the type is declared. And all you have to work with is the result. Artifacts of how the type was assembled can be seen in the tooltips the compiler provides sometimes, but functionally the two cases above are perfectly equivalent. There is no sense of order of operations after the type has been declared. It just is what it is.

Another example:

type A = ({ a: 1 } | { a: 2 }) & { b: 3 }
type B = { a: 1, b: 3 } | { a: 2, b: 3 }

type Test1 = A extends B ? true : false // true
type Test2 = B extends A ? true : false // true

All of which means that I believe the answer to your first question is "no".


Conversely, is there a way to do this subtractively, i.e. to remove foo: "qux" from ExtendedUnionInsideIntersectionType to produce UnionInsideIntersectionType?

Exclude works great here:

type WithoutQux = Exclude<ExtendedUnionInsideIntersectionType, { foo: 'qux' }>

As an alternative, it seems like the UnionInsideIntersectionType type should be generic, which lets you inject whatever else you want in there.

type UnionInsideIntersectionType<
  T extends Record<string, unknown> = Record<string, never>
> = ({ foo: "foo" } | { foo: "bar" } | T) & { baz: "baz"; };

type ExtendedUnionInsideIntersectionType =
  UnionInsideIntersectionType<{ foo: 'qux' }>

See playground

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading