Skip to content

mergeOptions

Merge two optional objects with type-safe undefined handling

88 bytes
since v12.9.0

Usage

Merges two option objects into a new object with superior type inference compared to Object.assign. The key advantage is proper handling of undefined values and optional properties.

import * as
import _
_
from 'radashi'
const
const defaults: {
port: number;
host: string;
}
defaults
= {
port: number
port
: 3000,
host: string
host
: 'localhost' }
const
const userConfig: {
port: number;
ssl: boolean;
}
userConfig
= {
port: number
port
: 8080,
ssl: boolean
ssl
: true }
import _
_
.
function mergeOptions<{
port: number;
host: string;
}, {
port: number;
ssl: boolean;
}>(a: {
port: number;
host: string;
}, b: {
port: number;
ssl: boolean;
}): {
port: number;
ssl: boolean;
host: string;
}

Merges two option objects into a new object.

  • If both arguments are defined, properties from the second object (b) will override those from the first one (a) when keys overlap
  • If either argument is undefined, the other is returned
  • If both are undefined, the result is undefined

@parama - The first options object, or undefined.

@paramb - The second options object, or undefined.

@returnsA merged object when both arguments are defined, otherwise the defined argument, or undefined if both are undefined.

@version12.9.0

@seehttps://radashi.js.org/reference/object/mergeOptions

@example

// Merging two objects with overlapping keys
mergeOptions({ a: 1, b: 2 }, { b: 3, c: 4 })
// => { a: 1, b: 3, c: 4 }
// First argument undefined
mergeOptions(undefined, { a: 1 })
// => { a: 1 }
// Second argument undefined
mergeOptions({ a: 1 }, undefined)
// => { a: 1 }
// Both arguments undefined
mergeOptions(undefined, undefined)
// => undefined

mergeOptions
(
const defaults: {
port: number;
host: string;
}
defaults
,
const userConfig: {
port: number;
ssl: boolean;
}
userConfig
)
// => { port: 8080, host: 'localhost', ssl: true }

Handling Undefined Arguments

When either argument is undefined, the function returns the other argument with proper typing. When both are undefined, the result is correctly typed as undefined.

import * as
import _
_
from 'radashi'
// First argument undefined → returns second
import _
_
.
function mergeOptions<undefined, {
b: number;
}>(a: undefined, b: {
b: number;
}): {
b: number;
}

Merges two option objects into a new object.

  • If both arguments are defined, properties from the second object (b) will override those from the first one (a) when keys overlap
  • If either argument is undefined, the other is returned
  • If both are undefined, the result is undefined

@parama - The first options object, or undefined.

@paramb - The second options object, or undefined.

@returnsA merged object when both arguments are defined, otherwise the defined argument, or undefined if both are undefined.

@version12.9.0

@seehttps://radashi.js.org/reference/object/mergeOptions

@example

// Merging two objects with overlapping keys
mergeOptions({ a: 1, b: 2 }, { b: 3, c: 4 })
// => { a: 1, b: 3, c: 4 }
// First argument undefined
mergeOptions(undefined, { a: 1 })
// => { a: 1 }
// Second argument undefined
mergeOptions({ a: 1 }, undefined)
// => { a: 1 }
// Both arguments undefined
mergeOptions(undefined, undefined)
// => undefined

mergeOptions
(
var undefined
undefined
, {
b: number
b
: 2 })
// => { b: 2 }
// Second argument undefined → returns first
import _
_
.
function mergeOptions<{
a: number;
}, undefined>(a: {
a: number;
}, b: undefined): {
a: number;
}

Merges two option objects into a new object.

  • If both arguments are defined, properties from the second object (b) will override those from the first one (a) when keys overlap
  • If either argument is undefined, the other is returned
  • If both are undefined, the result is undefined

@parama - The first options object, or undefined.

@paramb - The second options object, or undefined.

@returnsA merged object when both arguments are defined, otherwise the defined argument, or undefined if both are undefined.

@version12.9.0

@seehttps://radashi.js.org/reference/object/mergeOptions

@example

// Merging two objects with overlapping keys
mergeOptions({ a: 1, b: 2 }, { b: 3, c: 4 })
// => { a: 1, b: 3, c: 4 }
// First argument undefined
mergeOptions(undefined, { a: 1 })
// => { a: 1 }
// Second argument undefined
mergeOptions({ a: 1 }, undefined)
// => { a: 1 }
// Both arguments undefined
mergeOptions(undefined, undefined)
// => undefined

mergeOptions
({
a: number
a
: 1 },
var undefined
undefined
)
// => { a: 1 }
// Both undefined → returns undefined
import _
_
.
function mergeOptions<undefined, undefined>(a: undefined, b: undefined): undefined

Merges two option objects into a new object.

  • If both arguments are defined, properties from the second object (b) will override those from the first one (a) when keys overlap
  • If either argument is undefined, the other is returned
  • If both are undefined, the result is undefined

@parama - The first options object, or undefined.

@paramb - The second options object, or undefined.

@returnsA merged object when both arguments are defined, otherwise the defined argument, or undefined if both are undefined.

@version12.9.0

@seehttps://radashi.js.org/reference/object/mergeOptions

@example

// Merging two objects with overlapping keys
mergeOptions({ a: 1, b: 2 }, { b: 3, c: 4 })
// => { a: 1, b: 3, c: 4 }
// First argument undefined
mergeOptions(undefined, { a: 1 })
// => { a: 1 }
// Second argument undefined
mergeOptions({ a: 1 }, undefined)
// => { a: 1 }
// Both arguments undefined
mergeOptions(undefined, undefined)
// => undefined

mergeOptions
(
var undefined
undefined
,
var undefined
undefined
)
// => undefined

Type Safety with Undefined

The main advantage of mergeOptions over Object.assign is how it handles optional objects (T | undefined). With Object.assign, you often have to use workarounds that result in complex intersection types.

import * as
import _
_
from 'radashi'
type
type Config = {
x: number;
y?: string;
} | undefined
Config
= {
x: number
x
: number;
y?: string
y
?: string } | undefined
type
type Override = {
y: string;
z: boolean;
} | undefined
Override
= {
y: string
y
: string;
z: boolean
z
: boolean } | undefined
declare const
const config: Config
config
:
type Config = {
x: number;
y?: string;
} | undefined
Config
declare const
const override: Override
override
:
type Override = {
y: string;
z: boolean;
} | undefined
Override
// ❌ Using Object.assign with a type assertion forces `config` to be non-nullable,
// but `Config` originally allows `undefined`. This assertion changes the intent,
// and results in a complex intersection type
const bad1 =
var Object: ObjectConstructor

Provides functionality common to all JavaScript objects.

Object
.
ObjectConstructor.assign<{
x: number;
y?: string;
}, Override>(target: {
x: number;
y?: string;
}, source: Override): {
x: number;
y?: string;
} & {
y: string;
z: boolean;
} (+3 overloads)

Copy the values of all of the enumerable own properties from one or more source objects to a target object. Returns the target object.

@paramtarget The target object to copy to.

@paramsource The source object from which to copy properties.

assign
(
const config: Config
config
as
type NonNullable<T> = T & {}

Exclude null and undefined from T

NonNullable
<
type Config = {
x: number;
y?: string;
} | undefined
Config
>,
const override: Override
override
)
const bad1: {
x: number;
y?: string;
} & {
y: string;
z: boolean;
}
// Potential runtime error if `config` is undefined.
// ❌ Using Object.assign with an empty object as a workaround is hacky
const bad2 =
var Object: ObjectConstructor

Provides functionality common to all JavaScript objects.

Object
.
ObjectConstructor.assign<{}, Config, Override>(target: {}, source1: Config, source2: Override): {
x: number;
y?: string;
} & {
y: string;
z: boolean;
} (+3 overloads)

Copy the values of all of the enumerable own properties from one or more source objects to a target object. Returns the target object.

@paramtarget The target object to copy to.

@paramsource1 The first source object from which to copy properties.

@paramsource2 The second source object from which to copy properties.

assign
({},
const config: Config
config
,
const override: Override
override
)
const bad2: {
x: number;
y?: string;
} & {
y: string;
z: boolean;
}
// Still produces a complex intersection type
// ✅ mergeOptions models every runtime branch
const good =
import _
_
.
function mergeOptions<Config, Override>(a: Config, b: Override): _.MergeOptions<Config, Override>

Merges two option objects into a new object.

  • If both arguments are defined, properties from the second object (b) will override those from the first one (a) when keys overlap
  • If either argument is undefined, the other is returned
  • If both are undefined, the result is undefined

@parama - The first options object, or undefined.

@paramb - The second options object, or undefined.

@returnsA merged object when both arguments are defined, otherwise the defined argument, or undefined if both are undefined.

@version12.9.0

@seehttps://radashi.js.org/reference/object/mergeOptions

@example

// Merging two objects with overlapping keys
mergeOptions({ a: 1, b: 2 }, { b: 3, c: 4 })
// => { a: 1, b: 3, c: 4 }
// First argument undefined
mergeOptions(undefined, { a: 1 })
// => { a: 1 }
// Second argument undefined
mergeOptions({ a: 1 }, undefined)
// => { a: 1 }
// Both arguments undefined
mergeOptions(undefined, undefined)
// => undefined

mergeOptions
(
const config: Config
config
,
const override: Override
override
)
const good: _.MergeOptions<Config, Override>
// | undefined
// | { x: number; y?: string }
// | { y: string; z: boolean }
// | { x: number; y: string; z: boolean }
// No runtime error if `config` is undefined.

The type of good clearly shows all possible runtime states, making it easier to understand and work with.

Real-World Example

This pattern is common when merging user configuration with defaults:

import * as
import _
_
from 'radashi'
import type {
type MergeOptions<A extends object | undefined, B extends object | undefined> = (undefined extends A ? B : never) | (undefined extends B ? NonNullable<A> : never) | MergePresent<NonNullable<A>, NonNullable<B>>

Computes the merged type of two option objects, handling undefined and partials.

@version12.9.0

MergeOptions
} from 'radashi'
type
type UserConfig = {
theme?: "light" | "dark";
lang?: string;
} | undefined
UserConfig
= {
theme?: "light" | "dark"
theme
?: 'light' | 'dark';
lang?: string
lang
?: string } | undefined
type
type AppDefaults = {
theme: "light";
lang: "en";
}
AppDefaults
= {
theme: "light"
theme
: 'light';
lang: "en"
lang
: 'en' }
function
function getConfig(userPreferences: UserConfig, defaults: AppDefaults): MergeOptions<AppDefaults, UserConfig>
getConfig
(
userPreferences: UserConfig
userPreferences
:
type UserConfig = {
theme?: "light" | "dark";
lang?: string;
} | undefined
UserConfig
,
defaults: AppDefaults
defaults
:
type AppDefaults = {
theme: "light";
lang: "en";
}
AppDefaults
,
):
type MergeOptions<A extends object | undefined, B extends object | undefined> = (undefined extends A ? B : never) | (undefined extends B ? NonNullable<A> : never) | MergePresent<NonNullable<A>, NonNullable<B>>

Computes the merged type of two option objects, handling undefined and partials.

@version12.9.0

MergeOptions
<
type AppDefaults = {
theme: "light";
lang: "en";
}
AppDefaults
,
type UserConfig = {
theme?: "light" | "dark";
lang?: string;
} | undefined
UserConfig
> {
return
import _
_
.
function mergeOptions<AppDefaults, UserConfig>(a: AppDefaults, b: UserConfig): _.MergeOptions<AppDefaults, UserConfig>

Merges two option objects into a new object.

  • If both arguments are defined, properties from the second object (b) will override those from the first one (a) when keys overlap
  • If either argument is undefined, the other is returned
  • If both are undefined, the result is undefined

@parama - The first options object, or undefined.

@paramb - The second options object, or undefined.

@returnsA merged object when both arguments are defined, otherwise the defined argument, or undefined if both are undefined.

@version12.9.0

@seehttps://radashi.js.org/reference/object/mergeOptions

@example

// Merging two objects with overlapping keys
mergeOptions({ a: 1, b: 2 }, { b: 3, c: 4 })
// => { a: 1, b: 3, c: 4 }
// First argument undefined
mergeOptions(undefined, { a: 1 })
// => { a: 1 }
// Second argument undefined
mergeOptions({ a: 1 }, undefined)
// => { a: 1 }
// Both arguments undefined
mergeOptions(undefined, undefined)
// => undefined

mergeOptions
(
defaults: AppDefaults
defaults
,
userPreferences: UserConfig
userPreferences
)
// The return type covers both defaults-only and merged config branches.
}
// Runtime results:
function getConfig(userPreferences: UserConfig, defaults: AppDefaults): MergeOptions<AppDefaults, UserConfig>
getConfig
(
var undefined
undefined
, {
theme: "light"
theme
: 'light',
lang: "en"
lang
: 'en' })
// => { theme: 'light', lang: 'en' }
function getConfig(userPreferences: UserConfig, defaults: AppDefaults): MergeOptions<AppDefaults, UserConfig>
getConfig
({
theme?: "light" | "dark"
theme
: 'dark' }, {
theme: "light"
theme
: 'light',
lang: "en"
lang
: 'en' })
// => { theme: 'dark', lang: 'en' }

With Class Instances

Works seamlessly with class instances, merging their properties into a plain object.

import * as
import _
_
from 'radashi'
class
class Character
Character
{
constructor(
public
Character.name: string
name
: string,
public
Character.age: number
age
: number,
) {}
}
const
const anderson: Character
anderson
= new
constructor Character(name: string, age: number): Character
Character
('Thomas A. Anderson', 30)
const
const neo: {
name: string;
alias: string;
}
neo
= {
name: string
name
: 'Neo',
alias: string
alias
: 'The One' }
import _
_
.
function mergeOptions<Character, {
name: string;
alias: string;
}>(a: Character, b: {
name: string;
alias: string;
}): {
name: string;
alias: string;
age: number;
}

Merges two option objects into a new object.

  • If both arguments are defined, properties from the second object (b) will override those from the first one (a) when keys overlap
  • If either argument is undefined, the other is returned
  • If both are undefined, the result is undefined

@parama - The first options object, or undefined.

@paramb - The second options object, or undefined.

@returnsA merged object when both arguments are defined, otherwise the defined argument, or undefined if both are undefined.

@version12.9.0

@seehttps://radashi.js.org/reference/object/mergeOptions

@example

// Merging two objects with overlapping keys
mergeOptions({ a: 1, b: 2 }, { b: 3, c: 4 })
// => { a: 1, b: 3, c: 4 }
// First argument undefined
mergeOptions(undefined, { a: 1 })
// => { a: 1 }
// Second argument undefined
mergeOptions({ a: 1 }, undefined)
// => { a: 1 }
// Both arguments undefined
mergeOptions(undefined, undefined)
// => undefined

mergeOptions
(
const anderson: Character
anderson
,
const neo: {
name: string;
alias: string;
}
neo
)
// => { name: 'Neo', age: 30, alias: 'The One' }