Skip to content

Object

The object category covers types for inspecting keys, adjusting optionality, and combining records, plus a set of runtime helpers that carry the matching type transformation.

Sources: packages/type-plus/src/object and packages/type-plus/src/unpartial.ts.

type IsObject<T, $O extends $StrictOptions<$O, IsObject.$Options> = {}>
type IsNotObject<T, $O extends $StrictOptions<$O, IsNotObject.$Options> = {}>

Validate that T is an object or an object literal. Note that Function, Array, and tuple are also objects.

type R = IsObject<{ a: 1 }> // true
type R = IsObject<Function> // true
type R = IsObject<number> // false
type R = IsNotObject<number> // true

Both accept type branching options ($any, $never, $unknown, $then, $else, selection, distributive), plus exact to require T to be exactly object:

type R = IsObject<{ a: 1 }, { selection: 'filter' }> // { a: 1 }
type R = IsObject<number, { selection: 'filter' }> // never
type R = IsObject<{} | bigint, { distributive: false }> // false

IsObject.$Fn<$O> is IsObject as a type function, with the options $O applied. Pass it to Filter, Find, Some or DropMatch:

type R = TuplePlus.Filter<[1, { a: 1 }, object], IsObject.$Fn<{ exact: true }>> // [object]
type R = $Fn.Apply<IsObject.$Fn, { a: 1 }> // true
type KeyTypes = keyof any
type AnyRecord = Record<KeyTypes, any>

KeyTypes is string | number | symbol. AnyRecord is the constraint most of the record utilities in this category use.

🗑️ removed in 8.0.0: Except — use ObjectPlus.Omit instead.

🗑️ removed in 8.0.0: the top-level Pick and Omit — use ObjectPlus.Pick and ObjectPlus.Omit instead.

namespace ObjectPlus {
type Pick<T, K extends UnionKeys<T>>
type Omit<T, K extends UnionKeys<T>>
}

These replace the built-in Pick and Omit. They distribute over unions, so each branch of the union keeps its own keys. That is what the wider key constraint buys: UnionKeys<T> collects the keys of every member, where keyof T on a union gives only the shared ones.

import type { ObjectPlus } from 'type-plus'
type R = ObjectPlus.Pick<{ a: 1; b: 2 }, 'a'> // { a: 1 }
type R = ObjectPlus.Omit<{ a: 1; b: 2 }, 'a'> // { b: 2 }
type R = ObjectPlus.Omit<{ a: 1; b: 2 } | { a: 1; c: 3 }, 'a'> // { b: 2 } | { c: 3 }

They differ from the built-ins in ways that compile silently:

  • On a union, ObjectPlus.Pick picks from each member. A member with none of the keys becomes {}, which accepts almost any value.
  • On a union, ObjectPlus.Omit keeps each member’s own keys, where the built-in keeps only the shared ones.

They also reject what the built-ins accept: a key no member has (the built-in Omit takes any key), and a generic T assigned to ObjectPlus.Pick<T, K> or ObjectPlus.Omit<T, K>.

They live in ObjectPlus so that importing them does not shadow the built-in for the whole file.

The runtime pick() and omit() return the same shapes:

const r = omit({ a: 1, b: 2 }, 'a') // { b: number }

🗑️ removed in 8.0.0: PartialExcept — use PartialOmit instead.

🗑️ removed in 8.0.0: the top-level Partial and Required — use ObjectPlus.Partial and ObjectPlus.Required instead.

💀 deprecated since 8.0.0: RequiredExcept — use RequiredOmit instead.

namespace ObjectPlus { type Partial<T> }
type PartialPick<T, U extends UnionKeys<T>>
type PartialOmit<T, U extends UnionKeys<T>>
namespace ObjectPlus { type Required<T> }
type RequiredPick<T, U extends UnionKeys<T>>
type RequiredOmit<T, U extends UnionKeys<T>>

ObjectPlus.Partial<T> adds | undefined to each property so it works under exactOptionalPropertyTypes. With the flag off it is identical to the built-in Partial. ObjectPlus.Required<T> removes undefined from each property, including properties that were already required. The built-in Required only removes the ?. Both live in ObjectPlus for the same reason as ObjectPlus.Pick. The Pick and Omit variants apply the change to only some keys. Like ObjectPlus.Pick, they distribute over a union T and accept a key of any member.

type R = PartialPick<{ a: 1; b: 2 }, 'a'> // { b: 2 } & { a?: 1 | undefined }
type R = RequiredOmit<{ a?: 1; b?: 2 }, 'b'> // { b?: 2 } & { a: 1 }

RecursivePartial / RecursiveRequired / RecursiveIntersect

Section titled “RecursivePartial / RecursiveRequired / RecursiveIntersect”
type RecursivePartial<T>
type RecursiveRequired<T>
type RecursiveIntersect<T, U>

RecursivePartial makes every property optional, all the way down, including array elements.

type R = RecursivePartial<{ a: number; b: { c: string } }>
// { a?: number | undefined; b?: { c?: string | undefined } | undefined }

RecursiveIntersect intersects U into T at every level. The recursion terminates at level 7 due to a design limit of TypeScript.

type OptionalKeys<T>
type RequiredKeys<T extends AnyRecord>
type IsOptionalKey<T, K, $O extends $StrictOptions<$O, IsOptionalKey.$Options> = {}>
type IsNotOptionalKey<T, K, $O extends $StrictOptions<$O, IsNotOptionalKey.$Options> = {}>
type OptionalProps<T extends AnyRecord>
type KnownKeys<T>
type HasKey<T, K, $O extends $StrictOptions<$O, HasKey.$Options> = {}>
type HasNoKey<T, K, $O extends $StrictOptions<$O, HasNoKey.$Options> = {}>
type ValueOf<T>
type R = OptionalKeys<{ a?: 1; b: number }> // 'a'
type R = RequiredKeys<{ a?: 1; b: number }> // 'b'
type R = IsOptionalKey<{ a?: 1 }, 'a'> // true
type R = IsNotOptionalKey<{ a?: 1 }, 'a'> // false
type R = OptionalProps<{ a?: 1; b: number }> // { a?: 1 }
type R = HasKey<{ a: 1 }, 'b'> // false
type R = HasNoKey<{ a: 1 }, 'b'> // true
type R = ValueOf<{ a: 1; b: 2 }> // 1 | 2

KnownKeys<T> drops index signature keys, keeping only the literal keys.

IsNotOptionalKey is the inverse of IsOptionalKey, so a key T does not have passes it. HasNoKey is the inverse of HasKey.

IsOptionalKey, IsNotOptionalKey, HasKey and HasNoKey accept the full type branching options. The filter form keeps the keys that pass, so it composes into a key selection:

type R = IsOptionalKey<{ a?: 1; b: 2 }, 'a' | 'b', { selection: 'filter' }> // 'a'
type R = IsNotOptionalKey<{ a?: 1; b: 2 }, 'a' | 'b', { selection: 'filter' }> // 'b'
type R = HasKey<{ a: 1; b: 2 }, 'a' | 'c', { selection: 'filter' }> // 'a'
type R = HasNoKey<{ a: 1; b: 2 }, 'a' | 'c', { selection: 'filter' }> // 'c'
type R = HasKey<{ a: 1 }, 'b', { $then: 'yes'; $else: 'no' }> // 'no'

Before 8.0.0 both took Then and Else positionally (HasKey<T, K, Then, Else>); move them into { $then, $else }.

type IsDisjoint<A extends AnyRecord, B extends AnyRecord, $O extends $StrictOptions<$O, IsDisjoint.$Options> = {}>
type KeysWithDiffType<A extends AnyRecord, B extends AnyRecord>
type ANotB<A extends AnyRecord, B extends AnyRecord>
type BNotA<A extends AnyRecord, B extends AnyRecord>
type R = IsDisjoint<{ a: 1 }, { b: 2 }> // true
type R = KeysWithDiffType<{ a: 1; b: 2 }, { a: 1; b: 3 }> // 'b'
type R = ANotB<{ a: 1; b: 2 }, { a: 1 }> // { b: 2 }

IsDisjoint takes the type branching options and has IsDisjoint.$Fn<B>. Before 8.0.0 it returned boolean when each side had a key the other lacked, and never when either side was {}; it now returns false and true for those.

ANotB is never when the two types are equal, and A when they are disjoint.

type SpreadRecord<A extends AnyRecord, B extends AnyRecord>
type LeftJoin<A extends AnyRecord, B extends AnyRecord>
namespace ObjectPlus { type Merge<A extends AnyRecord, B extends AnyRecord> }

SpreadRecord is the type-level { ...a, ...b } where B wins on conflicts. LeftJoin keeps the keys of A not in B, then adds all of B. Each property keeps its ? and readonly modifiers from the side it comes from, so a key both declare takes B’s type and modifiers. ObjectPlus.Merge also handles Record inputs and required/optional joins. It models the spread faithfully, so the result is always writable: readonly on either side is dropped, and a get-only accessor - which is a readonly property - merges in as a plain writable data property.

type R = SpreadRecord<{ a: 1; b: 2 }, { b: 3 }> // { a: 1 } & { b: 3 }
import type { ObjectPlus } from 'type-plus'
type M = ObjectPlus.Merge<{ a: 1 }, { b: 2 }> // { a: 1 } & { b: 2 }
type G = ObjectPlus.Merge<{ get a(): 1 }, { b: 2 }> // { a: 1; b: 2 }
function required<T, R = T, S = T & R>(
source1: Partial<T>,
source2: Partial<R> | undefined | null,
source3?: Partial<S> | null,
): T & R & S
function requiredDeep<T, R = T, S = T & R>(
source1: Partial<T>,
source2: Partial<R> | undefined | null,
source3?: Partial<S> | null,
): T & R & S

🦴 utilities — merge up to three partial objects, left to right, into one value typed as the intersection of their full types. Both are re-exported from the unpartial package.

import { required, requiredDeep } from 'type-plus'
type Options = { a: number; b: { x: number; y?: number } }
const defaults: Options = { a: 1, b: { x: 1, y: 1 } }
const r1 = required<Options>(defaults, { b: { x: 2 } }) // { a: 1, b: { x: 2 } }
const r2 = requiredDeep<Options>(defaults, { b: { x: 2 } }) // { a: 1, b: { x: 2, y: 1 } }

required is shallow: a later source replaces a nested object, and a key it sets to undefined overwrites the earlier value. requiredDeep merges nested objects key by key, replaces arrays, and keeps the earlier value where a later source has undefined. Both skip null and undefined sources.

type Split<T extends AnyRecord, S extends AnyRecord>
function split(target, ...splitters): [...entries, remaining]

Splits one record into several based on splitter objects. The last entry of the result is whatever was not claimed by a splitter.

const [ab, rest] = split({ a: 1, b: 2, c: 3 }, { a: undefined, b: undefined })
// ab: { a: number; b: number }, rest: { c: number }
type Properties<T>

Extracts the property map of T, preserving optional and readonly modifiers, and merging the branches of an intersection.

type R = Properties<{ a: 1 } & { b: 2 }> // { a: 1; b: 2 }
type AdjustExactOptionalProps<T extends AnyRecord>

Adds undefined to the optional properties of T so the type works under the exactOptionalPropertyTypes compiler flag.

type R = AdjustExactOptionalProps<{ a: 1; b?: 2 }> // { b?: 2 | undefined } & { a: 1 }
Type Description
IsRecord<T, $O> true when T is assignable to Record<any, any> and is not an array. Takes the type branching options and has IsRecord.$Fn.
IsNotRecord<T, $O> The inverse of IsRecord, with the same options and IsNotRecord.$Fn.
ExcludePropType<T, U> Excludes U from the type of every property in T.
ReplaceProperty<T, K, V> Replaces the type of key K in T with V.
KeysOfOptional<T> 🗑️ removed in 8.0.0, use OptionalKeys<T> for the optional keys, keyof T for the key union
RecordValue<R> Infers the value type of a Record.
Function Description
pick(subject, ...props) Picks the listed properties, typed as ObjectPlus.Pick.
omit(subject, ...props) Omits the listed properties, typed as ObjectPlus.Omit.
facade(subject, ...props) Picks properties to expose a narrower view of subject.
split(target, ...splitters) Splits a record into several records plus the remainder.
record(value?) Creates a Record with widened key types.
hasKey(subject, ...keys) Checks the keys, typed as HasKey.
hasProperty(value, prop) Type guard narrowing value to value & Record<P, T[P]>.
getField(subject, key, defaultValue?) Reads a field from a possibly null/undefined subject.
mapKey, filterKey, findKey, forEachKey, everyKey, someKey, reduceByKey Array-style iteration over the keys of a record.
mapProperties(subject, mapper) Maps every property value of a record.
replaceProperty(subject, key, value) Returns a copy with key K replaced, typed as ReplaceProperty.
required(source1, source2, source3?) Merges up to three partial objects shallowly into a full one.
requiredDeep(source1, source2, source3?) Merges up to three partial objects recursively into a full one.
typeOverrideIncompatible<A>()(source, override) Returns { ...source, ...override } typed as A. override must supply exactly the properties ANotB<A, typeof source>: those source is missing or declares with an incompatible type.

🗑️ removed in 8.0.0: reduceKey — use reduceByKey instead.