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.
IsObject / IsNotObject
Section titled “IsObject / IsNotObject”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 }> // truetype R = IsObject<Function> // truetype R = IsObject<number> // false
type R = IsNotObject<number> // trueBoth 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' }> // nevertype R = IsObject<{} | bigint, { distributive: false }> // falseIsObject.$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 }> // trueAnyRecord and KeyTypes
Section titled “AnyRecord and KeyTypes”type KeyTypes = keyof anytype AnyRecord = Record<KeyTypes, any>KeyTypes is string | number | symbol.
AnyRecord is the constraint most of the record utilities in this category use.
ObjectPlus.Pick, ObjectPlus.Omit
Section titled “ObjectPlus.Pick, ObjectPlus.Omit”🗑️ 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.Pickpicks from each member. A member with none of the keys becomes{}, which accepts almost any value. - On a union,
ObjectPlus.Omitkeeps 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 }Partial and Required variants
Section titled “Partial and Required variants”🗑️ 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.
Key utilities
Section titled “Key utilities”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'> // truetype R = IsNotOptionalKey<{ a?: 1 }, 'a'> // falsetype R = OptionalProps<{ a?: 1; b: number }> // { a?: 1 }type R = HasKey<{ a: 1 }, 'b'> // falsetype R = HasNoKey<{ a: 1 }, 'b'> // truetype R = ValueOf<{ a: 1; b: 2 }> // 1 | 2KnownKeys<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 }.
Comparing records
Section titled “Comparing records”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 }> // truetype 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.
Combining records
Section titled “Combining records”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 }required and requiredDeep
Section titled “required and requiredDeep”function required<T, R = T, S = T & R>( source1: Partial<T>, source2: Partial<R> | undefined | null, source3?: Partial<S> | null,): T & R & Sfunction 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 }Properties
Section titled “Properties”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 }AdjustExactOptionalProps
Section titled “AdjustExactOptionalProps”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 }Other types
Section titled “Other types”| 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. |
Runtime functions
Section titled “Runtime functions”| 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.