@halvaradop/ts-utility-types/deep

Advanced utilities for deep manipulation of objects and nested types in TypeScript. These types allow you to merge, transform, filter, select, omit, and modify properties at any depth in a type-safe and expressive way.

import type * as Deep from "@halvaradop/ts-utility-types/deep"

Table of Contents

DeepMerge<Obj1, Obj2, ByUnion = false, PriorityObject = true>

Recursively merges two objects. If a property exists in both, the one from Obj1 takes priority (unless otherwise specified). You can merge as a union (ByUnion) or prioritize objects (PriorityObject).

interface Config {
    storePaths: string[]
    hooks: unknown[]
}

interface AppStore {
    path: string
    hooks: ArgsFunction[]
    storePath: { path: string }
}

// Expected: { storePaths: string[], path: string, hooks: ArgsFunction[] }
type MergeConfig = DeepMerge<Config, AppStore>

DeepUnion<Obj1, Obj2>

Deeply merges two objects, but conflicting properties are combined as a union of types.

interface BaseController {
    baseUrl: string
    routes: string[]
}

interface ConfigBase {
    baseUrl: string[]
    routes: Array<{ url: string; name: string }>
}

// Expected: { baseUrl: string | string[]; routes: string[] | Array<{ url: string; name: string }> }
type Union = DeepUnion<BaseController, ConfigBase>

DeepMergeAll<Array, ByUnion = false, PriorityObject = true>

Merges a tuple of object types into one, applying the rules of DeepMerge.

interface Foo {
    foo: string
}

interface Bar {
    bar: string
}

interface FooBar {
    bar: number
    foo: boolean
    foobar: string
}

// Expected: { foo: string | boolean, bar: string | number, foobar: string }
type Merge = DeepMergeAll<[Foo, Bar, FooBar], true>

DeepMutable<Obj>

Makes all properties of an object (and its nested objects) mutable (removes readonly).

interface Foo {
    readonly foo: { readonly bar: { readonly foobar: number } }
}

// Expected: { foo: { bar: { foobar: number } } }
type NonReadonlyFoo = DeepMutable<Foo>

DeepReadonly<Obj>

Makes all properties of an object (and its nested objects) readonly.

interface User {
    name: string
    address: { street: string; avenue: string }
}

// Expected: { readonly name: string, readonly address: { readonly street: string, readonly avenue: string } }
type ReadonlyUser = DeepReadonly<User>

DeepKeys<Obj, Depth = 6>

Returns all possible keys (paths) of an object, including nested ones, as dot-separated strings.

interface User {
    name: string
    address: { street: string; avenue: string }
}

// Expected: "name" | "address" | "address.street" | "address.avenue"
type UserKeys = DeepKeys<User>

DeepOmit<Obj, Path>

Omits a property at a given path from an object type, deeply.

interface User {
    name: string
    address: { street: string; avenue: string }
}

// Expected: { name: string, address: { avenue: string } }
type WithoutStreet = DeepOmit<User, "address.street">

DeepGet<Obj, Path>

Gets the value type at a given dot-separated path in an object, at any depth.

interface User {
    name: string
    address: { street: string; avenue: string }
}

// Expected: string
type UserName = DeepGet<User, "name">

// Expected: string
type UserStreet = DeepGet<User, "address.street">

DeepTruncate<Obj, Depth>

Truncates an object type at the specified depth, replacing deeper properties with empty objects.

interface Foo {
    foo: string
    bar: number
    foobar: {
        foo: boolean
        bar: string
        foobar: {
            foo: number
        }
    }
}

// Expected: { foo: string, bar: number, foobar: {} }
type TruncatedFoo = DeepTruncate<Foo, 1>

DeepPartial<Obj>

Makes all properties of an object type optional, recursively at all levels.

interface User {
    name: string
    address: { street: string; avenue: string }
}

// Expected: { name?: string, address?: { street?: string, avenue?: string } }
type UserOptional = DeepPartial<User>

DeepRequired<Obj>

Makes all properties of an object type required, recursively at all levels.

interface User {
    name?: string
    address?: { street?: string; avenue?: string }
}

// Expected: { name: string, address: { street: string, avenue: string } }
type UserRequired = DeepRequired<User>

DeepPick<Obj, Path>

Picks the property at a given dot-separated path from an object type, at any depth.

interface User {
    name: string
    address: { street: string; avenue: string }
}

// Expected: string
type UserPick = DeepPick<User, "address.street">

DeepNullable<Obj>

Appends null to all properties of an object type, recursively at all levels.

interface User {
    name: string
    address: { street: string; avenue: string }
}

// Expected: { name: string | null, address: { street: string | null, avenue: string | null } }
type UserNullable = DeepNullable<User>

DeepNonNullable<Obj>

Removes null from all properties of an object type, recursively at all levels.

interface User {
    name: string | null
    address: { street: string | null; avenue: string | null } | null
}

// Expected: { name: string, address: { street: string, avenue: string } }
type UserNonNullable = DeepNonNullable<User>

DeepFilter<Obj, Predicate>

Filters the properties of an object type at any depth, keeping only those that match the given predicate type.

interface User {
    name: string
    age: number
    address: { street: string; avenue: string }
}

type Filtered = DeepFilter<User, string>

DeepReplace<Obj, From, To>

Replaces all properties of type From with type To at any depth in an object type.

interface User {
    name: string
    age: number
    address: { street: string; avenue: string }
}

type Replaced = DeepReplace<User, string, number>

DeepSet<Obj, Path, Value>

Sets the value type at a given dot-separated path in an object type, at any depth.

interface User {
    name: string
    address: { street: string; avenue: string }
}

type Updated = DeepSet<User, "address.street", number>