Skip to content

Instantly share code, notes, and snippets.

@develax
Last active September 23, 2024 19:41
Show Gist options
  • Save develax/5fdb8f7b21eb9ab6bdccb66ceace27a5 to your computer and use it in GitHub Desktop.
Save develax/5fdb8f7b21eb9ab6bdccb66ceace27a5 to your computer and use it in GitHub Desktop.
TypeScript inferring types

TypeScript infer

Extract String

type ExtractStringType<T> = T extends `${infer U}` ? U : never;
type StrType1 = ExtractStringType<'ABC'>; // 'ABC'
type StrType2 = ExtractStringType<string>; // never
type StrType3 = ExtractStringType<123>; // never

Extracting types from unions

type ExtractNumberType<T> = T extends `${infer U}` ? (`${U}` extends `${number}` ? U : never) : never;
type NumberType = ExtractNumberType<'123'>; // "123"
type NumberType2 = ExtractNumberType<'123' | '456' | 'ABC'>; //  "123" | "456"

Extracting types from arrays

type ExtractArrayElementType<T extends readonly any[]> = T extends readonly (infer U)[] ? U : never; // handles *array or tuple*, restricts input types
type NumArrayType = ExtractArrayElementType<readonly number[]>; // number
type NumArrayType2 = ExtractArrayElementType<[1, 2, 3]>; // 3 | 1 | 2
type NumArrayType3 = ExtractArrayElementType<'ABC'>; // compiler error

type Flatten<Type> = Type extends ReadonlyArray<infer Item> ? Item : never; // handles only arrays
type Flatten2<Type> = Type extends readonly (infer U)[] ? U : never; // handles *array or tuple*

type FlattenType = Flatten<number[]>; // number
type FlattenType1 = Flatten<number[][]>; // number[]
type FlattenType2 = Flatten2<readonly number[]>; // number
type FlattenType3 = Flatten<'ABC'>; // no compiler error
type FlattenType4 = Flatten2<'ABC'>; // no compiler error

type FlattenRecursive<T> = T extends Array<infer U> ? FlattenRecursive<U> : T;
type FlattenRecursiveType = FlattenRecursive<number[]>; // number
type FlattenRecursiveType2 = FlattenRecursive<number[][]>; // number

Extracting types from tuples

type TupleToUnion<T extends any[]> = T extends [infer U, ...infer Rest]
 ? U | TupleToUnion<Rest>
 : never;

function getTupleHead<T extends any[]>(tuple: [...T]): TupleToUnion<T> {
 return tuple[0];
}

const result1 = getTupleHead(["hello", 42]); // result1 is inferred as string | number
const result2 = getTupleHead([true, false, true]); // result2 is inferred as boolean
type result3 = TupleToUnion<["hello", 42, false]>;

More Complex Examples:

// Type to extract the part before the underscore
type ExtractPrefix<T> = T extends `${infer Prefix}_suffix` ? Prefix : never;

// Test cases
type Example1 = ExtractPrefix<"user_id_suffix">;  // "user_id"
type Example2 = ExtractPrefix<"admin_token_suffix">;  // "admin_token"
type Example3 = ExtractPrefix<string>;  // never
type Example4 = ExtractPrefix<number>;  // never

// Type to extract both protocol and domain
type ExtractProtocolAndDomain<T> = T extends `${infer Protocol}://${infer Domain}/` ? [Protocol, Domain] : never;

// Test cases
type Example5 = ExtractProtocolAndDomain<"https://example.com/">;  // ["https", "example.com"]
type Example6 = ExtractProtocolAndDomain<"ftp://my-server.org/">;  // ["ftp", "my-server.org"]
type Example7 = ExtractProtocolAndDomain<string>;  // never
type Example8 = ExtractProtocolAndDomain<number>;  // never

// DATE
type ExtractDateParts<T> = T extends `${infer Year}-${infer Month}-${infer Day}` ? [Year, Month, Day] : never;

// Test cases
type DateParts1 = ExtractDateParts<"2023-09-21">;  // ["2023", "09", "21"]
type DateParts2 = ExtractDateParts<"1990-01-01">;  // ["1990", "01", "01"]
type InvalidDate = ExtractDateParts<string>;       // never

PLAYGROUND

infer in generic functions

Implicit infer for return value

function makeArray<T extends unknown[]>(...args: T): T {
  return args;
}

const numberArray = makeArray(1, 2, 3); // numberArray is inferred as `number[]`
const stringArray = makeArray('a', 'b', 'c'); // stringArray is inferred as `string[]`

Implicit infer for return value.

function getFirst<T>(arr: [T, ...any[]]): T {
  return arr[0];
}

const firstNumber = getFirst([1, 2, 3]); // firstNumber is inferred as number
const firstString = getFirst(['a', 'b', 'c']); // firstString is inferred as string

Functional interface + infer.

interface Mapper<T, U> {
 (input: T): U;
}

type MapArray<T, U> = {
 mapArray: <V extends Array<T>>(arr: V, fn: Mapper<T, U>) => Array<U>;
};

const mapObjectsImplementation: MapArray<{ id: string }, string> = {
 mapArray: (arr, fn) => arr.map(obj => fn(obj)),
};

const result = mapObjectsImplementation.mapArray(
 [{ id: "1" }, { id: "2" }],
 obj => obj.id
);
// result is inferred as string[]

PLAYGROUND

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