Skip to content

Instantly share code, notes, and snippets.

@ryangoree
Last active February 27, 2025 01:27
Show Gist options
  • Save ryangoree/5d48bb3b92fa02cd9eb34ee87d3c7050 to your computer and use it in GitHub Desktop.
Save ryangoree/5d48bb3b92fa02cd9eb34ee87d3c7050 to your computer and use it in GitHub Desktop.
Util types for unions

Union Types

A tiny set of utility types for working with unions.

📦 Install

npm i gist:5d48bb3b92fa02cd9eb34ee87d3c7050

Usage

UnionKey

Get a union of all keys from all members of a union.

import { UnionKey } from "union-types";

type Foo = { a: string; b: number };
type Bar = { a: boolean; b: bigint; c: string };

type FoobarKey = keyof (Foo | Bar);
// => "a" | "b" ❌ missing "c"

type FoobarKey = keyof (Foo & Bar);
// => string | number | symbol ❌ intersecting incompatible types returns never

type FoobarKey = UnionKey<Foo | Bar>;
// => "a" | "b" | "c" ✅

OptionalUnionKey

Get a union of all keys that are optional or missing in at least one member of a union.

import { OptionalUnionKey } from "union-types";

type Foo = { a: string; b?: number };
type Bar = { a: boolean; b: bigint; c: string };

type optionalFoobarKey = OptionalUnionKey<Foo | Bar>;
// => "b" | "c"
// "c" is included because it doesn't exist in Foo

RequiredUnionKey

Get a union of all keys that are required in all members of a union.

import { RequiredUnionKey } from "union-types";

type Foo = { a: string; b?: number };
type Bar = { a: boolean; b: bigint; c: string };

type RequiredFoobarKey = RequiredUnionKey<Foo | Bar>;
// => "a"

UnionValue

Get a union of all values for a key from all members of a union.

import { UnionValue } from "union-types";

type Foo = { a: string; b?: number };
type Bar = { a: boolean; b: bigint; c: string };

type FooBar_C = UnionValue<Foo | Bar, "c">;
// => string | undefined

Merge

Merge a union or intersection of objects into a single type.

import { Merge } from "union-types";

type GetBlockOptions = {
  includeTransactions?: boolean;
} & (
  | {
      blockHash: string;
      blockNumber?: undefined;
      blockTag?: undefined;
    }
  | {
      blockHash?: undefined;
      blockNumber: bigint;
      blockTag?: undefined;
    }
  | {
      blockHash?: undefined;
      blockNumber?: undefined;
      blockTag: string;
    }
)

type Merged = Merge<GetBlockOptions>;
// {
//   includeTransactions?: boolean | undefined;
//   blockHash?: string | undefined;
//   blockNumber?: bigint | undefined;
//   blockTag?: string | undefined;
// }
{
"version": "0.0.1",
"name": "union-types",
"main": "Unions.ts",
"type": "module",
"author": "Ryan Goree (https://github.com/ryangoree)"
}
/**
* Get a union of all keys from all members of `T`.
*
* @example
* ```ts
* type Foo = { a: string; b: number };
* type Bar = { a: boolean; b: bigint; c: string };
*
* type FoobarKey = keyof (Foo | Bar);
* // => "a" | "b" ❌ missing "c"
*
* type FoobarKey = keyof (Foo & Bar);
* // => string | number | symbol ❌ intersecting incompatible types returns never
*
* type FoobarKey = UnionKey<Foo | Bar>;
* // => "a" | "b" | "c" ✅
* ```
*/
export type UnionKey<T> = T extends T ? keyof T : never;
/**
* Get a union of all keys that are optional or missing in at least one member
* of `T`.
*
* @example
* ```ts
* type Foo = { a: string; b?: number };
* type Bar = { a: boolean; b: bigint; c: string };
*
* type OptionalFoobarKey = OptionalUnionKey<Foo | Bar>;
* // => "b" | "c"
* // "c" is included because it doesn't exist in Foo
* ```
*/
export type OptionalUnionKey<T> = keyof {
[K in UnionKey<T> as T extends {
[_ in K]: any;
}
? never
: K]: never;
};
/**
* Get a union of all keys that are required in all members of `T`.
*
* @example
* ```ts
* type Foo = { a: string; b?: number };
* type Bar = { a: boolean; b: bigint; c: string };
*
* type RequiredFoobarKey = RequiredUnionKey<Foo | Bar>;
* // => "a"
* ```
*/
export type RequiredUnionKey<T> = Exclude<UnionKey<T>, OptionalUnionKey<T>>;
/**
* Get a union of all values for `K` from all members of `T`.
*
* @example
* ```ts
* type Foo = { a: string; b?: number };
* type Bar = { a: boolean; b: bigint; c: string };
*
* type FooBar_A = UnionValue<Foo | Bar, "a">;
* // => string | boolean
*
* type FooBar_B = UnionValue<Foo | Bar, "b">;
* // => number | bigint | undefined
*
* type FooBar_C = UnionValue<Foo | Bar, "c">;
* // => string | undefined
* ```
*/
export type UnionValue<T, K extends UnionKey<T>> = T extends T
? K extends keyof T
? T[K]
: undefined
: never;
/**
* Merge a union or intersection of objects into a single type.
*
* @example
* ```ts
* type GetBlockOptions = {
* includeTransactions?: boolean;
* } & (
* | {
* blockHash: string;
* blockNumber?: undefined;
* blockTag?: undefined;
* }
* | {
* blockHash?: undefined;
* blockNumber: bigint;
* blockTag?: undefined;
* }
* | {
* blockHash?: undefined;
* blockNumber?: undefined;
* blockTag: string;
* }
* )
*
* type Merged = Merge<GetBlockOptions>;
* // {
* // includeTransactions?: boolean | undefined;
* // blockHash?: string | undefined;
* // blockNumber?: bigint | undefined;
* // blockTag?: string | undefined;
* // }
* ```
*/
export type Merge<T> = Eval<
{
[K in RequiredUnionKey<T>]: UnionValue<T, K>;
} & {
[K in OptionalUnionKey<T>]?: UnionValue<T, K>;
}
>;
type Eval<T> = { [K in keyof T]: T[K] } & {};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment