Skip to content

Instantly share code, notes, and snippets.

@Dovias
Last active October 7, 2024 17:48
Show Gist options
  • Save Dovias/3b20f435a47f002e55ffba38067fc73b to your computer and use it in GitHub Desktop.
Save Dovias/3b20f435a47f002e55ffba38067fc73b to your computer and use it in GitHub Desktop.
Typescript property substitution utility types

TypeScript recursive property substitution utility types

type IfNever<T, Y, N> = [T] extends [never] ? Y : N;
type Intersects<A, B> = IfNever<Extract<true, A extends B ? true : false>, false, true>;

type DeepSubstitute<
	T,
	K extends PropertyKey,
	SK extends IfNever<K, never, PropertyKey>,
	V,
	SV extends IfNever<V, never, unknown>,
	S extends object[] = []
> =
	T extends object
		? T extends S[number]
			? T
			: { [MK in keyof T as MK extends K ? SK : MK]:
					MK extends IfNever<K, PropertyKey, K>
						? Intersects<T[MK], V> extends true
							? IfNever<V, T[MK], SV>
							: DeepSubstitute<T[MK], K, SK, V, SV, [...S, T]>  
						: DeepSubstitute<T[MK], K, SK, V, SV, [...S, T]>; 
				}
		: T;

type DeepSubstituteProperty<
	T extends object,
	K extends PropertyKey,
	SK extends IfNever<K, never, PropertyKey>,
	V,
	SV extends IfNever<V, never, unknown>
> = DeepSubstitute<T, K, SK, V, SV>

type DeepSubstitutePropertyValue<
	T extends object,
	V,
	SV extends IfNever<V, never, unknown>
> = DeepSubstitute<T, never, never, V, SV>;

type DeepSubstitutePropertyKey<
	T extends object,
	K extends PropertyKey,
	SK extends IfNever<K, never, PropertyKey>
> = DeepSubstitute<T, K, SK, never, never>;

type DeepOmitProperty<
	T extends object,
	K extends PropertyKey
> = DeepSubstitutePropertyKey<T, K, never>;

type DeepCircularSubstitute<
	T,
	K extends PropertyKey,
	SK extends IfNever<K, never, PropertyKey>,
	V extends object = never,
	SV extends IfNever<V, never, unknown> = never,
	S extends object[] = []
> =
	T extends object
		? T extends S[number]
			? T
			: { [MK in keyof T as MK extends K
						? Intersects<T[MK], IfNever<V, object, V & S[number]>> extends true ? SK : MK
						: MK
					]: MK extends IfNever<K, PropertyKey, K>
						? Intersects<T[MK], V & S[number]> extends true
							? IfNever<V, T[MK], SV>
							: DeepCircularSubstitute<T[MK], K, SK, V, SV, [...S, T]>  
						: DeepCircularSubstitute<T[MK], K, SK, V, SV, [...S, T]>;
				}
		: T;

type DeepSubstituteCircularProperty<
	T extends object,
	K extends PropertyKey,
	SK extends IfNever<K, never, PropertyKey>,
	V extends object = never,
	SV extends IfNever<V, never, unknown> = never
> = DeepCircularSubstitute<T, K, SK, V, SV>;

type DeepSubstituteCircularPropertyValue<
	T extends object,
	V extends object,
	SV extends IfNever<V, never, unknown>
> = DeepCircularSubstitute<T, never, never, V, SV>;

type DeepSubstituteCircularPropertyKey<
	T extends object,
	K extends PropertyKey,
	SK extends IfNever<K, never, PropertyKey>
> = DeepCircularSubstitute<T, K, SK>;

type DeepOmitCircularProperty<
	T extends object,
	K extends PropertyKey
> = DeepSubstituteCircularPropertyKey<T, K, never>;

Example:

type Foo = {
  bar: {
    baz: string,
    buzz: boolean
  },
  booz: number
}

DeepSubstitutePropertyKey<Foo, "baz", "bluezz">:

type Foo = {
  bar: {
    bluezz: string,
    buzz: boolean
  },
  booz: number
}

DeepSubstitutePropertyValue<Foo, string, "literal">:

type Foo = {
  bar: {
    baz: "literal",
    buzz: boolean
  },
  booz: number
}

DeepOmitProperty<Foo, "buzz">:

type Foo = {
  bar: {
    baz: string,
  },
  booz: number
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment