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"
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>