Skip to content

Instantly share code, notes, and snippets.

@Alia5
Last active May 17, 2023 08:46
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Alia5/bc7f1dd772c76736c990ed6993996f22 to your computer and use it in GitHub Desktop.
Save Alia5/bc7f1dd772c76736c990ed6993996f22 to your computer and use it in GitHub Desktop.
Advanced Typescript Cheatsheet
export type Await<T> = T extends PromiseLike<infer U> ? Await<U> : T;
export type IsPromise<T> = PromiseLike<infer U> ? true : false;
export type Length<T> = T extends { length: infer L } ? L : never;
export type KeysOfType<O, T> = {
[K in keyof O]: O[K] extends T ? K : never;
}[keyof O];
// ConvertLiterals would convert literal types like `1337` to their base type like `number` if set to true
export type PickByType<O, T, ConvertLiterals extends boolean = false> = {
[K in KeysOfType<O, T>]: ConvertLiterals extends true ? T : O[K]
};
export type OneOf<T, Strict extends boolean = true> = {
[OuterKey in keyof T]: Strict extends false
? { [K in OuterKey]: T[K] }
: { [InnerKey in OuterKey|keyof T]?: InnerKey extends OuterKey ? T[OuterKey] : never } & { [TheKey in OuterKey]: T[OuterKey] }
}[keyof T];
type Push<T extends unknown[], U> = T extends [...infer R] ? [...T, U] : never;
type PushFront<T extends unknown[], U> = T extends [...infer R] ? [U, ...T] : never;
type Pop<T extends unknown[]> = T extends [...infer R, infer U] ? U : never;
type PopFront<T extends unknown[]> = T extends [infer U, ...infer R] ? U : never;
type Shift<T extends unknown[]> = T extends [infer U, ...infer R] ? R : never;
type ShiftRight<T extends unknown[]> = T extends [...infer R, infer U] ? R : never;
type Reverse<T extends unknown[], U extends unknown[] = []> = Length<T> extends 1 ? Push<U, Pop<T>> : Reverse<ShiftRight<T>, Push<U, Pop<T>>>;
type Filter<T extends unknown[], U> = T extends [] ? [] : T extends [infer F, ...infer R] ? F extends U ? Filter<R, U> : [F, ...Filter<R, U>] : never
type TupleIncludes<T extends unknown[], U> = Length<Filter<T, U>> extends Length<T> ? false : true
type StringIncludes<S extends string, D extends string> = S extends `${infer T}${D}${infer U}` ? true : false;
type Includes<T extends unknown[]|string, U> = T extends unknown[] ? TupleIncludes<T, U> : T extends string ? U extends string ? StringIncludes<T, U> : never : never;
type Split<S extends string, D extends string> =
string extends S ? string[] :
S extends '' ? [] :
S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] : [S];
type Join<T extends unknown[], D extends string> = string[] extends T ? string : T extends string[]
? PopFront<T> extends string ? Length<T> extends 1 ? `${PopFront<T>}` : `${PopFront<T>}${D}${Join<Shift<T>, D>}` : never
: never;
type ValidPaths<T> = keyof T extends never ? never : ({
[K in keyof T]: T[K] extends never ? never : T[K] extends Record<string|number|symbol, unknown>
? K extends string ? `${K}.${ValidPaths<T[K]>}` | K : never
: K
})[keyof T] & string;
type ValidPathTuples<T> = keyof T extends never ? never : ({
[K in keyof T]: T[K] extends never ? never : T[K] extends Record<string|number|symbol, unknown>
? [K, ...ValidPathTuples<T[K]>] | [K]
: [K]
})[keyof T];
type NestedType<T, P extends string> = (
Includes<P, '.'> extends true
? PopFront<Split<P, '.'>> extends keyof T
? NestedType<T[PopFront<Split<P, '.'>>], Join<Shift<Split<P, '.'>>, '.'>>
: never
: P extends keyof T ? T[P] : never
);
type NestedTypeByTuple<T, P extends string[]> = (
Length<P> extends 1
? Pop<P> extends keyof T ? T[Pop<P>] : never
: PopFront<P> extends keyof T ? Shift<P> extends string[]
? NestedTypeByTuple<T[PopFront<P>], Shift<P>>
: never : never
);
type NestedTypeUsingTuplesAgain<T, P extends ValidPaths<T>> = NestedTypeByTuple<T, Split<P, '.'>>;
@yaakov123
Copy link

yaakov123 commented Jan 1, 2023

Quick question, how could I make this work with interfaces where a key may be undefined. For example.

interface X {
  [path: string]: {
    a: string;
    b?: {
      c: number;
    };
  };
}

const a = getByPath({} as X, 'rest.b.c');  // a = never

Is there any way I can ignore undefined? @Alia5

@Alia5
Copy link
Author

Alia5 commented Jan 1, 2023

You need to wrap your type with optional keys in the Required util-type (builtin)

interface X {
  [path: string]: {
    a: string;
    b?: {
      c: number;
    };
  };
}

const a = GetByPath({} as Required<{[K in keyof X]: Required<X[K]>}>, 'rest.b.c'); // a = number

@yaakov123
Copy link

Thanks for taking the time to comment. Any way I can do this recursively throughout the whole object if it was nested arbitrarily deep?

@Alia5
Copy link
Author

Alia5 commented Jan 2, 2023

@boronine
Copy link

I ran into some strange behaviour where ValidPaths treated const enum as an object and iterated over its properties (charCodeAt etc.). Getting to the bottom of this issue is above my pay grade but I was able to create an alternative version of ValidPaths that doesn't suffer from this problem.

export type ValidPaths<T> = T extends string | number | boolean | null | undefined
  ? never
  : {
      [K in keyof T]: K extends string ? `${K}.${ValidPaths<T[K]>}` | K : never;
    }[keyof T];

@Alia5
Copy link
Author

Alia5 commented May 17, 2023

🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment